From e2a6bb12ebb7ee07fbd5a428603995dbaccd1ce9 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 02 十一月 2026 14:43:06 +0800
Subject: [PATCH] fix(hrm): 核算批量删除增加工资发放引用保护
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 151 insertions(+), 16 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 5f0019e..858e2b1 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
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationPageReqVO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmOvertimeApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceSummaryDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmEmployeeScheduleDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
@@ -18,6 +19,7 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmEmployeeScheduleMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmShiftMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmOvertimeApplicationMapper;
@@ -114,6 +116,8 @@
private HrmEmployeeScheduleMapper employeeScheduleMapper;
@Resource
private HrmShiftMapper shiftMapper;
+ @Resource
+ private HrmAttendanceSummaryMapper attendanceSummaryMapper;
@Override
public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -235,40 +239,55 @@
HrmSocialSecuritySchemeDO scheme,
LocalDate effectiveDate) {
// 璁$畻搴斿彂钖祫
- BigDecimal baseSalary = employeeSalary.getBaseSalary() != null ? employeeSalary.getBaseSalary() : BigDecimal.ZERO;
+ // 鍩烘湰宸ヨ祫缁熶竴鍙栥�屽憳宸ヤ俊鎭�嶄腑缁存姢鐨勫熀鏈柂璧勶紙钖叕妗f閲岀殑 baseSalary 涓嶅啀浣滀负鏍哥畻鏉ユ簮锛�
+ // 閬垮厤鍛樺伐淇℃伅涓庤柂閰。妗堜笉涓�鑷存椂锛屾牳绠楃粨鏋滀笌鍛樺伐妗f瀵逛笉涓婏級
+ BigDecimal baseSalary = employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO;
BigDecimal performanceSalary = employeeSalary.getPerformanceSalary() != null ? employeeSalary.getPerformanceSalary() : BigDecimal.ZERO;
+ // 宀椾綅宸ヨ祫 / 宸ラ緞宸ヨ祫锛氫笌鍩烘湰宸ヨ祫鍚屾簮锛屽彇鍛樺伐淇℃伅涓淮鎶ょ殑閲戦
+ BigDecimal postSalary = employee.getPostSalary() != null ? employee.getPostSalary() : BigDecimal.ZERO;
+ BigDecimal senioritySalary = employee.getSenioritySalary() != null ? employee.getSenioritySalary() : BigDecimal.ZERO;
BigDecimal mealAllowance = employeeSalary.getMealAllowance() != null ? employeeSalary.getMealAllowance() : BigDecimal.ZERO;
BigDecimal transportAllowance = employeeSalary.getTransportAllowance() != null ? employeeSalary.getTransportAllowance() : BigDecimal.ZERO;
BigDecimal otherAllowance = employeeSalary.getOtherAllowance() != null ? employeeSalary.getOtherAllowance() : BigDecimal.ZERO;
OvertimeSalary overtimeSalary = calculateOvertimeSalary(employee, employeeSalary, period, baseSalary);
BigDecimal overtimePay = overtimeSalary.pay();
- // 鎸夋帓鐝疮璁″嚭鍕ゅぉ鏁颁笌鎺掔彮鎬诲伐鏃讹紙鏃犳帓鐝椂鍑哄嫟澶╂暟鍥為��榛樿 22锛�
+ // 鎸夋帓鐝疮璁″簲鍑哄ぉ鏁颁笌鎺掔彮鎬诲伐鏃讹紙鏃犳帓鐝椂搴斿嚭澶╂暟鍥為��榛樿 22锛�
ScheduleStat scheduleStat = calculateScheduleStat(employeeId, period);
Integer workDays = scheduleStat.scheduledDays() > 0 ? scheduleStat.scheduledDays() : 22;
BigDecimal totalWorkHours = scheduleStat.totalWorkHours();
- // 搴斿彂宸ヨ祫鎬婚
- BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
- .add(transportAllowance).add(otherAllowance).add(overtimePay);
+ // 瀹炲嚭澶╂暟锛氬彇鑷�冨嫟姹囨�伙紙鏃犳眹鎬昏褰曟椂鎸� 0 澶勭悊锛�
+ Integer actualDays = 0;
+ HrmAttendanceSummaryDO attendanceSummary =
+ attendanceSummaryMapper.selectByUserIdAndYearMonth(employeeId, period);
+ if (attendanceSummary != null && attendanceSummary.getActualDays() != null) {
+ actualDays = attendanceSummary.getActualDays();
+ }
+
+ // 鍏ㄥ嫟濂栵細涓庡叾浠栨触璐翠竴鑷达紝鐢变汉宸ュ湪鏍哥畻鍗曚腑缁存姢锛屾柊寤烘椂榛樿 0
+ BigDecimal fullAttendanceBonus = BigDecimal.ZERO;
+
+ // 搴斿彂宸ヨ祫鎬婚锛堟敹鍏ラ」鍙e緞蹇呴』涓� calcActualSalary 淇濇寔涓�鑷达級
+ BigDecimal totalIncome = baseSalary.add(performanceSalary).add(postSalary).add(senioritySalary)
+ .add(fullAttendanceBonus)
+ .add(mealAllowance).add(transportAllowance).add(otherAllowance).add(overtimePay);
// 鑾峰彇绀句繚鍩烘暟锛氬憳宸ョぞ淇濇。妗� > 鍛樺伐鍩烘湰宸ヨ祫
BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
? socialSecurity.getSocialSecurityBase() : baseSalary;
- // 鑾峰彇鍏Н閲戝熀鏁帮細鍛樺伐绀句繚妗f > 鍛樺伐鍩烘湰宸ヨ祫
- BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
- ? socialSecurity.getHousingFundBase() : baseSalary;
-
- // 浠庢柟妗堜腑鑾峰彇缂寸撼姣斾緥锛堜紭鍏堜娇鐢ㄦ柟妗堜腑鐨勬瘮渚嬶紝鍚﹀垯浣跨敤榛樿姣斾緥锛�
+ // 浠庢柟妗堜腑鑾峰彇绀句繚缂寸撼姣斾緥锛堟柟妗堟湭閰嶇疆鏃跺洖閫�榛樿姣斾緥锛�
BigDecimal pensionPersonalRatio = (scheme != null && scheme.getPensionPersonalRatio() != null)
? scheme.getPensionPersonalRatio() : DEFAULT_PENSION_PERSONAL_RATIO;
BigDecimal medicalPersonalRatio = (scheme != null && scheme.getMedicalPersonalRatio() != null)
? scheme.getMedicalPersonalRatio() : DEFAULT_MEDICAL_PERSONAL_RATIO;
BigDecimal unemploymentPersonalRatio = (scheme != null && scheme.getUnemploymentPersonalRatio() != null)
? scheme.getUnemploymentPersonalRatio() : DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO;
- BigDecimal housingFundPersonalRatio = (scheme != null && scheme.getHousingFundPersonalRatio() != null)
- ? scheme.getHousingFundPersonalRatio() : DEFAULT_HOUSING_FUND_PERSONAL_RATIO;
+
+ // 鍏Н閲戜釜浜烘瘮渚嬶細鍙彇鏂规閰嶇疆鍊硷紝涓嶅啀鍥為��榛樿姣斾緥銆�
+ // 鍚﹀垯銆屽叕鍙告病閰嶅叕绉噾鏂规銆嶆椂浼氳鎸夐粯璁� 12% 鎵f锛堣繖灏辨槸瀹㈡埛鍙嶉鐨勯棶棰橈級銆�
+ BigDecimal housingFundPersonalRatio = scheme != null ? scheme.getHousingFundPersonalRatio() : null;
// 绀句繚鎵i櫎 = 鍩烘暟 脳 (鍏昏�佷釜浜烘瘮渚� + 鍖荤枟涓汉姣斾緥 + 澶变笟涓汉姣斾緥)
BigDecimal socialSecurityDeduction = socialSecurityBase
@@ -277,8 +296,15 @@
.add(socialSecurityBase.multiply(unemploymentPersonalRatio))
.setScale(2, RoundingMode.HALF_UP);
- // 鍏Н閲戞墸闄� = 鍩烘暟 脳 鍏Н閲戜釜浜烘瘮渚�
- BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP);
+ // 鍏Н閲戞墸闄わ細蹇呴』鍚屾椂婊¤冻銆屾柟妗堥厤缃簡鍏Н閲戞瘮渚� > 0銆嶄笖銆屽憳宸ユ。妗堥厤缃簡鍏Н閲戝熀鏁� > 0銆嶆墠鎵f锛�
+ // 浠讳竴涓嶆弧瓒抽兘瑙嗕负鍏徃鏈即绾冲叕绉噾锛屾墸娆句负 0銆�
+ BigDecimal housingFundDeduction = BigDecimal.ZERO;
+ BigDecimal housingFundBase = socialSecurity != null ? socialSecurity.getHousingFundBase() : null;
+ if (housingFundPersonalRatio != null && housingFundPersonalRatio.signum() > 0
+ && housingFundBase != null && housingFundBase.signum() > 0) {
+ housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio)
+ .setScale(2, RoundingMode.HALF_UP);
+ }
// 璁$畻璇峰亣鎵f
BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
@@ -308,19 +334,23 @@
.deptId(employee.getDeptId())
.baseSalary(baseSalary)
.performanceSalary(performanceSalary)
+ .postSalary(postSalary)
+ .senioritySalary(senioritySalary)
.overtimePay(overtimePay)
.mealAllowance(mealAllowance)
.transportAllowance(transportAllowance)
.otherAllowance(otherAllowance)
+ .fullAttendanceBonus(fullAttendanceBonus)
.socialSecurityDeduction(socialSecurityDeduction)
.housingFundDeduction(housingFundDeduction)
.taxDeduction(taxDeduction)
.otherDeduction(otherDeduction)
.leaveDeduction(leaveDeduction)
.actualSalary(actualSalary)
- .workDays(workDays) // 鎸夋帓鐝疮璁″簲鍑哄嫟澶╂暟锛屾棤鎺掔彮鏃跺洖閫� 22
+ .workDays(workDays) // 搴斿嚭澶╂暟锛氭寜鎺掔彮绱锛屾棤鎺掔彮鏃跺洖閫� 22
+ .actualDays(actualDays) // 瀹炲嚭澶╂暟锛氬彇鑷�冨嫟姹囨��
.overtimeHours(overtimeSalary.hours())
- .totalWorkHours(totalWorkHours)
+ .totalWorkHours(totalWorkHours) // 淇濈暀鍘嗗彶瀛楁锛屼笟鍔″凡鏀圭敤 actualDays
.status(0) // 寰呯‘璁�
.build();
}
@@ -663,6 +693,75 @@
}
}
+ @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.setPostSalary(nvl(calculation.getPostSalary()));
+ calculation.setSenioritySalary(nvl(calculation.getSenioritySalary()));
+ calculation.setOvertimePay(nvl(calculation.getOvertimePay()));
+ calculation.setMealAllowance(nvl(calculation.getMealAllowance()));
+ calculation.setTransportAllowance(nvl(calculation.getTransportAllowance()));
+ calculation.setOtherAllowance(nvl(calculation.getOtherAllowance()));
+ calculation.setFullAttendanceBonus(nvl(calculation.getFullAttendanceBonus()));
+ 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櫎椤瑰悎璁★紝淇濈暀涓や綅灏忔暟
+ *
+ * 鏀跺叆椤瑰繀椤讳笌 buildSalaryCalculation 涓� totalIncome 鐨勫彛寰勫畬鍏ㄤ竴鑷达紝
+ * 鍚﹀垯浼氬嚭鐜般�屾柊寤烘牳绠楃畻寰楀銆佷慨鏀逛竴娆℃牳绠楀崟瀹炲彂灏卞彉灏戙�嶇殑涓嶄竴鑷淬��
+ */
+ private static BigDecimal calcActualSalary(HrmSalaryCalculationDO c) {
+ BigDecimal income = nvl(c.getBaseSalary())
+ .add(nvl(c.getPostSalary()))
+ .add(nvl(c.getSenioritySalary()))
+ .add(nvl(c.getPerformanceSalary()))
+ .add(nvl(c.getFullAttendanceBonus()))
+ .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;
+ }
+
/**
* 鍐呴儴鐢熸垚鍙戞斁鍙拌处鏂规硶锛堜緵鑷姩鐢熸垚璋冪敤锛�
*/
@@ -779,4 +878,40 @@
salaryCalculationMapper.deleteById(id);
}
+ /**
+ * 鎵归噺鍒犻櫎鏍哥畻璁板綍锛堝叏閫夊悗涓�閿垹闄わ級
+ *
+ * 璇存槑锛�
+ * 1. 鎸変笟鍔¤姹傦紝鎵归噺鍒犻櫎涓嶉檺鍒舵牳绠楀崟鐘舵�侊紝鍕鹃�夊嵆鍒狅紱
+ * 2. 涓嶅瓨鍦ㄧ殑 ID 鐩存帴璺宠繃锛屼笉鎶ラ敊锛堥伩鍏嶅墠绔嬀閫夊垪琛ㄤ笌搴撲笉涓�鑷存椂鏁翠綋澶辫触锛夛紱
+ * 3. 宸茶銆屽伐璧勫彂鏀炬槑缁嗐�嶅紩鐢ㄧ殑璁板綍浼氳烦杩囦笉鍒狅紝閬垮厤鍙戞斁鏄庣粏鍙樻垚瀛ゅ効鏁版嵁銆�
+ *
+ * @return 瀹為檯鍒犻櫎鐨勬潯鏁帮紙鍙兘灏忎簬浼犲叆鏉℃暟锛�
+ */
+ @Override
+ public Integer deleteSalaryCalculationList(List<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return 0;
+ }
+ List<Long> existIds = salaryCalculationMapper.selectBatchIds(ids).stream()
+ .map(HrmSalaryCalculationDO::getId)
+ .collect(Collectors.toList());
+ if (CollUtil.isEmpty(existIds)) {
+ return 0;
+ }
+ // 寮曠敤淇濇姢锛氬凡杩涘叆宸ヨ祫鍙戞斁娴佺▼鐨勬牳绠楀崟涓嶅厑璁稿垹闄�
+ Set<Long> referencedIds = salaryPaymentDetailMapper.selectListByCalculationIds(existIds).stream()
+ .map(HrmSalaryPaymentDetailDO::getCalculationId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ List<Long> deletableIds = existIds.stream()
+ .filter(id -> !referencedIds.contains(id))
+ .collect(Collectors.toList());
+ if (CollUtil.isEmpty(deletableIds)) {
+ return 0;
+ }
+ salaryCalculationMapper.deleteByIds(deletableIds);
+ return deletableIds.size();
+ }
+
}
--
Gitblit v1.9.3