package cn.iocoder.yudao.module.hrm.service.employee; 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.controller.admin.employee.vo.HrmEmployeeContractPageReqVO; import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractRespVO; import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractSaveReqVO; import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractTerminateReqVO; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeContractDO; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeContractMapper; import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; 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.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*; /** * 员工合同 Service 实现类 * * @author 超级管理员 */ @Service @Validated public class HrmEmployeeContractServiceImpl implements HrmEmployeeContractService { /** * 附件业务记录类型 */ private static final String RECORD_TYPE = "hrm_employee_contract"; @Resource private HrmEmployeeContractMapper contractMapper; @Resource private HrmEmployeeService employeeService; @Resource private DeptApi deptApi; @Resource private StorageAttachmentApi storageAttachmentApi; @Resource private HrmNoRedisDAO noRedisDAO; @Override @Transactional(rollbackFor = Exception.class) public Long createContract(HrmEmployeeContractSaveReqVO createReqVO) { // 1. 校验员工存在 employeeService.validateEmployee(createReqVO.getEmployeeId()); // 2. 校验合同日期 validateContractDate(createReqVO); // 3. 校验同一员工时间重叠的合同 validateNoOverlap(createReqVO.getEmployeeId(), createReqVO.getStartDate(), createReqVO.getEndDate(), null); // 4. 生成合同编号 String contractNo = noRedisDAO.generateEmployeeContractNo(); // 5. 查询该员工已有合同,确定续签关系 List existing = contractMapper.selectListByEmployee(createReqVO.getEmployeeId()); // 6. 插入合同记录(新签合同即员工当前合同) HrmEmployeeContractDO contract = BeanUtils.toBean(createReqVO, HrmEmployeeContractDO.class); contract.setContractNo(contractNo); contract.setIsCurrent(true); // 续签:优先使用前端指定的 parentId(用户点击的那份合同),否则自动关联员工最近一份合同 if (createReqVO.getParentId() == null) { contract.setParentId(CollUtil.isEmpty(existing) ? null : existing.get(0).getId()); } if (contract.getTerminateStatus() == null) { contract.setTerminateStatus(HrmEmployeeContractMapper.TERMINATE_NORMAL); } // 7. 先清除旧合同的"当前合同"标记,再插入新合同 contractMapper.clearCurrentByEmployee(createReqVO.getEmployeeId()); contractMapper.insert(contract); // 8. 绑定附件 if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) { storageAttachmentApi.bindAttachments("file", RECORD_TYPE, contract.getId(), createReqVO.getBlobIds()); } return contract.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateContract(HrmEmployeeContractSaveReqVO updateReqVO) { // 1. 校验存在 validateContractExists(updateReqVO.getId()); // 2. 校验员工存在 employeeService.validateEmployee(updateReqVO.getEmployeeId()); // 3. 校验合同日期 validateContractDate(updateReqVO); // 4. 校验同一员工时间重叠的合同(排除自身) validateNoOverlap(updateReqVO.getEmployeeId(), updateReqVO.getStartDate(), updateReqVO.getEndDate(), updateReqVO.getId()); // 5. 更新合同记录 HrmEmployeeContractDO updateObj = BeanUtils.toBean(updateReqVO, HrmEmployeeContractDO.class); contractMapper.updateById(updateObj); // 6. 更新附件 storageAttachmentApi.updateAttachments("file", RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds()); } @Override @Transactional(rollbackFor = Exception.class) public void deleteContract(Long id) { // 1. 校验存在 validateContractExists(id); // 2. 删除合同 contractMapper.deleteById(id); // 3. 删除附件 storageAttachmentApi.deleteAttachmentsByRecord(RECORD_TYPE, id); } @Override @Transactional(rollbackFor = Exception.class) public void terminateContract(HrmEmployeeContractTerminateReqVO reqVO) { // 1. 校验存在 HrmEmployeeContractDO contract = validateContractExists(reqVO.getId()); // 2. 校验解除/终止参数 Integer terminateStatus = reqVO.getTerminateStatus(); if (terminateStatus == null || (terminateStatus != HrmEmployeeContractMapper.TERMINATE_CANCELED && terminateStatus != HrmEmployeeContractMapper.TERMINATE_TERMINATED) || reqVO.getTerminateDate() == null || StrUtil.isBlank(reqVO.getTerminateReason())) { throw exception(EMPLOYEE_CONTRACT_TERMINATE_REQUIRE_DATE_REASON); } // 3. 已解除/已终止的合同不允许重复操作 if (contract.getTerminateStatus() != null && contract.getTerminateStatus() != HrmEmployeeContractMapper.TERMINATE_NORMAL) { throw exception(EMPLOYEE_CONTRACT_TERMINATE_REQUIRE_DATE_REASON); } // 4. 更新解除/终止信息 HrmEmployeeContractDO updateObj = new HrmEmployeeContractDO(); updateObj.setId(contract.getId()); updateObj.setTerminateStatus(terminateStatus); updateObj.setTerminateDate(reqVO.getTerminateDate()); updateObj.setTerminateReason(reqVO.getTerminateReason()); // 5. 若解除/终止的是员工当前合同,清除当前标记 if (Boolean.TRUE.equals(contract.getIsCurrent())) { updateObj.setIsCurrent(false); } contractMapper.updateById(updateObj); } @Override public HrmEmployeeContractRespVO getContract(Long id) { HrmEmployeeContractDO contract = validateContractExists(id); HrmEmployeeContractRespVO respVO = BeanUtils.toBean(contract, HrmEmployeeContractRespVO.class); // 填充员工信息 HrmEmployeeDO employee = employeeService.getEmployee(contract.getEmployeeId()); if (employee != null) { Map deptMap = deptApi.getDeptMap(Collections.singleton(employee.getDeptId())); fillEmployeeInfo(respVO, employee, deptMap); } // 填充续签上一份合同编号 fillParentNo(respVO, contract.getParentId()); // 填充附件 respVO.setAttachmentList(storageAttachmentApi.listAttachments(RECORD_TYPE, id)); // 动态计算状态 respVO.setStatus(calculateStatus(contract, LocalDate.now())); return respVO; } @Override public PageResult getContractPage(HrmEmployeeContractPageReqVO pageReqVO) { LocalDate today = LocalDate.now(); PageResult pageResult = contractMapper.selectPage(pageReqVO, today); if (CollUtil.isEmpty(pageResult.getList())) { return PageResult.empty(); } // 批量获取员工信息 Set employeeIds = pageResult.getList().stream() .map(HrmEmployeeContractDO::getEmployeeId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map employeeMap = CollUtil.isEmpty(employeeIds) ? Collections.emptyMap() : employeeService.getEmployeeList(employeeIds).stream() .collect(Collectors.toMap(HrmEmployeeDO::getId, e -> e)); // 批量获取部门信息 Set deptIds = employeeMap.values().stream() .map(HrmEmployeeDO::getDeptId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds); // 批量获取续签上一份合同信息 Set parentIds = pageResult.getList().stream() .map(HrmEmployeeContractDO::getParentId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map parentMap = CollUtil.isEmpty(parentIds) ? Collections.emptyMap() : contractMapper.selectList(HrmEmployeeContractDO::getId, parentIds).stream() .collect(Collectors.toMap(HrmEmployeeContractDO::getId, c -> c)); // 组装 VO List list = pageResult.getList().stream().map(contract -> { HrmEmployeeContractRespVO respVO = BeanUtils.toBean(contract, HrmEmployeeContractRespVO.class); fillEmployeeInfo(respVO, employeeMap.get(contract.getEmployeeId()), deptMap); fillParentNo(respVO, contract.getParentId(), parentMap); respVO.setStatus(calculateStatus(contract, today)); return respVO; }).collect(Collectors.toList()); return new PageResult<>(list, pageResult.getTotal()); } @Override public Long getExpiringCount() { return contractMapper.selectCountByExpiring(LocalDate.now()); } private HrmEmployeeContractDO validateContractExists(Long id) { HrmEmployeeContractDO contract = contractMapper.selectById(id); if (contract == null) { throw exception(EMPLOYEE_CONTRACT_NOT_EXISTS); } return contract; } /** * 校验同一员工时间重叠的合同,避免一个员工同时存在多份未解除/未终止的生效合同 */ private void validateNoOverlap(Long employeeId, LocalDate startDate, LocalDate endDate, Long excludeId) { List overlaps = contractMapper.selectOverlapping(employeeId, startDate, endDate, excludeId); if (CollUtil.isNotEmpty(overlaps)) { throw exception(EMPLOYEE_CONTRACT_DATE_OVERLAP); } } /** * 填充续签上一份合同编号 */ private void fillParentNo(HrmEmployeeContractRespVO respVO, Long parentId) { if (parentId != null) { HrmEmployeeContractDO parent = contractMapper.selectById(parentId); if (parent != null) { respVO.setParentNo(parent.getContractNo()); } } } /** * 批量填充续签上一份合同编号 */ private void fillParentNo(HrmEmployeeContractRespVO respVO, Long parentId, Map parentMap) { if (parentId != null) { HrmEmployeeContractDO parent = parentMap.get(parentId); if (parent != null) { respVO.setParentNo(parent.getContractNo()); } } } private void validateContractDate(HrmEmployeeContractSaveReqVO reqVO) { // 非无固定期限合同(期限类型 != 2)必须填写结束日期 if (!Integer.valueOf(2).equals(reqVO.getContractTermType()) && reqVO.getEndDate() == null) { throw exception(EMPLOYEE_CONTRACT_END_DATE_REQUIRE_FOR_FIXED); } // 开始日期不能晚于结束日期 if (reqVO.getStartDate() != null && reqVO.getEndDate() != null && reqVO.getStartDate().isAfter(reqVO.getEndDate())) { throw exception(EMPLOYEE_CONTRACT_DATE_INVALID); } // 试用期开始日期不能晚于试用期结束日期 if (reqVO.getProbationStartDate() != null && reqVO.getProbationEndDate() != null && reqVO.getProbationStartDate().isAfter(reqVO.getProbationEndDate())) { throw exception(EMPLOYEE_CONTRACT_DATE_INVALID); } } /** * 动态计算合同状态 * * @param contract 合同记录 * @param today 当前日期 * @return 状态:1-待生效 2-生效中 3-即将到期 4-已到期 5-已解除 6-已终止 */ private Integer calculateStatus(HrmEmployeeContractDO contract, LocalDate today) { Integer terminateStatus = contract.getTerminateStatus(); if (terminateStatus != null && terminateStatus == HrmEmployeeContractMapper.TERMINATE_CANCELED) { return HrmEmployeeContractPageReqVO.STATUS_CANCELED; } if (terminateStatus != null && terminateStatus == HrmEmployeeContractMapper.TERMINATE_TERMINATED) { return HrmEmployeeContractPageReqVO.STATUS_TERMINATED; } LocalDate endDate = contract.getEndDate(); if (endDate != null && endDate.isBefore(today)) { return HrmEmployeeContractPageReqVO.STATUS_EXPIRED; } LocalDate startDate = contract.getStartDate(); if (startDate != null && startDate.isAfter(today)) { return HrmEmployeeContractPageReqVO.STATUS_PENDING; } if (endDate != null && !endDate.isAfter(today.plusDays(HrmEmployeeContractMapper.REMIND_DAYS))) { return HrmEmployeeContractPageReqVO.STATUS_EXPIRING_SOON; } return HrmEmployeeContractPageReqVO.STATUS_ACTIVE; } private void fillEmployeeInfo(HrmEmployeeContractRespVO respVO, HrmEmployeeDO employee, Map deptMap) { if (employee != null) { respVO.setEmployeeName(employee.getName()); respVO.setEmployeeNo(employee.getEmployeeNo()); if (employee.getDeptId() != null && deptMap != null) { DeptRespDTO dept = deptMap.get(employee.getDeptId()); if (dept != null) { respVO.setDeptName(dept.getName()); } } } } }