From cfbbc756ebbdbd5e64f1c77de132c1366ead51da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sat, 10 Oct 2026 13:50:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=87=91=E8=9D=B6fai?= =?UTF-8?q?led=E5=8D=95=E6=8D=AE=E5=AE=9A=E6=97=B6=E9=87=8D=E6=8E=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=EF=BC=88=E5=85=9C=E5=BA=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 KingdeeAutoSyncRetryTask:仅重推各域 failed,不扫 unsynced(存量审批通过但从未推送的历史单不自动补推,仍走手动按钮) - 4域新增 retryKingdeeFailed:逐单独立成败不中断,返回执行摘要;前置未就绪再次failed下轮继续,节点硬顺序由既有 A102-4/A302-4/A303-4/A801-3 预检保证,不做级联 - 收票重推以落库状态 pushed/assigned 判成败(批量推送方法逐单吞异常返回摘要) - 开关 thirdParty.bfi.autoSync.retryEnabled 默认关闭,cron 默认每10分钟;重推无次数上限(噪音可接受后再加 retry_count) --- .../service/IFormalSettlementService.java | 8 ++ .../service/IInvoiceApplicationService.java | 8 ++ .../service/IInvoiceReceiptService.java | 8 ++ .../service/IPaymentApplicationService.java | 8 ++ .../impl/FormalSettlementServiceImpl.java | 38 ++++++++ .../impl/InvoiceApplicationServiceImpl.java | 27 ++++++ .../impl/InvoiceReceiptServiceImpl.java | 35 +++++++ .../impl/PaymentApplicationServiceImpl.java | 27 ++++++ .../task/KingdeeAutoSyncRetryTask.java | 95 +++++++++++++++++++ 9 files changed, 254 insertions(+) create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeAutoSyncRetryTask.java 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 a341a8eb..f1fdb1aa 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 @@ -60,6 +60,14 @@ public interface IFormalSettlementService extends BaseService */ void markKingdeeSyncFailed(Long id, String reason); + /** + * 重推金蝶生成失败(kingdeeSyncStatus=failed 且审批通过)的正式结算单:逐单独立成败,失败不中断。 + * 只扫 failed 不扫 unsynced——存量"审批通过但从未推送"的历史单不自动补推,仍走手动按钮 + * + * @return 执行摘要 + */ + String retryKingdeeFailed(); + String syncKingdeeArResult(); List detailFees(Long detailId); void adjustDetail(PreSettlementDetailAdjustRequest request); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java index 6c31fd0d..81647ac5 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java @@ -74,5 +74,13 @@ public interface IInvoiceApplicationService extends BaseService { */ String pushKingdeeReceivedInvoice(List ids, boolean autoSync); + /** + * 重推金蝶推送失败(kingdeePushStatus=failed 且审批通过)的收票登记:逐单独立成败,失败不中断。 + * 只扫 failed 不扫 unsynced——存量"审批通过但从未推送"的历史单不自动补推,仍走手动按钮 + * + * @return 执行摘要 + */ + String retryKingdeeFailed(); + /** * 定时回写金蝶收票单结果(A801-3):金蝶侧删除/红冲/作废 → 回收为 failed 提示人工(不自动作废登记); * pushed(保存成功后中断)→ 指定补偿;assigned 仅补登缺失的收票单号 diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java index 1b5ced1c..6d4807af 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java @@ -73,6 +73,14 @@ public interface IPaymentApplicationService extends BaseService failedIds = list(Wrappers.lambdaQuery() + .eq(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED) + .eq(FormalSettlement::getApprovalStatus, TransportBusiConstant.APPROVED) + .orderByAsc(FormalSettlement::getCreateTime)) + .stream().map(FormalSettlement::getId).toList(); + return executeKingdeeRetry(failedIds); + } + + /** + * 逐单重推失败单据:单条失败独立 try/catch 不中断整批,返回执行摘要。 + * 推送前预检(A102-4/A302-4 幂等回填)保证重推不重复生成金蝶单据。 + * + * @param failedIds 失败单据id集合 + * @return 执行摘要 + */ + private String executeKingdeeRetry(List failedIds) { + if (Func.isEmpty(failedIds)) { + return "无生成失败单据"; + } + int success = 0; + List failures = new ArrayList<>(); + for (Long id : failedIds) { + try { + syncKingdee(id); + success++; + } catch (Exception exception) { + failures.add(id + ":" + exception.getMessage()); + } + } + String summary = "重推生成失败单据:本批 " + failedIds.size() + " 条,成功 " + success + " 条"; + if (!failures.isEmpty()) { + summary = summary + ",失败 " + failures.size() + " 条:" + String.join(";", failures); + } + return summary; + } + /** * 附件联带上传(A902):主单推送成功(含幂等预检命中)后调用,以本地 kingdeeBillId 定位金蝶单据; * 本地缺失(存量数据/幂等跳过路径未带回内码)时经 A102-4/A302-4 按防重键反查回填; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java index 679d9547..a226d906 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java @@ -553,6 +553,33 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl 500 ? value.substring(0, 500) : value; } + @Override + public String retryKingdeeFailed() { + List failedIds = list(Wrappers.lambdaQuery() + .eq(InvoiceApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED) + .eq(InvoiceApplication::getApprovalStatus, TransportBusiConstant.APPROVED) + .orderByAsc(InvoiceApplication::getCreateTime)) + .stream().map(InvoiceApplication::getId).toList(); + if (Func.isEmpty(failedIds)) { + return "无生成失败单据"; + } + int success = 0; + List failures = new ArrayList<>(); + for (Long id : failedIds) { + try { + syncKingdee(id); + success++; + } catch (Exception exception) { + failures.add(id + ":" + exception.getMessage()); + } + } + String summary = "重推生成失败单据:本批 " + failedIds.size() + " 条,成功 " + success + " 条"; + if (!failures.isEmpty()) { + summary = summary + ",失败 " + failures.size() + " 条:" + String.join(";", failures); + } + return summary; + } + /** * 附件联带上传(A902):主单推送成功(含 A303-4 勾稽回填)后调用,以本地 kingdeeBillId 定位金蝶单据; * 本地缺失(存量数据)跳过待重推补传;附件上传异常独立捕获,绝不影响主单同步结果 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 e041053d..157c1b98 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 @@ -637,6 +637,41 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl failedIds = list(Wrappers.lambdaQuery() + .eq(InvoiceReceipt::getKingdeePushStatus, KingdeeBfiConstant.RECEIVED_PUSH_FAILED) + .eq(InvoiceReceipt::getApprovalStatus, TransportBusiConstant.APPROVED) + .orderByAsc(InvoiceReceipt::getCreateTime)) + .stream().map(InvoiceReceipt::getId).toList(); + if (Func.isEmpty(failedIds)) { + return "无推送失败单据"; + } + int success = 0; + List failures = new ArrayList<>(); + for (Long id : failedIds) { + try { + // 批量推送方法逐单吞异常返回摘要,成败以推送落库状态为准(pushed/assigned 视为成功) + pushKingdeeReceivedInvoice(List.of(id), true); + InvoiceReceipt pushed = getById(id); + String pushStatus = pushed == null ? null : pushed.getKingdeePushStatus(); + if (KingdeeBfiConstant.RECEIVED_PUSH_PUSHED.equals(pushStatus) + || KingdeeBfiConstant.RECEIVED_PUSH_ASSIGNED.equals(pushStatus)) { + success++; + } else { + failures.add(id + ":" + (pushed == null ? "单据不存在" : Func.toStrWithEmpty(pushed.getKingdeePushMsg(), "推送未完成"))); + } + } catch (Exception exception) { + failures.add(id + ":" + exception.getMessage()); + } + } + String summary = "重推推送失败单据:本批 " + failedIds.size() + " 条,成功 " + success + " 条"; + if (!failures.isEmpty()) { + summary = summary + ",失败 " + failures.size() + " 条:" + String.join(";", failures); + } + return summary; + } + /** * 单张收票登记推送编排:前置校验 → A102-4 预检 → A801-3 幂等预检 → A303-2 保存 → pushed 落库 → * A302-6 指定应付 → assigned;保存/指定阶段失败置 failed,断点可凭防重键续推。 diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java index 41efc41b..419cf0f3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java @@ -539,6 +539,33 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl failedIds = list(Wrappers.lambdaQuery() + .eq(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED) + .eq(PaymentApplication::getApprovalStatus, TransportBusiConstant.APPROVED) + .orderByAsc(PaymentApplication::getCreateTime)) + .stream().map(PaymentApplication::getId).toList(); + if (Func.isEmpty(failedIds)) { + return "无生成失败单据"; + } + int success = 0; + List failures = new ArrayList<>(); + for (Long id : failedIds) { + try { + syncKingdee(id); + success++; + } catch (Exception exception) { + failures.add(id + ":" + exception.getMessage()); + } + } + String summary = "重推生成失败单据:本批 " + failedIds.size() + " 条,成功 " + success + " 条"; + if (!failures.isEmpty()) { + summary = summary + ",失败 " + failures.size() + " 条:" + String.join(";", failures); + } + return summary; + } + @Override @Transactional(rollbackFor = Exception.class) public void unlockBill(Long id, String source) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeAutoSyncRetryTask.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeAutoSyncRetryTask.java new file mode 100644 index 00000000..ba488e55 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/task/KingdeeAutoSyncRetryTask.java @@ -0,0 +1,95 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. + * Copyright of this software remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.task; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.transport.service.IFormalSettlementService; +import org.springblade.transport.service.IInvoiceApplicationService; +import org.springblade.transport.service.IInvoiceReceiptService; +import org.springblade.transport.service.IPaymentApplicationService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 金蝶自动同步失败定时重推任务(兜底):仅重推各域 failed 单据,不扫描 unsynced—— + * 存量"审批通过但从未推送"的历史单不自动补推(避免上线即批量推历史数据),仍走手动按钮。 + *

+ * 前置未就绪(如上游应付单未生成)→ 预检拦截 → 再次 failed → 下轮继续; + * 前置就绪 → 自动续推成功(节点硬顺序由既有 A102-4/A302-4/A303-4/A801-3 预检保证,不做级联)。 + *

+ * ponytail: 重推无次数上限,永久性坏数据(如联行号未维护)会每轮失败一次; + * 噪音可接受后,再加 retry_count 上限列。 + *

+ * 调度配置(Nacos thirdParty.bfi.autoSync.*):retryEnabled 开关、retryCron 表达式。 + * + * @author Chill + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class KingdeeAutoSyncRetryTask { + + private final IPaymentApplicationService paymentApplicationService; + private final IFormalSettlementService formalSettlementService; + private final IInvoiceApplicationService invoiceApplicationService; + private final IInvoiceReceiptService invoiceReceiptService; + + /** failed 重推开关,代码默认关闭(安全兜底),由 Nacos thirdParty.bfi.autoSync.retryEnabled 按环境显式开启 */ + @Value("${thirdParty.bfi.autoSync.retryEnabled:false}") + private boolean retryEnabled; + + /** failed 定时重推调度,默认每10分钟执行一次 */ + @Scheduled(cron = "${thirdParty.bfi.autoSync.retryCron:0 */10 * * * ?}") + public void retryKingdeeFailed() { + if (!retryEnabled) { + return; + } + // ① 结算单(A102-2/A302-2) + try { + String summary = formalSettlementService.retryKingdeeFailed(); + log.info("定时重推金蝶生成失败结算单:{}", summary); + } catch (Exception exception) { + log.error("定时重推金蝶生成失败结算单异常", exception); + } + // ② 付款申请(A103-1/A103-3) + try { + String summary = paymentApplicationService.retryKingdeeFailed(); + log.info("定时重推金蝶生成失败付款申请:{}", summary); + } catch (Exception exception) { + log.error("定时重推金蝶生成失败付款申请异常", exception); + } + // ③ 开票申请(A303-1) + try { + String summary = invoiceApplicationService.retryKingdeeFailed(); + log.info("定时重推金蝶生成失败开票申请:{}", summary); + } catch (Exception exception) { + log.error("定时重推金蝶生成失败开票申请异常", exception); + } + // ④ 收票登记(A303-2+A302-6) + try { + String summary = invoiceReceiptService.retryKingdeeFailed(); + log.info("定时重推金蝶推送失败收票登记:{}", summary); + } catch (Exception exception) { + log.error("定时重推金蝶推送失败收票登记异常", exception); + } + } +}