tuqin
9 天以前 155319d1e4f8e61f5f3b60a783be035f03b47c66
fix(hrm): 修复各列表员工姓名全空 + 保存核算重复插入

## 问题一:列表里员工姓名、部门全空

现象:薪酬核算列表「员工姓名」一列完全空白。

根因与上一提交同源:所有业务表的 user_id 存的都是「系统账号 ID」
(system_users.id),但这些 Controller 用 getEmployee(userId) 去查,
该方法按「员工档案主键」(hrm_employee.id)查,两者不是一套 ID。
例:张睿 user_id=234,而 hrm_employee.id 只有 1~83,永远查不到。

数据库佐证:各表 user_id 匹配 system_users.id 的比例
hrm_attendance_record 1162/1162
hrm_attendance_exception 6/6
hrm_attendance_summary 166/166
hrm_employee_social_security 83/83
hrm_salary_payment_detail 84/84
全部 100%,匹配 hrm_employee.id 的基本为 0。

修复(统一改用 getEmployeeByUserId):
- HrmSalaryCalculationController getSalaryCalculation / fillUserInfo
- HrmSalaryPaymentController fillUserInfo
- HrmAttendanceExceptionController 详情填充
- HrmAttendanceRecordController 详情填充
- HrmAttendanceSummaryController 详情填充
- HrmEmployeeSocialSecurityController fillEmployeeInfo

注意:HrmAttendanceRecordServiceImpl / HrmAttendanceSummaryServiceImpl
里的 getEmployee(userId) 未改 —— 那是打卡/月汇总的业务逻辑,
调用方传的是 employee.getId(),语义正确,只是参数名有误导。

## 问题二:保存核算会重复插入

现象:同一员工同一周期出现多条核算记录。

根因:saveSalaryCalculationList 在 id 为空时直接 insert,完全不查重。
前端「预览数据 → 保存核算」走的正是这个接口,点几次就插几条。

修复:新增前按 (userId, period) 查重
- 已存在且状态非「待确认」 → 跳过,不覆盖已确认/已发放数据
- 已存在且是「待确认」 → 覆盖重算结果,保留原单号
- 不存在 → 正常新增

