diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java index 98758bab..ad1c345a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java @@ -491,6 +491,16 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl + * 编排流程:参数去重校验 → 批量组装推送计划(分摊/镜像/结算单/结算明细一次取回防 N+1)→ + * A102-4 应付单已生成预检 → A801-3 幂等预检(金蝶已有收票单则跳过保存续推指定)→ + * 逐单推送(单笔失败不中断整批)→ 汇总成功/失败笔数返回 + * + * @param ids 收票登记主键集合 + * @return 推送结果汇总(本批笔数、成功/失败笔数及失败原因明细) + */ @Override public String pushKingdeeReceivedInvoice(List ids) { if (Func.isEmpty(ids)) { @@ -500,26 +510,31 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl receipts = listByIds(distinctIds); if (receipts.size() != distinctIds.size()) { throw new ServiceException("部分收票登记不存在或已删除"); } // 批量组装推送计划:分摊关系/镜像/结算单/结算明细一次取回,避免逐单查库 N+1 + // 1) 收票登记与正式结算单的分摊关系(按登记id分组),用于"一票一应付"校验及防重键取数来源 Map> relationsByReceipt = settlementRelationMapper.selectList( Wrappers.lambdaQuery() .in(InvoiceReceiptSettlement::getInvoiceReceiptId, distinctIds) .orderByAsc(InvoiceReceiptSettlement::getCreateTime)).stream() .collect(Collectors.groupingBy(InvoiceReceiptSettlement::getInvoiceReceiptId)); + // 2) 票池镜像(A801-1 同步落库的发票数据),提供明细行 itemsJson、票种编码、购方单位等推送必填字段 List mirrorIds = receipts.stream().map(InvoiceReceipt::getKingdeeInvoicePoolId) .filter(Objects::nonNull).distinct().toList(); Map mirrorById = mirrorIds.isEmpty() ? Map.of() : invoicePoolMapper.selectBatchIds(mirrorIds).stream() .collect(Collectors.toMap(KingdeeInvoicePool::getId, Function.identity(), (first, second) -> first)); + // 3) 分摊关系指向的正式结算单,后续校验须为审批通过的应付类型结算单 List settlementIds = relationsByReceipt.values().stream().flatMap(List::stream) .map(InvoiceReceiptSettlement::getFormalSettlementId).filter(Objects::nonNull).distinct().toList(); Map settlementById = settlementIds.isEmpty() ? Map.of() : formalSettlementMapper.selectBatchIds(settlementIds).stream() .collect(Collectors.toMap(FormalSettlement::getId, Function.identity(), (first, second) -> first)); + // 4) 应付结算单的明细行(按行号升序),用于组装 A302-6 指定应付的防重键 apBillSrcCode List payableSettlementIds = settlementById.values().stream() .filter(settlement -> "payable".equals(settlement.getSettlementType())) .map(FormalSettlement::getId).toList(); @@ -529,19 +544,27 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl planByReceipt = new LinkedHashMap<>(); for (InvoiceReceipt receipt : receipts) { planByReceipt.put(receipt.getId(), buildReceivedInvoicePushPlan(receipt, relationsByReceipt.getOrDefault(receipt.getId(), List.of()), mirrorById, settlementById, detailsBySettlement)); } + long blockedCount = planByReceipt.values().stream().filter(plan -> plan.blockedReason() != null).count(); + log.info("推送金蝶收票单计划组装完成:本批 {} 笔,前置校验拦截 {} 笔", planByReceipt.size(), blockedCount); // A102-4 应付单已生成预检:本批防重键一次批量查询,缺单的登记逐单提示先完成付款侧同步 List apSrcCodes = planByReceipt.values().stream() .filter(plan -> plan.blockedReason() == null) .map(ReceivedInvoicePushPlan::apBillSrcCode).filter(Func::isNotBlank).distinct().toList(); Map apBillNoBySrcCode = apSrcCodes.isEmpty() ? Map.of() : kingdeeApBillSyncSupport.queryApBillExistNos(apSrcCodes); + log.info("推送金蝶收票单 A102-4 预检:待校验防重键 {} 个,已生成财务应付单 {} 个", apSrcCodes.size(), apBillNoBySrcCode.size()); // A801-3 幂等预检:按购方单位(接口必填)分组回看窗口翻页,本地按防重键匹配(金蝶已存在则跳过保存续推指定) Map kingdeeRowByInvoiceNo = queryKingdeeReceivedInvoiceRows(planByReceipt); + log.info("推送金蝶收票单 A801-3 预检:金蝶已存在收票单 {} 笔,命中单据将跳过保存续推指定", kingdeeRowByInvoiceNo.size()); KingdeeSyncContext context = KingdeeSyncContext.empty(); int success = 0; List failures = new ArrayList<>(); @@ -559,6 +582,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl