feat(hrm): 薪酬核算支持修改核算记录(仅待确认可改,实发工资后端重算)

- 新增 PUT /hrm/salary/calculation/update
- 校验状态:非待确认(已确认/已发放)不可修改
- 实发工资由后端按收入/扣除项重算,防前端传值不一致
- 单号/周期/员工/部门/状态等字段不允许通过该接口变更
已修改4个文件
64 ■■■■■ 文件已修改
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -128,6 +128,7 @@
    ErrorCode CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT = new ErrorCode(1_030_005_029, "不支持的合同模板类型");
    ErrorCode CONTRACT_TEMPLATE_LOAD_FAILED = new ErrorCode(1_030_005_030, "合同模板加载失败");
    ErrorCode EMPLOYEE_REGULARIZE_NEED_LABOR_CONTRACT = new ErrorCode(1_030_005_031, "该员工当前合同不是劳动合同,无法转正");
    ErrorCode SALARY_CALCULATION_UPDATE_FAIL = new ErrorCode(1_030_005_032, "修改失败,原因:仅待确认状态可修改,请先撤销确认");
    // ========== 班次管理 1-030-009-000 ==========
    ErrorCode SHIFT_NOT_EXISTS = new ErrorCode(1_030_009_000, "班次不存在");
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java
@@ -215,4 +215,13 @@
        return success(true);
    }
    @PutMapping("/update")
    @Operation(summary = "修改薪酬核算记录", description = "仅待确认状态可修改;实发工资由后端按收入/扣除项重算")
    @PreAuthorize("@ss.hasPermission('hrm:salary:calculate')")
    public CommonResult<Boolean> updateSalaryCalculation(@Valid @RequestBody HrmSalaryCalculationRespVO updateReqVO) {
        HrmSalaryCalculationDO calculation = BeanUtils.toBean(updateReqVO, HrmSalaryCalculationDO.class);
        salaryCalculationService.updateSalaryCalculation(calculation);
        return success(true);
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java
@@ -81,4 +81,11 @@
     */
    void saveSalaryCalculationList(List<HrmSalaryCalculationDO> calculationList);
    /**
     * 修改单条薪酬核算记录(仅待确认状态可修改,实发工资由后端重算)
     *
     * @param calculation 核算记录
     */
    void updateSalaryCalculation(HrmSalaryCalculationDO calculation);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -663,6 +663,53 @@
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSalaryCalculation(HrmSalaryCalculationDO calculation) {
        HrmSalaryCalculationDO exist = salaryCalculationMapper.selectById(calculation.getId());
        if (exist == null) {
            throw exception(SALARY_CALCULATION_NOT_EXISTS);
        }
        // 仅待确认状态可修改:已确认需先撤销;已发放不可改
        if (exist.getStatus() == null || !Objects.equals(exist.getStatus(), 0)) {
            throw exception(SALARY_CALCULATION_UPDATE_FAIL);
        }
        // 以下字段不允许通过修改接口变更,一律以库中为准
        calculation.setNo(exist.getNo());
        calculation.setPeriod(exist.getPeriod());
        calculation.setUserId(exist.getUserId());
        calculation.setDeptId(exist.getDeptId());
        calculation.setStatus(exist.getStatus());
        calculation.setConfirmTime(exist.getConfirmTime());
        calculation.setConfirmUserId(exist.getConfirmUserId());
        calculation.setPaymentTime(exist.getPaymentTime());
        // 实发工资由后端重算,避免前端传入值与明细不一致
        calculation.setActualSalary(calcActualSalary(calculation));
        salaryCalculationMapper.updateById(calculation);
    }
    /**
     * 计算实发工资 = 收入项合计 - 扣除项合计,保留两位小数
     */
    private static BigDecimal calcActualSalary(HrmSalaryCalculationDO c) {
        BigDecimal income = nvl(c.getBaseSalary())
                .add(nvl(c.getPerformanceSalary()))
                .add(nvl(c.getOvertimePay()))
                .add(nvl(c.getMealAllowance()))
                .add(nvl(c.getTransportAllowance()))
                .add(nvl(c.getOtherAllowance()));
        BigDecimal deduction = nvl(c.getSocialSecurityDeduction())
                .add(nvl(c.getHousingFundDeduction()))
                .add(nvl(c.getTaxDeduction()))
                .add(nvl(c.getOtherDeduction()))
                .add(nvl(c.getLeaveDeduction()));
        return income.subtract(deduction).setScale(2, RoundingMode.HALF_UP);
    }
    private static BigDecimal nvl(BigDecimal v) {
        return v == null ? BigDecimal.ZERO : v;
    }
    /**
     * 内部生成发放台账方法(供自动生成调用)
     */