From b5ca4fa6acd8c7ea8ed1400b133eb2eb6b5cb8ba Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 14 十月 2026 15:22:16 +0800
Subject: [PATCH] feat(hrm): 社保公积金方案支持按部门绑定,修比例输入精度

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java |  114 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 98 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 8be05ab..c2b563e 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,8 @@
 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.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;
@@ -16,6 +18,8 @@
 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.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;
@@ -39,8 +43,12 @@
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.YearMonth;
+import java.util.Collections;
+import java.util.HashMap;
 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;
@@ -79,6 +87,8 @@
     @Resource
     private HrmSocialSecuritySchemeMapper socialSecuritySchemeMapper;
     @Resource
+    private HrmSocialSecuritySchemeService socialSecuritySchemeService;
+    @Resource
     private HrmNoRedisDAO noRedisDAO;
     @Resource
     private DeptApi deptApi;
@@ -96,6 +106,10 @@
     private HrmSalaryPaymentMapper salaryPaymentMapper;
     @Resource
     private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+    @Resource
+    private HrmEmployeeScheduleMapper employeeScheduleMapper;
+    @Resource
+    private HrmShiftMapper shiftMapper;
 
     @Override
     public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -129,6 +143,8 @@
                 .toList();
 
         // 2. 閬嶅巻鍛樺伐杩涜钖叕鏍哥畻
+        // 閮ㄩ棬 -> 鏂规 鐨勮В鏋愮粨鏋滃湪鏈鏍哥畻鍐呯紦瀛橈紝閬垮厤閫愬憳宸ラ噸澶嶆煡搴�
+        Map<Long, HrmSocialSecuritySchemeDO> deptSchemeCache = new HashMap<>();
         for (HrmEmployeeDO employee : employees) {
             // employeeId 鏄憳宸D锛岀敤浜庡叧鑱旇柂閰。妗堛�佺ぞ淇濇。妗堛�佹牳绠楄褰�
             Long employeeId = employee.getId();
@@ -154,11 +170,8 @@
 
             // 2.3 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堬紙鎸夊憳宸D鏌ヨ锛屽彇鏈�鏂扮敓鏁堢殑锛�
             HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
-            // 2.4 濡傛灉鏈夌ぞ淇濇。妗堬紝鑾峰彇缁戝畾鐨勬柟妗�
-            HrmSocialSecuritySchemeDO scheme = null;
-            if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
-                scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
-            }
+            // 2.4 鑾峰彇鏂规锛氬憳宸ユ。妗堟寚瀹� > 鎵�鍦ㄩ儴闂ㄧ粦瀹� > 鍏ㄥ眬榛樿
+            HrmSocialSecuritySchemeDO scheme = resolveScheme(socialSecurity, employee, effectiveDate, deptSchemeCache);
 
             // 2.5 鏋勫缓鏍哥畻璁板綍锛堝寘鍚墸闄よ绠楋紝閫氳繃鏂规鑾峰彇姣斾緥锛�
             HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
@@ -225,17 +238,31 @@
         OvertimeSalary overtimeSalary = calculateOvertimeSalary(employee, employeeSalary, period, baseSalary);
         BigDecimal overtimePay = overtimeSalary.pay();
 
+        // 鎸夋帓鐝疮璁″嚭鍕ゅぉ鏁颁笌鎺掔彮鎬诲伐鏃讹紙鏃犳帓鐝椂鍑哄嫟澶╂暟鍥為��榛樿 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);
 
-        // 鑾峰彇绀句繚鍩烘暟锛氬憳宸ョぞ淇濇。妗� > 鍛樺伐鍩烘湰宸ヨ祫
+        // 鑾峰彇绀句繚鍩烘暟锛氬憳宸ョぞ淇濇。妗� > 鏂规榛樿缂磋垂鍩烘暟 > 鍛樺伐鍩烘湰宸ヨ祫
         BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
-                ? socialSecurity.getSocialSecurityBase() : baseSalary;
+                ? socialSecurity.getSocialSecurityBase()
+                : (scheme != null && scheme.getDefaultSocialSecurityBase() != null
+                        ? scheme.getDefaultSocialSecurityBase() : baseSalary);
 
-        // 鑾峰彇鍏Н閲戝熀鏁帮細鍛樺伐绀句繚妗f > 鍛樺伐鍩烘湰宸ヨ祫
-        BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
-                ? socialSecurity.getHousingFundBase() : baseSalary;
+        // 鑾峰彇鍏Н閲戝熀鏁帮細鍛樺伐绀句繚妗f > 鏂规榛樿鍏Н閲戝熀鏁帮紙澶т簬 0 鏃讹級> 绀句繚鍩烘暟
+        BigDecimal housingFundBase;
+        if (socialSecurity != null && socialSecurity.getHousingFundBase() != null) {
+            housingFundBase = socialSecurity.getHousingFundBase();
+        } else if (scheme != null && scheme.getDefaultHousingFundBase() != null
+                && scheme.getDefaultHousingFundBase().compareTo(BigDecimal.ZERO) > 0) {
+            housingFundBase = scheme.getDefaultHousingFundBase();
+        } else {
+            housingFundBase = socialSecurityBase;
+        }
 
         // 浠庢柟妗堜腑鑾峰彇缂寸撼姣斾緥锛堜紭鍏堜娇鐢ㄦ柟妗堜腑鐨勬瘮渚嬶紝鍚﹀垯浣跨敤榛樿姣斾緥锛�
         BigDecimal pensionPersonalRatio = (scheme != null && scheme.getPensionPersonalRatio() != null)
