From 0bd6994efea85e468c06b58cd0395cd4066d31cd Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 12 六月 2026 14:39:07 +0800
Subject: [PATCH] 根据单个员工计算工资

---
 src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java |  544 +++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 417 insertions(+), 127 deletions(-)

diff --git a/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
index 7d59c26..11c4060 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -2,35 +2,34 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.framework.web.domain.AjaxResult;
-import com.ruoyi.production.dto.UserAccountDto;
-import com.ruoyi.production.dto.UserProductionAccountingDto;
+import com.ruoyi.production.bean.dto.UserAccountDto;
+import com.ruoyi.production.bean.dto.UserProductionAccountingDto;
 import com.ruoyi.production.service.SalesLedgerProductionAccountingService;
 import com.ruoyi.project.system.domain.SysDept;
 import com.ruoyi.project.system.domain.SysUser;
-import com.ruoyi.project.system.domain.SysUserDept;
 import com.ruoyi.project.system.mapper.SysDeptMapper;
-import com.ruoyi.project.system.mapper.SysUserDeptMapper;
 import com.ruoyi.project.system.mapper.SysUserMapper;
 import com.ruoyi.staff.controller.TaxCalculator;
-import com.ruoyi.staff.dto.StaffOnJobDto;
-import com.ruoyi.staff.mapper.SchemeInsuranceDetailMapper;
-import com.ruoyi.staff.mapper.StaffOnJobMapper;
-import com.ruoyi.staff.pojo.SchemeApplicableStaff;
-import com.ruoyi.staff.mapper.SchemeApplicableStaffMapper;
-import com.ruoyi.staff.pojo.SchemeInsuranceDetail;
-import com.ruoyi.staff.pojo.StaffOnJob;
-import com.ruoyi.staff.service.IStaffOnJobService;
+import com.ruoyi.staff.mapper.*;
+import com.ruoyi.staff.pojo.*;
 import com.ruoyi.staff.service.SchemeApplicableStaffService;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
-import javax.annotation.Resource;
 import java.math.BigDecimal;
-import java.util.*;
+import java.math.RoundingMode;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -42,28 +41,20 @@
  * @since 2026-03-05 11:50:17
  */
 @Service
+@RequiredArgsConstructor
 public class SchemeApplicableStaffServiceImpl extends ServiceImpl<SchemeApplicableStaffMapper, SchemeApplicableStaff> implements SchemeApplicableStaffService {
 
-    @Autowired
-    private SchemeApplicableStaffMapper schemeApplicableStaffMapper;
-
-    @Autowired
-    private SchemeInsuranceDetailMapper schemeInsuranceDetailMapper;
-
-    @Autowired
-    private SysUserDeptMapper sysUserDeptMapper;
-
-    @Autowired
-    private SysUserMapper sysUserMapper;
-
-    @Autowired
-    private SysDeptMapper sysDeptMapper;
-
-    @Autowired
-    private StaffOnJobMapper staffOnJobMapper;
-
-    @Autowired
-    private SalesLedgerProductionAccountingService salesLedgerProductionAccountingService;
+    private final SchemeApplicableStaffMapper schemeApplicableStaffMapper;
+    private final SchemeInsuranceDetailMapper schemeInsuranceDetailMapper;
+    private final SysUserMapper sysUserMapper;
+    private final SysDeptMapper sysDeptMapper;
+    private final StaffOnJobMapper staffOnJobMapper;
+    private final PersonalShiftMapper personalShiftMapper;
+    private final PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
+    private final SalesLedgerProductionAccountingService salesLedgerProductionAccountingService;
+    private final SubsidyConfigurationMapper subsidyConfigurationMapper;
+    private final StaffSalaryMainMapper staffSalaryMainMapper;
+    private final StaffSalaryDetailMapper staffSalaryDetailMapper;
 
 
     @Override
@@ -74,6 +65,7 @@
                 schemeApplicableStaffLambdaQueryWrapper.like(SchemeApplicableStaff::getTitle, schemeApplicableStaff.getTitle());
             }
         }
+        schemeApplicableStaffLambdaQueryWrapper.orderByDesc(SchemeApplicableStaff::getId);
         Page<SchemeApplicableStaff> page1 = schemeApplicableStaffMapper.selectPage(page, schemeApplicableStaffLambdaQueryWrapper);
         List<Long> collect = page1.getRecords().stream().map(SchemeApplicableStaff::getId).collect(Collectors.toList());
         if(CollectionUtils.isEmpty(collect)){
@@ -172,95 +164,235 @@
             return; // 鎴栬繑鍥炵┖鍒楄〃锛屾牴鎹笟鍔¢渶姹傝皟鏁�
         }
         Long staffId = id.longValue();
