From 612172aacb1b05162270ff846441f1c6336d0321 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期二, 03 十一月 2026 22:02:49 +0800
Subject: [PATCH] fix(hrm): 缺勤扣款计薪基数改为基本+岗位+工龄+绩效
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 559 ++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 481 insertions(+), 78 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 8279b4e..647f3a1 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
@@ -5,6 +5,10 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
@@ -13,7 +17,12 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO;
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;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper;
@@ -22,8 +31,15 @@
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentDetailMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
+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;
import org.springframework.transaction.annotation.Transactional;
@@ -34,8 +50,11 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -82,11 +101,23 @@
@Resource
private HrmLeaveApplicationMapper leaveApplicationMapper;
@Resource
+ private HrmOvertimeApplicationMapper overtimeApplicationMapper;
+ @Resource
+ private HrmSalaryStructureMapper salaryStructureMapper;
+ @Resource
+ private HrmOvertimeWageConfigMapper overtimeWageConfigMapper;
+ @Resource
private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
@Resource
private HrmSalaryPaymentMapper salaryPaymentMapper;
@Resource
private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+ @Resource
+ private HrmEmployeeScheduleMapper employeeScheduleMapper;
+ @Resource
+ private HrmShiftMapper shiftMapper;
+ @Resource
+ private HrmAttendanceSummaryMapper attendanceSummaryMapper;
@Override
public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -130,7 +161,10 @@
if (existCalculations.size() > 1) {
throw exception(SALARY_CALCULATION_DUPLICATE, employee.getName(), period);
}
- continue; // 宸插瓨鍦ㄥ垯璺宠繃
+ // 宸茬‘璁ゆ垨宸插彂鏀剧殑璁板綍涓嶅彲鑷姩鏀瑰啓锛涘緟纭璁板綍鍏佽閲嶇畻锛屼互鍚告敹鍚庢潵瀹℃壒閫氳繃鐨勫姞鐝��
+ if (existCalculations.get(0).getStatus() != 0) {
+ continue;
+ }
}
// 2.2 鑾峰彇鍛樺伐钖叕妗f锛堟寜鍛樺伐ID鏌ヨ锛�
@@ -151,15 +185,20 @@
// 2.5 鏋勫缓鏍哥畻璁板綍锛堝寘鍚墸闄よ绠楋紝閫氳繃鏂规鑾峰彇姣斾緥锛�
HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
- // 2.6 鐢熸垚鏍哥畻鍗曞彿
- String no = noRedisDAO.generateSalaryCalculationNo();
- while (salaryCalculationMapper.selectByNo(no) != null) {
- no = noRedisDAO.generateSalaryCalculationNo();
+ // 2.6 淇濆瓨锛氶娆$敓鎴愬崟鍙凤紱宸叉湁寰呯‘璁よ褰曞垯淇濈暀鍗曞彿骞惰鐩栭噸绠楃粨鏋�
+ if (CollUtil.isNotEmpty(existCalculations)) {
+ HrmSalaryCalculationDO existing = existCalculations.get(0);
+ calculation.setId(existing.getId());
+ calculation.setNo(existing.getNo());
+ salaryCalculationMapper.updateById(calculation);
+ } else {
+ String no = noRedisDAO.generateSalaryCalculationNo();
+ while (salaryCalculationMapper.selectByNo(no) != null) {
+ no = noRedisDAO.generateSalaryCalculationNo();
+ }
+ calculation.setNo(no);
+ salaryCalculationMapper.insert(calculation);
}
- calculation.setNo(no);
-
- // 2.7 淇濆瓨鏍哥畻璁板綍
- salaryCalculationMapper.insert(calculation);
}
}
@@ -174,13 +213,14 @@
}
/**
- * 鏋勫缓榛樿鐨勫憳宸ヨ柂閰。妗堬紙鍩轰簬鍛樺伐琛ㄧ殑鍩烘湰宸ヨ祫锛�
+ * 鏋勫缓榛樿鐨勫憳宸ヨ柂閰。妗堛�傚熀鏈伐璧勭粺涓�鍙栧憳宸ユ。妗堬紝缁╂晥宸ヨ祫榛樿涓嶈嚜鍔ㄧ敓鎴愩��
*/
private HrmEmployeeSalaryDO buildDefaultEmployeeSalary(Long employeeId, HrmEmployeeDO employee) {
+ BigDecimal baseSalary = employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO;
return HrmEmployeeSalaryDO.builder()
.userId(employeeId) // 浣跨敤鍛樺伐ID
.salaryStructureId(employee.getSalaryStructureId())
- .baseSalary(employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO)
+ .baseSalary(baseSalary)
.performanceSalary(BigDecimal.ZERO)
.mealAllowance(BigDecimal.ZERO)
.transportAllowance(BigDecimal.ZERO)
@@ -199,34 +239,63 @@
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;
- BigDecimal overtimePay = BigDecimal.ZERO;
+ OvertimeSalary overtimeSalary = calculateOvertimeSalary(employee, employeeSalary, period, baseSalary);
+ BigDecimal overtimePay = overtimeSalary.pay();
- // 搴斿彂宸ヨ祫鎬婚
- BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
- .add(transportAllowance).add(otherAllowance).add(overtimePay);
+ // 搴斿嚭澶╂暟锛氫紭鍏堝彇鑰冨嫟姹囨�婚噷鐨勩�屽簲鍑哄嫟澶╂暟銆嶏紙瀹㈡埛鎸夊疄闄呮湀浠界淮鎶わ紝濡� 30 澶╋級锛�
+ // 娌℃湁姹囨�绘椂閫�鍥炴帓鐝疮璁″ぉ鏁帮紝鍐嶉��鍥為粯璁� 22
+ ScheduleStat scheduleStat = calculateScheduleStat(employeeId, period);
+ Integer workDays = scheduleStat.scheduledDays() > 0 ? scheduleStat.scheduledDays() : 22;
+ BigDecimal totalWorkHours = scheduleStat.totalWorkHours();
- // 鑾峰彇绀句繚鍩烘暟锛堝憳宸ユ。妗堜腑鐨勪釜鎬у寲鍩烘暟锛�
+ // 瀹炲嚭澶╂暟锛氬彇鑷�冨嫟姹囨�汇��
+ // 娉ㄦ剰锛歯ull 琛ㄧず璇ユ湀灏氭湭鐢熸垚鑰冨嫟姹囨�伙紝姝ゆ椂涓嶅仛銆岀己鍕ゆ墸娆俱�嶏紙瑙嗕负婊″嫟锛屽彧鎸夎鍋囧崟鎵o級锛�
+ // 鍚﹀垯涓�閲嶇畻灏变細鎶婃墍鏈夋病褰曡�冨嫟鐨勫憳宸ュ伐璧勬墸鍏夈��
+ Integer actualDays = null;
+ HrmAttendanceSummaryDO attendanceSummary =
+ attendanceSummaryMapper.selectByUserIdAndYearMonth(employeeId, period);
+ if (attendanceSummary != null) {
+ if (attendanceSummary.getActualDays() != null) {
+ actualDays = attendanceSummary.getActualDays();
+ }
+ if (attendanceSummary.getWorkDays() != null && attendanceSummary.getWorkDays() > 0) {
+ workDays = attendanceSummary.getWorkDays();
+ }
+ }
+
+ // 鍏ㄥ嫟濂栵細涓庡叾浠栨触璐翠竴鑷达紝鐢变汉宸ュ湪鏍哥畻鍗曚腑缁存姢锛屾柊寤烘椂榛樿 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
@@ -235,11 +304,24 @@
.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);
+ // 缂哄嫟鎵f锛堣鍋� + 鏃峰伐锛�
+ // 璁¤柂鍩烘暟 = 鍩烘湰宸ヨ祫 + 宀椾綅宸ヨ祫 + 宸ラ緞宸ヨ祫 + 缁╂晥宸ヨ祫锛堣繖浜涗笌鍑哄嫟澶╂暟鐩存帴鐩稿叧锛夛紱
+ // 涓嶅惈鍔犵彮璐�/鍚勯」琛ヨ创/鍏ㄥ嫟濂栵紙杩欎簺涓嶉殢鍑哄嫟澶╂暟鎶樼畻锛夈��
+ // 娉ㄦ剰锛氫笉鑳界敤"鍩烘湰宸ヨ祫"褰撳熀鏁扳�斺�斿緢澶氬憳宸ュ伐璧勪富浣撴槸宀椾綅宸ヨ祫锛屽惁鍒欏疄鍑� 0 澶╂椂鍙墸鎺夊熀鏈伐璧勶紝宀椾綅宸ヨ祫鐓у彂銆�
+ BigDecimal attendanceBaseSalary = baseSalary
+ .add(postSalary).add(senioritySalary).add(performanceSalary);
+ BigDecimal leaveDeduction = calculateAttendanceDeduction(
+ employee, period, attendanceBaseSalary, workDays, actualDays);
// 璁$畻涓◣鎵i櫎
// 搴旂撼绋庢墍寰楅 = 搴斿彂宸ヨ祫 - 绀句繚鎵i櫎 - 鍏Н閲戞墸闄� - 璧峰緛鐐�(5000)
@@ -255,83 +337,263 @@
BigDecimal otherDeduction = BigDecimal.ZERO;
- // 璁$畻瀹炲彂宸ヨ祫 = 搴斿彂 - 鎵i櫎
- BigDecimal totalDeduction = socialSecurityDeduction.add(housingFundDeduction)
- .add(taxDeduction).add(otherDeduction).add(leaveDeduction);
- BigDecimal actualSalary = totalIncome.subtract(totalDeduction);
+ // 璇存槑锛氬疄鍙戝伐璧勭粺涓�璧� calcActualSalary 璁$畻锛堟敹鍏� - 鎵i櫎锛屼笖涓嶅厑璁镐负璐燂級锛�
+ // 涓庛�屼慨鏀规牳绠楀崟 / 鎵归噺淇濆瓨銆嶄繚鎸佸悓涓�鍙e緞锛岄伩鍏嶅嚭鐜拌礋鏁板伐璧�
- return HrmSalaryCalculationDO.builder()
+ HrmSalaryCalculationDO calculation = HrmSalaryCalculationDO.builder()
.period(period)
.userId(employeeId) // 浣跨敤鍛樺伐ID
.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(22) // 榛樿宸ヤ綔鏃�
- .overtimeHours(BigDecimal.ZERO)
+ .workDays(workDays) // 搴斿嚭澶╂暟锛氫紭鍏堣�冨嫟姹囨�伙紝鍏舵鎺掔彮绱锛屾渶鍚庡洖閫� 22
+ .actualDays(actualDays) // 瀹炲嚭澶╂暟锛氬彇鑷�冨嫟姹囨��
+ .overtimeHours(overtimeSalary.hours())
+ .totalWorkHours(totalWorkHours) // 淇濈暀鍘嗗彶瀛楁锛屼笟鍔″凡鏀圭敤 actualDays
.status(0) // 寰呯‘璁�
.build();
+ calculation.setActualSalary(calcActualSalary(calculation));
+ return calculation;
}
/**
- * 璁$畻璇峰亣鎵f
+ * 璁$畻銆岀己鍕ゆ墸娆俱��= 璇峰亣鎵f + 鏃峰伐鎵f
*
- * @param employee 鍛樺伐淇℃伅
- * @param period 鏍哥畻鍛ㄦ湡锛堝2026-02锛�
- * @param baseSalary 鍩烘湰宸ヨ祫
- * @return 璇峰亣鎵f閲戦
+ * <p>瑙勫垯锛�
+ * <ol>
+ * <li>鏃ュ伐璧� = 鍩烘湰宸ヨ祫 / 搴斿嚭澶╂暟锛堜笌搴斿嚭澶╂暟鍙e緞涓�鑷达紝閬垮厤缂哄嫟澶╂暟瓒呰繃璁¤柂澶╂暟鏃舵墸瓒咃級锛�</li>
+ * <li>璇峰亣澶╂暟鎸夈�岃鍋囨墸娆鹃厤缃�嶇殑姣斾緥鎵o細璋冧紤绛夋湭閰嶇疆鐨勭被鍨嬫瘮渚嬭涓� 0锛堜笉鎵o級锛岀梾鍋囨寜閰嶇疆姣斾緥鎵o紱</li>
+ * <li>鑻ヨ鏈堝凡鐢熸垚鑰冨嫟姹囨�伙紙actualDays 涓嶄负 null锛夛細
+ * 鏃峰伐澶╂暟 = 搴斿嚭澶╂暟 鈭� 瀹炲嚭澶╂暟 鈭� 璇峰亣澶╂暟锛堜笉涓鸿礋锛夛紝鎸� 100% 鎵o紱</li>
+ * <li>鑻ユ湭鐢熸垚鑰冨嫟姹囨�伙紙actualDays 涓� null锛夛紝瑙嗕负婊″嫟锛屼笉鍋氭椃宸ユ墸娆撅紝鍙寜璇峰亣鍗曟墸锛�</li>
+ * <li>鎵f鎬婚灏侀《涓哄熀鏈伐璧勶紝閬垮厤鍑虹幇璐熷伐璧勩��</li>
+ * </ol>
+ *
+ * @param employee 鍛樺伐淇℃伅
+ * @param period 鏍哥畻鍛ㄦ湡锛堝 2026-02锛�
+ * @param attendanceBase 璁¤柂鍩烘暟 = 鍩烘湰 + 宀椾綅 + 宸ラ緞 + 缁╂晥锛堜笌鍑哄嫟鐩存帴鐩稿叧鐨勫浐瀹氬伐璧勶級
+ * @param workDays 搴斿嚭澶╂暟锛堟帓鐝疮璁★紝鏃犳帓鐝椂浼� 22锛�
+ * @param actualDays 瀹炲嚭澶╂暟锛堝彇鑷�冨嫟姹囨�伙紱null 琛ㄧず璇ユ湀鏈敓鎴愭眹鎬伙級
+ * @return 缂哄嫟鎵f閲戦锛堝皝椤朵负璁¤柂鍩烘暟锛�
*/
- private BigDecimal calculateLeaveDeduction(HrmEmployeeDO employee, String period, BigDecimal baseSalary) {
- // 瑙f瀽鍛ㄦ湡鑾峰彇鏈堜唤寮�濮嬪拰缁撴潫鏃堕棿
- YearMonth yearMonth = YearMonth.parse(period);
- LocalDateTime monthStart = yearMonth.atDay(1).atStartOfDay();
- LocalDateTime monthEnd = yearMonth.atEndOfMonth().atTime(23, 59, 59);
+ private BigDecimal calculateAttendanceDeduction(HrmEmployeeDO employee, String period,
+ BigDecimal attendanceBase, Integer workDays, Integer actualDays) {
+ int shouldDays = (workDays != null && workDays > 0) ? workDays : 22;
+ // 鏃ュ伐璧� = 璁¤柂鍩烘暟 / 搴斿嚭澶╂暟锛堜繚鐣� 4 浣嶏紝鏈�鍚庣粺涓�淇濈暀 2 浣嶏級
+ BigDecimal dailySalary = attendanceBase.divide(new BigDecimal(shouldDays), 4, RoundingMode.HALF_UP);
- // 鏌ヨ鍛樺伐鍦ㄨ鏈堝凡瀹℃壒閫氳繃鐨勮鍋囪褰�
- // 娉ㄦ剰锛氳鍋囩敵璇风殑 userId 鏄郴缁熺敤鎴稩D锛岄渶瑕侀�氳繃鍛樺伐琛ㄨ幏鍙�
+ BigDecimal totalDeduction = BigDecimal.ZERO;
+ BigDecimal leaveDays = BigDecimal.ZERO;
+
+ // 1. 璇峰亣鎵f锛氭寜璇峰亣绫诲瀷鐨勯厤缃瘮渚�
Long systemUserId = employee.getUserId();
- if (systemUserId == null) {
- return BigDecimal.ZERO;
- }
- List<HrmLeaveApplicationDO> leaveApplications = leaveApplicationMapper
- .selectApprovedListByUserIdAndTimeRange(systemUserId, monthStart, monthEnd);
-
- if (leaveApplications.isEmpty()) {
- return BigDecimal.ZERO;
+ if (systemUserId != null) {
+ YearMonth yearMonth = YearMonth.parse(period);
+ LocalDateTime monthStart = yearMonth.atDay(1).atStartOfDay();
+ LocalDateTime monthEnd = yearMonth.atEndOfMonth().atTime(23, 59, 59);
+ List<HrmLeaveApplicationDO> leaveApplications = leaveApplicationMapper
+ .selectApprovedListByUserIdAndTimeRange(systemUserId, monthStart, monthEnd);
+ if (CollUtil.isNotEmpty(leaveApplications)) {
+ List<HrmLeaveTypeConfigDO> configList = leaveTypeConfigMapper.selectList();
+ Map<Integer, BigDecimal> configMap = configList.stream()
+ .filter(c -> c.getStatus() != null && c.getStatus() == 0) // 鍙彇鍚敤鐨勯厤缃�
+ .collect(Collectors.toMap(HrmLeaveTypeConfigDO::getLeaveType,
+ HrmLeaveTypeConfigDO::getDeductionRatio));
+ for (HrmLeaveApplicationDO application : leaveApplications) {
+ BigDecimal duration = application.getDuration() != null
+ ? application.getDuration() : BigDecimal.ZERO;
+ // 鏈厤缃殑绫诲瀷锛堝璋冧紤锛夋瘮渚嬫寜 0 澶勭悊 = 涓嶆墸娆�
+ BigDecimal deductionRatio = configMap.getOrDefault(application.getLeaveType(), BigDecimal.ZERO);
+ totalDeduction = totalDeduction.add(
+ dailySalary.multiply(duration).multiply(deductionRatio));
+ leaveDays = leaveDays.add(duration);
+ }
+ }
}
- // 鑾峰彇鎵�鏈夎鍋囩被鍨嬮厤缃�
- List<HrmLeaveTypeConfigDO> configList = leaveTypeConfigMapper.selectList();
- Map<Integer, BigDecimal> configMap = configList.stream()
- .filter(c -> c.getStatus() == 0) // 鍙彇鍚敤鐨勯厤缃�
- .collect(Collectors.toMap(HrmLeaveTypeConfigDO::getLeaveType, HrmLeaveTypeConfigDO::getDeductionRatio));
-
- // 璁$畻鏃ュ伐璧勶紙鍩烘湰宸ヨ祫 / 21.75锛屾寜鍥藉瑙勫畾鐨勫伐浣滄棩璁$畻锛�
- BigDecimal dailySalary = baseSalary.divide(new BigDecimal("21.75"), 2, RoundingMode.HALF_UP);
-
- // 绱璇峰亣鎵f
- BigDecimal totalLeaveDeduction = BigDecimal.ZERO;
- for (HrmLeaveApplicationDO application : leaveApplications) {
- Integer leaveType = application.getLeaveType();
- BigDecimal duration = application.getDuration() != null ? application.getDuration() : BigDecimal.ZERO;
- BigDecimal deductionRatio = configMap.getOrDefault(leaveType, BigDecimal.ZERO);
-
- // 璇峰亣鎵f = 鏃ュ伐璧� 脳 璇峰亣澶╂暟 脳 鎵f姣斾緥
- BigDecimal deduction = dailySalary.multiply(duration).multiply(deductionRatio)
- .setScale(2, RoundingMode.HALF_UP);
- totalLeaveDeduction = totalLeaveDeduction.add(deduction);
+ // 2. 鏃峰伐鎵f锛氱己鍕ゅぉ鏁颁腑鏈璇峰亣鍗曡鐩栫殑閮ㄥ垎锛屾寜 100% 鎵o紙浠呭綋鏈夎�冨嫟姹囨�绘椂锛�
+ if (actualDays != null) {
+ BigDecimal absentDays = new BigDecimal(shouldDays - actualDays).subtract(leaveDays);
+ if (absentDays.signum() > 0) {
+ totalDeduction = totalDeduction.add(dailySalary.multiply(absentDays));
+ }
}
- return totalLeaveDeduction;
+ // 3. 灏侀《锛氭墸娆句笉瓒呰繃鍩烘湰宸ヨ祫
+ BigDecimal result = totalDeduction.setScale(2, RoundingMode.HALF_UP);
+ BigDecimal cap = attendanceBase.setScale(2, RoundingMode.HALF_UP);
+ return result.compareTo(cap) > 0 ? cap : result;
+ }
+
+ /**
+ * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇凤紝璁$畻鍔犵彮宸ヨ祫涓庡姞鐝椂闀裤��
+ *
+ * <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) {
+ if (employee.getUserId() == null) {
+ return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+ }
+ YearMonth month = YearMonth.parse(period);
+ List<HrmOvertimeApplicationDO> applications = overtimeApplicationMapper.selectApprovedByUserAndDateRange(
+ employee.getUserId(), month.atDay(1), month.atEndOfMonth());
+ if (CollUtil.isEmpty(applications)) {
+ return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+ }
+ // 绠$悊绫诲埆涓嶈鍔犵彮宸ヨ祫
+ 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;
+ 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 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) {}
+
+ private record ScheduleStat(int scheduledDays, BigDecimal totalWorkHours) {}
+
+ /**
+ * 鎸夋帓鐝〃绱鍛樺伐鐨勫嚭鍕ゅぉ鏁颁笌鎺掔彮鎬诲伐鏃躲��
+ * 浠呯粺璁℃湁鐝鐨勬帓鐝褰曪紝鐝鏍囧噯宸ユ椂涔嬪拰鍗充负鎺掔彮宸ユ椂銆�
+ */
+ private ScheduleStat calculateScheduleStat(Long employeeId, String period) {
+ YearMonth yearMonth = YearMonth.parse(period);
+ List<HrmEmployeeScheduleDO> schedules = employeeScheduleMapper.selectListByEmployeeIdAndPeriod(
+ employeeId, yearMonth.atDay(1), yearMonth.atEndOfMonth());
+ if (CollUtil.isEmpty(schedules)) {
+ return new ScheduleStat(0, BigDecimal.ZERO);
+ }
+ Set<Long> shiftIds = schedules.stream().map(HrmEmployeeScheduleDO::getShiftId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ Map<Long, HrmShiftDO> shiftMap = CollUtil.isEmpty(shiftIds) ? Collections.emptyMap()
+ : shiftMapper.selectBatchIds(shiftIds).stream()
+ .collect(Collectors.toMap(HrmShiftDO::getId, shift -> shift, (a, b) -> a));
+ int scheduledDays = 0;
+ BigDecimal totalWorkHours = BigDecimal.ZERO;
+ for (HrmEmployeeScheduleDO schedule : schedules) {
+ HrmShiftDO shift = schedule.getShiftId() == null ? null : shiftMap.get(schedule.getShiftId());
+ if (shift == null) {
+ continue;
+ }
+ scheduledDays++;
+ totalWorkHours = totalWorkHours.add(shift.getWorkHours() != null ? shift.getWorkHours() : BigDecimal.ZERO);
+ }
+ return new ScheduleStat(scheduledDays, totalWorkHours.setScale(2, RoundingMode.HALF_UP));
}
@Override
@@ -445,6 +707,9 @@
@Transactional(rollbackFor = Exception.class)
public void saveSalaryCalculationList(List<HrmSalaryCalculationDO> calculationList) {
for (HrmSalaryCalculationDO calculation : calculationList) {
+ // 缂哄嫟鎵f锛堣鍋� + 鏃峰伐锛変笌瀹炲彂宸ヨ祫缁熶竴鐢卞悗绔噸绠楋紝
+ // 淇濊瘉鍒楄〃閲屾敼銆屽簲鍑哄ぉ鏁� / 瀹炲嚭澶╂暟銆嶅悗閲戦璺熺潃鍙�
+ recalcAttendanceDeduction(calculation);
if (calculation.getId() == null) {
// 鏂板璁板綍锛岀敓鎴愭牳绠楀崟鍙�
String no = noRedisDAO.generateSalaryCalculationNo();
@@ -459,6 +724,108 @@
salaryCalculationMapper.updateById(calculation);
}
}
+ }
+
+ @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()));
+ // 缂哄嫟鎵f锛堣鍋� + 鏃峰伐锛変笌瀹炲彂宸ヨ祫缁熶竴鐢卞悗绔噸绠楋紝
+ // 淇濊瘉鍒楄〃閲屾敼銆屽簲鍑哄ぉ鏁� / 瀹炲嚭澶╂暟銆嶅悗閲戦璺熺潃鍙橈紝涓斾笉鍙楀墠绔紶鍏ョ殑 leaveDeduction 褰卞搷
+ recalcAttendanceDeduction(calculation);
+ salaryCalculationMapper.updateById(calculation);
+ }
+
+ /**
+ * 閲嶇畻銆岀己鍕ゆ墸娆俱�嶄笌銆屽疄鍙戝伐璧勩��
+ *
+ * <p>鏍哥畻鍗� userId 瀛樼殑鏄憳宸D锛坔rm_employee.id锛夈�傛寜銆屽簲鍑哄ぉ鏁� / 瀹炲嚭澶╂暟 + 璇峰亣鍗曘�嶉噸绠�
+ * 缂哄嫟鎵f锛堣鍋囨寜閰嶇疆姣斾緥鎵c�佹椃宸ユ寜 100% 鎵c�佸皝椤惰钖熀鏁帮級锛屽啀閲嶇畻瀹炲彂宸ヨ祫锛堜笉鍙负璐燂級銆�
+ *
+ * <p>璁¤柂鍩烘暟 = 鍩烘湰 + 宀椾綅 + 宸ラ緞 + 缁╂晥锛堜笌鍑哄嫟鐩存帴鐩稿叧鐨勫浐瀹氬伐璧勯儴鍒嗭級锛�
+ * 涓嶈兘鐢ㄥ熀鏈伐璧勫崟鐙皝椤讹紝鍚﹀垯宸ヨ祫涓讳綋涓哄矖浣嶅伐璧勭殑鍛樺伐瀹炲嚭 0 澶╂椂浠嶄細鍙戞斁澶ч儴鍒嗗伐璧勩��
+ */
+ private void recalcAttendanceDeduction(HrmSalaryCalculationDO calculation) {
+ HrmEmployeeDO employee = calculation.getUserId() != null
+ ? employeeMapper.selectById(calculation.getUserId()) : null;
+ if (employee != null && calculation.getPeriod() != null) {
+ BigDecimal attendanceBaseSalary = nvl(calculation.getBaseSalary())
+ .add(nvl(calculation.getPostSalary()))
+ .add(nvl(calculation.getSenioritySalary()))
+ .add(nvl(calculation.getPerformanceSalary()));
+ BigDecimal deduction = calculateAttendanceDeduction(
+ employee, calculation.getPeriod(),
+ attendanceBaseSalary,
+ calculation.getWorkDays(),
+ calculation.getActualDays());
+ calculation.setLeaveDeduction(deduction);
+ } else {
+ calculation.setLeaveDeduction(nvl(calculation.getLeaveDeduction()));
+ }
+ calculation.setActualSalary(calcActualSalary(calculation));
+ }
+
+ /**
+ * 璁$畻瀹炲彂宸ヨ祫 = 鏀跺叆椤瑰悎璁� - 鎵i櫎椤瑰悎璁★紝淇濈暀涓や綅灏忔暟
+ *
+ * 鏀跺叆椤瑰繀椤讳笌 buildSalaryCalculation 涓� totalIncome 鐨勫彛寰勫畬鍏ㄤ竴鑷达紝
+ * 鍚﹀垯浼氬嚭鐜般�屾柊寤烘牳绠楃畻寰楀銆佷慨鏀逛竴娆℃牳绠楀崟瀹炲彂灏卞彉灏戙�嶇殑涓嶄竴鑷淬��
+ *
+ * 瀹炲彂宸ヨ祫涓嶅厑璁镐负璐燂細缂哄嫟鎵f绛夊凡灏侀《涓哄熀鏈伐璧勶紝浣嗙ぞ淇�/鍏Н閲�/涓◣鍙犲姞鍚庝粛鍙兘涓鸿礋锛�
+ * 涓氬姟涓婃寜銆屾湰鏈堟棤宸ヨ祫銆嶅鐞嗭紙缃� 0锛夛紝閬垮厤鍑虹幇鍛樺伐鍊掓瑺鍏徃鐨勬儏鍐点��
+ */
+ 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()));
+ BigDecimal actual = income.subtract(deduction).setScale(2, RoundingMode.HALF_UP);
+ return actual.signum() < 0 ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : actual;
+ }
+
+ private static BigDecimal nvl(BigDecimal v) {
+ return v == null ? BigDecimal.ZERO : v;
}
/**
@@ -577,4 +944,40 @@
salaryCalculationMapper.deleteById(id);
}
-}
\ No newline at end of file
+ /**
+ * 鎵归噺鍒犻櫎鏍哥畻璁板綍锛堝叏閫夊悗涓�閿垹闄わ級
+ *
+ * 璇存槑锛�
+ * 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