已验证:列表姓名/部门正常显示;对已确认记录重复保存,记录数保持 1 条。
已修改7个文件
61 ■■■■■ 文件已修改
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
@@ -77,9 +77,9 @@
            return success(null);
        }
        HrmAttendanceExceptionRespVO respVO = BeanUtils.toBean(attendanceException, HrmAttendanceExceptionRespVO.class);
        // 填充员工和部门信息(userId 是员工ID)
        // 填充员工和部门信息(userId 是系统账号 ID,不是员工档案 ID)
        if (attendanceException.getUserId() != null) {
            HrmEmployeeDO employee = employeeService.getEmployee(attendanceException.getUserId());
            HrmEmployeeDO employee = employeeService.getEmployeeByUserId(attendanceException.getUserId());
            if (employee != null) {
                respVO.setUserName(employee.getName());
            }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
@@ -164,9 +164,9 @@
            return success(null);
        }
        HrmAttendanceRecordRespVO respVO = BeanUtils.toBean(attendanceRecord, HrmAttendanceRecordRespVO.class);
        // 填充员工和部门信息(userId 是员工ID)
        // 填充员工和部门信息(userId 是系统账号 ID,不是员工档案 ID)
        if (attendanceRecord.getUserId() != null) {
            HrmEmployeeDO employee = employeeService.getEmployee(attendanceRecord.getUserId());
            HrmEmployeeDO employee = employeeService.getEmployeeByUserId(attendanceRecord.getUserId());
            if (employee != null) {
                respVO.setUserName(employee.getName());
                respVO.setPhone(employee.getPhone());
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceSummaryController.java
@@ -105,8 +105,8 @@
            return success(null);
        }
        HrmAttendanceSummaryRespVO respVO = BeanUtils.toBean(summary, HrmAttendanceSummaryRespVO.class);
        // 填充员工和部门信息
        HrmEmployeeDO employee = employeeService.getEmployee(userId);
        // 填充员工和部门信息(userId 是系统账号 ID,不是员工档案 ID)
        HrmEmployeeDO employee = employeeService.getEmployeeByUserId(userId);
        if (employee != null) {
            respVO.setUserName(employee.getName());
        }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmEmployeeSocialSecurityController.java
@@ -193,7 +193,7 @@
     */
    private void fillEmployeeInfo(HrmEmployeeSocialSecurityRespVO respVO, Long userId) {
        // userId 是员工ID,通过员工表获取姓名和部门
        HrmEmployeeDO employee = employeeService.getEmployee(userId);
        HrmEmployeeDO employee = employeeService.getEmployeeByUserId(userId);
        if (employee != null) {
            respVO.setUserName(employee.getName());
            if (employee.getDeptId() != null) {
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java
@@ -90,16 +90,18 @@
     * 填充员工和部门信息(userId 是员工ID)
     */
    private void fillUserInfo(List<HrmSalaryCalculationRespVO> respList, List<HrmSalaryCalculationDO> calculationList) {
        // userId 是员工ID,批量获取员工信息
        Set<Long> employeeIds = calculationList.stream()
        // 注意:核算记录的 userId 存的是「系统账号 ID」(system_users.id),
        // 不是员工档案主键(hrm_employee.id)。用 getEmployee 会永远查不到,
        // 导致列表里「员工姓名」一列全空。必须用 getEmployeeByUserId。
        Set<Long> userIds = calculationList.stream()
                .map(HrmSalaryCalculationDO::getUserId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
        for (Long employeeId : employeeIds) {
            HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
        for (Long userId : userIds) {
            HrmEmployeeDO employee = employeeService.getEmployeeByUserId(userId);
            if (employee != null) {
                employeeMap.put(employeeId, employee);
                employeeMap.put(userId, employee);
            }
        }
@@ -133,9 +135,11 @@
            return success(null);
        }
        HrmSalaryCalculationRespVO respVO = BeanUtils.toBean(salaryCalculation, HrmSalaryCalculationRespVO.class);
        // 填充员工和部门信息(userId 是员工ID)
        // 填充员工和部门信息
        // 注意:核算记录的 userId 存的是「系统账号 ID」(system_users.id),
        // 不是员工档案主键(hrm_employee.id),必须用 getEmployeeByUserId 查
        if (salaryCalculation.getUserId() != null) {
            HrmEmployeeDO employee = employeeService.getEmployee(salaryCalculation.getUserId());
            HrmEmployeeDO employee = employeeService.getEmployeeByUserId(salaryCalculation.getUserId());
            if (employee != null) {
                respVO.setUserName(employee.getName());
            }
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java
@@ -140,15 +140,17 @@
     */
    private void fillUserInfo(List<HrmSalaryPaymentDetailRespVO> respList, List<HrmSalaryPaymentDetailDO> detailList) {
        // 批量获取员工信息
        Set<Long> employeeIds = detailList.stream()
        // 注意:发放明细的 userId 存的是「系统账号 ID」(system_users.id),
        // 不是员工档案主键(hrm_employee.id),必须用 getEmployeeByUserId 查
        Set<Long> userIds = detailList.stream()
                .map(HrmSalaryPaymentDetailDO::getUserId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, HrmEmployeeDO> employeeMap = new HashMap<>();
        for (Long employeeId : employeeIds) {
            HrmEmployeeDO employee = employeeService.getEmployee(employeeId);
        for (Long userId : userIds) {
            HrmEmployeeDO employee = employeeService.getEmployeeByUserId(userId);
            if (employee != null) {
                employeeMap.put(employeeId, employee);
                employeeMap.put(userId, employee);
            }
        }
        // 批量获取部门信息
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -642,7 +642,24 @@
    public void saveSalaryCalculationList(List<HrmSalaryCalculationDO> calculationList) {
        for (HrmSalaryCalculationDO calculation : calculationList) {
            if (calculation.getId() == null) {
                // 新增记录,生成核算单号
                // 新增前先查重:同一员工(系统账号 ID)+ 同一周期 只允许存在一条记录。
                // 前端「预览数据 → 保存核算」走的就是这里,不查重会重复插入。
                List<HrmSalaryCalculationDO> existList = salaryCalculationMapper
                        .selectListByUserIdAndPeriod(calculation.getUserId(), calculation.getPeriod());
                if (CollUtil.isNotEmpty(existList)) {
                    HrmSalaryCalculationDO existing = existList.get(0);
                    // 已确认/已发放的记录不允许被覆盖,直接跳过
                    if (existing.getStatus() != null && existing.getStatus() != 0) {
                        continue;
                    }
                    // 待确认的记录:覆盖重算结果,保留原单号
                    calculation.setId(existing.getId());
                    calculation.setNo(existing.getNo());
                    calculation.setStatus(0);
                    salaryCalculationMapper.updateById(calculation);
                    continue;
                }
                // 确实是新增,生成核算单号
                String no = noRedisDAO.generateSalaryCalculationNo();
                while (salaryCalculationMapper.selectByNo(no) != null) {
                    no = noRedisDAO.generateSalaryCalculationNo();