yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -1,19 +1,24 @@
package cn.iocoder.yudao.module.hrm.service.approval;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
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.HrmTransferApplicationPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmTransferApplicationSaveReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmTransferApplicationDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmTransferApplicationMapper;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmTransferTypeEnum;
@@ -33,6 +38,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -48,7 +54,7 @@
/**
 * 调岗申请 Service 实现类
 *
 * @author 芋道源码
 * @author 超级管理员
 */
@Service
@Validated
@@ -66,6 +72,8 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private HrmEmployeeService employeeService;
    @Resource
    private BpmProcessInstanceApi bpmProcessInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
@@ -73,6 +81,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -87,14 +97,14 @@
        }
        // 3. 生成申请单号
        String no = generateUniqueNo();
        // 4. 设置申请人信息
        // 4. 设置调岗人(员工ID):为空时取当前登录用户对应的员工
        Long userId = createReqVO.getUserId();
        if (userId == null) {
            userId = getCurrentUserId();
            HrmEmployeeDO currentEmployee = employeeService.getEmployeeByUserId(getCurrentUserId());
            userId = currentEmployee != null ? currentEmployee.getId() : null;
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        if (user == null) {
            userId = getCurrentUserId();
        if (userId != null && employeeService.getEmployee(userId) == null) {
            throw exception(EMPLOYEE_NOT_EXISTS);
        }
        // 5. 插入
        HrmTransferApplicationDO application = BeanUtils.toBean(createReqVO, HrmTransferApplicationDO.class);
@@ -168,9 +178,9 @@
        }
        HrmTransferApplicationRespVO respVO = BeanUtils.toBean(application, HrmTransferApplicationRespVO.class);
        if (application.getUserId() != null) {
            AdminUserRespDTO user = adminUserApi.getUser(application.getUserId());
            if (user != null) {
                respVO.setUserName(user.getNickname());
            HrmEmployeeDO employee = employeeService.getEmployee(application.getUserId());
            if (employee != null) {
                respVO.setUserName(employee.getName());
            }
        }
        fillDeptNames(respVO, application);
@@ -215,7 +225,14 @@
                .map(HrmTransferApplicationDO::getUserId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
        // userId 存的是员工ID,按员工表查询姓名
        Map<Long, String> employeeNameMap = new HashMap<>();
        for (Long userId : userIds) {
            HrmEmployeeDO employee = employeeService.getEmployee(userId);
            if (employee != null) {
                employeeNameMap.put(userId, employee.getName());
            }
        }
        Set<Long> deptIds = pageResult.getList().stream()
                .flatMap(app -> Stream.of(app.getCurrentDeptId(), app.getTargetDeptId()))
                .filter(Objects::nonNull)
@@ -228,8 +245,8 @@
        Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
        List<HrmTransferApplicationRespVO> list = pageResult.getList().stream().map(app -> {
            HrmTransferApplicationRespVO vo = BeanUtils.toBean(app, HrmTransferApplicationRespVO.class);
            if (app.getUserId() != null && userMap.containsKey(app.getUserId())) {
                vo.setUserName(userMap.get(app.getUserId()).getNickname());
            if (app.getUserId() != null && employeeNameMap.containsKey(app.getUserId())) {
                vo.setUserName(employeeNameMap.get(app.getUserId()));
            }
            if (app.getCurrentDeptId() != null && deptMap.containsKey(app.getCurrentDeptId())) {
                vo.setCurrentDeptName(deptMap.get(app.getCurrentDeptId()).getName());
@@ -258,17 +275,41 @@
            throw exception(TRANSFER_APPLICATION_SUBMIT_FAIL_NOT_DRAFT);
        }
        validateApproveCategoryAndProcessDefinition();
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
        // 未指定流程定义 Key 时,自动取「调动审批」分类下当前活跃的流程,
        // 避免前端未传 key 导致接口参数缺失直接报系统异常
        String key = resolveProcessDefinitionKey(processDefinitionKey);
        ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(key);
        if (processDefinition == null) {
            throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
                .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)));
                .setProcessDefinitionKey(key).setBusinessKey(String.valueOf(id)));
        transferApplicationMapper.updateById(HrmTransferApplicationDO.builder()
                .id(id)
                .processInstanceId(processInstanceId)
                .status(HrmAuditStatusEnum.PROCESS.getStatus())
                .build());
    }
    /**
     * 解析流程定义 Key:优先使用调用方传入值,为空时取「调动审批」分类下最新的活跃流程
     */
    private String resolveProcessDefinitionKey(String processDefinitionKey) {
        if (StrUtil.isNotBlank(processDefinitionKey)) {
            return processDefinitionKey.trim();
        }
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            throw exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
        }
        Set<String> processDefinitionIds = convertSet(definitionInfoList, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
        List<ProcessDefinition> processDefinitions = bpmProcessDefinitionService.getProcessDefinitionList(processDefinitionIds);
        return processDefinitions.stream()
                .filter(pd -> !pd.isSuspended())
                .max(Comparator.comparingInt(ProcessDefinition::getVersion))
                .map(ProcessDefinition::getKey)
                .orElseThrow(() -> exception(TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS));
    }
    private void validateApproveCategoryAndProcessDefinition() {
@@ -291,6 +332,26 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelTransferApplication(Long id, Long userId) {
        HrmTransferApplicationDO application = validateTransferApplicationExists(id);
        if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
            throw exception(TRANSFER_APPLICATION_CANCEL_FAIL_NOT_PROCESS);
        }
        // 取消对应的 BPM 流程实例(以发起人身份撤销)
        if (StrUtil.isNotBlank(application.getProcessInstanceId())) {
            BpmProcessInstanceCancelReqVO cancelReqVO = new BpmProcessInstanceCancelReqVO();
            cancelReqVO.setId(application.getProcessInstanceId());
            cancelReqVO.setReason("员工撤销调岗申请");
            bpmProcessInstanceService.cancelProcessInstanceByStartUser(userId, cancelReqVO);
        }
        transferApplicationMapper.updateById(HrmTransferApplicationDO.builder()
                .id(id)
                .status(HrmAuditStatusEnum.CANCEL.getStatus())
                .build());
    }
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList() {
        validateApproveCategoryAndProcessDefinition();
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService