yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -663,6 +663,66 @@
        }
    }
    @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());
        // 金额字段为 null 时统一按 0 处理:MyBatis-Plus 的 updateById 默认忽略 null,
        // 否则会出现「实发工资按 0 计算、但明细仍保留旧值」的不一致
        calculation.setBaseSalary(nvl(calculation.getBaseSalary()));
        calculation.setPerformanceSalary(nvl(calculation.getPerformanceSalary()));
        calculation.setOvertimePay(nvl(calculation.getOvertimePay()));
        calculation.setMealAllowance(nvl(calculation.getMealAllowance()));
        calculation.setTransportAllowance(nvl(calculation.getTransportAllowance()));
        calculation.setOtherAllowance(nvl(calculation.getOtherAllowance()));
        calculation.setSocialSecurityDeduction(nvl(calculation.getSocialSecurityDeduction()));
        calculation.setHousingFundDeduction(nvl(calculation.getHousingFundDeduction()));
        calculation.setTaxDeduction(nvl(calculation.getTaxDeduction()));
        calculation.setOtherDeduction(nvl(calculation.getOtherDeduction()));
        calculation.setLeaveDeduction(nvl(calculation.getLeaveDeduction()));
        // 实发工资由后端重算,避免前端传入值与明细不一致
        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;
    }
    /**
     * 内部生成发放台账方法(供自动生成调用)
     */