package cn.iocoder.yudao.module.hrm.service.approval;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.json.JSONUtil;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
|
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
|
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
|
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
|
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationPageReqVO;
|
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationRespVO;
|
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationApplicationSaveReqVO;
|
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmResignationHandoverReqVO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmResignationApplicationDO;
|
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmResignationApplicationMapper;
|
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
|
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
|
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
|
import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
|
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
|
import cn.iocoder.yudao.module.hrm.service.handover.HrmUserHandoverService;
|
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
import cn.iocoder.yudao.module.system.api.dept.PostApi;
|
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
|
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import org.flowable.engine.repository.ProcessDefinition;
|
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.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
import java.util.stream.Stream;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
|
|
/**
|
* 离职申请 Service 实现类
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class HrmResignationApplicationServiceImpl implements HrmResignationApplicationService {
|
|
private static final Integer HANDOVER_STATUS_NOT_STARTED = 0;
|
private static final Integer HANDOVER_STATUS_IN_PROGRESS = 10;
|
private static final Integer HANDOVER_STATUS_COMPLETED = 20;
|
|
/** 员工状态:离职 */
|
private static final Integer EMPLOYEE_STATUS_RESIGNED = 3;
|
|
@Resource
|
private HrmResignationApplicationMapper resignationApplicationMapper;
|
@Resource
|
private HrmNoRedisDAO noRedisDAO;
|
@Resource
|
private DeptApi deptApi;
|
@Resource
|
private PostApi postApi;
|
@Resource
|
private AdminUserApi adminUserApi;
|
@Resource
|
private BpmProcessInstanceApi bpmProcessInstanceApi;
|
@Resource
|
private BpmCategoryService bpmCategoryService;
|
@Resource
|
private StorageAttachmentApi storageAttachmentApi;
|
@Resource
|
private BpmProcessDefinitionService bpmProcessDefinitionService;
|
@Resource
|
private BpmProcessInstanceService bpmProcessInstanceService;
|
@Resource
|
private HrmUserHandoverService handoverService;
|
@Resource
|
private HrmEmployeeMapper employeeMapper;
|
@Resource
|
private HrmEmployeeService employeeService;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createResignationApplication(HrmResignationApplicationSaveReqVO createReqVO) {
|
Long userId = createReqVO.getUserId() != null ? createReqVO.getUserId() : getCurrentUserId();
|
AdminUserRespDTO user = adminUserApi.getUser(userId);
|
if (user == null) {
|
throw new ServiceException(RESIGNATION_APPLICANT_NOT_EXISTS.getCode(),
|
String.format("申请人(用户ID=%s)不存在,请重新选择申请人后重试", userId));
|
}
|
HrmResignationApplicationDO existing = resignationApplicationMapper.selectByUserIdAndStatusNot(userId,
|
HrmAuditStatusEnum.CANCEL.getStatus());
|
if (existing != null) {
|
throw new ServiceException(RESIGNATION_APPLICATION_EXISTS.getCode(),
|
String.format("申请人[%s]已存在未撤销的离职申请(单号=%s,状态=%s),请先撤销或删除原申请后再新建",
|
user.getNickname(), existing.getNo(),
|
HrmAuditStatusEnum.getNameByStatus(existing.getStatus())));
|
}
|
HrmResignationApplicationDO application = BeanUtils.toBean(createReqVO, HrmResignationApplicationDO.class);
|
application.setNo(generateUniqueNo());
|
application.setUserId(userId);
|
application.setDeptId(createReqVO.getDeptId() != null ? createReqVO.getDeptId() : user.getDeptId());
|
application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
|
application.setHandoverStatus(HANDOVER_STATUS_NOT_STARTED);
|
application.setHandoverDataStatus(HANDOVER_STATUS_NOT_STARTED);
|
// 统一解析交接员工与系统用户,确保员工ID、用户ID成对落库
|
resolveHandoverTarget(application, userId);
|
resignationApplicationMapper.insert(application);
|
storageAttachmentApi.bindAttachments("file", "hrm_resignation_application",
|
application.getId(), createReqVO.getBlobIds());
|
return application.getId();
|
}
|
|
private Long getCurrentUserId() {
|
return SecurityFrameworkUtils.getLoginUserId();
|
}
|
|
private String generateUniqueNo() {
|
String no = noRedisDAO.generateResignationApplicationNo();
|
if (resignationApplicationMapper.selectByNo(no) != null) {
|
return generateUniqueNo();
|
}
|
return no;
|
}
|
|
@Override
|
public void updateResignationApplication(HrmResignationApplicationSaveReqVO updateReqVO) {
|
HrmResignationApplicationDO application = validateResignationApplicationExists(updateReqVO.getId());
|
if (!isEditableResignationStatus(application.getStatus())) {
|
throw exception(RESIGNATION_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
|
}
|
HrmResignationApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmResignationApplicationDO.class);
|
// 不覆盖申请人;交接人统一解析成“员工ID + 用户ID”
|
updateObj.setUserId(null);
|
boolean handoverProvided = updateReqVO.getHandoverEmployeeId() != null
|
|| updateReqVO.getHandoverUserId() != null;
|
if (handoverProvided) {
|
resolveHandoverTarget(updateObj, application.getUserId());
|
Integer currentHandoverStatus = application.getHandoverStatus() != null
|
? application.getHandoverStatus() : HANDOVER_STATUS_NOT_STARTED;
|
if (ObjUtil.notEqual(currentHandoverStatus, HANDOVER_STATUS_NOT_STARTED)
|
&& !Objects.equals(updateObj.getHandoverEmployeeId(), application.getHandoverEmployeeId())) {
|
throw new ServiceException(RESIGNATION_HANDOVER_CHANGE_AFTER_START.getCode(),
|
String.format("当前交接状态为[%s],交接人[%s]不能再更换;请先将交接状态回退为[未开始];若已完成交接,请重新提交离职申请",
|
getHandoverStatusName(currentHandoverStatus),
|
getEmployeeDisplayName(application.getHandoverEmployeeId())));
|
}
|
}
|
resignationApplicationMapper.updateById(updateObj);
|
storageAttachmentApi.updateAttachments("file", "hrm_resignation_application", updateReqVO.getId(), updateReqVO.getBlobIds());
|
}
|
|
@Override
|
public void deleteResignationApplication(Long id) {
|
HrmResignationApplicationDO application = validateResignationApplicationExists(id);
|
if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
|
throw exception(RESIGNATION_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
|
}
|
storageAttachmentApi.deleteAttachmentsByRecord("hrm_resignation_application", id);
|
resignationApplicationMapper.deleteById(id);
|
}
|
|
private HrmResignationApplicationDO validateResignationApplicationExists(Long id) {
|
HrmResignationApplicationDO application = resignationApplicationMapper.selectById(id);
|
if (application == null) {
|
throw exception(RESIGNATION_APPLICATION_NOT_EXISTS);
|
}
|
return application;
|
}
|
|
private boolean isEditableResignationStatus(Integer status) {
|
return Objects.equals(status, HrmAuditStatusEnum.DRAFT.getStatus())
|
|| Objects.equals(status, HrmAuditStatusEnum.REJECT.getStatus());
|
}
|
|
@Override
|
public HrmResignationApplicationDO getResignationApplication(Long id) {
|
return resignationApplicationMapper.selectById(id);
|
}
|
|
@Override
|
public HrmResignationApplicationRespVO getResignationApplicationDetail(Long id) {
|
HrmResignationApplicationDO application = getResignationApplication(id);
|
if (application == null) {
|
return null;
|
}
|
HrmResignationApplicationRespVO respVO = BeanUtils.toBean(application, HrmResignationApplicationRespVO.class);
|
fillUserNames(respVO, application);
|
fillApplicantEmployeeInfo(respVO, application);
|
if (application.getDeptId() != null) {
|
DeptRespDTO dept = deptApi.getDept(application.getDeptId());
|
if (dept != null) {
|
respVO.setDeptName(dept.getName());
|
}
|
}
|
respVO.setResignationTypeName(HrmResignationTypeEnum.getNameByStatus(application.getResignationType()));
|
respVO.setHandoverStatusName(getHandoverStatusName(application.getHandoverStatus()));
|
respVO.setHandoverDataStatusName(getHandoverStatusName(application.getHandoverDataStatus()));
|
respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
|
respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_resignation_application", id));
|
return respVO;
|
}
|
|
private void fillUserNames(HrmResignationApplicationRespVO respVO, HrmResignationApplicationDO application) {
|
Set<Long> userIds = Stream.of(application.getUserId(), application.getHandoverUserId())
|
.filter(Objects::nonNull).collect(Collectors.toSet());
|
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
|
if (application.getUserId() != null && userMap.containsKey(application.getUserId())) {
|
respVO.setUserName(userMap.get(application.getUserId()).getNickname());
|
}
|
if (application.getHandoverUserId() != null && userMap.containsKey(application.getHandoverUserId())) {
|
respVO.setHandoverUserName(userMap.get(application.getHandoverUserId()).getNickname());
|
}
|
}
|
|
/**
|
* 填充申请人员工信息:员工ID 与岗位名称,供前端回显"申请人 + 岗位"
|
*/
|
private void fillApplicantEmployeeInfo(HrmResignationApplicationRespVO respVO,
|
HrmResignationApplicationDO application) {
|
if (application.getUserId() == null) {
|
return;
|
}
|
HrmEmployeeDO employee = employeeService.getEmployeeByUserId(application.getUserId());
|
if (employee == null) {
|
return;
|
}
|
respVO.setEmployeeId(employee.getId());
|
respVO.setEmployeeName(employee.getName());
|
if (StrUtil.isBlank(employee.getPostIds())) {
|
return;
|
}
|
List<Long> postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
|
if (CollUtil.isEmpty(postIds)) {
|
return;
|
}
|
Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
|
respVO.setPostName(postIds.stream()
|
.filter(postMap::containsKey)
|
.map(id -> postMap.get(id).getName())
|
.collect(Collectors.joining(",")));
|
}
|
|
private String getHandoverStatusName(Integer handoverStatus) {
|
if (handoverStatus == null) {
|
return "";
|
}
|
if (Objects.equals(handoverStatus, HANDOVER_STATUS_NOT_STARTED)) {
|
return "未开始";
|
}
|
if (Objects.equals(handoverStatus, HANDOVER_STATUS_IN_PROGRESS)) {
|
return "进行中";
|
}
|
if (Objects.equals(handoverStatus, HANDOVER_STATUS_COMPLETED)) {
|
return "已完成";
|
}
|
return "";
|
}
|
|
@Override
|
public PageResult<HrmResignationApplicationRespVO> getResignationApplicationPage(HrmResignationApplicationPageReqVO pageReqVO) {
|
PageResult<HrmResignationApplicationDO> pageResult = resignationApplicationMapper.selectPage(pageReqVO);
|
if (pageResult.getList().isEmpty()) {
|
return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
|
}
|
Set<Long> userIds = pageResult.getList().stream()
|
.flatMap(app -> Stream.of(app.getUserId(), app.getHandoverUserId()))
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
|
Set<Long> deptIds = pageResult.getList().stream()
|
.map(HrmResignationApplicationDO::getDeptId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
|
List<HrmResignationApplicationRespVO> list = pageResult.getList().stream().map(app -> {
|
HrmResignationApplicationRespVO vo = BeanUtils.toBean(app, HrmResignationApplicationRespVO.class);
|
if (app.getUserId() != null && userMap.containsKey(app.getUserId())) {
|
vo.setUserName(userMap.get(app.getUserId()).getNickname());
|
}
|
// 申请人员工信息:员工未建档时保持为空
|
HrmEmployeeDO applicantEmployee = app.getUserId() == null ? null
|
: employeeService.getEmployeeByUserId(app.getUserId());
|
if (applicantEmployee != null) {
|
vo.setEmployeeId(applicantEmployee.getId());
|
vo.setEmployeeName(applicantEmployee.getName());
|
}
|
if (app.getHandoverUserId() != null && userMap.containsKey(app.getHandoverUserId())) {
|
vo.setHandoverUserName(userMap.get(app.getHandoverUserId()).getNickname());
|
}
|
if (app.getDeptId() != null && deptMap.containsKey(app.getDeptId())) {
|
vo.setDeptName(deptMap.get(app.getDeptId()).getName());
|
}
|
vo.setResignationTypeName(HrmResignationTypeEnum.getNameByStatus(app.getResignationType()));
|
vo.setHandoverStatusName(getHandoverStatusName(app.getHandoverStatus()));
|
vo.setHandoverDataStatusName(getHandoverStatusName(app.getHandoverDataStatus()));
|
vo.setStatusName(HrmAuditStatusEnum.getNameByStatus(app.getStatus()));
|
return vo;
|
}).collect(Collectors.toList());
|
return new PageResult<>(list, pageResult.getTotal());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void submitResignationApplication(Long id, String processDefinitionKey, Long userId) {
|
HrmResignationApplicationDO application = validateResignationApplicationExists(id);
|
if (!isEditableResignationStatus(application.getStatus())) {
|
throw exception(RESIGNATION_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
|
}
|
// 全局免审批:业务校验通过后直接执行审批通过逻辑,无需流程模型。
|
if (bpmProcessInstanceApi.isGlobalAutoApprove()) {
|
resignationApplicationMapper.startApproval(id, "", HrmAuditStatusEnum.PROCESS.getStatus());
|
updateResignationApplicationAuditStatus(id, HrmAuditStatusEnum.APPROVE.getStatus());
|
return;
|
}
|
|
ProcessDefinition processDefinition = resolveActiveProcessDefinition(processDefinitionKey);
|
String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
|
.setProcessDefinitionKey(processDefinition.getKey()).setBusinessKey(String.valueOf(id)));
|
resignationApplicationMapper.startApproval(id, processInstanceId, HrmAuditStatusEnum.PROCESS.getStatus());
|
}
|
|
private void validateApproveCategoryAndProcessDefinition() {
|
List<String> codes = Collections.singletonList(APPROVE_CATEGORY_CODE);
|
Map<String, ?> categoryMap = bpmCategoryService.getCategoryMap(codes);
|
if (!categoryMap.containsKey(APPROVE_CATEGORY_CODE)) {
|
throw exception(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
|
}
|
List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
|
.getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
|
if (CollUtil.isEmpty(definitionInfoList)) {
|
throw exception(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
|
}
|
Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
|
List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
|
boolean hasActiveDefinition = processDefinitions.stream().anyMatch(pd -> !pd.isSuspended());
|
if (!hasActiveDefinition) {
|
throw exception(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
|
}
|
}
|
|
/**
|
* 解析离职审批可用的流程定义
|
* <p>
|
* 前端历史版本把分类编码(hrm_resignation_approve)当作流程 Key 传入,
|
* 这里优先使用传入 Key,匹配不到时回退到标准 Key,再回退到该分类下唯一的激活流程。
|
*/
|
private ProcessDefinition resolveActiveProcessDefinition(String processDefinitionKey) {
|
validateApproveCategoryAndProcessDefinition();
|
List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
|
.getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
|
Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
|
List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
|
Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
|
for (ProcessDefinition pd : processDefinitions) {
|
if (pd.isSuspended()) {
|
continue;
|
}
|
ProcessDefinition existing = latestVersionMap.get(pd.getKey());
|
if (existing == null || pd.getVersion() > existing.getVersion()) {
|
latestVersionMap.put(pd.getKey(), pd);
|
}
|
}
|
if (latestVersionMap.isEmpty()) {
|
throw exception(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
|
}
|
if (StrUtil.isNotBlank(processDefinitionKey) && latestVersionMap.containsKey(processDefinitionKey)) {
|
return latestVersionMap.get(processDefinitionKey);
|
}
|
if (latestVersionMap.containsKey(BPM_PROCESS_DEFINITION_KEY)) {
|
return latestVersionMap.get(BPM_PROCESS_DEFINITION_KEY);
|
}
|
if (latestVersionMap.size() == 1) {
|
return latestVersionMap.values().iterator().next();
|
}
|
throw new ServiceException(RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS.getCode(),
|
String.format("离职审批流程不唯一(分类=%s,可用Key=%s),请传入具体的流程定义Key后重试",
|
APPROVE_CATEGORY_CODE, String.join(",", latestVersionMap.keySet())));
|
}
|
|
@Override
|
public List<Map<String, Object>> getApproveProcessDefinitionList() {
|
validateApproveCategoryAndProcessDefinition();
|
List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
|
.getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
|
Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
|
List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
|
Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
|
for (ProcessDefinition pd : processDefinitions) {
|
if (pd.isSuspended()) continue;
|
ProcessDefinition existing = latestVersionMap.get(pd.getKey());
|
if (existing == null || pd.getVersion() > existing.getVersion()) {
|
latestVersionMap.put(pd.getKey(), pd);
|
}
|
}
|
List<Map<String, Object>> result = new ArrayList<>();
|
for (ProcessDefinition pd : latestVersionMap.values()) {
|
Map<String, Object> item = new HashMap<>();
|
item.put("id", pd.getId());
|
item.put("key", pd.getKey());
|
item.put("name", pd.getName());
|
item.put("version", pd.getVersion());
|
result.add(item);
|
}
|
return result;
|
}
|
|
@Override
|
public void updateResignationApplicationAuditStatus(Long id, Integer status) {
|
HrmResignationApplicationDO application = validateResignationApplicationExists(id);
|
if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
|
log.error("[updateResignationApplicationAuditStatus][application({}) 不处于审批中,无法更新审批结果({})]", id, status);
|
throw exception(RESIGNATION_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
|
}
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(id)
|
.status(status)
|
.approveTime(java.time.LocalDateTime.now())
|
.build());
|
}
|
|
@Override
|
public HrmResignationApplicationDO getResignationApplicationByProcessInstanceId(String processInstanceId) {
|
return resignationApplicationMapper.selectByProcessInstanceId(processInstanceId);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateHandoverStatus(HrmResignationHandoverReqVO reqVO) {
|
HrmResignationApplicationDO application = validateResignationApplicationExists(reqVO.getId());
|
Integer targetStatus = reqVO.getHandoverStatus();
|
if (!Objects.equals(targetStatus, HANDOVER_STATUS_NOT_STARTED)
|
&& !Objects.equals(targetStatus, HANDOVER_STATUS_IN_PROGRESS)
|
&& !Objects.equals(targetStatus, HANDOVER_STATUS_COMPLETED)) {
|
throw new ServiceException(RESIGNATION_HANDOVER_STATUS_INVALID.getCode(),
|
String.format("交接状态值[%s]不合法,仅支持 0(未开始)、10(进行中)、20(已完成)", targetStatus));
|
}
|
if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.APPROVE.getStatus())) {
|
throw new ServiceException(RESIGNATION_HANDOVER_NOT_APPROVED.getCode(),
|
String.format("离职申请[%s]当前审批状态为[%s],审批通过后才能交接;请先完成审批再操作",
|
application.getNo(), HrmAuditStatusEnum.getNameByStatus(application.getStatus())));
|
}
|
Integer currentStatus = application.getHandoverStatus() != null
|
? application.getHandoverStatus() : HANDOVER_STATUS_NOT_STARTED;
|
if (Objects.equals(currentStatus, HANDOVER_STATUS_COMPLETED)
|
&& !Objects.equals(targetStatus, HANDOVER_STATUS_COMPLETED)) {
|
throw new ServiceException(RESIGNATION_HANDOVER_COMPLETED_CANNOT_REVERT.getCode(),
|
String.format("当前交接状态为[已完成],不能修改为[%s];如需重新交接,请删除该离职申请后重新提交",
|
getHandoverStatusName(targetStatus)));
|
}
|
// 审批通过后允许一步完成交接:未开始或进行中均可直接置为已完成,避免多一次无意义的点击
|
if (Objects.equals(targetStatus, HANDOVER_STATUS_COMPLETED)) {
|
completeHandover(application);
|
return;
|
}
|
if (Objects.equals(targetStatus, HANDOVER_STATUS_NOT_STARTED)
|
&& !Objects.equals(currentStatus, HANDOVER_STATUS_NOT_STARTED)
|
&& !Objects.equals(currentStatus, HANDOVER_STATUS_IN_PROGRESS)) {
|
throw new ServiceException(RESIGNATION_HANDOVER_COMPLETED_CANNOT_REVERT.getCode(),
|
String.format("当前交接状态为[%s],不能回退为[未开始];如需重新交接,请删除该离职申请后重新提交",
|
getHandoverStatusName(currentStatus)));
|
}
|
// 进行中:必须已指定交接员工;未开始:允许在未指定交接人时回退
|
if (Objects.equals(targetStatus, HANDOVER_STATUS_IN_PROGRESS)) {
|
resolveStoredHandoverTarget(application);
|
}
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(application.getId())
|
.handoverStatus(targetStatus)
|
.handoverDataStatus(targetStatus)
|
.build());
|
}
|
|
/**
|
* 完成工作交接:写入 员工ID + 用户ID 交接映射,交接人通过 expandUserIds 获得离职人的业务数据
|
*/
|
private void completeHandover(HrmResignationApplicationDO application) {
|
resolveStoredHandoverTarget(application);
|
// 申请人未建员工档案时(如管理员账号),仍建立用户级交接映射,保证其业务数据能转交;员工侧 ID 为空
|
HrmEmployeeDO fromEmployee = employeeService.getEmployeeByUserId(application.getUserId());
|
if (fromEmployee == null) {
|
log.warn("[completeHandover][离职申请({}) 申请人(用户ID={}) 未关联员工档案,仅建立用户级交接映射]",
|
application.getNo(), application.getUserId());
|
}
|
handoverService.createHandover(fromEmployee != null ? fromEmployee.getId() : null,
|
application.getUserId(),
|
application.getHandoverEmployeeId(), application.getHandoverUserId(), application.getId());
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(application.getId())
|
.handoverStatus(HANDOVER_STATUS_COMPLETED)
|
.handoverDataStatus(HANDOVER_STATUS_COMPLETED)
|
.build());
|
}
|
|
/**
|
* 校验并回填已保存的交接人(兼容历史数据只存了 handoverUserId 的情况)
|
*/
|
private void resolveStoredHandoverTarget(HrmResignationApplicationDO application) {
|
if (application.getHandoverEmployeeId() == null && application.getHandoverUserId() == null) {
|
throw new ServiceException(RESIGNATION_HANDOVER_TARGET_REQUIRED.getCode(),
|
String.format("离职申请[%s]未指定交接员工,无法开始或完成交接;请先编辑申请并选择交接人",
|
application.getNo()));
|
}
|
resolveHandoverTarget(application, application.getUserId());
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(application.getId())
|
.handoverEmployeeId(application.getHandoverEmployeeId())
|
.handoverUserId(application.getHandoverUserId())
|
.build());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void cancelResignationApplication(Long id, Long userId) {
|
HrmResignationApplicationDO application = validateResignationApplicationExists(id);
|
Integer status = application.getStatus();
|
if (Objects.equals(status, HrmAuditStatusEnum.DRAFT.getStatus())) {
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(id)
|
.status(HrmAuditStatusEnum.CANCEL.getStatus())
|
.build());
|
return;
|
}
|
if (!Objects.equals(status, HrmAuditStatusEnum.PROCESS.getStatus())) {
|
throw new ServiceException(RESIGNATION_APPLICATION_CANCEL_FAIL_STATUS.getCode(),
|
String.format("离职申请[%s]当前审批状态为[%s],仅草稿或审批中状态可以撤销",
|
application.getNo(), HrmAuditStatusEnum.getNameByStatus(status)));
|
}
|
if (StrUtil.isNotBlank(application.getProcessInstanceId())) {
|
BpmProcessInstanceCancelReqVO cancelReqVO = new BpmProcessInstanceCancelReqVO();
|
cancelReqVO.setId(application.getProcessInstanceId());
|
cancelReqVO.setReason("申请人撤销离职申请:" + application.getNo());
|
Long startUserId = userId != null ? userId : application.getUserId();
|
bpmProcessInstanceService.cancelProcessInstanceByStartUser(startUserId, cancelReqVO);
|
// 流程监听器会同步更新状态;兜底处理未收到回调的场景
|
HrmResignationApplicationDO latest = resignationApplicationMapper.selectById(id);
|
if (latest != null && Objects.equals(latest.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(id)
|
.status(HrmAuditStatusEnum.CANCEL.getStatus())
|
.build());
|
}
|
return;
|
}
|
// 无流程实例(历史免审批数据)直接撤销
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(id)
|
.status(HrmAuditStatusEnum.CANCEL.getStatus())
|
.build());
|
}
|
|
@Override
|
public int autoLeaveResignationApplication() {
|
LocalDate today = LocalDate.now();
|
List<HrmResignationApplicationDO> applications = resignationApplicationMapper
|
.selectListByStatusAndExpectedDateLe(HrmAuditStatusEnum.APPROVE.getStatus(), today);
|
int count = 0;
|
for (HrmResignationApplicationDO application : applications) {
|
if (application.getUserId() == null) {
|
log.warn("[autoLeaveResignationApplication][申请({}) 缺少申请人用户ID,跳过]", application.getId());
|
continue;
|
}
|
// 1. 停用系统登录账号
|
AdminUserRespDTO user = adminUserApi.getUser(application.getUserId());
|
if (user == null) {
|
log.warn("[autoLeaveResignationApplication][申请({}) 申请人(用户ID={}) 不存在,跳过账号停用]",
|
application.getId(), application.getUserId());
|
} else if (!Objects.equals(user.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
|
adminUserApi.updateUserStatus(application.getUserId(), CommonStatusEnum.DISABLE.getStatus());
|
log.info("[autoLeaveResignationApplication][用户({}) 到达离职日期({}),系统账号已停用]",
|
application.getUserId(), application.getExpectedDate());
|
}
|
// 2. 员工档案置为离职
|
HrmEmployeeDO employee = employeeService.getEmployeeByUserId(application.getUserId());
|
if (employee == null) {
|
log.warn("[autoLeaveResignationApplication][申请({}) 申请人(用户ID={}) 无员工档案,跳过;请先建立员工档案]",
|
application.getId(), application.getUserId());
|
continue;
|
}
|
if (!Objects.equals(employee.getEmployeeStatus(), EMPLOYEE_STATUS_RESIGNED)) {
|
employeeService.updateEmployeeStatusToResignedByUserId(application.getUserId(),
|
application.getExpectedDate());
|
log.info("[autoLeaveResignationApplication][员工({}) 到达离职日期({}),状态已更新为离职]",
|
employee.getId(), application.getExpectedDate());
|
}
|
resignationApplicationMapper.updateById(HrmResignationApplicationDO.builder()
|
.id(application.getId())
|
.actualDate(application.getExpectedDate())
|
.build());
|
count++;
|
}
|
return count;
|
}
|
|
/**
|
* 解析交接员工与系统用户,保证 handoverEmployeeId / handoverUserId 成对且有效
|
* <p>
|
* 兼容前端把“交接人”下拉的员工ID写入 handoverUserId 的历史提交方式:
|
* 先按 handoverEmployeeId 解析;没有时把 handoverUserId 当员工ID尝试;再按用户ID解析。
|
*/
|
private void resolveHandoverTarget(HrmResignationApplicationDO application, Long applyUserId) {
|
Long employeeId = application.getHandoverEmployeeId();
|
Long userId = application.getHandoverUserId();
|
if (employeeId == null && userId == null) {
|
return;
|
}
|
HrmEmployeeDO employee;
|
if (employeeId != null) {
|
employee = employeeMapper.selectById(employeeId);
|
if (employee == null) {
|
throw new ServiceException(RESIGNATION_HANDOVER_EMPLOYEE_NOT_EXISTS.getCode(),
|
String.format("交接员工(ID=%s)不存在,请重新选择交接员工", employeeId));
|
}
|
} else {
|
// 历史前端:handoverUserId 实际传的是员工ID
|
employee = employeeMapper.selectById(userId);
|
if (employee == null) {
|
AdminUserRespDTO user = adminUserApi.getUser(userId);
|
if (user == null) {
|
throw new ServiceException(RESIGNATION_HANDOVER_EMPLOYEE_NOT_EXISTS.getCode(),
|
String.format("交接人(ID=%s)在员工档案和系统用户中均不存在,请重新选择交接员工", userId));
|
}
|
employee = employeeMapper.selectByUserId(userId);
|
if (employee == null) {
|
throw new ServiceException(RESIGNATION_HANDOVER_EMPLOYEE_NO_USER.getCode(),
|
String.format("交接人[%s](用户ID=%s)未关联员工档案,无法确定交接员工;请先在员工管理中为该用户建立员工档案",
|
user.getNickname(), userId));
|
}
|
}
|
employeeId = employee.getId();
|
}
|
if (employee.getUserId() == null) {
|
throw new ServiceException(RESIGNATION_HANDOVER_EMPLOYEE_NO_USER.getCode(),
|
String.format("交接员工[%s](员工ID=%s)未关联系统用户,无法转移其用户数据;请先在员工管理中关联系统用户",
|
employee.getName(), employee.getId()));
|
}
|
if (applyUserId != null && Objects.equals(employee.getUserId(), applyUserId)) {
|
throw new ServiceException(RESIGNATION_HANDOVER_SELF.getCode(),
|
String.format("交接人[%s](员工ID=%s)与申请人是同一人,请选择其他员工",
|
employee.getName(), employee.getId()));
|
}
|
if (Objects.equals(employee.getEmployeeStatus(), EMPLOYEE_STATUS_RESIGNED)) {
|
throw new ServiceException(RESIGNATION_HANDOVER_EMPLOYEE_RESIGNED.getCode(),
|
String.format("交接员工[%s](员工ID=%s)当前已是离职状态,请重新选择在职员工",
|
employee.getName(), employee.getId()));
|
}
|
application.setHandoverEmployeeId(employeeId);
|
application.setHandoverUserId(employee.getUserId());
|
}
|
|
private String getEmployeeDisplayName(Long employeeId) {
|
if (employeeId == null) {
|
return "未选择";
|
}
|
HrmEmployeeDO employee = employeeMapper.selectById(employeeId);
|
return employee != null ? String.format("%s(ID=%s)", employee.getName(), employeeId)
|
: String.format("ID=%s", employeeId);
|
}
|
|
}
|