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.mapper.ProductionProductMainMapper;
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.SchemeApplicableStaffMapper;
import com.ruoyi.staff.mapper.SchemeInsuranceDetailMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.SchemeApplicableStaff;
import com.ruoyi.staff.pojo.SchemeInsuranceDetail;
import com.ruoyi.staff.pojo.StaffOnJob;
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.DayOfWeek;
import java.time.YearMonth;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.time.LocalDate;
/**
*
* 社保方案适用人员表 服务实现类
*
*
* @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 SalesLedgerProductionAccountingService salesLedgerProductionAccountingService;
private final ProductionProductMainMapper productionProductMainMapper;
@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) {
if (id == null || map == null) {
return;
}
Long staffId = id.longValue();
StaffOnJob staffOnJobDto = staffOnJobMapper.selectById(staffId);
if(staffOnJobDto == null){
return;
}
BigDecimal basicSalary = resolveBasicSalary(map, staffOnJobDto);
BigDecimal attendanceBonus = resolveAttendanceBonus(staffOnJobDto);
BigDecimal otherIncome = normalizeMoney(getBigDecimal(map, "otherIncome"));
BigDecimal otherDeduct = normalizeMoney(getBigDecimal(map, "otherDeduct"));
BigDecimal socialSupplementAmount = resolveSocialSupplementAmount(map, staffOnJobDto);
BigDecimal socialPersonal = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
BigDecimal fundPersonal = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
int monthWorkDays = calculateWorkDays(date);
BigDecimal standardHours = calculateStandardHours(monthWorkDays);
BigDecimal actualOutput = resolveActualOutput(map);
BigDecimal standardQuantity = resolveStandardQuantity(map);
BigDecimal conversionFactor = resolveConversionFactor(map);
Long sysUserId = getUidByStaffId(staffId);
UserProductionAccountingDto userProductionAccountingDto = new UserProductionAccountingDto();
userProductionAccountingDto.setUserId(sysUserId);
userProductionAccountingDto.setDate(date);
UserAccountDto byUserId = salesLedgerProductionAccountingService.getByUserId(userProductionAccountingDto);
BigDecimal repairWorkHour = resolveRepairWorkHour(sysUserId, date);
BigDecimal convertedWorkHours = resolveConvertedWorkHours(
map,
actualOutput,
standardQuantity,
conversionFactor,
repairWorkHour,
byUserId
);
BigDecimal conversionRatio = calculateConversionRatio(standardHours, convertedWorkHours);
// 查询该人员对应的社保方案
List schemeList = schemeApplicableStaffMapper.selectSchemeByStaffId(staffId);
if (!CollectionUtils.isEmpty(schemeList)) {
for (SchemeApplicableStaff scheme : schemeList) {
List detailList = schemeApplicableStaffMapper.selectDetailBySchemeId(scheme.getId());
if(CollectionUtils.isEmpty(detailList)){
continue;
}
for (SchemeInsuranceDetail detail : detailList) {
if("公积金".equals(detail.getInsuranceType())){
fundPersonal = calculateByEmployeeIdType(detail.getInsuranceType(), fundPersonal, basicSalary, staffOnJobDto, detail);
}else{
socialPersonal = calculateByEmployeeIdType(detail.getInsuranceType(), socialPersonal, basicSalary, staffOnJobDto, detail);
}
}
}
}
// 工资 = 全勤奖金 + 基本工资 * 折合倍数
BigDecimal grossSalary = attendanceBonus.add(basicSalary.multiply(conversionRatio)).add(otherIncome);
BigDecimal salaryTax = TaxCalculator.calculateMonthlyTax(
grossSalary,
socialPersonal.add(socialSupplementAmount),
fundPersonal
);
BigDecimal deductSalary = salaryTax
.add(fundPersonal)
.add(socialPersonal)
.add(socialSupplementAmount)
.add(otherDeduct);
BigDecimal netSalary = grossSalary.subtract(deductSalary);
if (!hasText(map, "staffName")) {
map.put("staffName", staffOnJobDto.getStaffName());
}
if (!hasText(map, "nation")) {
map.put("nation", staffOnJobDto.getNation());
}
map.put("basicSalary", basicSalary);
map.put("attendanceBonus", attendanceBonus);
map.put("monthWorkDays", monthWorkDays);
map.put("standardHours", standardHours);
map.put("actualOutput", actualOutput);
map.put("standardQuantity", standardQuantity);
map.put("conversionFactor", conversionFactor);
map.put("convertedWorkHours", convertedWorkHours);
map.put("monthTimeTotal", convertedWorkHours);
map.put("conversionRatio", conversionRatio);
map.put("pieceSalary", convertedWorkHours);
map.put("hourlySalary", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
map.put("otherIncome", otherIncome);
map.put("socialPersonal", socialPersonal);
map.put("fundPersonal", fundPersonal);
map.put("otherDeduct", otherDeduct);
map.put("socialSupplementAmount", socialSupplementAmount);
map.put("grossSalary", grossSalary);
map.put("salaryTax", salaryTax);
map.put("deductSalary", deductSalary);
map.put("netSalary", netSalary);
}
/**
* 解析员工基础工资。
* 优先使用工资表里本次传入的值,未传时回落到员工档案中的基础工资。
*/
private BigDecimal resolveBasicSalary(Map map, StaffOnJob staffOnJobDto) {
// 员工基础工资优先取工资表传入值,没有则回落到员工档案中的基础工资。
return hasValue(map, "basicSalary")
? normalizeMoney(getBigDecimal(map, "basicSalary"))
: normalizeMoney(staffOnJobDto.getBasicSalary());
}
/**
* 获取员工最新全勤奖金。
* 全勤奖金统一以员工档案配置为准。
*/
private BigDecimal resolveAttendanceBonus(StaffOnJob staffOnJobDto) {
// 全勤奖金直接取员工档案里的最新配置。
return staffOnJobDto.getAttendanceBonus() == null
? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
: normalizeMoney(staffOnJobDto.getAttendanceBonus());
}
/**
* 解析社保补缴金额。
* 如果工资表传入了补缴金额则直接使用,否则按员工所属部门最新方案计算。
*/
private BigDecimal resolveSocialSupplementAmount(Map map, StaffOnJob staffOnJobDto) {
return hasValue(map, "socialSupplementAmount")
? normalizeMoney(getBigDecimal(map, "socialSupplementAmount"))
: calculateLatestDeptSocialSupplementAmount(staffOnJobDto);
}
/**
* 计算月标准工时。
* 规则:工作日天数 * 8 小时。
*/
private BigDecimal calculateStandardHours(int monthWorkDays) {
// 月标准工时 = 工作日天数 * 8 小时。
return BigDecimal.valueOf(monthWorkDays)
.multiply(new BigDecimal("8"))
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 解析当月实际产量。
*/
private BigDecimal resolveActualOutput(Map map) {
return hasValue(map, "actualOutput")
? normalizeMoney(getBigDecimal(map, "actualOutput"))
: BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
/**
* 解析工序标准数量。
*/
private BigDecimal resolveStandardQuantity(Map map) {
return hasValue(map, "standardQuantity")
? normalizeMoney(getBigDecimal(map, "standardQuantity"))
: BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
/**
* 解析工序折算系数,未配置时默认 1。
*/
private BigDecimal resolveConversionFactor(Map map) {
return hasValue(map, "conversionFactor")
? normalizeMoney(getBigDecimal(map, "conversionFactor"))
: BigDecimal.ONE.setScale(2, RoundingMode.HALF_UP);
}
/**
* 汇总轻微返工的补修理工时。
* 按系统用户 ID 和工资月份统计,并作为月时间合计的一部分。
*/
private BigDecimal resolveRepairWorkHour(Long sysUserId, String date) {
if (sysUserId == null || sysUserId <= 0 || StringUtils.isEmpty(date)) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
LocalDate monthStart = YearMonth.parse(date).atDay(1);
LocalDate monthEnd = YearMonth.parse(date).atEndOfMonth();
return productionProductMainMapper.selectList(new LambdaQueryWrapper()
.eq(com.ruoyi.production.pojo.ProductionProductMain::getUserId, sysUserId)
.eq(com.ruoyi.production.pojo.ProductionProductMain::getReportType, 1)
.ge(com.ruoyi.production.pojo.ProductionProductMain::getCreateTime, monthStart.atStartOfDay())
.le(com.ruoyi.production.pojo.ProductionProductMain::getCreateTime, monthEnd.atTime(23, 59, 59)))
.stream()
.map(item -> normalizeMoney(item.getRepairWorkHour()))
.reduce(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), BigDecimal::add)
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 计算月时间合计。
* 计件场景使用实际产量、标准数量和折算系数换算;
* 没有计件数据时,回落到生产核算汇总值;
* 最后再叠加返工补修理工时。
*/
private BigDecimal resolveConvertedWorkHours(Map map,
BigDecimal actualOutput,
BigDecimal standardQuantity,
BigDecimal conversionFactor,
BigDecimal repairWorkHour,
UserAccountDto byUserId) {
if (hasValue(map, "monthTimeTotal")) {
BigDecimal monthTimeTotal = normalizeMoney(getBigDecimal(map, "monthTimeTotal"));
if (monthTimeTotal.compareTo(BigDecimal.ZERO) > 0) {
return monthTimeTotal;
}
}
BigDecimal convertedWorkHours = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
// 计件折算工时 = 当月实际产量 / 工序标准数量 * 8 * 工序折算系数
if (actualOutput.compareTo(BigDecimal.ZERO) > 0 && standardQuantity.compareTo(BigDecimal.ZERO) > 0) {
convertedWorkHours = actualOutput
.divide(standardQuantity, 8, RoundingMode.HALF_UP)
.multiply(new BigDecimal("8"))
.multiply(conversionFactor)
.setScale(2, RoundingMode.HALF_UP);
} else if (byUserId != null) {
convertedWorkHours = normalizeMoney(byUserId.getAccountBalance().add(byUserId.getAccount()));
}
return convertedWorkHours.add(repairWorkHour).setScale(2, RoundingMode.HALF_UP);
}
/**
* 计算折合倍数。
* 规则:月时间合计 / 月标准工时。
*/
private BigDecimal calculateConversionRatio(BigDecimal standardHours, BigDecimal convertedWorkHours) {
return standardHours.compareTo(BigDecimal.ZERO) == 0
? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
: convertedWorkHours.divide(standardHours, 8, RoundingMode.HALF_UP)
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 通过员工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();
}
/**
* 计算
* @param type
* @param bigDecimal
* @param staffOnJobDto
* @param detail
* @return
*/
public BigDecimal calculateByEmployeeIdType(String type,BigDecimal bigDecimal, BigDecimal basicSalary, 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 = basicSalary.multiply(detail.getPersonalRatio().divide(new BigDecimal("100"), 2));
bigDecimal = bigDecimal.add(multiply);
}
bigDecimal = bigDecimal.add(detail.getPersonalFixed());
return bigDecimal;
}
private BigDecimal calculateLatestDeptSocialSupplementAmount(StaffOnJob staffOnJobDto) {
if (staffOnJobDto == null || staffOnJobDto.getSysDeptId() == null) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
SchemeApplicableStaff latestScheme = schemeApplicableStaffMapper.selectLatestSchemeByDeptId(staffOnJobDto.getSysDeptId());
if (latestScheme == null) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
List detailList = schemeApplicableStaffMapper.selectDetailBySchemeId(latestScheme.getId());
if (CollectionUtils.isEmpty(detailList)) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
BigDecimal socialSupplementAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
for (SchemeInsuranceDetail detail : detailList) {
if (!"公积金".equals(detail.getInsuranceType())) {
socialSupplementAmount = calculateByEmployeeIdType(
detail.getInsuranceType(),
socialSupplementAmount,
normalizeMoney(staffOnJobDto.getBasicSalary()),
staffOnJobDto,
detail
);
}
}
return socialSupplementAmount.setScale(2, RoundingMode.HALF_UP);
}
private boolean hasValue(Map map, String key) {
return map != null && map.get(key) != null;
}
private boolean hasText(Map map, String key) {
if (map == null) {
return false;
}
Object value = map.get(key);
return value != null && !String.valueOf(value).trim().isEmpty();
}
private BigDecimal getBigDecimal(Map map, String key) {
if (map == null) {
return null;
}
Object value = map.get(key);
if (value == null) {
return null;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value));
}
String text = String.valueOf(value).trim();
if (text.isEmpty()) {
return null;
}
return new BigDecimal(text);
}
private BigDecimal normalizeMoney(BigDecimal value) {
return value == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : value.setScale(2, RoundingMode.HALF_UP);
}
private int calculateWorkDays(String date) {
if (StringUtils.isEmpty(date)) {
return 0;
}
YearMonth yearMonth = YearMonth.parse(date);
int workDays = 0;
for (int day = 1; day <= yearMonth.lengthOfMonth(); day++) {
DayOfWeek dayOfWeek = yearMonth.atDay(day).getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
workDays++;
}
}
return workDays;
}
}