diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java index 6cb95845..2fdd3d55 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java @@ -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 List detailFees(Long detailId); void adjustDetail(PreSettlementDetailAdjustRequest request); void refreshPaymentSummary(Long settlementId); + + /** + * 批量刷新收款汇总:固定数次查询完成 N 张结算单的重算,避免逐单刷新的 N+1 + * + * @param settlementIds 结算单id集合 + */ + void refreshPaymentSummaries(Collection settlementIds); + void refreshPaymentSummariesForPreSettlement(Long preSettlementId); String applyPayment(FormalSettlementPaymentRequest request); List applyPayments(FormalSettlementBatchPaymentRequest request); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java index b3376ce7..abec063b 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java @@ -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 settlementIds) { + List ids = settlementIds == null ? List.of() : settlementIds.stream() + .filter(Objects::nonNull).distinct().toList(); + if (ids.isEmpty()) { + return; + } + // 结算单一次锁行查询替代逐单 selectOne(id 升序加锁,与逐单版加锁顺序一致防死锁) + List settlements = baseMapper.selectList(Wrappers.lambdaQuery() + .in(FormalSettlement::getId, ids) + .orderByAsc(FormalSettlement::getId) + .last("FOR UPDATE")); + if (settlements.isEmpty()) { + return; + } + List 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> relationsBySettlement = paymentApplicationSettlementMapper.selectList( + Wrappers.lambdaQuery() + .in(PaymentApplicationSettlement::getFormalSettlementId, settledIds) + .eq(PaymentApplicationSettlement::getIsDeleted, 0)).stream() + .collect(Collectors.groupingBy(PaymentApplicationSettlement::getFormalSettlementId)); + // 付款申请(按结算单直挂,payment_type=结算) + Map> applicationsBySettlement = paymentApplicationMapper.selectList( + Wrappers.lambdaQuery() + .in(PaymentApplication::getSettlementId, settledIds) + .eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT) + .eq(PaymentApplication::getIsDeleted, 0)).stream() + .collect(Collectors.groupingBy(PaymentApplication::getSettlementId)); + // 关系表引用的付款申请状态(全量一次) + Set relationApplicationIds = relationsBySettlement.values().stream() + .flatMap(List::stream).map(PaymentApplicationSettlement::getPaymentApplicationId) + .filter(Objects::nonNull).collect(Collectors.toSet()); + Map relationStatuses = relationApplicationIds.isEmpty() ? Map.of() + : paymentApplicationMapper.selectList(Wrappers.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> sourcesBySettlement = hasPayable + ? sourceMapper.selectList(Wrappers.lambdaQuery() + .in(FormalSettlementSource::getFormalSettlementId, settledIds) + .eq(FormalSettlementSource::getIsDeleted, 0)).stream() + .collect(Collectors.groupingBy(FormalSettlementSource::getFormalSettlementId)) + : Map.of(); + // 收款认领分摊聚合(仅应收结算单需要) + Map receiptClaimPaidBySettlement = hasReceivable + ? receiptClaimSettlementMapper.selectList(Wrappers.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 relations = relationsBySettlement + .getOrDefault(settlement.getId(), List.of()); + // 直挂付款申请需排除该结算单关系表已引用的申请(与单笔版 notIn 语义一致) + Set ownRelationApplicationIds = relations.stream() + .map(PaymentApplicationSettlement::getPaymentApplicationId) + .filter(Objects::nonNull).collect(Collectors.toSet()); + List 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.lambdaQuery() - .eq(FormalSettlementSource::getPreSettlementId, preSettlementId) - .eq(FormalSettlementSource::getIsDeleted, 0)).stream() - .map(FormalSettlementSource::getFormalSettlementId).distinct() - .forEach(this::refreshPaymentSummary); + List settlementIds = sourceMapper.selectList(Wrappers.lambdaQuery() + .eq(FormalSettlementSource::getPreSettlementId, preSettlementId) + .eq(FormalSettlementSource::getIsDeleted, 0)).stream() + .map(FormalSettlementSource::getFormalSettlementId).filter(Objects::nonNull).distinct().toList(); + refreshPaymentSummaries(settlementIds); } private List listSummaryFees(Long settlementId) { @@ -1393,54 +1483,65 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl query = Wrappers.lambdaQuery() - .eq(PaymentApplication::getSettlementId, settlement.getId()) - .eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT) - .eq(PaymentApplication::getIsDeleted, 0); List relations = paymentApplicationSettlementMapper.selectList( Wrappers.lambdaQuery() .eq(PaymentApplicationSettlement::getFormalSettlementId, settlement.getId()) .eq(PaymentApplicationSettlement::getIsDeleted, 0)); Set relationApplicationIds = relations.stream().map(PaymentApplicationSettlement::getPaymentApplicationId) .filter(Objects::nonNull).collect(Collectors.toSet()); + LambdaQueryWrapper query = Wrappers.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 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 relationStatuses = relationApplicationIds.isEmpty() ? Map.of() : paymentApplicationMapper.selectList( Wrappers.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 sources = sourceMapper.selectList(Wrappers.lambdaQuery() - .eq(FormalSettlementSource::getFormalSettlementId, settlement.getId()) - .eq(FormalSettlementSource::getIsDeleted, 0)); + List sources = data.sources() != null ? data.sources() + : sourceMapper.selectList(Wrappers.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.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.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 relations, + List directApplications, Map relationStatuses, + List sources, BigDecimal receiptClaimPaid) { + } + private SettlementAdjustmentVO toAdjustmentVO(SettlementAdjustment entity, String kingdeeBillNo) { SettlementAdjustmentVO vo = Objects.requireNonNull( BeanUtil.copyProperties(entity, SettlementAdjustmentVO.class));