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 java.util.Map;
|
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
|
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
|
|
/**
|
* 安全生产 - 隐患登记与闭环 Service
|
*
|
* <p>完整闭环:上报 → 分派 → 整改 → 复查(通过则闭环,不通过退回整改)。
|
* 每个环节都校验当前状态,防止跳步或重复操作。</p>
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class SafetyHazardService {
|
|
private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
@Resource
|
private SafetyHazardMapper hazardMapper;
|
@Resource
|
private SafetyRefResolver refResolver;
|
@Resource
|
private NotifyMessageSendApi notifyMessageSendApi;
|
@Resource
|
private AdminUserService adminUserService;
|
|
/**
|
* 隐患上报(含移动端拍照上传的 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 = hazardMapper.selectByIdForUpdate(reqVO.getId());
|
if (hazard == null) throw exception(SAFETY_HAZARD_NOT_EXISTS);
|
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);
|
Long notifyUserId = assignee.getUserId() != null ? assignee.getUserId() : reqVO.getNotifyUserId();
|
if (notifyUserId == null) throw exception(SAFETY_HAZARD_ASSIGNEE_NO_ACCOUNT);
|
if (assignee.getUserId() != null && reqVO.getNotifyUserId() != null && !assignee.getUserId().equals(reqVO.getNotifyUserId())) {
|
throw cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException("责任人已绑定账号,通知接收账号必须与其一致");
|
}
|
adminUserService.validateUserList(List.of(notifyUserId));
|
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);
|
NotifySendSingleToUserReqDTO notice = new NotifySendSingleToUserReqDTO();
|
notice.setUserId(notifyUserId);
|
notice.setTemplateCode("safety_hazard_assigned");
|
notice.setTemplateParams(Map.of("code", hazard.getCode(), "title", hazard.getTitle(),
|
"deadline", reqVO.getRectifyDeadline() == null ? "未设置" : reqVO.getRectifyDeadline().toString()));
|
if (notifyMessageSendApi.sendSingleMessageToAdmin(notice) == null) {
|
throw exception(SAFETY_HAZARD_NOTICE_UNAVAILABLE);
|
}
|
}
|
|
/**
|
* 提交整改结果
|
*/
|
@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);
|
}
|
|
/**
|
* 复查:通过则闭环,不通过退回整改。
|
*
|
* <p>校验整改人与复查人不能是同一人,保证闭环的两级分离。</p>
|
*/
|
@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<SafetyHazardDO> getHazardPage(SafetyHazardPageReqVO reqVO) {
|
reqVO.normalizeTimeRange();
|
return hazardMapper.selectPage(reqVO);
|
}
|
|
/**
|
* 批量回填上报人 / 整改责任人 / 复查人的最新姓名
|
*/
|
public void fillPersonNames(List<SafetyHazardDO> hazards) {
|
if (CollUtil.isEmpty(hazards)) {
|
return;
|
}
|
var ids = new java.util.ArrayList<Long>(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;
|
}
|
|
}
|