yxh
5 天以前 89bb0237ead5fb632fcc14c2c52276496e19843f
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceFundtaxServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.tax.VatTaxUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.domain.ServiceFund;
import com.ruoyi.project.domain.ServiceFunddetail;
@@ -23,6 +24,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
@@ -171,66 +173,128 @@
    /**
     * 批量算税主入口
     * 1. 校验参数
     * 2. 校验批次合法性(如有必要)
     * 3. 查询本批次所有ServiceFund(按id升序)
     * 4. 预查询本月所有身份证号的累计金额,缓存到Map,提升后续算税效率
     * 5. 遍历每个ServiceFund,批量算税
     * 2. 只允许最新批次算税
     * 3. 对当前批次 service_fundtax 加行锁,防并发重复算税
     * 4. 查询本批次所有 ServiceFund(按 id 升序),并重置 istax
     * 5. 预查询本月所有身份证号的累计金额,缓存到 Map
     * 6. 校验当月是否存在历史口径(v1)算税记录,防止新旧口径混算
     * 7. 遍历每个 ServiceFund 逐单算税,汇总后回写 service_fundtax
     * <p>
     * 整批在一个事务内完成:任一条失败则整批回滚,不会出现半批数据。
     *
     * @param fundTaxVO 批次算税参数
     * @return 是否算税成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean batchFundTax(FundTaxVO fundTaxVO) {
        log.info("批量算税的fundTaxVO入参为:{}", fundTaxVO);
        // 1. 校验参数
        if (fundTaxVO.getFundTaxId() == null) {
            throw new BaseException("分批算税出问题了,请检查后再进行计算");
        }
        // 2. 校验批次合法性(仅限修改操作)
        // 2. 只允许最新批次算税
        if (fundTaxVO.getAddOrupdate() == 1) {
            Integer maxFundTaxId = serviceFundtaxMapper.getMaxFundTaxId();
            log.info("批量算税的maxFundTaxId为:{}", maxFundTaxId);
            if (!fundTaxVO.getFundTaxId().equals(Long.valueOf(maxFundTaxId))) {
            if (maxFundTaxId == null || !fundTaxVO.getFundTaxId().equals(Long.valueOf(maxFundTaxId))) {
                throw new BaseException("该批数据不能算税");
            }
        }
        // 3. 查询本批次所有ServiceFund(按id升序)
        // 3. 对当前批次汇总单加行锁(事务内有效),防止并发重复算税
        ServiceFundtax fundtax = serviceFundtaxMapper.selectByIdForUpdate(fundTaxVO.getFundTaxId());
        if (fundtax == null) {
            throw new BaseException("未找到算税汇总单,ID=" + fundTaxVO.getFundTaxId());
        }
        // 4. 查询本批次所有 ServiceFund(按 id 升序)
        ServiceFund serviceFund = new ServiceFund();
        serviceFund.setFundTaxId(fundTaxVO.getFundTaxId());
        serviceFund.setDel_flag(0);
        List<ServiceFund> serviceFunds = serviceFundService.selectServiceFundList(serviceFund);
        for (ServiceFund serviceFund1 : serviceFunds) {
            serviceFundMapper.updateFundIsTexById(serviceFund1.getId(), 0L);
        }
        // 按id升序排序,保证处理顺序一致
        serviceFunds.sort(Comparator.comparing(ServiceFund::getId));
        if (CollectionUtils.isEmpty(serviceFunds)) {
            log.warn("未查询到相关ServiceFund,fundTaxId={}", fundTaxVO.getFundTaxId());
            return false;
        }
        // 重置本批 istax 与算税时间,使本月累计查询不会重复计入本批
        for (ServiceFund serviceFund1 : serviceFunds) {
            serviceFundMapper.updateFundIsTexById(serviceFund1.getId(), 0L);
        }
        // 按 id 升序排序,保证处理顺序一致
        serviceFunds.sort(Comparator.comparing(ServiceFund::getId));
        // 4. 预查询本月所有身份证号的累计金额,缓存到Map,提升后续算税效率
        TaxMoneyByItemEO taxMoneyVO = new TaxMoneyByItemEO();
        // 5. 预查询本月所有身份证号的累计金额,缓存到 Map,提升后续算税效率
        Date taxTime = new Date();
        Date firstDay = Date.from(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay(ZoneId.systemDefault()).toInstant());
        TaxMoneyByItemEO taxMoneyVO = new TaxMoneyByItemEO();
        taxMoneyVO.setFirstDay(firstDay);
        taxMoneyVO.setTaxTime(new Date());
        taxMoneyVO.setTaxTime(taxTime);
        Map<String, TaxMoneySumEO> tempTaxSumMap = new HashMap<>();
        List<TaxMoneySumEO> taxSum = serviceFunddetailMapper.getTaxSum(taxMoneyVO);
        for (TaxMoneySumEO taxMoneySumEO : taxSum) {
            tempTaxSumMap.put(taxMoneySumEO.getIDCardNo(), taxMoneySumEO);
        if (CollectionUtils.isNotEmpty(taxSum)) {
            for (TaxMoneySumEO taxMoneySumEO : taxSum) {
                tempTaxSumMap.put(taxMoneySumEO.getIDCardNo(), taxMoneySumEO);
                // 6. 当月已存在的历史口径(v1)记录会让累计基数新旧混算,直接阻止
                if (taxMoneySumEO.getV1Count() != null && taxMoneySumEO.getV1Count() > 0) {
                    throw new BaseException("当月已存在旧口径(v1)算税记录,为避免新旧口径混算,"
                            + "本月请继续使用旧口径或从下个月起再按新口径算税。人员身份证号:"
                            + taxMoneySumEO.getIDCardNo());
                }
            }
        }
        // 5. 遍历每个ServiceFund,批量算税
        // 7. 遍历每个 ServiceFund,逐单算税并汇总
        BigDecimal pretaxTotal = BigDecimal.ZERO;
        BigDecimal vatTotal = BigDecimal.ZERO;
        BigDecimal cityTaxTotal = BigDecimal.ZERO;
        BigDecimal laborPretaxTotal = BigDecimal.ZERO;
        BigDecimal taxTotal = BigDecimal.ZERO;
        BigDecimal taxedTotal = BigDecimal.ZERO;
        for (ServiceFund serviceFund1 : serviceFunds) {
            ServiceFunddetail serviceFunddetail = new ServiceFunddetail();
            serviceFunddetail.setFundid(serviceFund1.getId());
            List<ServiceFunddetail> serviceFunddetails = serviceFunddetailMapper.selectServiceFunddetailList(serviceFunddetail);
            // 传入累计金额缓存,提升算税效率
            serviceFunddetailService.calculateTax(serviceFunddetails, tempTaxSumMap);
            if (CollectionUtils.isEmpty(serviceFunddetails)) {
                throw new BaseException("单据没有可算税的明细,单据ID=" + serviceFund1.getId()
                        + ",捐献案例=" + serviceFund1.getDonorname());
            }
            // 传入累计金额缓存与统一算税时间
            serviceFunddetailService.calculateTax(serviceFunddetails, tempTaxSumMap, taxTime);
            for (ServiceFunddetail detail : serviceFunddetails) {
                pretaxTotal = pretaxTotal.add(toMoney(detail.getAmount()));
                vatTotal = vatTotal.add(toMoney(detail.getVatAmount()));
                cityTaxTotal = cityTaxTotal.add(toMoney(detail.getCityTaxAmount()));
                laborPretaxTotal = laborPretaxTotal.add(toMoney(detail.getLaborPretaxAmount()));
                taxTotal = taxTotal.add(toMoney(detail.getTaxamount()));
                taxedTotal = taxedTotal.add(toMoney(detail.getTaxedamount()));
            }
        }
        // 8. 回写汇总单 service_fundtax(6 个金额 + 算税状态 + 版本)
        ServiceFundtax updateFundtax = new ServiceFundtax();
        updateFundtax.setId(fundTaxVO.getFundTaxId());
        updateFundtax.setPretaxamount(VatTaxUtils.roundMoney(pretaxTotal));
        updateFundtax.setVatAmount(VatTaxUtils.roundMoney(vatTotal));
        updateFundtax.setCityTaxAmount(VatTaxUtils.roundMoney(cityTaxTotal));
        updateFundtax.setLaborPretaxAmount(VatTaxUtils.roundMoney(laborPretaxTotal));
        updateFundtax.setTaxamount(VatTaxUtils.roundMoney(taxTotal));
        updateFundtax.setTaxMoney(VatTaxUtils.roundMoney(taxTotal));
        updateFundtax.setTaxedamount(VatTaxUtils.roundMoney(taxedTotal));
        updateFundtax.setTaxVersion(VatTaxUtils.TAX_VERSION_V2);
        updateFundtax.setIstax(1);
        updateFundtax.setTaxedtime(taxTime);
        serviceFundtaxMapper.updateById(updateFundtax);
        log.info("批量算税完成,fundTaxId={},税前={},增值税={},城建税={},劳务税前={},劳务个税={},劳务税后={}",
                fundTaxVO.getFundTaxId(), pretaxTotal, vatTotal, cityTaxTotal, laborPretaxTotal, taxTotal, taxedTotal);
        return true;
    }
    /**
     * null 安全的金额转换
     */
    private static BigDecimal toMoney(Double value) {
        return value == null ? BigDecimal.ZERO : BigDecimal.valueOf(value);
    }
}