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.hrm.api.employee.HrmEmployeeApi;
|
import cn.iocoder.yudao.module.hrm.api.employee.dto.HrmEmployeeSimpleDTO;
|
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificatePageReqVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificateRespVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificateSaveReqVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyCertificateDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyCertificateMapper;
|
import cn.iocoder.yudao.module.mes.enums.safety.SafetyCertificateStatusEnum;
|
import cn.iocoder.yudao.module.mes.enums.safety.SafetyCertificateTypeEnum;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.time.LocalDate;
|
import java.time.temporal.ChronoUnit;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.stream.Collectors;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
|
|
/**
|
* 安全生产 - 特种作业证台账 Service
|
*
|
* <p>核心能力:证书到期自动预警。状态由 {@link #resolveStatus} 根据有效期实时推导,
|
* 不依赖手工维护,避免过期未及时更新状态。</p>
|
*
|
* <p>持证人通过 employeeId 关联人力资源的员工档案:
|
* 新增/修改时从档案带出姓名、工号、部门并落为快照;查询时实时回填最新档案信息,
|
* 员工调岗后证件列表也会跟着显示新部门。</p>
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
public class SafetyCertificateService {
|
|
/** 默认提前预警天数 */
|
public static final int DEFAULT_WARN_DAYS = 30;
|
|
@Resource
|
private SafetyCertificateMapper certificateMapper;
|
@Resource
|
private HrmEmployeeApi hrmEmployeeApi;
|
@Resource
|
private SafetyRefResolver refResolver;
|
|
public Long createCertificate(SafetyCertificateSaveReqVO reqVO) {
|
reqVO.setId(null);
|
validateCertificateForCreate(reqVO);
|
SafetyCertificateDO cert = buildCertificate(reqVO, null);
|
certificateMapper.insert(cert);
|
return cert.getId();
|
}
|
|
public void updateCertificate(SafetyCertificateSaveReqVO reqVO) {
|
SafetyCertificateDO old = validateCertificateExists(reqVO.getId());
|
validateCertificateForUpdate(reqVO, old);
|
SafetyCertificateDO cert = buildCertificate(reqVO, old);
|
certificateMapper.updateById(cert);
|
}
|
|
public void deleteCertificate(Long id) {
|
validateCertificateExists(id);
|
certificateMapper.deleteById(id);
|
}
|
|
public SafetyCertificateDO getCertificate(Long id) {
|
return validateCertificateExists(id);
|
}
|
|
public PageResult<SafetyCertificateDO> getCertificatePage(SafetyCertificatePageReqVO reqVO) {
|
return certificateMapper.selectPage(reqVO);
|
}
|
|
/**
|
* 查询即将到期 / 已过期的证件列表
|
*
|
* @param days 提前预警天数,为空使用默认 30 天
|
*/
|
public List<SafetyCertificateDO> getExpiringCertificates(Integer days) {
|
int warnDays = days == null || days <= 0 ? DEFAULT_WARN_DAYS : days;
|
return certificateMapper.selectExpiringBefore(LocalDate.now().plusDays(warnDays));
|
}
|
|
/**
|
* 批量补全持证人的最新员工档案信息(姓名、工号、部门、手机、关联用户)
|
*
|
* <p>查询时用档案实时值覆盖快照值,保证员工调岗后台账同步;
|
* 员工档案已被删除时回退到快照值,保证历史证件仍可展示。</p>
|
*/
|
public void fillEmployeeInfo(List<SafetyCertificateDO> certs) {
|
List<Long> employeeIds = certs.stream()
|
.map(SafetyCertificateDO::getEmployeeId)
|
.filter(Objects::nonNull).distinct().toList();
|
// 注意:employeeMap 后续会用 cert.getEmployeeId()(可能为 null)去 get,
|
// 而 Map.of() 不接受 null key、get(null) 会抛 NPE,所以空集合必须返回 HashMap
|
Map<Long, HrmEmployeeSimpleDTO> employeeMap = employeeIds.isEmpty() ? new HashMap<>()
|
: hrmEmployeeApi.getEmployeeList(employeeIds).stream()
|
.collect(Collectors.toMap(HrmEmployeeSimpleDTO::getId, e -> e, (a, b) -> a));
|
certs.forEach(cert -> {
|
HrmEmployeeSimpleDTO employee = employeeMap.get(cert.getEmployeeId());
|
if (employee != null) {
|
cert.setEmployeeName(employee.getName());
|
cert.setEmployeeNo(employee.getEmployeeNo());
|
cert.setDeptName(employee.getDeptName());
|
}
|
});
|
}
|
|
/**
|
* 批量补全展示字段,持证人档案一次性查好,避免逐条查询
|
*/
|
public void fillDisplayFieldsBatch(List<SafetyCertificateDO> certs, List<SafetyCertificateRespVO> resps) {
|
var employeeMap = refResolver.employeeMap(certs.stream().map(SafetyCertificateDO::getEmployeeId).toList());
|
for (int i = 0; i < certs.size(); i++) {
|
fillDisplayFields(certs.get(i), resps.get(i), employeeMap.get(certs.get(i).getEmployeeId()));
|
}
|
}
|
|
/**
|
* 单个证件的展示字段补全(类型名称、状态名称、剩余天数、持证人档案)
|
*/
|
public void fillDisplayFields(SafetyCertificateDO cert, SafetyCertificateRespVO respVO) {
|
fillDisplayFields(cert, respVO, cert.getEmployeeId() == null ? null
|
: hrmEmployeeApi.getEmployee(cert.getEmployeeId()));
|
}
|
|
private void fillDisplayFields(SafetyCertificateDO cert, SafetyCertificateRespVO respVO,
|
HrmEmployeeSimpleDTO employee) {
|
respVO.setCertTypeName(SafetyCertificateTypeEnum.getNameByType(cert.getCertType()));
|
String status = resolveStatus(cert.getExpireDate(), cert.getWarnDays(), cert.getStatus());
|
respVO.setStatus(status);
|
respVO.setStatusName(SafetyCertificateStatusEnum.getNameByStatus(status));
|
if (cert.getExpireDate() != null) {
|
respVO.setRemainingDays(ChronoUnit.DAYS.between(LocalDate.now(), cert.getExpireDate()));
|
}
|
if (employee != null) {
|
respVO.setEmployeeName(employee.getName());
|
respVO.setEmployeeNo(employee.getEmployeeNo());
|
respVO.setDeptName(employee.getDeptName());
|
respVO.setPhone(employee.getPhone());
|
respVO.setUserId(employee.getUserId());
|
}
|
}
|
|
/**
|
* 根据有效期推导状态:已注销保持不变;过期 → EXPIRED;在预警期内 → EXPIRING;否则 VALID
|
*
|
* @param expireDate 有效期
|
* @param warnDays 提前预警天数
|
* @param current 当前状态,用于保护 REVOKED
|
*/
|
public static String resolveStatus(LocalDate expireDate, Integer warnDays, String current) {
|
if (SafetyCertificateStatusEnum.REVOKED.getStatus().equals(current)) {
|
return SafetyCertificateStatusEnum.REVOKED.getStatus();
|
}
|
if (expireDate == null) {
|
return SafetyCertificateStatusEnum.VALID.getStatus();
|
}
|
LocalDate today = LocalDate.now();
|
if (expireDate.isBefore(today)) {
|
return SafetyCertificateStatusEnum.EXPIRED.getStatus();
|
}
|
int days = warnDays == null || warnDays <= 0 ? DEFAULT_WARN_DAYS : warnDays;
|
if (!expireDate.isAfter(today.plusDays(days))) {
|
return SafetyCertificateStatusEnum.EXPIRING.getStatus();
|
}
|
return SafetyCertificateStatusEnum.VALID.getStatus();
|
}
|
|
/**
|
* 构建 DO:持证人姓名/工号/部门从员工档案带出并落为快照
|
*/
|
private SafetyCertificateDO buildCertificate(SafetyCertificateSaveReqVO reqVO, SafetyCertificateDO old) {
|
HrmEmployeeSimpleDTO employee = hrmEmployeeApi.getEmployee(reqVO.getEmployeeId());
|
if (employee == null) {
|
throw exception(SAFETY_CERT_EMPLOYEE_NOT_EXISTS);
|
}
|
SafetyCertificateDO cert = BeanUtils.toBean(reqVO, SafetyCertificateDO.class);
|
cert.setEmployeeId(employee.getId());
|
cert.setEmployeeName(employee.getName());
|
cert.setEmployeeNo(employee.getEmployeeNo());
|
cert.setDeptName(employee.getDeptName());
|
cert.setCertNo(StrUtil.trim(reqVO.getCertNo()));
|
cert.setWarnDays(reqVO.getWarnDays() == null ? DEFAULT_WARN_DAYS : reqVO.getWarnDays());
|
// 已注销状态不允许通过编辑改回
|
String currentStatus = old == null ? reqVO.getStatus() : old.getStatus();
|
cert.setStatus(resolveStatus(reqVO.getExpireDate(), cert.getWarnDays(), currentStatus));
|
cert.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
|
return cert;
|
}
|
|
private void validateCertificateForCreate(SafetyCertificateSaveReqVO reqVO) {
|
validateExpireAfterIssue(reqVO);
|
if (certificateMapper.selectByCertNo(StrUtil.trim(reqVO.getCertNo())) != null) {
|
throw exception(SAFETY_CERT_NO_DUPLICATE);
|
}
|
}
|
|
private void validateCertificateForUpdate(SafetyCertificateSaveReqVO reqVO, SafetyCertificateDO old) {
|
validateExpireAfterIssue(reqVO);
|
SafetyCertificateDO byCertNo = certificateMapper.selectByCertNo(StrUtil.trim(reqVO.getCertNo()));
|
if (byCertNo != null && !byCertNo.getId().equals(old.getId())) {
|
throw exception(SAFETY_CERT_NO_DUPLICATE);
|
}
|
}
|
|
private void validateExpireAfterIssue(SafetyCertificateSaveReqVO reqVO) {
|
if (reqVO.getIssueDate() != null && reqVO.getExpireDate() != null
|
&& !Objects.requireNonNull(reqVO.getExpireDate()).isAfter(reqVO.getIssueDate())) {
|
throw exception(SAFETY_CERT_EXPIRE_BEFORE_ISSUE);
|
}
|
}
|
|
private SafetyCertificateDO validateCertificateExists(Long id) {
|
SafetyCertificateDO cert = id == null ? null : certificateMapper.selectById(id);
|
if (cert == null) {
|
throw exception(SAFETY_CERT_NOT_EXISTS);
|
}
|
return cert;
|
}
|
|
}
|