From 714fb8a069c9e87033a4201c210cb43ac438ba39 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 19 十月 2026 10:57:02 +0800
Subject: [PATCH] fix(hrm): 薪酬核算修改时金额字段 null 归 0,避免实发工资与明细不一致
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 195 ++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 171 insertions(+), 24 deletions(-)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
index 6cf8b19..a8822da 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -32,6 +32,11 @@
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmOvertimeWageConfigMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmployeeCategoryEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmOvertimeTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -97,6 +102,8 @@
private HrmOvertimeApplicationMapper overtimeApplicationMapper;
@Resource
private HrmSalaryStructureMapper salaryStructureMapper;
+ @Resource
+ private HrmOvertimeWageConfigMapper overtimeWageConfigMapper;
@Resource
private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
@Resource
@@ -371,8 +378,16 @@
}
/**
- * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇枫�傚皬鏃跺伐璧勬寜鍩烘湰宸ヨ祫 / 21.75 / 8 璁$畻锛�
- * 宸ヤ綔鏃ラ粯璁� 1.5 鍊嶃�佷紤鎭棩榛樿 2 鍊嶃�佹硶瀹氳妭鍋囨棩榛樿 3 鍊嶏紝鍧囧彲鐢辫柂閰粨鏋勮鐩栥��
+ * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇凤紝璁$畻鍔犵彮宸ヨ祫涓庡姞鐝椂闀裤��
+ *
+ * <p>鎸夈�屽憳宸ョ被鍒�嶅樊寮傚寲鏍哥畻锛�
+ * <ul>
+ * <li>鎶�宸ワ細鍔犵彮钖祫鎸� 1:1 鏍哥畻锛屼笌骞虫椂鏃惰柂涓�鑷达紙鍩烘湰宸ヨ祫 / 21.75 / 8锛夛紱</li>
+ * <li>鏅�氬憳宸ワ細鎸夊矖浣嶉厤缃殑鍥哄畾鍔犵彮鏃惰柂锛堥粯璁� 20 鍏�/灏忔椂锛夋牳绠楋紱</li>
+ * <li>绠$悊锛氫笉璁″姞鐝伐璧勶紱</li>
+ * </ul>
+ * 鍛戒腑宀椾綅閰嶇疆鏃讹紝鎸夐厤缃殑鏈�澶у姞鐝椂闀夸笂闄愬鍔犵彮鏃堕暱灏侀《锛�
+ * 鏈懡涓换浣曞矖浣嶉厤缃笖闈炴妧宸�/绠$悊鏃讹紝鍥為��鏃ч�昏緫锛堣柂閰粨鏋勫�嶇巼 / 鍔犵彮绫诲瀷榛樿鍊嶇巼锛夈��
*/
private OvertimeSalary calculateOvertimeSalary(HrmEmployeeDO employee, HrmEmployeeSalaryDO employeeSalary,
String period, BigDecimal baseSalary) {
@@ -385,36 +400,108 @@
if (CollUtil.isEmpty(applications)) {
return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
}
- Long structureId = employeeSalary.getSalaryStructureId() != null
- ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
- HrmSalaryStructureDO structure = structureId == null ? null : salaryStructureMapper.selectById(structureId);
- BigDecimal workdayMultiplier = structure != null && structure.getOvertimePayRatio() != null
- ? structure.getOvertimePayRatio() : HrmOvertimeTypeEnum.WORKDAY.getSalaryMultiplier();
- BigDecimal restDayMultiplier = structure != null && structure.getRestDayOvertimePayRatio() != null
- ? structure.getRestDayOvertimePayRatio() : HrmOvertimeTypeEnum.REST_DAY.getSalaryMultiplier();
- BigDecimal legalHolidayMultiplier = structure != null && structure.getLegalHolidayOvertimePayRatio() != null
- ? structure.getLegalHolidayOvertimePayRatio() : HrmOvertimeTypeEnum.LEGAL_HOLIDAY.getSalaryMultiplier();
- BigDecimal hourlySalary = baseSalary.divide(new BigDecimal("21.75"), 8, RoundingMode.HALF_UP)
+ // 绠$悊绫诲埆涓嶈鍔犵彮宸ヨ祫
+ HrmEmployeeCategoryEnum category = HrmEmployeeCategoryEnum.valueOfCategory(employee.getEmployeeCategory());
+ if (category == HrmEmployeeCategoryEnum.MANAGER) {
+ return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+ }
+ // 鍛樺伐宀椾綅鍛戒腑鐨勫姞鐝伐璧勯厤缃紙鍙栨椂钖渶楂樸�佷笂闄愭渶瀹界殑涓�鏉★級
+ OvertimeWageRule wageRule = resolveOvertimeWageRule(employee);
+ // 骞虫椂鏃惰柂 = 鍩烘湰宸ヨ祫 / 21.75 / 8
+ BigDecimal baseHourlySalary = baseSalary.divide(new BigDecimal("21.75"), 8, RoundingMode.HALF_UP)
.divide(new BigDecimal("8"), 8, RoundingMode.HALF_UP);
+
BigDecimal totalHours = BigDecimal.ZERO;
BigDecimal totalPay = BigDecimal.ZERO;
- for (HrmOvertimeApplicationDO application : applications) {
- BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
- HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
- if (type == null || hours.signum() <= 0) {
- continue;
+ if (category == HrmEmployeeCategoryEnum.TECHNICIAN || wageRule != null) {
+ // 鎶�宸ワ細1:1锛堜笌骞虫椂鏃惰柂涓�鑷达級锛涙櫘閫氬憳宸ュ懡涓厤缃細鎸夐厤缃浐瀹氭椂钖紝鍧囦笉鍖哄垎鍔犵彮绫诲瀷鍊嶇巼
+ BigDecimal hourlySalary = category == HrmEmployeeCategoryEnum.TECHNICIAN
+ ? baseHourlySalary : wageRule.hourlyRate();
+ for (HrmOvertimeApplicationDO application : applications) {
+ BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
+ HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
+ if (type == null || hours.signum() <= 0) {
+ continue;
+ }
+ totalHours = totalHours.add(hours);
}
- BigDecimal multiplier = switch (type) {
- case WORKDAY -> workdayMultiplier;
- case REST_DAY -> restDayMultiplier;
- case LEGAL_HOLIDAY -> legalHolidayMultiplier;
- };
- totalHours = totalHours.add(hours);
- totalPay = totalPay.add(hourlySalary.multiply(hours).multiply(multiplier));
+ // 鎸夊矖浣嶉厤缃皝椤跺姞鐝椂闀�
+ BigDecimal paidHours = wageRule != null && wageRule.maxOvertimeHours() != null
+ && totalHours.compareTo(wageRule.maxOvertimeHours()) > 0
+ ? wageRule.maxOvertimeHours() : totalHours;
+ totalPay = hourlySalary.multiply(paidHours);
+ } else {
+ // 鍏煎鏃ч�昏緫锛氭棤宀椾綅閰嶇疆銆佷笖闈炴妧宸�/绠$悊
+ Long structureId = employeeSalary.getSalaryStructureId() != null
+ ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
+ HrmSalaryStructureDO structure = structureId == null ? null : salaryStructureMapper.selectById(structureId);
+ BigDecimal workdayMultiplier = structure != null && structure.getOvertimePayRatio() != null
+ ? structure.getOvertimePayRatio() : HrmOvertimeTypeEnum.WORKDAY.getSalaryMultiplier();
+ BigDecimal restDayMultiplier = structure != null && structure.getRestDayOvertimePayRatio() != null
+ ? structure.getRestDayOvertimePayRatio() : HrmOvertimeTypeEnum.REST_DAY.getSalaryMultiplier();
+ BigDecimal legalHolidayMultiplier = structure != null && structure.getLegalHolidayOvertimePayRatio() != null
+ ? structure.getLegalHolidayOvertimePayRatio() : HrmOvertimeTypeEnum.LEGAL_HOLIDAY.getSalaryMultiplier();
+ for (HrmOvertimeApplicationDO application : applications) {
+ BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
+ HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
+ if (type == null || hours.signum() <= 0) {
+ continue;
+ }
+ BigDecimal multiplier = switch (type) {
+ case WORKDAY -> workdayMultiplier;
+ case REST_DAY -> restDayMultiplier;
+ case LEGAL_HOLIDAY -> legalHolidayMultiplier;
+ };
+ totalHours = totalHours.add(hours);
+ totalPay = totalPay.add(baseHourlySalary.multiply(hours).multiply(multiplier));
+ }
}
return new OvertimeSalary(totalHours.setScale(1, RoundingMode.HALF_UP),
totalPay.setScale(2, RoundingMode.HALF_UP));
}
+
+ /**
+ * 瑙f瀽鍛樺伐宀椾綅瀵瑰簲鐨勫姞鐝伐璧勮鍒欙細鏃惰柂鍙栧懡涓厤缃腑鐨勬渶澶у�硷紱
+ * 涓婇檺鍙栤�滄渶瀹解�濓紙瀛樺湪鏈缃笂闄愮殑閰嶇疆鍒欒涓轰笉闄愶紝鍚﹀垯鍙栨渶澶у�硷級銆�
+ */
+ private OvertimeWageRule resolveOvertimeWageRule(HrmEmployeeDO employee) {
+ if (StrUtil.isEmpty(employee.getPostIds())) {
+ return null;
+ }
+ List<Long> postIds;
+ try {
+ postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
+ } catch (Exception ex) {
+ return null;
+ }
+ if (CollUtil.isEmpty(postIds)) {
+ return null;
+ }
+ List<HrmOvertimeWageConfigDO> configs = overtimeWageConfigMapper.selectEnabledListByPostIds(postIds);
+ if (CollUtil.isEmpty(configs)) {
+ return null;
+ }
+ BigDecimal hourlyRate = null;
+ BigDecimal maxHours = null;
+ boolean unlimited = false;
+ for (HrmOvertimeWageConfigDO config : configs) {
+ if (config.getHourlyRate() != null
+ && (hourlyRate == null || config.getHourlyRate().compareTo(hourlyRate) > 0)) {
+ hourlyRate = config.getHourlyRate();
+ }
+ if (config.getMaxOvertimeHours() == null) {
+ unlimited = true;
+ } else if (!unlimited && (maxHours == null || config.getMaxOvertimeHours().compareTo(maxHours) > 0)) {
+ maxHours = config.getMaxOvertimeHours();
+ }
+ }
+ if (hourlyRate == null) {
+ return null;
+ }
+ return new OvertimeWageRule(hourlyRate, unlimited ? null : maxHours);
+ }
+
+ private record OvertimeWageRule(BigDecimal hourlyRate, BigDecimal maxOvertimeHours) {}
private record OvertimeSalary(BigDecimal hours, BigDecimal pay) {}
@@ -576,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 澶勭悊锛歁yBatis-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);
+ }
+
+ /**
+ * 璁$畻瀹炲彂宸ヨ祫 = 鏀跺叆椤瑰悎璁� - 鎵i櫎椤瑰悎璁★紝淇濈暀涓や綅灏忔暟
+ */
+ 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;
+ }
+
/**
* 鍐呴儴鐢熸垚鍙戞斁鍙拌处鏂规硶锛堜緵鑷姩鐢熸垚璋冪敤锛�
*/
--
Gitblit v1.9.3