package cn.iocoder.yudao.module.mes.service.safety; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard.*; import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyHazardDO; import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyHazardMapper; import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardLevelEnum; import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardSourceEnum; import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardStatusEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*; /** * 安全生产 - 隐患登记与闭环 Service * *

完整闭环:上报 → 分派 → 整改 → 复查(通过则闭环,不通过退回整改)。 * 每个环节都校验当前状态,防止跳步或重复操作。

* * @author 超级管理员 */ @Service @Validated public class SafetyHazardService { private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd"); @Resource private SafetyHazardMapper hazardMapper; @Resource private SafetyRefResolver refResolver; /** * 隐患上报(含移动端拍照上传的 photoUrls) */ @Transactional(rollbackFor = Exception.class) public Long createHazard(SafetyHazardSaveReqVO reqVO) { reqVO.setId(null); SafetyHazardDO hazard = BeanUtils.toBean(reqVO, SafetyHazardDO.class); hazard.setCode(hazardMapper.selectNextCode("HZ" + LocalDate.now().format(CODE_DATE))); hazard.setTitle(StrUtil.trim(reqVO.getTitle())); hazard.setReporterId(reqVO.getReporterId()); hazard.setReporter(refResolver.requireEmployee(reqVO.getReporterId(), SAFETY_EMPLOYEE_REQUIRED).getName()); hazard.setLevel(StrUtil.blankToDefault(reqVO.getLevel(), SafetyHazardLevelEnum.GENERAL.getLevel())); hazard.setSource(StrUtil.blankToDefault(reqVO.getSource(), SafetyHazardSourceEnum.SELF_CHECK.getSource())); hazard.setReportTime(reqVO.getReportTime() == null ? LocalDateTime.now() : reqVO.getReportTime()); hazard.setStatus(SafetyHazardStatusEnum.REPORTED.getStatus()); hazard.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim()); hazardMapper.insert(hazard); return hazard.getId(); } /** * 修改隐患基础信息(仅未分派前允许改关键字段) */ public void updateHazard(SafetyHazardSaveReqVO reqVO) { SafetyHazardDO old = validateHazardExists(reqVO.getId()); if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(old.getStatus()) && !SafetyHazardStatusEnum.ASSIGNED.getStatus().equals(old.getStatus())) { throw exception(SAFETY_HAZARD_STATUS_ERROR); } SafetyHazardDO update = BeanUtils.toBean(reqVO, SafetyHazardDO.class); // 编号与状态不允许通过普通编辑修改 update.setCode(null); update.setStatus(null); hazardMapper.updateById(update); } /** * 分派整改责任人 */ @Transactional(rollbackFor = Exception.class) public void assignHazard(SafetyHazardAssignReqVO reqVO) { SafetyHazardDO hazard = validateHazardExists(reqVO.getId()); if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(hazard.getStatus()) && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) { throw exception(SAFETY_HAZARD_STATUS_ERROR); } if (reqVO.getAssigneeId() == null) { throw exception(SAFETY_HAZARD_ASSIGNEE_REQUIRED); } var assignee = refResolver.requireEmployee(reqVO.getAssigneeId(), SAFETY_HAZARD_ASSIGNEE_REQUIRED); SafetyHazardDO update = new SafetyHazardDO(); update.setId(hazard.getId()); update.setAssigneeId(assignee.getId()); update.setAssignee(assignee.getName()); update.setAssignTime(LocalDateTime.now()); update.setRectifyDeadline(reqVO.getRectifyDeadline()); update.setStatus(SafetyHazardStatusEnum.RECTIFYING.getStatus()); if (StrUtil.isNotBlank(reqVO.getRemark())) { update.setRemark(reqVO.getRemark().trim()); } hazardMapper.updateById(update); } /** * 提交整改结果 */ @Transactional(rollbackFor = Exception.class) public void rectifyHazard(SafetyHazardRectifyReqVO reqVO) { SafetyHazardDO hazard = validateHazardExists(reqVO.getId()); if (!SafetyHazardStatusEnum.RECTIFYING.getStatus().equals(hazard.getStatus()) && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) { throw exception(SAFETY_HAZARD_STATUS_ERROR); } SafetyHazardDO update = new SafetyHazardDO(); update.setId(hazard.getId()); update.setRectifyDesc(reqVO.getRectifyDesc().trim()); update.setRectifyPhotoUrls(reqVO.getRectifyPhotoUrls()); update.setRectifyTime(LocalDateTime.now()); update.setStatus(SafetyHazardStatusEnum.RECHECKING.getStatus()); hazardMapper.updateById(update); } /** * 复查:通过则闭环,不通过退回整改。 * *

校验整改人与复查人不能是同一人,保证闭环的两级分离。

*/ @Transactional(rollbackFor = Exception.class) public void recheckHazard(SafetyHazardRecheckReqVO reqVO) { SafetyHazardDO hazard = validateHazardExists(reqVO.getId()); if (!SafetyHazardStatusEnum.RECHECKING.getStatus().equals(hazard.getStatus())) { throw exception(SAFETY_HAZARD_STATUS_ERROR); } var rechecker = refResolver.requireEmployee(reqVO.getRecheckerId(), SAFETY_EMPLOYEE_REQUIRED); // 用员工编号比较,比姓名比较可靠(同名不同人 / 改名都不会误判) if (hazard.getAssigneeId() != null && hazard.getAssigneeId().equals(rechecker.getId())) { throw exception(SAFETY_HAZARD_SAME_OPERATOR); } boolean passed = "PASSED".equals(reqVO.getRecheckResult()); SafetyHazardDO update = new SafetyHazardDO(); update.setId(hazard.getId()); update.setRecheckerId(rechecker.getId()); update.setRechecker(rechecker.getName()); update.setRecheckTime(LocalDateTime.now()); update.setRecheckResult(reqVO.getRecheckResult()); update.setRecheckRemark(reqVO.getRecheckRemark()); if (passed) { update.setStatus(SafetyHazardStatusEnum.CLOSED.getStatus()); update.setCloseTime(LocalDateTime.now()); } else { update.setStatus(SafetyHazardStatusEnum.REJECTED.getStatus()); } hazardMapper.updateById(update); } public void deleteHazard(Long id) { validateHazardExists(id); hazardMapper.deleteById(id); } public SafetyHazardDO getHazard(Long id) { return validateHazardExists(id); } public PageResult getHazardPage(SafetyHazardPageReqVO reqVO) { reqVO.normalizeTimeRange(); return hazardMapper.selectPage(reqVO); } /** * 批量回填上报人 / 整改责任人 / 复查人的最新姓名 */ public void fillPersonNames(List hazards) { if (CollUtil.isEmpty(hazards)) { return; } var ids = new java.util.ArrayList(hazards.size() * 3); hazards.forEach(h -> { ids.add(h.getReporterId()); ids.add(h.getAssigneeId()); ids.add(h.getRecheckerId()); }); var employeeMap = refResolver.employeeMap(ids); hazards.forEach(hazard -> { var reporter = employeeMap.get(hazard.getReporterId()); if (reporter != null) hazard.setReporter(reporter.getName()); var assignee = employeeMap.get(hazard.getAssigneeId()); if (assignee != null) hazard.setAssignee(assignee.getName()); var rechecker = employeeMap.get(hazard.getRecheckerId()); if (rechecker != null) hazard.setRechecker(rechecker.getName()); }); } /** * 展示字段补全(等级/来源/状态名称、超期标记) */ public static void fillHazardDisplay(SafetyHazardDO hazard, SafetyHazardRespVO respVO) { respVO.setLevelName(SafetyHazardLevelEnum.getNameByLevel(hazard.getLevel())); respVO.setSourceName(SafetyHazardSourceEnum.getNameBySource(hazard.getSource())); respVO.setStatusName(SafetyHazardStatusEnum.getNameByStatus(hazard.getStatus())); respVO.setRecheckResultName(switch (StrUtil.blankToDefault(hazard.getRecheckResult(), "")) { case "PASSED" -> "通过"; case "REJECTED" -> "不通过"; default -> ""; }); boolean overdue = hazard.getRectifyDeadline() != null && hazard.getRectifyDeadline().isBefore(LocalDate.now()) && !SafetyHazardStatusEnum.isFinished(hazard.getStatus()); respVO.setOverdue(overdue); } private SafetyHazardDO validateHazardExists(Long id) { SafetyHazardDO hazard = id == null ? null : hazardMapper.selectById(id); if (hazard == null) { throw exception(SAFETY_HAZARD_NOT_EXISTS); } return hazard; } }