package com.ruoyi.staff.service.impl; 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.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.mapper.SysDeptMapper; import com.ruoyi.project.system.mapper.SysUserMapper; import com.ruoyi.staff.controller.TaxCalculator; import com.ruoyi.staff.mapper.*; import com.ruoyi.staff.pojo.*; import com.ruoyi.staff.service.SchemeApplicableStaffService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; 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; import java.util.stream.Collectors; /** *

* 社保方案适用人员表 服务实现类 *

* * @author 芯导软件(江苏)有限公司 * @since 2026-03-05 11:50:17 */ @Service @RequiredArgsConstructor public class SchemeApplicableStaffServiceImpl extends ServiceImpl implements SchemeApplicableStaffService { 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 public AjaxResult listPage(Page page, SchemeApplicableStaff schemeApplicableStaff) { LambdaQueryWrapper schemeApplicableStaffLambdaQueryWrapper = new LambdaQueryWrapper<>(); if(schemeApplicableStaff != null){ if(StringUtils.isNotEmpty(schemeApplicableStaff.getTitle())){ schemeApplicableStaffLambdaQueryWrapper.like(SchemeApplicableStaff::getTitle, schemeApplicableStaff.getTitle()); } } schemeApplicableStaffLambdaQueryWrapper.orderByDesc(SchemeApplicableStaff::getId); Page page1 = schemeApplicableStaffMapper.selectPage(page, schemeApplicableStaffLambdaQueryWrapper); List collect = page1.getRecords().stream().map(SchemeApplicableStaff::getId).collect(Collectors.toList()); if(CollectionUtils.isEmpty(collect)){ return AjaxResult.success(page1); } List schemeInsuranceDetails = schemeInsuranceDetailMapper .selectList(new LambdaQueryWrapper() .in(SchemeInsuranceDetail::getSchemeId, collect)); page1.getRecords().forEach(item -> { item.setSchemeInsuranceDetailList(schemeInsuranceDetails .stream() .filter(detail -> detail.getSchemeId().equals(item.getId())) .collect(Collectors.toList())); SysUser sysUser = sysUserMapper.selectUserById(item.getCreateUser().longValue()); item.setCreateUserName(sysUser == null ? "未知" : sysUser.getNickName()); // 获取部门信息 String[] split = item.getDeptIds().split(","); List sysDepts = sysDeptMapper.selectList(new LambdaQueryWrapper() .in(SysDept::getDeptId, Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList()))); if(!CollectionUtils.isEmpty(sysDepts)){ item.setDeptNames(sysDepts.stream().map(SysDept::getDeptName).collect(Collectors.joining(","))); } }); return AjaxResult.success(page1); } public void setSchemeApplicableStaffUserInfo(SchemeApplicableStaff schemeApplicableStaff) { // 通过部门获取人员id String[] split = schemeApplicableStaff.getDeptIds().split(","); List staffOnJobs = staffOnJobMapper.selectList(new LambdaQueryWrapper() .in(StaffOnJob::getSysDeptId, Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList()))); if(CollectionUtils.isEmpty(staffOnJobs)){ throw new IllegalArgumentException("部门下无员工"); } schemeApplicableStaff.setStaffIds(staffOnJobs .stream() .map(StaffOnJob::getId) .filter(Objects::nonNull) // 过滤掉 null 值 .map(String::valueOf) .collect(Collectors.joining( ","))); schemeApplicableStaff.setStaffNames(staffOnJobs.stream().map(StaffOnJob::getStaffName).collect(Collectors.joining(","))); } @Override public AjaxResult add(SchemeApplicableStaff schemeApplicableStaff) { if(schemeApplicableStaff == null){ return AjaxResult.error("参数错误"); } if(CollectionUtils.isEmpty(schemeApplicableStaff.getSchemeInsuranceDetailList())){ return AjaxResult.error("请选择方案明细"); } setSchemeApplicableStaffUserInfo(schemeApplicableStaff); //根据部门设置用户信息 int insert = schemeApplicableStaffMapper.insert(schemeApplicableStaff); schemeApplicableStaff.getSchemeInsuranceDetailList().forEach(item -> { item.setSchemeId(schemeApplicableStaff.getId()); schemeInsuranceDetailMapper.insert(item); }); return AjaxResult.success(insert); } @Override public AjaxResult updateSchemeApplicableStaff(SchemeApplicableStaff schemeApplicableStaff) { if(schemeApplicableStaff == null){ return AjaxResult.error("参数错误"); } setSchemeApplicableStaffUserInfo(schemeApplicableStaff); //根据部门设置用户信息 int update = schemeApplicableStaffMapper.updateById(schemeApplicableStaff); // 先删,重新绑定 schemeInsuranceDetailMapper.delete(new LambdaQueryWrapper() .eq(SchemeInsuranceDetail::getSchemeId, schemeApplicableStaff.getId())); schemeApplicableStaff.getSchemeInsuranceDetailList().forEach(item -> { item.setSchemeId(schemeApplicableStaff.getId()); schemeInsuranceDetailMapper.insert(item); }); return AjaxResult.success(update); } @Override public AjaxResult delete(List ids) { if (CollectionUtils.isEmpty(ids)) { return AjaxResult.error("参数错误"); } int delete = schemeApplicableStaffMapper.deleteBatchIds(ids); schemeInsuranceDetailMapper.delete(new LambdaQueryWrapper() .in(SchemeInsuranceDetail::getSchemeId, ids)); return AjaxResult.success(delete); } /** * 通过员工id计算社保方案 * @param id */ public void calculateByEmployeeId(Integer id,Map map,String date) { // 1. 入参校验 if (id == null) { 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("socialSupplementAmount", BigDecimal.ZERO); // 社保补缴金额 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); // 根据排班数据计算白班天数和夜班天数 BigDecimal dayDays = new BigDecimal("0.00"); BigDecimal nightDays = new BigDecimal("0.00"); // 查询当月排班记录 List shiftList = personalShiftMapper.selectList(new LambdaQueryWrapper() .eq(PersonalShift::getStaffOnJobId, staffId) .like(PersonalShift::getWorkTime, date)); if(!CollectionUtils.isEmpty(shiftList)){ // 收集所有班次配置ID List configIds = shiftList.stream() .map(PersonalShift::getPersonalAttendanceLocationConfigId) .filter(Objects::nonNull) .collect(Collectors.toList()); if(!CollectionUtils.isEmpty(configIds)){ // 查询班次配置信息 List configList = personalAttendanceLocationConfigMapper.selectList( new LambdaQueryWrapper() .in(PersonalAttendanceLocationConfig::getId, configIds)); // 构建班次配置映射 Map 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("日")){ dayDays = dayDays.add(BigDecimal.ONE); } else if(shiftName.contains("夜")){ nightDays = nightDays.add(BigDecimal.ONE); } } } } } map.put("dayDays", dayDays); // 白班天数 map.put("nightDays", nightDays); // 夜班天数 // 查询餐补和夜班补贴 // 计算餐补(回族特有)和夜班补助 BigDecimal mealAmount = new BigDecimal("0.00"); // 餐补 BigDecimal nightAmount = new BigDecimal("0.00"); // 夜班补助 // 获取餐补和夜班补贴标准(从补贴配置中获取) 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; } String nation = staffOnJobDto.getNation(); if("回族".equals(nation)){ mealAmount = dayDays.add(nightDays).multiply(mealStandard); } // 夜班补助:夜班天数*夜班补贴标准 nightAmount = nightDays.multiply(nightStandard); map.put("mealAmount", mealAmount); // 餐补 map.put("nightAmount", nightAmount); // 夜班补助 // 调用基本工资 basicSalary = staffOnJobDto.getBasicSalary(); map.put("basicSalary", basicSalary); // 应发工资(基本工资+餐补+夜班补助+其他收入) grossSalary = basicSalary.add(mealAmount).add(nightAmount).add(otherIncome); map.put("grossSalary", grossSalary); // 实发工资初始值 netSalary = grossSalary; map.put("netSalary", netSalary); // 个税金额(无社保版) 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); } // 2. 查询该人员对应的社保方案 List schemeList = schemeApplicableStaffMapper.selectSchemeByStaffId(staffId); if (CollectionUtils.isEmpty(schemeList)) { return; // 无匹配方案,返回空列表 } YearMonth targetMonth = parseTargetMonth(date); SchemeApplicableStaff currentScheme = resolveCurrentScheme(schemeList, targetMonth); if (currentScheme == null) { return; } List 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); } } 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); // 应扣工资 = 个税 + 公积金个人 + 社保个人 + 其他支出 deductSalary = bigDecimal.add(fundPersonal).add(socialPersonal).add(otherDeduct); map.put("deductSalary", deductSalary); // 实发工资 = 应发工资 - 应扣工资 netSalary = grossSalary.subtract(deductSalary); map.put("netSalary", netSalary); } /** * 通过员工Id获取用户id * @param staffId * @return */ public Long getUidByStaffId(Long staffId){ StaffOnJob staffOnJob = staffOnJobMapper.selectById(staffId); if(staffOnJob == null){ return -1L; // 返回不存在Id } SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper() .eq(SysUser::getUserName, staffOnJob.getStaffNo()) .eq(SysUser::getDelFlag, "0") .last("limit 1")); if(sysUser == null){ return -1L; // 返回不存在Id } return sysUser.getUserId(); } /** * 计算单项保险金额 */ public BigDecimal calculateInsuranceAmount(StaffOnJob staffOnJobDto, SchemeInsuranceDetail detail) { if (staffOnJobDto == null || detail == null) { return BigDecimal.ZERO; } 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 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 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 salaryMainList = staffSalaryMainMapper.selectList( new LambdaQueryWrapper() .likeRight(StaffSalaryMain::getSalaryMonth, yearPrefix) // 匹配当年所有月份 .lt(StaffSalaryMain::getSalaryMonth, currentMonthStr) // 小于目标月份 ); if (CollectionUtils.isEmpty(salaryMainList)) { return BigDecimal.ZERO; } // 4. 提取薪资主表ID列表 List mainIds = salaryMainList.stream() .map(StaffSalaryMain::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(mainIds)) { return BigDecimal.ZERO; } // 5. 查询对应的薪资明细表 List detailList = staffSalaryDetailMapper.selectList( new LambdaQueryWrapper() .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 List defaultIfNull(List list) { return list == null ? Collections.emptyList() : list; } private BigDecimal defaultZero(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } }