tuqin
2 天以前 1c22a7631e4ecceac9d2152fa7876bb9e6f592f4
fix(hrm): 修复新增调岗申请报 500(原部门/原岗位未回填)

问题:人力资源 → 调岗申请 → 新增调岗申请,提交报 500「系统异常」,
日志为 java.sql.SQLException: Field 'current_dept_id' doesn't have a default value。

根因(前端后端都有问题):
1. 库里 hrm_transfer_application 的 current_dept_id / current_post_id 是 NOT NULL 且无默认值,
但前端表单只有 currentDeptName / currentPostName 两个只读展示框,
从来不会提交 currentDeptId / currentPostId,插入必然失败。
2. 前端 form.vue 传了 employeeId,但后端 HrmTransferApplicationSaveReqVO 没有这个字段,被直接忽略。
3. 前端 onOpenChange 里写的是 employees[0](注释自称「暂时取第一个员工作为示例」),
调岗对象根本不对,不是用户想调的人。

改动:
后端
- SaveReqVO 新增 employeeId(被调岗员工)。
- createTransferApplication 重排逻辑:先按 employeeId(或 userId)查员工档案,
自动补全 userId / currentDeptId / currentPostId(post_ids 存 JSON 数组,取第一个),
补全后再做「目标部门/岗位不能与原部门/岗位相同」的校验
(原来校验在补全之前,补全出来的原部门会绕过校验)。
- 补全不到时抛明确业务错误,不再直接漏出 SQL 异常:
TRANSFER_APPLICATION_CURRENT_DEPT_REQUIRED / _CURRENT_POST_REQUIRED。
- RespVO 新增 employeeId,详情接口按 userId 反查员工档案回填,供前端修改时回显。

前端
- data.ts 新增「被调岗员工」下拉(ApiSelect,走 /hrm/employee/simple-list),
查看/修改场景禁用。
- form.vue 去掉 employees[0] 的示例逻辑,改为监听 employeeId 变化,
选中员工后带出原部门/原岗位名称(ID 由后端按档案补全)。
顺带移除不再需要的 currentEmployeeId。

验证(蓝羽 mom-lyzy 实测):
- 只传 employeeId 新增 → 成功,落库 user_id=345、current_dept_id=125、current_post_id=48,
与员工档案完全一致
- 目标部门选成原部门 → 正确拦截「调岗失败,原因:目标部门与原部门相同」
- 详情接口返回 employeeId=111、userId=345、currentDeptName=财务部
已修改4个文件
92 ■■■■■ 文件已修改
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -50,6 +50,9 @@
    ErrorCode TRANSFER_APPLICATION_SAME_DEPT = new ErrorCode(1_030_002_005, "调岗失败,原因:目标部门与原部门相同");
    ErrorCode TRANSFER_APPLICATION_SAME_POST = new ErrorCode(1_030_002_006, "调岗失败,原因:目标岗位与原岗位相同");
    ErrorCode TRANSFER_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_002_007, "调岗审批流程不存在");
    ErrorCode TRANSFER_APPLICATION_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_002_008, "调岗失败,原因:员工档案不存在,请先完善员工信息");
    ErrorCode TRANSFER_APPLICATION_CURRENT_DEPT_REQUIRED = new ErrorCode(1_030_002_009, "调岗失败,原因:无法确定员工当前部门,请先在员工档案中配置部门");
    ErrorCode TRANSFER_APPLICATION_CURRENT_POST_REQUIRED = new ErrorCode(1_030_002_010, "调岗失败,原因:无法确定员工当前岗位,请先在员工档案中配置岗位");
    // ========== 离职申请 1-030-003-000 ==========
    ErrorCode RESIGNATION_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_003_000, "离职申请不存在");
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
@@ -24,6 +24,9 @@
    @Schema(description = "申请人姓名", example = "张三")
    private String userName;
    @Schema(description = "被调岗员工ID(按 userId 反查员工档案,供前端修改时回显)", example = "1")
    private Long employeeId;
    @Schema(description = "原部门ID", example = "1")
    private Long currentDeptId;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java
