huminmin
4 天以前 70a0b21595941890a9d26cfde01ff17e3427214a
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -2,36 +2,39 @@
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.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.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.SchemeApplicableStaffMapper;
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.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.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;
/**
 * <p>
@@ -42,28 +45,16 @@
 * @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 SalesLedgerProductionAccountingService salesLedgerProductionAccountingService;
    private final ProductionProductMainMapper productionProductMainMapper;
    @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)){
@@ -167,100 +159,236 @@
     * @param id
     */
    public void calculateByEmployeeId(Integer id,Map<String, Object> map,String date) {
        // 1. 入参校验
        if (id == null) {
            return; // 或返回空列表,根据业务需求调整
        if (id == null || map == 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("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);
        // 调用基本工资
        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);
        // 计时工资 计件工资
        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(getUidByStaffId(staffId));
        userProductionAccountingDto.setUserId(sysUserId);
        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. 查询该人员对应的社保方案
        BigDecimal repairWorkHour = resolveRepairWorkHour(sysUserId, date);
        BigDecimal convertedWorkHours = resolveConvertedWorkHours(
                map,
                actualOutput,
                standardQuantity,
                conversionFactor,
                repairWorkHour,
                byUserId
        );
        BigDecimal conversionRatio = calculateConversionRatio(standardHours, convertedWorkHours);
        // 查询该人员对应的社保方案
        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));
        if (!CollectionUtils.isEmpty(schemeList)) {
            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 = 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);
        // 个税金额(社保版)
        bigDecimal = TaxCalculator.calculateMonthlyTax(basicSalary, socialPersonal, fundPersonal);
        map.put("salaryTax", bigDecimal);
        // 应扣工资
        map.put("deductSalary", bigDecimal.add(fundPersonal).add(socialPersonal));
        // 实发工资
        map.put("netSalary", basicSalary.subtract(bigDecimal).subtract(fundPersonal).subtract(socialPersonal));
        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<String, Object> 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<String, Object> 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<String, Object> map) {
        return hasValue(map, "actualOutput")
                ? normalizeMoney(getBigDecimal(map, "actualOutput"))
                : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * 解析工序标准数量。
     */
    private BigDecimal resolveStandardQuantity(Map<String, Object> map) {
        return hasValue(map, "standardQuantity")
                ? normalizeMoney(getBigDecimal(map, "standardQuantity"))
                : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * 解析工序折算系数,未配置时默认 1。
     */
    private BigDecimal resolveConversionFactor(Map<String, Object> 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<com.ruoyi.production.pojo.ProductionProductMain>()
                        .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<String, Object> 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);
    }
    /**
@@ -291,17 +419,96 @@
     * @param detail
     * @return
     */
    public BigDecimal calculateByEmployeeIdType(String type,BigDecimal bigDecimal, StaffOnJob staffOnJobDto,SchemeInsuranceDetail detail) {
    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 = staffOnJobDto.getBasicSalary().multiply(detail.getPersonalRatio().divide(new BigDecimal("100"), 2));
            // 调用当前工资表中的基本工资
            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<SchemeInsuranceDetail> 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<String, Object> map, String key) {
        return map != null && map.get(key) != null;
    }
    private boolean hasText(Map<String, Object> 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<String, Object> 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;
    }
}