| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | /** |
| | | * 批量算税主入口 |
| | | * 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); |
| | | } |
| | | |
| | | } |