@@ -15,6 +15,9 @@
    @Schema(description = "申请ID", example = "1")
    private Long id;
    @Schema(description = "被调岗员工ID(前端表单选择,用于自动带出原部门/原岗位/申请人)", example = "1")
    private Long employeeId;
    @Schema(description = "申请人ID", example = "1")
    private Long userId;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -2,6 +2,8 @@
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.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
@@ -13,10 +15,12 @@
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.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmTransferTypeEnum;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
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.storage.StorageAttachmentApi;
@@ -73,29 +77,58 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private HrmEmployeeService employeeService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createTransferApplication(HrmTransferApplicationSaveReqVO createReqVO) {
        // 1. 校验目标部门与原部门不能相同
        // 1. 解析申请人 & 补全原部门/原岗位
        // 前端表单只让用户选择「被调岗员工」,不直接提交 currentDeptId/currentPostId,
        // 而这两个字段在库里是 NOT NULL,缺省会直接抛 SQL 异常(Field 'current_dept_id' doesn't have a default value)
        Long userId = createReqVO.getUserId();
        Long employeeId = createReqVO.getEmployeeId();
        HrmEmployeeDO employee = null;
        if (employeeId != null) {
            employee = employeeService.getEmployee(employeeId);
        }
        if (employee == null && userId != null) {
            employee = employeeService.getEmployeeByUserId(userId);
        }
        if (userId == null && employee != null) {
            userId = employee.getUserId();
        }
        if (userId == null) {
            userId = getCurrentUserId();
            if (employee == null) {
                employee = employeeService.getEmployeeByUserId(userId);
            }
        }
        if (adminUserApi.getUser(userId) == null) {
            userId = getCurrentUserId();
        }
        if (createReqVO.getCurrentDeptId() == null && employee != null) {
            createReqVO.setCurrentDeptId(employee.getDeptId());
        }
        if (createReqVO.getCurrentPostId() == null && employee != null) {
            createReqVO.setCurrentPostId(parseFirstPostId(employee.getPostIds()));
        }
        if (createReqVO.getCurrentDeptId() == null) {
            throw exception(TRANSFER_APPLICATION_CURRENT_DEPT_REQUIRED);
        }
        if (createReqVO.getCurrentPostId() == null) {
            throw exception(TRANSFER_APPLICATION_CURRENT_POST_REQUIRED);
        }
        // 2. 校验目标部门与原部门不能相同(放在补全之后,否则补全出来的原部门绕过了校验)
        if (Objects.equals(createReqVO.getTargetDeptId(), createReqVO.getCurrentDeptId())) {
            throw exception(TRANSFER_APPLICATION_SAME_DEPT);
        }
        // 2. 校验目标岗位与原岗位不能相同
        // 3. 校验目标岗位与原岗位不能相同
        if (Objects.equals(createReqVO.getTargetPostId(), createReqVO.getCurrentPostId())) {
            throw exception(TRANSFER_APPLICATION_SAME_POST);
        }
        // 3. 生成申请单号
        // 4. 生成申请单号
        String no = generateUniqueNo();
        // 4. 设置申请人信息
        Long userId = createReqVO.getUserId();
        if (userId == null) {
            userId = getCurrentUserId();
        }
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        if (user == null) {
            userId = getCurrentUserId();
        }
        // 5. 插入
        HrmTransferApplicationDO application = BeanUtils.toBean(createReqVO, HrmTransferApplicationDO.class);
        application.setNo(no);
@@ -105,6 +138,27 @@
        storageAttachmentApi.bindAttachments("file", "hrm_transfer_application",
                application.getId(), createReqVO.getBlobIds());
        return application.getId();
    }
    /**
     * 取员工岗位列表中的第一个作为「原岗位」
     *
     * <p>hrm_employee.post_ids 以 JSON 数组字符串存储,例如 {@code [20,21]}
     *
     * @param postIds 岗位编号 JSON 字符串
     * @return 第一个岗位编号;解析不出来时返回 {@code null}
     */
    private Long parseFirstPostId(String postIds) {
        if (StrUtil.isBlank(postIds)) {
            return null;
        }
        try {
            List<Long> ids = JSONUtil.toList(postIds, Long.class);
            return CollUtil.isEmpty(ids) ? null : ids.get(0);
        } catch (Exception ex) {
            log.warn("[parseFirstPostId][postIds({}) 解析失败]", postIds, ex);
            return null;
        }
    }
    private Long getCurrentUserId() {
@@ -172,6 +226,11 @@
            if (user != null) {
                respVO.setUserName(user.getNickname());
            }
            // 表里只存了 user_id,前端修改时需要用 employeeId 回显「被调岗员工」,这里按用户反查员工档案
            HrmEmployeeDO employee = employeeService.getEmployeeByUserId(application.getUserId());
            if (employee != null) {
                respVO.setEmployeeId(employee.getId());
            }
        }
        fillDeptNames(respVO, application);
        fillPostNames(respVO, application);