perf: 结算单收款汇总批量化——refreshPaymentSummaries 固定数次批量查询替代逐单N+1,calculatePaymentSummary 拆纯计算重载保持口径一致
This commit is contained in:
+9
@@ -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);
|
||||
|
||||
+145
-30
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user