refactor: 抽取正式结算单收款汇总批量装载公共逻辑统一读写口径

This commit is contained in:
刘泉佳
2026-11-05 15:09:25 +08:00
parent 1b811181a8
commit fd82d9604f
@@ -233,8 +233,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
/** /**
* 列表页批量 Entity → VO:收款汇总各数据源与预结算单号按本页一次批量查询后内存分发, * 列表页批量 Entity → VO:收款汇总各数据源与预结算单号按本页一次批量查询后内存分发,
* 计算口径与单笔版 toVO 完全一致(复用 calculatePaymentSummary 纯计算重载), * 计算口径与单笔版 toVO 完全一致,且与 refreshPaymentSummaries 写回路径共用批量装载与计算(loadPaymentSummaryBatchData)。
* 固定约 6 次查询替代逐行 toVO 的 4~5 次/行(100 行约 500 次 SQL)。
* *
* @param entities 本页结算单 * @param entities 本页结算单
* @return 本页 VO 列表 * @return 本页 VO 列表
@@ -243,9 +242,33 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
if (Func.isEmpty(entities)) { if (Func.isEmpty(entities)) {
return new ArrayList<>(); return new ArrayList<>();
} }
List<Long> ids = entities.stream().map(FormalSettlement::getId).toList(); PaymentSummaryBatchData batch = this.loadPaymentSummaryBatchData(
boolean hasPayable = entities.stream().anyMatch(item -> "payable".equals(item.getSettlementType())); entities.stream().map(FormalSettlement::getId).toList());
boolean hasReceivable = entities.stream().anyMatch(item -> "receivable".equals(item.getSettlementType())); List<FormalSettlementVO> voList = new ArrayList<>(entities.size());
for (FormalSettlement entity : entities) {
FormalSettlementVO vo = FormalSettlementWrapper.build().entityVO(entity);
vo.setInvoiceStatus(this.invoiceStatus(entity.getInvoiceAmount(), entity.getSettlementAmount()));
PaymentSummary summary = this.paymentSummaryFor(entity, batch);
vo.setAppliedPaymentAmount(summary.appliedAmount());
vo.setPaidAmount(summary.paidAmount());
vo.setRemainingPayableAmount(this.money(entity.getSettlementAmount()).subtract(summary.paidAmount())
.max(BigDecimal.ZERO));
vo.setPreSettlementNos(batch.sourcesBySettlement().getOrDefault(entity.getId(), List.of()).stream()
.map(FormalSettlementSource::getPreSettlementNo).collect(Collectors.joining(",")));
voList.add(vo);
}
return voList;
}
/**
* 批量收款汇总数据装载(列表页 toVOList 读路径与 refreshPaymentSummaries 写回路径共用):
* 固定 6 次查询取回本批结算单全部汇总数据源并按结算单id分组,替代两处各自维护的重复查询块。
*/
private PaymentSummaryBatchData loadPaymentSummaryBatchData(Collection<Long> settlementIds) {
List<Long> ids = settlementIds.stream().filter(Objects::nonNull).distinct().toList();
if (ids.isEmpty()) {
return new PaymentSummaryBatchData(Map.of(), Map.of(), Map.of(), Map.of(), Map.of(), Map.of());
}
// 结算-付款申请关系(全量) // 结算-付款申请关系(全量)
Map<Long, List<PaymentApplicationSettlement>> relationsBySettlement = paymentApplicationSettlementMapper.selectList( Map<Long, List<PaymentApplicationSettlement>> relationsBySettlement = paymentApplicationSettlementMapper.selectList(
Wrappers.<PaymentApplicationSettlement>lambdaQuery() Wrappers.<PaymentApplicationSettlement>lambdaQuery()
@@ -275,53 +298,51 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
Wrappers.<FormalSettlementSource>lambdaQuery() Wrappers.<FormalSettlementSource>lambdaQuery()
.in(FormalSettlementSource::getFormalSettlementId, ids)).stream() .in(FormalSettlementSource::getFormalSettlementId, ids)).stream()
.collect(Collectors.groupingBy(FormalSettlementSource::getFormalSettlementId)); .collect(Collectors.groupingBy(FormalSettlementSource::getFormalSettlementId));
// 收款认领分摊聚合(仅应收结算单需要) // 收款认领分摊聚合(仅应收结算单消费)
Map<Long, BigDecimal> receiptClaimPaidBySettlement = hasReceivable Map<Long, BigDecimal> receiptClaimPaidBySettlement = receiptClaimSettlementMapper.selectList(
? receiptClaimSettlementMapper.selectList(Wrappers.<ReceiptClaimSettlement>lambdaQuery() Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.in(ReceiptClaimSettlement::getFormalSettlementId, ids) .in(ReceiptClaimSettlement::getFormalSettlementId, ids)
.eq(ReceiptClaimSettlement::getStatus, 1) .eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream() .eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId, .collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId,
Collectors.reducing(BigDecimal.ZERO, Collectors.reducing(BigDecimal.ZERO,
item -> this.money(item.getAllocatedReceiptAmount()), BigDecimal::add))) item -> this.money(item.getAllocatedReceiptAmount()), BigDecimal::add)));
: Map.of(); // 项目预付核销合计(仅应付结算单消费,一次 group-by 避免逐单查询)
// 项目预付核销合计(仅应付结算单需要,一次 group-by 避免逐单查询) Map<Long, BigDecimal> writeOffAmountsBySettlement = this.projectAdvanceWriteOffService.sumBySettlementIds(
Map<Long, BigDecimal> writeOffAmountsBySettlement = hasPayable TransportBusiConstant.SETTLEMENT_BILL_FORMAL, ids);
? this.projectAdvanceWriteOffService.sumBySettlementIds( return new PaymentSummaryBatchData(relationsBySettlement, applicationsBySettlement, relationStatuses,
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, sourcesBySettlement, receiptClaimPaidBySettlement, writeOffAmountsBySettlement);
entities.stream().filter(item -> "payable".equals(item.getSettlementType())) }
.map(FormalSettlement::getId).toList())
: Map.of();
List<FormalSettlementVO> voList = new ArrayList<>(entities.size()); /**
for (FormalSettlement entity : entities) { * 单张结算单收款汇总纯计算:从批量装载数据取数组装
FormalSettlementVO vo = FormalSettlementWrapper.build().entityVO(entity); * (直挂付款申请排除该结算单关系表已引用的申请,与单笔版 notIn 语义一致)。
vo.setInvoiceStatus(this.invoiceStatus(entity.getInvoiceAmount(), entity.getSettlementAmount())); */
List<PaymentApplicationSettlement> relations = relationsBySettlement private PaymentSummary paymentSummaryFor(FormalSettlement settlement, PaymentSummaryBatchData batch) {
.getOrDefault(entity.getId(), List.of()); List<PaymentApplicationSettlement> relations = batch.relationsBySettlement()
// 直挂付款申请需排除该结算单关系表已引用的申请(与单笔版 notIn 语义一致) .getOrDefault(settlement.getId(), List.of());
Set<Long> ownRelationApplicationIds = relations.stream() Set<Long> ownRelationApplicationIds = relations.stream()
.map(PaymentApplicationSettlement::getPaymentApplicationId) .map(PaymentApplicationSettlement::getPaymentApplicationId)
.filter(Objects::nonNull).collect(Collectors.toSet()); .filter(Objects::nonNull).collect(Collectors.toSet());
List<PaymentApplication> directApplications = applicationsBySettlement List<PaymentApplication> directApplications = batch.applicationsBySettlement()
.getOrDefault(entity.getId(), List.of()).stream() .getOrDefault(settlement.getId(), List.of()).stream()
.filter(item -> !ownRelationApplicationIds.contains(item.getId())) .filter(item -> !ownRelationApplicationIds.contains(item.getId()))
.toList(); .toList();
PaymentSummary summary = this.calculatePaymentSummary(entity, new PaymentSummaryData( return this.calculatePaymentSummary(settlement, new PaymentSummaryData(
relations, directApplications, relationStatuses, relations, directApplications, batch.relationStatuses(),
sourcesBySettlement.getOrDefault(entity.getId(), List.of()).stream() batch.sourcesBySettlement().getOrDefault(settlement.getId(), List.of()).stream()
.filter(item -> Objects.equals(item.getIsDeleted(), 0)).toList(), .filter(item -> Objects.equals(item.getIsDeleted(), 0)).toList(),
receiptClaimPaidBySettlement.getOrDefault(entity.getId(), BigDecimal.ZERO), batch.receiptClaimPaidBySettlement().getOrDefault(settlement.getId(), BigDecimal.ZERO),
writeOffAmountsBySettlement)); batch.writeOffAmountsBySettlement()));
vo.setAppliedPaymentAmount(summary.appliedAmount()); }
vo.setPaidAmount(summary.paidAmount());
vo.setRemainingPayableAmount(this.money(entity.getSettlementAmount()).subtract(summary.paidAmount()) /**
.max(BigDecimal.ZERO)); * 批量收款汇总装载数据(结算单id → 各数据源分组)
vo.setPreSettlementNos(sourcesBySettlement.getOrDefault(entity.getId(), List.of()).stream() */
.map(FormalSettlementSource::getPreSettlementNo).collect(Collectors.joining(","))); private record PaymentSummaryBatchData(Map<Long, List<PaymentApplicationSettlement>> relationsBySettlement,
voList.add(vo); Map<Long, List<PaymentApplication>> applicationsBySettlement, Map<Long, String> relationStatuses,
} Map<Long, List<FormalSettlementSource>> sourcesBySettlement,
return voList; Map<Long, BigDecimal> receiptClaimPaidBySettlement, Map<Long, BigDecimal> writeOffAmountsBySettlement) {
} }
/** /**
@@ -2191,72 +2212,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
return; return;
} }
List<Long> settledIds = settlements.stream().map(FormalSettlement::getId).toList(); List<Long> settledIds = settlements.stream().map(FormalSettlement::getId).toList();
boolean hasPayable = settlements.stream().anyMatch(item -> "payable".equals(item.getSettlementType())); PaymentSummaryBatchData batch = this.loadPaymentSummaryBatchData(settledIds);
boolean hasReceivable = settlements.stream().anyMatch(item -> "receivable".equals(item.getSettlementType()));
// 结算-付款申请关系(全量)
Map<Long, List<PaymentApplicationSettlement>> relationsBySettlement = paymentApplicationSettlementMapper.selectList(
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
.in(PaymentApplicationSettlement::getFormalSettlementId, settledIds)
.eq(PaymentApplicationSettlement::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(PaymentApplicationSettlement::getFormalSettlementId));
// 付款申请(按结算单直挂,payment_type=结算)
Map<Long, List<PaymentApplication>> applicationsBySettlement = paymentApplicationMapper.selectList(
Wrappers.<PaymentApplication>lambdaQuery()
.in(PaymentApplication::getSettlementId, settledIds)
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
.eq(PaymentApplication::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(PaymentApplication::getSettlementId));
// 关系表引用的付款申请状态(全量一次)
Set<Long> relationApplicationIds = relationsBySettlement.values().stream()
.flatMap(List::stream).map(PaymentApplicationSettlement::getPaymentApplicationId)
.filter(Objects::nonNull).collect(Collectors.toSet());
Map<Long, String> relationStatuses = relationApplicationIds.isEmpty() ? Map.of()
: paymentApplicationMapper.selectList(Wrappers.<PaymentApplication>lambdaQuery()
.select(PaymentApplication::getId, PaymentApplication::getApprovalStatus)
.in(PaymentApplication::getId, relationApplicationIds)
.eq(PaymentApplication::getIsDeleted, 0)).stream()
.collect(Collectors.toMap(PaymentApplication::getId, PaymentApplication::getApprovalStatus,
(first, second) -> first));
// 预收来源(仅应付结算单需要)
Map<Long, List<FormalSettlementSource>> sourcesBySettlement = hasPayable
? sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.in(FormalSettlementSource::getFormalSettlementId, settledIds)
.eq(FormalSettlementSource::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(FormalSettlementSource::getFormalSettlementId))
: Map.of();
// 收款认领分摊聚合(仅应收结算单需要)
Map<Long, BigDecimal> receiptClaimPaidBySettlement = hasReceivable
? receiptClaimSettlementMapper.selectList(Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.in(ReceiptClaimSettlement::getFormalSettlementId, settledIds)
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId,
Collectors.reducing(BigDecimal.ZERO,
item -> money(item.getAllocatedReceiptAmount()), BigDecimal::add)))
: Map.of();
// 项目预付核销合计(仅应付结算单需要,一次 group-by 避免逐单查询)
Map<Long, BigDecimal> writeOffAmountsBySettlement = hasPayable
? this.projectAdvanceWriteOffService.sumBySettlementIds(
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, settledIds)
: Map.of();
for (FormalSettlement settlement : settlements) { for (FormalSettlement settlement : settlements) {
List<PaymentApplicationSettlement> relations = relationsBySettlement PaymentSummary summary = this.paymentSummaryFor(settlement, batch);
.getOrDefault(settlement.getId(), List.of());
// 直挂付款申请需排除该结算单关系表已引用的申请(与单笔版 notIn 语义一致)
Set<Long> ownRelationApplicationIds = relations.stream()
.map(PaymentApplicationSettlement::getPaymentApplicationId)
.filter(Objects::nonNull).collect(Collectors.toSet());
List<PaymentApplication> directApplications = applicationsBySettlement
.getOrDefault(settlement.getId(), List.of()).stream()
.filter(item -> !ownRelationApplicationIds.contains(item.getId()))
.toList();
PaymentSummary summary = calculatePaymentSummary(settlement, new PaymentSummaryData(
relations, directApplications, relationStatuses,
sourcesBySettlement.getOrDefault(settlement.getId(), List.of()),
receiptClaimPaidBySettlement.getOrDefault(settlement.getId(), BigDecimal.ZERO),
writeOffAmountsBySettlement));
settlement.setAppliedPaymentAmount(summary.appliedAmount()); settlement.setAppliedPaymentAmount(summary.appliedAmount());
settlement.setPaidAmount(summary.paidAmount()); settlement.setPaidAmount(summary.paidAmount());
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount()) settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())