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;
}
}