perf: 结算单收款汇总批量化——refreshPaymentSummaries 固定数次批量查询替代逐单N+1,calculatePaymentSummary 拆纯计算重载保持口径一致

This commit is contained in:
刘泉佳
2026-09-29 01:02:33 +08:00
parent f97237afe5
commit 98614a25cc
2 changed files with 154 additions and 30 deletions
@@ -18,6 +18,7 @@ import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import java.util.Collection;
import java.util.List;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
@@ -54,6 +55,14 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
List<FormalSettlementDetailFee> detailFees(Long detailId);
void adjustDetail(PreSettlementDetailAdjustRequest request);
void refreshPaymentSummary(Long settlementId);
/**
* 批量刷新收款汇总:固定数次查询完成 N 张结算单的重算,避免逐单刷新的 N+1
*
* @param settlementIds 结算单id集合
*/
void refreshPaymentSummaries(Collection<Long> settlementIds);
void refreshPaymentSummariesForPreSettlement(Long preSettlementId);
String applyPayment(FormalSettlementPaymentRequest request);
List<String> applyPayments(FormalSettlementBatchPaymentRequest request);
@@ -99,6 +99,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -1182,15 +1183,104 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
updateById(settlement);
}
/**
* 批量刷新收款汇总:固定数次查询完成 N 张结算单的重算,避免逐单 refreshPaymentSummary 的 N+1。
* 计算口径与单笔版一致(数据由批量查询供给,纯计算复用 calculatePaymentSummary 重载)。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void refreshPaymentSummaries(Collection<Long> settlementIds) {
List<Long> ids = settlementIds == null ? List.of() : settlementIds.stream()
.filter(Objects::nonNull).distinct().toList();
if (ids.isEmpty()) {
return;
}
// 结算单一次锁行查询替代逐单 selectOne(id 升序加锁,与逐单版加锁顺序一致防死锁)
List<FormalSettlement> settlements = baseMapper.selectList(Wrappers.<FormalSettlement>lambdaQuery()
.in(FormalSettlement::getId, ids)
.orderByAsc(FormalSettlement::getId)
.last("FOR UPDATE"));
if (settlements.isEmpty()) {
return;
}
List<Long> settledIds = settlements.stream().map(FormalSettlement::getId).toList();
boolean hasPayable = settlements.stream().anyMatch(item -> "payable".equals(item.getSettlementType()));
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)).stream()
.collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId,
Collectors.reducing(BigDecimal.ZERO,
item -> money(item.getAllocatedReceiptAmount()), BigDecimal::add)))
: Map.of();
for (FormalSettlement settlement : settlements) {
List<PaymentApplicationSettlement> relations = relationsBySettlement
.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)));
settlement.setAppliedPaymentAmount(summary.appliedAmount());
settlement.setPaidAmount(summary.paidAmount());
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())
.subtract(summary.paidAmount()).max(BigDecimal.ZERO));
settlement.setPaymentStatus(paymentStatus(summary.paidAmount(), settlement.getSettlementAmount()));
}
updateBatchById(settlements);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refreshPaymentSummariesForPreSettlement(Long preSettlementId) {
if (preSettlementId == null) return;
sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getPreSettlementId, preSettlementId)
.eq(FormalSettlementSource::getIsDeleted, 0)).stream()
.map(FormalSettlementSource::getFormalSettlementId).distinct()
.forEach(this::refreshPaymentSummary);
List<Long> settlementIds = sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getPreSettlementId, preSettlementId)
.eq(FormalSettlementSource::getIsDeleted, 0)).stream()
.map(FormalSettlementSource::getFormalSettlementId).filter(Objects::nonNull).distinct().toList();
refreshPaymentSummaries(settlementIds);
}
private List<FormalSettlementSummaryFee> listSummaryFees(Long settlementId) {
@@ -1393,54 +1483,65 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
}
private PaymentSummary calculatePaymentSummary(FormalSettlement settlement) {
LambdaQueryWrapper<PaymentApplication> query = Wrappers.<PaymentApplication>lambdaQuery()
.eq(PaymentApplication::getSettlementId, settlement.getId())
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
.eq(PaymentApplication::getIsDeleted, 0);
List<PaymentApplicationSettlement> relations = paymentApplicationSettlementMapper.selectList(
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
.eq(PaymentApplicationSettlement::getFormalSettlementId, settlement.getId())
.eq(PaymentApplicationSettlement::getIsDeleted, 0));
Set<Long> relationApplicationIds = relations.stream().map(PaymentApplicationSettlement::getPaymentApplicationId)
.filter(Objects::nonNull).collect(Collectors.toSet());
LambdaQueryWrapper<PaymentApplication> query = Wrappers.<PaymentApplication>lambdaQuery()
.eq(PaymentApplication::getSettlementId, settlement.getId())
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
.eq(PaymentApplication::getIsDeleted, 0);
if (!relationApplicationIds.isEmpty()) query.notIn(PaymentApplication::getId, relationApplicationIds);
List<PaymentApplication> applications = paymentApplicationMapper.selectList(query);
BigDecimal appliedAmount = applications.stream()
.filter(item -> TransportBusiConstant.REVIEWING.equals(item.getApprovalStatus()))
.map(PaymentApplication::getAppliedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal paidAmount = applications.stream()
.filter(item -> TransportBusiConstant.APPROVED.equals(item.getApprovalStatus()))
.map(PaymentApplication::getPaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
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));
appliedAmount = appliedAmount.add(relations.stream()
.filter(item -> TransportBusiConstant.REVIEWING.equals(relationStatuses.get(item.getPaymentApplicationId())))
return calculatePaymentSummary(settlement, new PaymentSummaryData(relations, applications,
relationStatuses, null, null));
}
/**
* 收款汇总纯计算:查询数据由调用方供给(单笔版自行查询;批量版由 refreshPaymentSummaries 一次供给多单)。
* sources / receiptClaimPaid 为 null 时按需自行查询(仅 payable/receivable 分支使用)。
*/
private PaymentSummary calculatePaymentSummary(FormalSettlement settlement, PaymentSummaryData data) {
BigDecimal appliedAmount = data.directApplications().stream()
.filter(item -> TransportBusiConstant.REVIEWING.equals(item.getApprovalStatus()))
.map(PaymentApplication::getAppliedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal paidAmount = data.directApplications().stream()
.filter(item -> TransportBusiConstant.APPROVED.equals(item.getApprovalStatus()))
.map(PaymentApplication::getPaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
appliedAmount = appliedAmount.add(data.relations().stream()
.filter(item -> TransportBusiConstant.REVIEWING.equals(data.relationStatuses().get(item.getPaymentApplicationId())))
.map(PaymentApplicationSettlement::getAppliedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
paidAmount = paidAmount.add(relations.stream()
.filter(item -> TransportBusiConstant.APPROVED.equals(relationStatuses.get(item.getPaymentApplicationId())))
paidAmount = paidAmount.add(data.relations().stream()
.filter(item -> TransportBusiConstant.APPROVED.equals(data.relationStatuses().get(item.getPaymentApplicationId())))
.map(PaymentApplicationSettlement::getPaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
if ("payable".equals(settlement.getSettlementType())) {
List<FormalSettlementSource> sources = sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementSource::getIsDeleted, 0));
List<FormalSettlementSource> sources = data.sources() != null ? data.sources()
: sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementSource::getIsDeleted, 0));
appliedAmount = appliedAmount.add(sources.stream().map(FormalSettlementSource::getAdvanceAppliedAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add));
paidAmount = paidAmount.add(sources.stream().map(FormalSettlementSource::getAdvancePaidAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add));
}
if ("receivable".equals(settlement.getSettlementType())) {
paidAmount = receiptClaimSettlementMapper.selectList(
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getFormalSettlementId, settlement.getId())
.eq(ReceiptClaimSettlement::getStatus, 1))
.stream().map(ReceiptClaimSettlement::getAllocatedReceiptAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
paidAmount = data.receiptClaimPaid() != null ? data.receiptClaimPaid()
: receiptClaimSettlementMapper.selectList(
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getFormalSettlementId, settlement.getId())
.eq(ReceiptClaimSettlement::getStatus, 1))
.stream().map(ReceiptClaimSettlement::getAllocatedReceiptAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
return new PaymentSummary(money(appliedAmount), money(paidAmount));
}
@@ -1459,6 +1560,20 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private record PaymentSummary(BigDecimal appliedAmount, BigDecimal paidAmount) {
}
/**
* 收款汇总计算的供给数据(批量场景一次查询多张结算单后按单分发)
*
* @param relations 结算-付款申请关系行
* @param directApplications 直挂付款申请(已排除关系表引用的申请)
* @param relationStatuses 付款申请id → 审批状态
* @param sources 预收来源行(仅应付结算单;null 表示由纯计算按需查询)
* @param receiptClaimPaid 收款认领分摊聚合(仅应收结算单;null 表示由纯计算按需查询)
*/
private record PaymentSummaryData(List<PaymentApplicationSettlement> relations,
List<PaymentApplication> directApplications, Map<Long, String> relationStatuses,
List<FormalSettlementSource> sources, BigDecimal receiptClaimPaid) {
}
private SettlementAdjustmentVO toAdjustmentVO(SettlementAdjustment entity, String kingdeeBillNo) {
SettlementAdjustmentVO vo = Objects.requireNonNull(
BeanUtil.copyProperties(entity, SettlementAdjustmentVO.class));