@@ -295,10 +322,35 @@
                 .otherDeduction(otherDeduction)
                 .leaveDeduction(leaveDeduction)
                 .actualSalary(actualSalary)
-                .workDays(22) // 榛樿宸ヤ綔鏃�
+                .workDays(workDays) // 鎸夋帓鐝疮璁″簲鍑哄嫟澶╂暟锛屾棤鎺掔彮鏃跺洖閫� 22
                 .overtimeHours(overtimeSalary.hours())
+                .totalWorkHours(totalWorkHours)
                 .status(0) // 寰呯‘璁�
                 .build();
+    }
+
+    /**
+     * 瑙f瀽鍛樺伐鏍哥畻鐢ㄧ殑绀句繚鍏Н閲戞柟妗�
+     *
+     * 浼樺厛绾э細鍛樺伐绀句繚妗f鎸囧畾鐨勬柟妗堬紙涓埆瑕嗙洊锛�> 鍛樺伐鎵�鍦ㄩ儴闂ㄧ粦瀹氱殑鏂规 > 鍏ㄥ眬榛樿鏂规銆�
+     * 閮藉彇涓嶅埌鏃惰繑鍥� null锛岀敱 {@link #buildSalaryCalculation} 浣跨敤榛樿姣斾緥甯搁噺銆�
+     *
+     * @param deptSchemeCache 鏈鏍哥畻鍐呫�岄儴闂� -> 鏂规銆嶇殑缂撳瓨锛堝厑璁� null 鍊硷紝鐢ㄤ簬缂撳瓨"璇ラ儴闂ㄦ棤鏂规"锛�
+     */
+    private HrmSocialSecuritySchemeDO resolveScheme(HrmEmployeeSocialSecurityDO socialSecurity, HrmEmployeeDO employee,
+                                                    LocalDate periodDate,
+                                                    Map<Long, HrmSocialSecuritySchemeDO> deptSchemeCache) {
+        if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
+            HrmSocialSecuritySchemeDO scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
+            if (scheme != null) {
+                return scheme;
+            }
+        }
+        Long deptId = employee.getDeptId();
+        if (!deptSchemeCache.containsKey(deptId)) {
+            deptSchemeCache.put(deptId, socialSecuritySchemeService.resolveSchemeByDeptId(deptId, periodDate));
+        }
+        return deptSchemeCache.get(deptId);
     }
 
     /**
@@ -401,6 +453,37 @@
 
     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
     @Transactional(rollbackFor = Exception.class)
     public void confirmSalaryCalculation(List<Long> ids) {
@@ -483,6 +566,8 @@
 
         // 2. 閬嶅巻鍛樺伐瀹炴椂璁$畻钖叕
         List<HrmSalaryCalculationDO> resultList = new java.util.ArrayList<>();
+        // 閮ㄩ棬 -> 鏂规 鐨勮В鏋愮粨鏋滃湪鏈棰勮鍐呯紦瀛橈紝閬垮厤閫愬憳宸ラ噸澶嶆煡搴�
+        Map<Long, HrmSocialSecuritySchemeDO> deptSchemeCache = new HashMap<>();
         for (HrmEmployeeDO employee : employees) {
             Long employeeId = employee.getId();
 
@@ -495,11 +580,8 @@
 
             // 2.2 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堬紙鎸夊憳宸D鏌ヨ锛屽彇鏈�鏂扮敓鏁堢殑锛�
             HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
-            // 2.3 濡傛灉鏈夌ぞ淇濇。妗堬紝鑾峰彇缁戝畾鐨勬柟妗�
-            HrmSocialSecuritySchemeDO scheme = null;
-            if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
-                scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
-            }
+            // 2.3 鑾峰彇鏂规锛氬憳宸ユ。妗堟寚瀹� > 鎵�鍦ㄩ儴闂ㄧ粦瀹� > 鍏ㄥ眬榛樿
+            HrmSocialSecuritySchemeDO scheme = resolveScheme(socialSecurity, employee, effectiveDate, deptSchemeCache);
 
             // 2.4 鏋勫缓鏍哥畻璁板綍锛堝疄鏃惰绠楋紝涓嶅瓨鍌級
             HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);

--
Gitblit v1.9.3