From b852254d90e7d1955db31db154193ec8ee84d8b3 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 09 九月 2026 10:57:59 +0800
Subject: [PATCH] feat(hrm): 新增员工用工性质、班组归属字段及考勤异常处理优化
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 105 insertions(+), 4 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..b6b375f 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,22 +5,30 @@
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.attendance.HrmAttendanceSummaryDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO;
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.HrmSalaryStructureDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
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.employee.HrmEmployeeMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseResultMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmLeaveTypeConfigMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryCalculationMapper;
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.HrmSalaryStructureMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -63,6 +71,24 @@
private static final BigDecimal DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO = new BigDecimal("0.005");
private static final BigDecimal DEFAULT_HOUSING_FUND_PERSONAL_RATIO = new BigDecimal("0.12");
+ /**
+ * 鍔犵彮宸ヨ祫鍊嶇巼榛樿鍊硷紙钖叕缁撴瀯鏈厤缃椂浣跨敤锛�
+ */
+ private static final BigDecimal DEFAULT_OVERTIME_PAY_RATIO = new BigDecimal("1.5");
+
+ /**
+ * 榛樿搴斿嚭鍕ゅぉ鏁帮紙鏃犺�冨嫟姹囨�绘椂鐨勫厹搴曞�硷級
+ */
+ private static final int DEFAULT_WORK_DAYS = 22;
+
+ /**
+ * 缁╂晥宸ヨ祫妗g骇绯绘暟锛�1-浼樼 2-鑹ソ 3-鍚堟牸 4-寰呮敼杩�
+ */
+ private static final BigDecimal PERFORMANCE_GRADE_FACTOR_EXCELLENT = new BigDecimal("1.2");
+ private static final BigDecimal PERFORMANCE_GRADE_FACTOR_GOOD = new BigDecimal("1.0");
+ private static final BigDecimal PERFORMANCE_GRADE_FACTOR_PASS = new BigDecimal("0.8");
+ private static final BigDecimal PERFORMANCE_GRADE_FACTOR_IMPROVE = new BigDecimal("0.6");
+
@Resource
private HrmSalaryCalculationMapper salaryCalculationMapper;
@Resource
@@ -87,6 +113,14 @@
private HrmSalaryPaymentMapper salaryPaymentMapper;
@Resource
private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+ @Resource
+ private HrmAttendanceSummaryMapper attendanceSummaryMapper;
+ @Resource
+ private HrmPerformanceAppraiseMapper performanceAppraiseMapper;
+ @Resource
+ private HrmPerformanceAppraiseResultMapper performanceAppraiseResultMapper;
+ @Resource
+ private HrmSalaryStructureMapper salaryStructureMapper;
@Override
public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -204,7 +238,21 @@
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;
+ // ===== 鑰冨嫟閾捐矾锛氬簲鍑哄嫟澶╂暟 / 鍔犵彮鏃堕暱 / 缂哄嫟鎵f =====
+ HrmAttendanceSummaryDO attendanceSummary = attendanceSummaryMapper.selectByUserIdAndYearMonth(employeeId, period);
+ int workDays = attendanceSummary != null && attendanceSummary.getWorkDays() != null
+ ? attendanceSummary.getWorkDays() : DEFAULT_WORK_DAYS;
+ BigDecimal overtimeHours = attendanceSummary != null && attendanceSummary.getOvertimeHours() != null
+ ? attendanceSummary.getOvertimeHours() : BigDecimal.ZERO;
+ // ===== 鍔犵彮宸ヨ祫 = 鏃惰柂 脳 鍔犵彮鏃堕暱 脳 鍊嶇巼锛堟椂钖� = 鍩烘湰宸ヨ祫 梅 (搴斿嚭鍕ゅぉ 脳 8)锛� =====
BigDecimal overtimePay = BigDecimal.ZERO;
+ if (overtimeHours.compareTo(BigDecimal.ZERO) > 0 && workDays > 0) {
+ BigDecimal hourlyRate = baseSalary.divide(BigDecimal.valueOf(workDays * 8L), 4, RoundingMode.HALF_UP);
+ overtimePay = hourlyRate.multiply(overtimeHours).multiply(getOvertimePayRatio(employeeSalary, employee))
+ .setScale(2, RoundingMode.HALF_UP);
+ }
+ // ===== 缁╂晥宸ヨ祫锛氭寜褰撴湀缁╂晥缁撴灉妗g骇绯绘暟鎶樼畻锛堟棤缁撴灉鎸夊叏棰濓級 =====
+ performanceSalary = calculatePerformanceSalary(performanceSalary, employeeId, period);
// 搴斿彂宸ヨ祫鎬婚
BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
@@ -238,8 +286,8 @@
// 鍏Н閲戞墸闄� = 鍩烘暟 脳 鍏Н閲戜釜浜烘瘮渚�
BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP);
- // 璁$畻璇峰亣鎵f
- BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary);
+ // 璁$畻缂哄嫟鎵f锛堣�冨嫟鍙e緞锛氱己鍕ゅぉ鏁� = 搴斿嚭鍕� 鈭� 瀹為檯鍑哄嫟锛涙棤鑰冨嫟姹囨�绘椂鍥為��鍒拌鍋囩敵璇峰彛寰勶級
+ BigDecimal leaveDeduction = calculateDeduction(attendanceSummary, baseSalary, workDays, employee, period);
// 璁$畻涓◣鎵i櫎
// 搴旂撼绋庢墍寰楅 = 搴斿彂宸ヨ祫 - 绀句繚鎵i櫎 - 鍏Н閲戞墸闄� - 璧峰緛鐐�(5000)
@@ -276,13 +324,66 @@
.otherDeduction(otherDeduction)
.leaveDeduction(leaveDeduction)
.actualSalary(actualSalary)
- .workDays(22) // 榛樿宸ヤ綔鏃�
- .overtimeHours(BigDecimal.ZERO)
+ .workDays(workDays) // 鍔ㄦ�佸簲鍑哄嫟澶╂暟锛堟潵鑷�冨嫟姹囨�伙級
+ .overtimeHours(overtimeHours) // 鍔犵彮鏃堕暱锛堟潵鑷�冨嫟姹囨�伙級
.status(0) // 寰呯‘璁�
.build();
}
/**
+ * 璁$畻缂哄嫟鎵f锛氫紭鍏堟寜鑰冨嫟姹囨�伙紙缂哄嫟澶╂暟 = 搴斿嚭鍕� 鈭� 瀹為檯鍑哄嫟锛夛紱鏃犳眹鎬绘椂鍥為��鍒拌鍋囩敵璇峰彛寰�
+ */
+ private BigDecimal calculateDeduction(HrmAttendanceSummaryDO attendanceSummary, BigDecimal baseSalary,
+ int workDays, HrmEmployeeDO employee, String period) {
+ if (attendanceSummary != null && workDays > 0 && attendanceSummary.getActualDays() != null) {
+ int absentDays = Math.max(0, workDays - attendanceSummary.getActualDays());
+ if (absentDays == 0) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal daySalary = baseSalary.divide(BigDecimal.valueOf(workDays), 4, RoundingMode.HALF_UP);
+ return daySalary.multiply(BigDecimal.valueOf(absentDays)).setScale(2, RoundingMode.HALF_UP);
+ }
+ return calculateLeaveDeduction(employee, period, baseSalary);
+ }
+
+ /**
+ * 璁$畻缁╂晥宸ヨ祫锛氭寜褰撴湀宸插畬鎴愪笖鍖归厤鏍哥畻鍛ㄦ湡鐨勬湀搴︾哗鏁堣�冩牳缁撴灉鐨勬。绾х郴鏁版姌绠�
+ */
+ private BigDecimal calculatePerformanceSalary(BigDecimal performanceSalary, Long employeeId, String period) {
+ HrmPerformanceAppraiseDO appraise = performanceAppraiseMapper.selectMonthlyCompletedByPeriod(period);
+ if (appraise == null) {
+ return performanceSalary;
+ }
+ HrmPerformanceAppraiseResultDO result = performanceAppraiseResultMapper
+ .selectByAppraiseIdAndEmployeeId(appraise.getId(), employeeId);
+ if (result == null || result.getGrade() == null) {
+ return performanceSalary;
+ }
+ BigDecimal factor = switch (result.getGrade()) {
+ case 1 -> PERFORMANCE_GRADE_FACTOR_EXCELLENT;
+ case 2 -> PERFORMANCE_GRADE_FACTOR_GOOD;
+ case 3 -> PERFORMANCE_GRADE_FACTOR_PASS;
+ case 4 -> PERFORMANCE_GRADE_FACTOR_IMPROVE;
+ default -> BigDecimal.ONE;
+ };
+ return performanceSalary.multiply(factor).setScale(2, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 鑾峰彇鍔犵彮宸ヨ祫鍊嶇巼锛堣柂閰粨鏋勬湭閰嶇疆鏃堕粯璁� 1.5锛�
+ */
+ private BigDecimal getOvertimePayRatio(HrmEmployeeSalaryDO employeeSalary, HrmEmployeeDO employee) {
+ Long structureId = employeeSalary.getSalaryStructureId() != null
+ ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
+ if (structureId == null) {
+ return DEFAULT_OVERTIME_PAY_RATIO;
+ }
+ HrmSalaryStructureDO structure = salaryStructureMapper.selectById(structureId);
+ return structure != null && structure.getOvertimePayRatio() != null
+ ? structure.getOvertimePayRatio() : DEFAULT_OVERTIME_PAY_RATIO;
+ }
+
+ /**
* 璁$畻璇峰亣鎵f
*
* @param employee 鍛樺伐淇℃伅
--
Gitblit v1.9.3