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<HrmEmployeeContractDO> 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<Long, DeptRespDTO> 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<HrmEmployeeContractRespVO> getContractPage(HrmEmployeeContractPageReqVO pageReqVO) {
|
LocalDate today = LocalDate.now();
|
PageResult<HrmEmployeeContractDO> pageResult = contractMapper.selectPage(pageReqVO, today);
|
if (CollUtil.isEmpty(pageResult.getList())) {
|
return PageResult.empty();
|
}
|
// 批量获取员工信息
|
Set<Long> employeeIds = pageResult.getList().stream()
|
.map(HrmEmployeeContractDO::getEmployeeId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, HrmEmployeeDO> employeeMap = CollUtil.isEmpty(employeeIds) ? Collections.emptyMap() :
|
employeeService.getEmployeeList(employeeIds).stream()
|
.collect(Collectors.toMap(HrmEmployeeDO::getId, e -> e));
|
// 批量获取部门信息
|
Set<Long> deptIds = employeeMap.values().stream()
|
.map(HrmEmployeeDO::getDeptId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
|
// 批量获取续签上一份合同信息
|
Set<Long> parentIds = pageResult.getList().stream()
|
.map(HrmEmployeeContractDO::getParentId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, HrmEmployeeContractDO> parentMap = CollUtil.isEmpty(parentIds) ? Collections.emptyMap() :
|
contractMapper.selectList(HrmEmployeeContractDO::getId, parentIds).stream()
|
.collect(Collectors.toMap(HrmEmployeeContractDO::getId, c -> c));
|
// 组装 VO
|
List<HrmEmployeeContractRespVO> 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<HrmEmployeeContractDO> 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<Long, HrmEmployeeContractDO> 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<Long, DeptRespDTO> 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());
|
}
|
}
|
}
|
}
|
|
}
|