关于社保补缴的计算,如果社保保险设置有多条数据,根据创建时间来判断,当月即以后得参考标准用对应的配置,并且需要统计当年从1月到现在缴纳的所有社保金额,和最新的社保比列得出的金额进行比较,计算需要补缴的金额,只有社会保险设置新增一条数据的当月会计算,其余月份对应的都是0
已修改1个文件
230 ■■■■ 文件已修改
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java 230 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -21,7 +21,12 @@
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
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;
@@ -48,6 +53,8 @@
    private final PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
    private final SalesLedgerProductionAccountingService salesLedgerProductionAccountingService;
    private final SubsidyConfigurationMapper subsidyConfigurationMapper;
    private final StaffSalaryMainMapper staffSalaryMainMapper;
    private final StaffSalaryDetailMapper staffSalaryDetailMapper;
    @Override
@@ -165,6 +172,7 @@
        BigDecimal basicSalary = new BigDecimal("0.00");
        map.put("fundPersonal", fundPersonal); // 公积金
        map.put("socialPersonal", socialPersonal); // 社保金额
        map.put("socialSupplementAmount", BigDecimal.ZERO); // 社保补缴金额
        map.put("basicSalary", basicSalary); // 基本工资
        // 个税金额
        BigDecimal salaryTax = new BigDecimal("0.00");
@@ -299,23 +307,36 @@
        if (CollectionUtils.isEmpty(schemeList)) {
            return; // 无匹配方案,返回空列表
        }
        // 3. 为每个方案关联明细列表
        for (SchemeApplicableStaff scheme : schemeList) {
            List<SchemeInsuranceDetail> detailList = schemeApplicableStaffMapper.selectDetailBySchemeId(scheme.getId());
            // 根据明细列表计算社保缴费金额
            if(CollectionUtils.isEmpty(detailList)){
                continue;
        YearMonth targetMonth = parseTargetMonth(date);
        SchemeApplicableStaff currentScheme = resolveCurrentScheme(schemeList, targetMonth);
        if (currentScheme == null) {
            return;
            }
            for (SchemeInsuranceDetail detail : detailList) {
        List<SchemeInsuranceDetail> currentDetailList = defaultIfNull(
                schemeApplicableStaffMapper.selectDetailBySchemeId(currentScheme.getId())
        );
        for (SchemeInsuranceDetail detail : currentDetailList) {
            BigDecimal amount = calculateInsuranceAmount(staffOnJobDto, detail);
                if("公积金".equals(detail.getInsuranceType())){
                    fundPersonal = fundPersonal.add(calculateByEmployeeIdType(detail.getInsuranceType(),fundPersonal, staffOnJobDto, detail));
                fundPersonal = fundPersonal.add(amount);
                }else{
                    socialPersonal = socialPersonal.add(calculateByEmployeeIdType(detail.getInsuranceType(),socialPersonal, staffOnJobDto, detail));
                socialPersonal = socialPersonal.add(amount);
                }
            }
        BigDecimal socialSupplementAmount = calculateSocialSupplementAmount(
                staffId,
                staffOnJobDto,
                targetMonth,
                currentScheme,
                currentDetailList,
                socialPersonal
        );
        if (socialSupplementAmount.compareTo(BigDecimal.ZERO) > 0) {
            socialPersonal = socialPersonal.add(socialSupplementAmount);
        }
        map.put("socialPersonal", socialPersonal);
        map.put("fundPersonal", fundPersonal);
        map.put("socialSupplementAmount", socialSupplementAmount);
        // 个税金额(社保版)
        bigDecimal = TaxCalculator.calculateMonthlyTax(basicSalary, socialPersonal, fundPersonal);
        map.put("salaryTax", bigDecimal);
@@ -351,24 +372,181 @@
    }
    /**
     * 计算
     * @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月到目标月份之前已缴纳的社保个人部分总额
     *
     * 查询逻辑:
     * 1. 根据目标月份获取当年年份前缀(如 "2024-")
     * 2. 查询该年份下所有早于目标月份的薪资主表记录
     * 3. 根据薪资主表ID查询对应的薪资明细表
     * 4. 按员工ID筛选并累加社保个人部分金额
     *
     * @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)  // 按员工ID筛选
        );
        if (CollectionUtils.isEmpty(detailList)) {
            return BigDecimal.ZERO;
        }
        // 6. 累加社保个人部分金额
        return detailList.stream()
                .map(StaffSalaryDetail::getSocialPersonal)  // 获取社保个人部分
                .filter(Objects::nonNull)
                .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;
    }
}