xiaoyi
7 天以前 05ab6e3f69a5e73624b2573db043f5923e8f7d8b
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyCertificateService.java
@@ -1,8 +1,11 @@
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;
@@ -16,8 +19,11 @@
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.*;
@@ -27,6 +33,10 @@
 *
 * <p>核心能力:证书到期自动预警。状态由 {@link #resolveStatus} 根据有效期实时推导,
 * 不依赖手工维护,避免过期未及时更新状态。</p>
 *
 * <p>持证人通过 employeeId 关联人力资源的员工档案:
 * 新增/修改时从档案带出姓名、工号、部门并落为快照;查询时实时回填最新档案信息,
 * 员工调岗后证件列表也会跟着显示新部门。</p>
 *
 * @author 超级管理员
 */
@@ -39,11 +49,15 @@
    @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);
        SafetyCertificateDO cert = buildCertificate(reqVO, null);
        certificateMapper.insert(cert);
        return cert.getId();
    }
@@ -51,7 +65,7 @@
    public void updateCertificate(SafetyCertificateSaveReqVO reqVO) {
        SafetyCertificateDO old = validateCertificateExists(reqVO.getId());
        validateCertificateForUpdate(reqVO, old);
        SafetyCertificateDO cert = buildCertificate(reqVO);
        SafetyCertificateDO cert = buildCertificate(reqVO, old);
        certificateMapper.updateById(cert);
    }
@@ -79,15 +93,64 @@
    }
    /**
     * 分页结果的展示字段补全(类型名称、状态名称、剩余天数)
     * 批量补全持证人的最新员工档案信息(姓名、工号、部门、手机、关联用户)
     *
     * <p>查询时用档案实时值覆盖快照值,保证员工调岗后台账同步;
     * 员工档案已被删除时回退到快照值,保证历史证件仍可展示。</p>
     */
    public static void fillDisplayFields(SafetyCertificateDO cert, SafetyCertificateRespVO respVO) {
    public void fillEmployeeInfo(List<SafetyCertificateDO> certs) {
        List<Long> employeeIds = certs.stream()
                .map(SafetyCertificateDO::getEmployeeId)
                .filter(Objects::nonNull).distinct().toList();
        // 用 new HashMap<>() 而非 Map.of():employeeId 允许为 null,
        // 后面 employeeMap.get(cert.getEmployeeId()) 在空集合场景会 get(null),
        // Map.of() 的 get(null) 会抛 NPE 导致证件列表接口 500。
        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());
        }
    }
@@ -116,12 +179,24 @@
        return SafetyCertificateStatusEnum.VALID.getStatus();
    }
    private SafetyCertificateDO buildCertificate(SafetyCertificateSaveReqVO reqVO) {
    /**
     * 构建 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.setEmployeeName(StrUtil.trim(reqVO.getEmployeeName()));
        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());
        cert.setStatus(resolveStatus(reqVO.getExpireDate(), cert.getWarnDays(), reqVO.getStatus()));
        // 已注销状态不允许通过编辑改回
        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;
    }
@@ -138,11 +213,6 @@
        SafetyCertificateDO byCertNo = certificateMapper.selectByCertNo(StrUtil.trim(reqVO.getCertNo()));
        if (byCertNo != null && !byCertNo.getId().equals(old.getId())) {
            throw exception(SAFETY_CERT_NO_DUPLICATE);
        }
        // 保留原状态中的「已注销」,不允许通过编辑随意改回
        if (SafetyCertificateStatusEnum.REVOKED.getStatus().equals(old.getStatus())
                && !SafetyCertificateStatusEnum.REVOKED.getStatus().equals(reqVO.getStatus())) {
            reqVO.setStatus(SafetyCertificateStatusEnum.REVOKED.getStatus());
        }
    }