-        // 绀句繚閲戦
-        BigDecimal socialPersonal = new BigDecimal("0.00");
-        // 鍏Н閲戦噾棰�
-        BigDecimal fundPersonal = new BigDecimal("0.00");
-        // 鍩烘湰宸ヨ祫
-        BigDecimal basicSalary = new BigDecimal("0.00");
-        map.put("fundPersonal", fundPersonal); // 鍏Н閲�
-        map.put("socialPersonal", socialPersonal); // 绀句繚閲戦
-        map.put("basicSalary", basicSalary); // 鍩烘湰宸ヨ祫
-        // 涓◣閲戦
-        BigDecimal salaryTax = new BigDecimal("0.00");
-        map.put("salaryTax", salaryTax);
-        // 璁′欢宸ヨ祫
-        BigDecimal pieceSalary = new BigDecimal("0.00");
-        map.put("pieceSalary", pieceSalary);
-        // 璁℃椂宸ヨ祫
-        BigDecimal hourlySalary = new BigDecimal("0.00");
-        map.put("hourlySalary", hourlySalary);
-        // 鍏朵粬鏀跺叆
-        BigDecimal otherIncome = new BigDecimal("0.00");
-        map.put("otherIncome", otherIncome);
-        // 鍏朵粬鏀嚭
-        BigDecimal otherDeduct = new BigDecimal("0.00");
-        map.put("otherDeduct", otherDeduct);
-        // 搴斿彂宸ヨ祫
-        BigDecimal grossSalary = new BigDecimal("0.00");
-        map.put("grossSalary", grossSalary);
-        // 搴旀墸宸ヨ祫
-        BigDecimal deductSalary = new BigDecimal("0.00");
-        map.put("deductSalary", deductSalary);
-        // 瀹炲彂宸ヨ祫
-        BigDecimal netSalary = new BigDecimal("0.00");
-        map.put("netSalary", netSalary);
-        // 璋冪敤鍩烘湰宸ヨ祫
+        // 鍓嶇浼犲叆鐨勫彲缂栬緫鍊间紭鍏堬紝鍏朵綑瀛楁鐢卞悗绔粺涓�璁$畻
+        boolean hasBasicSalaryInput = hasValue(map, "basicSalary");
+        boolean hasDayDaysInput = hasValue(map, "dayDays");
+        boolean hasNightDaysInput = hasValue(map, "nightDays");
+        boolean hasPieceSalaryInput = hasValue(map, "pieceSalary");
+        boolean hasHourlySalaryInput = hasValue(map, "hourlySalary");
+        boolean hasSocialPersonalInput = hasValue(map, "socialPersonal");
+        boolean hasFundPersonalInput = hasValue(map, "fundPersonal");
+        boolean hasSocialSupplementAmountInput = hasValue(map, "socialSupplementAmount");
+
+        BigDecimal basicSalary = getBigDecimal(map, "basicSalary");
+        BigDecimal dayDays = getBigDecimal(map, "dayDays");
+        BigDecimal nightDays = getBigDecimal(map, "nightDays");
+        BigDecimal pieceSalary = getBigDecimal(map, "pieceSalary");
+        BigDecimal hourlySalary = getBigDecimal(map, "hourlySalary");
+        BigDecimal otherIncome = getBigDecimal(map, "otherIncome");
+        BigDecimal otherDeduct = getBigDecimal(map, "otherDeduct");
+        BigDecimal socialPersonalInput = getBigDecimal(map, "socialPersonal");
+        BigDecimal fundPersonalInput = getBigDecimal(map, "fundPersonal");
+        BigDecimal socialSupplementAmountInput = getBigDecimal(map, "socialSupplementAmount");
+        BigDecimal socialPersonal = BigDecimal.ZERO;
+        BigDecimal fundPersonal = BigDecimal.ZERO;
+        BigDecimal salaryTax = BigDecimal.ZERO;
+        BigDecimal grossSalary = BigDecimal.ZERO;
+        BigDecimal deductSalary = BigDecimal.ZERO;
+        BigDecimal netSalary = BigDecimal.ZERO;
+        BigDecimal dayDaysInput = dayDays;
+        BigDecimal nightDaysInput = nightDays;
+        otherIncome = defaultIfNull(otherIncome);
+        otherDeduct = defaultIfNull(otherDeduct);
+        map.put("socialSupplementAmount", BigDecimal.ZERO);
+
+        // 鏌ヨ褰撴湀鎺掔彮璁板綍
+        BigDecimal computedDayDays = BigDecimal.ZERO;
+        BigDecimal computedNightDays = BigDecimal.ZERO;
+        if (!hasDayDaysInput || !hasNightDaysInput) {
+            List<PersonalShift> shiftList = personalShiftMapper.selectList(new LambdaQueryWrapper<PersonalShift>()
+                    .eq(PersonalShift::getStaffOnJobId, staffId)
+                    .like(PersonalShift::getWorkTime, date));
+
+            if(!CollectionUtils.isEmpty(shiftList)){
+                // 鏀堕泦鎵�鏈夌彮娆¢厤缃甀D
+                List<Integer> configIds = shiftList.stream()
+                        .map(PersonalShift::getPersonalAttendanceLocationConfigId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+
+                if(!CollectionUtils.isEmpty(configIds)){
+                    // 鏌ヨ鐝閰嶇疆淇℃伅
+                    List<PersonalAttendanceLocationConfig> configList = personalAttendanceLocationConfigMapper.selectList(
+                            new LambdaQueryWrapper<PersonalAttendanceLocationConfig>()
+                                    .in(PersonalAttendanceLocationConfig::getId, configIds));
+
+                    // 鏋勫缓鐝閰嶇疆鏄犲皠
+                    Map<Integer, String> configMap = configList.stream()
+                            .collect(Collectors.toMap(
+                                    PersonalAttendanceLocationConfig::getId,
+                                    PersonalAttendanceLocationConfig::getShift,
+                                    (a, b) -> a));
+
+                    // 缁熻鐧界彮鍜屽鐝ぉ鏁�
+                    for (PersonalShift shift : shiftList) {
+                        Integer configId = shift.getPersonalAttendanceLocationConfigId();
+                        String shiftName = configMap.get(configId);
+                        if(shiftName != null){
+                            // 鏍规嵁鐝鍚嶇О鍒ゆ柇锛氬寘鍚�"鐧�"鎴�"鏃�"涓虹櫧鐝紝鍖呭惈"澶�"涓哄鐝�
+                            if(shiftName.contains("鐧�") || shiftName.contains("鏃�")){
+                                computedDayDays = computedDayDays.add(BigDecimal.ONE);
+                            } else if(shiftName.contains("澶�")){
+                                computedNightDays = computedNightDays.add(BigDecimal.ONE);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        dayDays = hasDayDaysInput ? defaultIfNull(dayDaysInput) : computedDayDays;
+        nightDays = hasNightDaysInput ? defaultIfNull(nightDaysInput) : computedNightDays;
+        map.put("dayDays", dayDays); // 鐧界彮澶╂暟
+        map.put("nightDays", nightDays); // 澶滅彮澶╂暟
+
+        // 鑾峰彇椁愯ˉ鍜屽鐝ˉ璐存爣鍑嗭紙浠庤ˉ璐撮厤缃腑鑾峰彇锛�
+        SubsidyConfiguration subsidyConfig = subsidyConfigurationMapper.selectOne(null);
+        BigDecimal mealStandard = new BigDecimal("0.00"); // 椁愯ˉ鏍囧噯
+        BigDecimal nightStandard = new BigDecimal("0.00"); // 澶滅彮琛ヨ创鏍囧噯
+        if(subsidyConfig != null){
+            mealStandard = subsidyConfig.getMealAmount() != null ? subsidyConfig.getMealAmount() : new BigDecimal("0.00");
+            nightStandard = subsidyConfig.getNightAmount() != null ? subsidyConfig.getNightAmount() : new BigDecimal("0.00");
+        }
+
+        // 椁愯ˉ锛氭皯鏃忎负鍥炴棌鐗规湁锛岃绠楁柟寮忎负(鐧界彮澶╂暟+澶滅彮澶╂暟)*椁愯ˉ鏍囧噯
         StaffOnJob staffOnJobDto = staffOnJobMapper.selectById(staffId);
         if(staffOnJobDto == null){
             return;
         }
-        basicSalary = staffOnJobDto.getBasicSalary();
-        map.put("basicSalary", basicSalary);
-        // 搴斿彂宸ヨ祫
-        map.put("grossSalary", basicSalary);
-        // 涓◣閲戦锛堟棤绀句繚鐗堬級
-        BigDecimal bigDecimal = TaxCalculator.calculateMonthlyTax(basicSalary, socialPersonal, fundPersonal);
-        map.put("salaryTax", bigDecimal);
-        // 璁℃椂宸ヨ祫 璁′欢宸ヨ祫
-        UserProductionAccountingDto userProductionAccountingDto = new UserProductionAccountingDto();
-        userProductionAccountingDto.setUserId(getUidByStaffId(staffId));
-        userProductionAccountingDto.setDate(date);
-        UserAccountDto byUserId = salesLedgerProductionAccountingService.getByUserId(userProductionAccountingDto);
-        if(byUserId != null){
-            map.put("pieceSalary", byUserId.getAccountBalance());
-            map.put("hourlySalary", byUserId.getAccount());
-            // 搴斿彂 瀹炲彂澧炲姞
-            grossSalary = grossSalary.add(byUserId.getAccountBalance()).add(byUserId.getAccount());
-            map.put("grossSalary", grossSalary);
-            netSalary = netSalary.add(byUserId.getAccountBalance()).add(byUserId.getAccount());
-            map.put("netSalary", netSalary);
+
+        if (!hasBasicSalaryInput) {
+            basicSalary = defaultIfNull(staffOnJobDto.getBasicSalary());
+        } else {
+            basicSalary = defaultIfNull(basicSalary);
         }
+
+        String nation = staffOnJobDto.getNation();
+        // 鏌ヨ椁愯ˉ鍜屽鐝ˉ璐�
+        // 璁$畻椁愯ˉ锛堝洖鏃忕壒鏈夛級鍜屽鐝ˉ鍔�
+        BigDecimal mealAmount = new BigDecimal("0.00"); // 椁愯ˉ
+        BigDecimal nightAmount = new BigDecimal("0.00"); // 澶滅彮琛ュ姪
+        if("鍥炴棌".equals(nation)){
+            mealAmount = dayDays.add(nightDays).multiply(mealStandard);
+        }
+
+        // 澶滅彮琛ュ姪锛氬鐝ぉ鏁�*澶滅彮琛ヨ创鏍囧噯
+        nightAmount = nightDays.multiply(nightStandard);
+
+        map.put("mealAmount", mealAmount); // 椁愯ˉ
+        map.put("nightAmount", nightAmount); // 澶滅彮琛ュ姪
+
+        // 璋冪敤鍩烘湰宸ヨ祫
+        map.put("basicSalary", basicSalary);
+        map.put("otherIncome", otherIncome);
+        map.put("otherDeduct", otherDeduct);
+        // 璁℃椂宸ヨ祫 璁′欢宸ヨ祫
+        if (!hasPieceSalaryInput || !hasHourlySalaryInput) {
+            UserProductionAccountingDto userProductionAccountingDto = new UserProductionAccountingDto();
+            userProductionAccountingDto.setUserId(getUidByStaffId(staffId));
+            userProductionAccountingDto.setDate(date);
+            UserAccountDto byUserId = salesLedgerProductionAccountingService.getByUserId(userProductionAccountingDto);
+            if(byUserId != null){
+                if (!hasPieceSalaryInput) {
+                    pieceSalary = byUserId.getAccountBalance();
+                }
+                if (!hasHourlySalaryInput) {
+                    hourlySalary = byUserId.getAccount();
+                }
+            }
+        }
+        map.put("pieceSalary", defaultIfNull(pieceSalary));
+        map.put("hourlySalary", defaultIfNull(hourlySalary));
+        // 搴斿彂宸ヨ祫锛堝熀鏈伐璧�+椁愯ˉ+澶滅彮琛ュ姪+璁′欢宸ヨ祫+璁℃椂宸ヨ祫+鍏朵粬鏀跺叆锛�
+        grossSalary = basicSalary
+                .add(mealAmount)
+                .add(nightAmount)
+                .add(defaultIfNull(pieceSalary))
+                .add(defaultIfNull(hourlySalary))
+                .add(otherIncome);
+        map.put("grossSalary", grossSalary);
+        // 瀹炲彂宸ヨ祫鍒濆鍊�
+        netSalary = grossSalary;
+        map.put("netSalary", netSalary);
         // 2. 鏌ヨ璇ヤ汉鍛樺搴旂殑绀句繚鏂规
         List<SchemeApplicableStaff> schemeList = schemeApplicableStaffMapper.selectSchemeByStaffId(staffId);
         if (CollectionUtils.isEmpty(schemeList)) {
             return; // 鏃犲尮閰嶆柟妗堬紝杩斿洖绌哄垪琛�
         }
-        // 3. 涓烘瘡涓柟妗堝叧鑱旀槑缁嗗垪琛�
-        for (SchemeApplicableStaff scheme : schemeList) {
-            List<SchemeInsuranceDetail> detailList = schemeApplicableStaffMapper.selectDetailBySchemeId(scheme.getId());
-            // 鏍规嵁鏄庣粏鍒楄〃璁$畻绀句繚缂磋垂閲戦
-            if(CollectionUtils.isEmpty(detailList)){
-                continue;
-            }
-            for (SchemeInsuranceDetail detail : detailList) {
-                if("鍏Н閲�".equals(detail.getInsuranceType())){
-                    fundPersonal = fundPersonal.add(calculateByEmployeeIdType(detail.getInsuranceType(),fundPersonal, staffOnJobDto, detail));
-                }else{
-                    socialPersonal = socialPersonal.add(calculateByEmployeeIdType(detail.getInsuranceType(),socialPersonal, staffOnJobDto, detail));
-                }
+        YearMonth targetMonth = parseTargetMonth(date);
+        SchemeApplicableStaff currentScheme = resolveCurrentScheme(schemeList, targetMonth);
+        if (currentScheme == null) {
+            return;
+        }
+        List<SchemeInsuranceDetail> currentDetailList = defaultIfNull(
+                schemeApplicableStaffMapper.selectDetailBySchemeId(currentScheme.getId())
+        );
+        for (SchemeInsuranceDetail detail : currentDetailList) {
+            BigDecimal amount = calculateInsuranceAmount(staffOnJobDto, detail);
+            if ("鍏Н閲�".equals(detail.getInsuranceType())) {
+                fundPersonal = fundPersonal.add(amount);
+            } else {
+                socialPersonal = socialPersonal.add(amount);
             }
         }
-        map.put("socialPersonal", socialPersonal);
-        map.put("fundPersonal", fundPersonal);
+        BigDecimal currentMonthSocialPersonal = hasSocialPersonalInput ? defaultIfNull(socialPersonalInput) : socialPersonal;
+        if (!hasSocialPersonalInput) {
+            map.put("socialPersonal", currentMonthSocialPersonal);
+        }
+        BigDecimal resolvedFundPersonal = hasFundPersonalInput ? defaultIfNull(fundPersonalInput) : fundPersonal;
+        if (!hasFundPersonalInput) {
+            map.put("fundPersonal", resolvedFundPersonal);
+        }
+        BigDecimal socialSupplementAmount = calculateSocialSupplementAmount(
+                staffId,
+                staffOnJobDto,
+                targetMonth,
+                currentScheme,
+                currentDetailList,
+                currentMonthSocialPersonal
+        );
+        if (hasSocialSupplementAmountInput) {
+            socialSupplementAmount = defaultIfNull(socialSupplementAmountInput);
+        }
+        BigDecimal totalSocialPersonal = currentMonthSocialPersonal.add(socialSupplementAmount);
+        map.put("socialSupplementAmount", socialSupplementAmount);
         // 涓◣閲戦锛堢ぞ淇濈増锛�
-        bigDecimal = TaxCalculator.calculateMonthlyTax(basicSalary, socialPersonal, fundPersonal);
+        BigDecimal bigDecimal = TaxCalculator.calculateMonthlyTax(grossSalary, totalSocialPersonal, resolvedFundPersonal);
         map.put("salaryTax", bigDecimal);
-        // 搴旀墸宸ヨ祫
-        map.put("deductSalary", bigDecimal.add(fundPersonal).add(socialPersonal));
-        // 瀹炲彂宸ヨ祫
-        map.put("netSalary", basicSalary.subtract(bigDecimal).subtract(fundPersonal).subtract(socialPersonal));
 
+        // 搴旀墸宸ヨ祫 = 涓◣ + 鍏Н閲戜釜浜� + 绀句繚涓汉 + 绀句繚琛ョ即 + 鍏朵粬鏀嚭
+        deductSalary = bigDecimal.add(resolvedFundPersonal).add(totalSocialPersonal).add(otherDeduct);
+        map.put("deductSalary", deductSalary);
+
+        // 瀹炲彂宸ヨ祫 = 搴斿彂宸ヨ祫 - 搴旀墸宸ヨ祫
+        netSalary = grossSalary.subtract(deductSalary);
+        map.put("netSalary", netSalary);
+
+    }
+
+    private BigDecimal getBigDecimal(Map<String, Object> map, String key) {
+        if (map == null || !map.containsKey(key) || map.get(key) == null) {
+            return null;
+        }
+        Object value = map.get(key);
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Number) {
+            return new BigDecimal(value.toString());
+        }
+        String text = String.valueOf(value).trim();
+        if (text.isEmpty()) {
+            return null;
+        }
+        return new BigDecimal(text);
+    }
+
+    private boolean hasValue(Map<String, Object> map, String key) {
+        return map != null && map.containsKey(key) && map.get(key) != null;
+    }
+
+    private BigDecimal defaultIfNull(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
     }
 
     /**
@@ -284,24 +416,182 @@
     }
 
     /**
-     * 璁$畻
-     * @param type
-     * @param bigDecimal
-     * @param staffOnJobDto
-     * @param detail
-     * @return
+     * 璁$畻鍗曢」淇濋櫓閲戦
      */
-    public BigDecimal calculateByEmployeeIdType(String type,BigDecimal bigDecimal, StaffOnJob staffOnJobDto,SchemeInsuranceDetail detail) {
-        // 鍒ゆ柇鏄惁璋冪敤鍩烘湰宸ヨ祫
-        if (detail.getUseBasicSalary() == 1) {
-            BigDecimal divide = detail.getPaymentBase().multiply(detail.getPersonalRatio()).divide(new BigDecimal("100"), 2);
-            bigDecimal = bigDecimal.add(divide);
-        }else{
-            // 璋冪敤鍩烘湰宸ヨ祫
-            BigDecimal multiply = staffOnJobDto.getBasicSalary().multiply(detail.getPersonalRatio().divide(new BigDecimal("100"), 2));
-            bigDecimal = bigDecimal.add(multiply);
+    public BigDecimal calculateInsuranceAmount(StaffOnJob staffOnJobDto, SchemeInsuranceDetail detail) {
+        if (staffOnJobDto == null || detail == null) {
+            return BigDecimal.ZERO;
         }
-        bigDecimal = bigDecimal.add(detail.getPersonalFixed());
-        return bigDecimal;
+        BigDecimal ratio = detail.getPersonalRatio() == null ? BigDecimal.ZERO : detail.getPersonalRatio();
+        BigDecimal fixed = detail.getPersonalFixed() == null ? BigDecimal.ZERO : detail.getPersonalFixed();
+        BigDecimal baseAmount;
+        if (detail.getUseBasicSalary() == 1) {
+            baseAmount = detail.getPaymentBase() == null ? BigDecimal.ZERO : detail.getPaymentBase();
+        } else {
+            baseAmount = staffOnJobDto.getBasicSalary() == null ? BigDecimal.ZERO : staffOnJobDto.getBasicSalary();
+        }
+        BigDecimal ratioAmount = baseAmount.multiply(ratio).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
+        return ratioAmount.add(fixed);
+    }
+
+    private SchemeApplicableStaff resolveCurrentScheme(List<SchemeApplicableStaff> schemeList, YearMonth targetMonth) {
+        if (CollectionUtils.isEmpty(schemeList)) {
+            return null;
+        }
+        if (targetMonth == null) {
+            return schemeList.get(0);
+        }
+        for (SchemeApplicableStaff scheme : schemeList) {
+            if (scheme.getCreateTime() == null) {
+                continue;
+            }
+            YearMonth schemeMonth = YearMonth.from(scheme.getCreateTime());
+            if (!schemeMonth.isAfter(targetMonth)) {
+                return scheme;
+            }
+        }
+        return schemeList.get(schemeList.size() - 1);
+    }
+
+    /**
+     * 璁$畻绀句繚琛ョ即閲戦
+     *
+     * 涓氬姟閫昏緫锛�
+     * 1. 鍙湁鍦ㄧぞ淇濇柟妗堣缃柊澧炵殑褰撴湀鎵嶈绠楄ˉ缂撮噾棰濓紝鍏朵綑鏈堜唤杩斿洖0
+     * 2. 鏍规嵁鏈�鏂扮ぞ淇濇瘮渚嬭绠楀綋鏈堝強浠ュ悗搴旂即绾崇殑閲戦鏍囧噯
+     * 3. 缁熻褰撳勾1鏈堝埌褰撳墠鏈堝凡缂寸撼鐨勭ぞ淇濇�婚
+     * 4. 鐢ㄦ渶鏂版爣鍑嗚绠楃殑鏈熸湜鎬婚涓庡疄闄呭凡缂寸撼閲戦鐨勫樊棰濆嵆涓鸿ˉ缂撮噾棰�
+     *
+     * @param staffId              鍛樺伐ID
+     * @param staffOnJobDto        鍛樺伐鍦ㄨ亴淇℃伅锛堢敤浜庤绠楃ぞ淇濋噾棰濓級
+     * @param targetMonth          鐩爣璁$畻鏈堜唤
+     * @param currentScheme        褰撳墠绀句繚鏂规
+     * @param currentDetailList    褰撳墠绀句繚鏂规鏄庣粏鍒楄〃
+     * @param currentMonthSocialPersonal 褰撳墠鏈堜釜浜虹ぞ淇濋噾棰�
+     * @return 绀句繚琛ョ即閲戦锛堣礋鏁版垨0杩斿洖0锛�
+     */
+    private BigDecimal calculateSocialSupplementAmount(Long staffId,
+                                                       StaffOnJob staffOnJobDto,
+                                                       YearMonth targetMonth,
+                                                       SchemeApplicableStaff currentScheme,
+                                                       List<SchemeInsuranceDetail> currentDetailList,
+                                                       BigDecimal currentMonthSocialPersonal) {
+        // 1. 鍙傛暟鏍¢獙锛氫换涓�蹇呭~鍙傛暟涓虹┖锛岃繑鍥�0
+        if (staffId == null || staffOnJobDto == null || targetMonth == null || currentScheme == null || currentScheme.getCreateTime() == null) {
+            return BigDecimal.ZERO;
+        }
+
+        // 2. 鍒ゆ柇鏄惁涓虹ぞ淇濇柟妗堟柊澧炲綋鏈�
+        //    鍙湁鏂板绀句繚璁剧疆鐨勫綋鏈堟墠璁$畻琛ョ即锛屽叾浣欐湀浠借繑鍥�0
+        YearMonth schemeMonth = YearMonth.from(currentScheme.getCreateTime());
+        if (!schemeMonth.equals(targetMonth)) {
+            return BigDecimal.ZERO;
+        }
+
+        // 3. 璁$畻鏈�鏂版湀搴︾ぞ淇濅釜浜洪噾棰濓紙鎺掗櫎鍏Н閲戯級
+        BigDecimal latestMonthlySocial = defaultIfNull(currentDetailList).stream()
+                .filter(Objects::nonNull)
+                .filter(detail -> !"鍏Н閲�".equals(detail.getInsuranceType()))  // 鎺掗櫎鍏Н閲�
+                .map(detail -> calculateInsuranceAmount(staffOnJobDto, detail))   // 璁$畻姣忛」绀句繚閲戦
+                .reduce(BigDecimal.ZERO, BigDecimal::add);  // 绱姞姹傚拰
+
+        // 濡傛灉鏈�鏂版湀搴︾ぞ淇濋噾棰濅负0鎴栬礋鏁帮紝鏃犻渶琛ョ即
+        if (latestMonthlySocial.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+
+        // 4. 缁熻褰撳勾1鏈堝埌褰撳墠鏈堜箣鍓嶅凡缂寸撼鐨勭ぞ淇濇�婚
+        BigDecimal paidYtdSocial = sumPaidSocialPersonalBeforeMonth(staffId, targetMonth);
+
+        // 5. 璁$畻鎸夋渶鏂版爣鍑嗗簲缂寸撼鐨勫勾搴︽�婚
+        //    鏈熸湜鎬婚 = 鏈�鏂版湀搴︾ぞ淇� 脳 褰撳墠鏈堜唤锛�1-12锛�
+        BigDecimal expectedYtdSocial = latestMonthlySocial.multiply(BigDecimal.valueOf(targetMonth.getMonthValue()));
+
+        // 6. 璁$畻琛ョ即閲戦
+        //    琛ョ即閲戦 = 鏈熸湜鎬婚 - 宸茬即绾虫�婚 - 褰撳墠鏈堝凡璁$畻鐨勭ぞ淇�
+        BigDecimal supplement = expectedYtdSocial.subtract(paidYtdSocial).subtract(defaultZero(currentMonthSocialPersonal));
+
+        // 7. 杩斿洖缁撴灉锛氳礋鏁版垨0杩斿洖0锛屾鏁颁繚鐣欎袱浣嶅皬鏁�
+        return supplement.compareTo(BigDecimal.ZERO) > 0 ? supplement.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO;
+    }
+
+    /**
+     * 缁熻鍛樺伐褰撳勾1鏈堝埌鐩爣鏈堜唤涔嬪墠宸茬即绾崇殑绀句繚涓汉閮ㄥ垎鎬婚
+     * 杩欓噷鍚屾椂鍖呭惈 socialPersonal 鍜� socialSupplementAmount锛岄伩鍏嶅巻鍙茶ˉ缂存儏褰㈣閲嶅璁$畻銆�
+     *
+     * 鏌ヨ閫昏緫锛�
+     * 1. 鏍规嵁鐩爣鏈堜唤鑾峰彇褰撳勾骞翠唤鍓嶇紑锛堝 "2024-"锛�
+     * 2. 鏌ヨ璇ュ勾浠戒笅鎵�鏈夋棭浜庣洰鏍囨湀浠界殑钖祫涓昏〃璁板綍
+     * 3. 鏍规嵁钖祫涓昏〃ID鏌ヨ瀵瑰簲鐨勮柂璧勬槑缁嗚〃
+     * 4. 鎸夊憳宸D绛涢�夊苟绱姞绀句繚涓汉閮ㄥ垎閲戦
+     *
+     * @param staffId      鍛樺伐ID
+     * @param targetMonth  鐩爣鏈堜唤锛堢粺璁℃埅姝㈠埌璇ユ湀浠戒箣鍓嶏級
+     * @return 宸茬即绾崇殑绀句繚涓汉閮ㄥ垎鎬婚
+     */
+    private BigDecimal sumPaidSocialPersonalBeforeMonth(Long staffId, YearMonth targetMonth) {
+        // 1. 鍙傛暟鏍¢獙
+        if (staffId == null || targetMonth == null) {
+            return BigDecimal.ZERO;
+        }
+
+        // 2. 鏋勫缓鏌ヨ鏉′欢
+        String yearPrefix = targetMonth.getYear() + "-";  // 骞翠唤鍓嶇紑锛屽 "2024-"
+        String currentMonthStr = targetMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));  // 鐩爣鏈堜唤瀛楃涓诧紝濡� "2024-03"
+
+        // 3. 鏌ヨ褰撳勾1鏈堝埌鐩爣鏈堜唤涔嬪墠鐨勮柂璧勪富琛�
+        List<StaffSalaryMain> salaryMainList = staffSalaryMainMapper.selectList(
+                new LambdaQueryWrapper<StaffSalaryMain>()
+                        .likeRight(StaffSalaryMain::getSalaryMonth, yearPrefix)  // 鍖归厤褰撳勾鎵�鏈夋湀浠�
+                        .lt(StaffSalaryMain::getSalaryMonth, currentMonthStr)    // 灏忎簬鐩爣鏈堜唤
+        );
+        if (CollectionUtils.isEmpty(salaryMainList)) {
+            return BigDecimal.ZERO;
+        }
+
+        // 4. 鎻愬彇钖祫涓昏〃ID鍒楄〃
+        List<Long> mainIds = salaryMainList.stream()
+                .map(StaffSalaryMain::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(mainIds)) {
+            return BigDecimal.ZERO;
+        }
+
+        // 5. 鏌ヨ瀵瑰簲鐨勮柂璧勬槑缁嗚〃
+        List<StaffSalaryDetail> detailList = staffSalaryDetailMapper.selectList(
+                new LambdaQueryWrapper<StaffSalaryDetail>()
+                        .in(StaffSalaryDetail::getMainId, mainIds)        // 鏍规嵁涓昏〃ID鏌ヨ
+                        .eq(StaffSalaryDetail::getStaffOnJobId, staffId)  // 鎸夊憳宸D绛涢��
+        );
+        if (CollectionUtils.isEmpty(detailList)) {
+            return BigDecimal.ZERO;
+        }
+
+        // 6. 绱姞绀句繚涓汉閮ㄥ垎閲戦
+        return detailList.stream()
+                .map(detail -> defaultZero(detail.getSocialPersonal())
+                        .add(defaultZero(detail.getSocialSupplementAmount())))
+                .reduce(BigDecimal.ZERO, BigDecimal::add);  // 绱姞姹傚拰
+    }
+
+    private YearMonth parseTargetMonth(String date) {
+        if (StringUtils.isEmpty(date)) {
+            return null;
+        }
+        String normalized = date.length() >= 7 ? date.substring(0, 7) : date;
+        try {
+            return YearMonth.parse(normalized, DateTimeFormatter.ofPattern("yyyy-MM"));
+        } catch (DateTimeParseException ex) {
+            return null;
+        }
+    }
+
+    private <T> List<T> defaultIfNull(List<T> list) {
+        return list == null ? Collections.emptyList() : list;
+    }
+
+    private BigDecimal defaultZero(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
     }
 }

--
Gitblit v1.9.3