From bce360350cfec9383bf64670eb8f13e28069fdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Sun, 11 Oct 2026 15:51:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=A1=E6=89=B9=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=90=8C=E6=AD=A5=E9=87=91=E8=9D=B6=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...‡自动同步金蝶设计(事件触发+定时重推+手动兜底).md | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 doc/docs/业务流程/2026-10-05-审批通过自动同步金蝶设计(事件触发+定时重推+手动兜底).md diff --git a/doc/docs/业务流程/2026-10-05-审批通过自动同步金蝶设计(事件触发+定时重推+手动兜底).md b/doc/docs/业务流程/2026-10-05-审批通过自动同步金蝶设计(事件触发+定时重推+手动兜底).md new file mode 100644 index 00000000..5f50817d --- /dev/null +++ b/doc/docs/业务流程/2026-10-05-审批通过自动同步金蝶设计(事件触发+定时重推+手动兜底).md @@ -0,0 +1,177 @@ +# 审批通过自动同步金蝶设计(事件触发 + 定时重推 + 手动兜底) + +> 整理日期:2026-10-05 +> 目标:推送到金蝶的各单据,**审批通过后自动推送,省掉页面手动点「同步金蝶」按钮这一步**;节点间的先后顺序由既有硬预检 + 失败重推消化,全程无需人工点击。 +> 依据:`2026-09-27-金蝶(星瀚)BFI接口调用全链路梳理(按业务域汇总·更新版).md`、`TMS业财融合-集成流程图文字版与业务流程梳理.md`、当前代码(探索核实)。 + +--- + +## 1. 范围 + +| 业务域 | 推送链路 | 现状触发 | 本设计 | +|---|---|---|---| +| 正式结算单(应付/应收) | A102-4 预检 → A102-2 / A302-2 | 手动按钮 | ✅ 审批通过自动推 | +| 付款申请(正式/预付) | A102-4 硬校验 → A103-2 预检 → A103-1 / A103-3 | 手动按钮(单/批) | ✅ 审批通过自动推 | +| 开票申请 | A302-4 硬校验 → A303-4 预检 → A303-1 逐张次 | 手动按钮 | ✅ 审批通过自动推 | +| 收票登记 | A102-4 预检 + A801-3 预检 → A303-2 → A302-6 | 手动批量按钮 | ✅ 审批通过自动推 | +| 客商档案 | A902-2 | 已是审批通过自动推 | ➖ 不动(先例) | +| 收款认领 | A301-3 | 认领页面动作事务内推送 + ReceiptClaimPushTask 定时 | ➖ 不动(无审批节点) | +| 查询回写类 | A103-2 / A303-5 / A801-3 / A301-1 | 定时/手动 | ➖ 不动(不是推送) | + +## 2. 现状与差距(代码核实结论) + +1. **无工作流引擎**:blade-transport 全模块零 flowable 引用,审批 = 直接置 `approvalStatus=approved`(`TransportBusiConstant.java:44`)。 +2. **审批有两条入口**,自动推送必须全覆盖: + - 手动:`POST /xxx/approve` → `service.approve(request)`; + - OA 回调:`MkProcessPublicController` → `MkProcessMessageService` → `MkProcessBizHandlers.approveFromProcess`。 + - ⚠️ 其中 **formal-settlement 与 payment-application 的 OA handler 直接置 approved、绕过 `service.approve`**(付款侧因此漏掉票据额度联动 `useBillBalance`,结算侧不写变更记录——既有不一致,本设计顺手统一)。 +3. **推送失败落库口径不一致**:付款/收票失败落 `failed`;结算单/开票失败整体回滚保持 `unsynced`("没推过"与"推失败"无法区分,总纲 11.2-#3 缺陷)。 +4. **事件设施现成**:voucher 模块已有 `ApplicationEventPublisher` + `@TransactionalEventListener(AFTER_COMMIT)` 模式。 +5. 审批端点全部单条,无批量审批入口。 +6. 客商档案先例:审批后推送异常被吞(Controller 层 try/catch + Service 层落 failed),**推送失败不影响审批结果**。 + +## 3. 总体方案 + +``` + ┌─ 手动 POST /xxx/approve ─→ service.approve() ─┐ +审批通过(两条入口)─┤ ├─ 事务内置 approved + 发布 KingdeeBillApprovedEvent + └─ OA 回调 approveFromProcess ─────────────────┘ (OA handler 统一改走 service.approve) + │ 事务提交(AFTER_COMMIT) + ▼ + KingdeeApprovalSyncListener(域开关判断 + 全量 try/catch 吞异常) + │ 复用手动按钮同一 Service 方法 + ┌────────────┬────────────┴──────────┬──────────────┐ + ▼ ▼ ▼ ▼ + 结算单 sync 付款申请 sync 开票申请 sync 收票 push + (A102-2/A302-2) (A103-1/A103-3) (A303-1) (A303-2+A302-6) + │ │ │ │ + 成功→synced 成功→synced 成功→synced 成功→pushed/assigned + 失败→failed 失败→failed 失败→failed 失败→failed + └────────────┴───────────┬───────────┴──────────────┘ + ▼ + KingdeeAutoSyncRetryTask(定时,默认关):仅重推 failed + 前置未就绪(如应付单未生成)→ 预检拦截 → 再次 failed → 下轮继续 + 前置就绪 → 自动续推成功(硬顺序由预检保证,不做级联) +``` + +三条不变式: +1. **审批结果不受推送影响**:事件在审批事务提交后才消费,监听器整体 try/catch,推送异常只落库 + 记日志(客商先例)。 +2. **只有审批状态真正变为 approved 的那次才发布事件**(重复回调被 approve 的状态校验拒绝,天然不重复推送)。 +3. **复用手动按钮同一 Service 方法**:自动推送与手动推送走完全相同的校验、预检、回写逻辑,不另写推送路径。 + +## 4. 设计要点 + +### 4.1 触发点(覆盖双入口) + +- 新增事件 `KingdeeBillApprovedEvent(String bizType, Long id)`,`bizType` 复用 `MkProcessBizHandlers` 既有取值(`formal-settlement` / `payment-application` / `invoice-application` / `invoice-receipt`)。 +- 4 个实体的 `service.approve()` 在状态置 approved 成功后 `publishEvent`(须确保 approve 方法在事务内执行——均有变更记录写入,已是 @Transactional;无事务时 `@TransactionalEventListener` 默认丢弃事件,实现时核实兜底)。 +- **统一 OA 回调**:`MkProcessBizHandlers` 中 formal-settlement(:309-318)与 payment-application(:372-380)的 `approveFromProcess` 改为调 `service.approve(request)`,与 invoice-application / invoice-receipt 的 handler 写法对齐。副作用即修复:付款 OA 审批补上票据额度联动 `useBillBalance`、结算 OA 审批补上变更记录。 +- 客商档案已有自己的审批后推送,不动。 + +### 4.2 执行模型:AFTER_COMMIT 同步推送 + +- 监听器 `KingdeeApprovalSyncListener` 用 `@TransactionalEventListener(phase = AFTER_COMMIT)`,事务提交后在当前线程调用推送(与客商档案现网行为一致)。正常推送秒级;Feign 超时上限 10s 连接 / 60s 读取。 +- `ponytail:` 同步推送占用审批请求线程,若 BFI 网关大面积变慢导致审批接口可感知变慢,再把监听器内部改为提交到守护线程池(`MkProcessMessageService` 已有同款裸 `ScheduledExecutorService` 模式,1 处改动)。 +- 路由: + +| bizType | 调用(与手动按钮相同) | +|---|---| +| formal-settlement | `formalSettlementService.syncKingdee(id)`(内部按 settlementType 路由 A102-2/A302-2) | +| payment-application | `paymentApplicationService.syncKingdee(id)`(内部路由 A103-1/A103-3) | +| invoice-application | `invoiceApplicationService.syncKingdee(id)` | +| invoice-receipt | `invoiceReceiptService.pushKingdeeReceivedInvoice(List.of(id))` | + +### 4.3 失败统一落 failed(修 11.2-#3) + +- 结算单、开票申请的推送方法改为**"事务回滚后独立事务落 failed + 失败原因"**(沿用付款申请已有 `markKingdeeSyncFailed` 模式),与付款/收票口径统一。无需新列、无需 SQL(状态字段均为 varchar,新增 `failed` 取值即可)。 +- 收票自动推送遇到推送计划不满足(未恰好分摊 1 张审批通过应付结算单、查验未通过等,现写 `blockedReason` 不动状态):**自动推送路径下置 failed + 原因**,纳入重推循环;前置补齐后重推自动续上。 +- 前端列表的金蝶状态筛选/展示补 `failed` 选项(结算单/开票)。 + +### 4.4 节点先后顺序:预检保证,不做级联 + +- 每个节点只在**自身审批通过**时推自己;跨节点硬顺序(A102-2→A103-1/A303-2、A302-2→A303-1)仍由既有 A102-4/A302-4 硬预检强制。 +- 前置未就绪(上游还没推成)→ 本次推送失败落 failed(原因"前置应付/应收单未生成")→ 重推任务下轮再试 → 上游就绪后自动续推成功。预检与断点续传机制天然支持。 +- **不做级联**(上游推完自动连推下游):失败传播复杂、且下游有自己的审批节点,级联会绕过"审批通过才推"的语义。 + +### 4.5 定时重推任务(兜底) + +- 新增 `KingdeeAutoSyncRetryTask`(`task/` 包,@Component + @Value 开关 + @Scheduled cron,与既有任务同款)。 +- **只重推 `failed`,不扫描 `unsynced`**——存量"审批通过但从未推送"的历史单不自动补推(避免上线即批量推历史数据),仍走手动按钮。 +- 每域候选集(均要求未作废): + +| 域 | 查询条件 | 动作 | +|---|---|---| +| 结算单 | `kingdee_sync_status='failed'` 且 approved | `syncKingdee(id)` | +| 付款申请 | `kingdee_status='failed'` 且 approved | `syncKingdee(id)` | +| 开票申请 | `kingdee_status='failed'` 且 approved | `syncKingdee(id)` | +| 收票登记 | `kingdee_push_status='failed'` 且 approved | `pushKingdeeReceivedInvoice(List.of(id))` | + +- 逐条 try/catch,单条失败不中断;与付款勾稽纠偏(A103-2 查到即纠正 synced)天然兼容:重推前预检命中远端已落单会幂等回填,不会重复生成。 +- 开关 `thirdParty.bfi.autoSync.retryEnabled` 默认 false,cron 默认 `0 */10 * * * ?`。 +- `ponytail:` 重推无次数上限,永久性坏数据(如联行号未维护)会每 10 分钟失败一次;噪音可接受后,再加 retry_count 上限列。 + +### 4.6 配置开关与灰度 + +监听器内按域判断,4 个开关全部默认 false(Nacos 显式打开),与既有定时任务开关风格一致: + +```yaml +third-party: + bfi: + autoSync: + settlement: false # 结算单审批后自动推 A102-2/A302-2 + payment: false # 付款申请审批后自动推 A103-1/A103-3 + invoiceApply: false # 开票申请审批后自动推 A303-1 + receipt: false # 收票登记审批后自动推 A303-2+A302-6 + retryEnabled: false # failed 定时重推 +``` + +> 注意配置前缀:transport 侧读 `third-party.bfi`(BfiProperties 现行前缀,与 nacos 键对齐),落地时以实际前缀为准。 +> 关闭某域开关 = 该域回到现状(审批不推送、手动按钮照用);重推开关独立,可只关重推保手动。 + +### 4.7 保持不变的部分 + +- 手动同步按钮**全部保留**(失败重推与人工兜底入口,三入口并存:自动 + 定时 + 手动)。 +- 结算单汇率未维护 → 照旧"不传不阻断",不在审批侧强校验。 +- 定时回写任务(PaymentKingdeeResultTask / CustomerKingdeeSyncTask / ReceiptClaimPushTask)不动。 +- 状态守卫式锁单(synced 禁止作废等)不动;B901 锁单回调本分支未实现,与本设计无耦合。 + +## 5. 改动清单(文件级) + +| # | 文件 | 改动 | +|---|---|---| +| 1 | `transport/event/KingdeeBillApprovedEvent.java` | 新增 record(bizType, id) | +| 2 | `transport/listener/KingdeeApprovalSyncListener.java` | 新增:AFTER_COMMIT 监听 + 域开关 + 路由 4 个 sync 方法 + 全量 try/catch | +| 3 | `FormalSettlementServiceImpl.approve`(:539) | 末尾发布事件 | +| 4 | `PaymentApplicationServiceImpl.approve`(:400) | 末尾发布事件 | +| 5 | `InvoiceApplicationServiceImpl.approve`(:483) | 末尾发布事件 | +| 6 | `InvoiceReceiptServiceImpl.approve`(:587) | 末尾发布事件 | +| 7 | `MkProcessBizHandlers`(formal-settlement :309 / payment-application :372) | `approveFromProcess` 改调 `service.approve(request)`(统一入口 + 修复 useBillBalance 绕过) | +| 8 | `FormalSettlementServiceImpl` / `InvoiceApplicationServiceImpl` 推送失败路径 | 事务回滚后独立事务落 `failed` + 原因(沿用 `markKingdeeSyncFailed` 模式) | +| 9 | `InvoiceReceiptServiceImpl` 推送计划 blocked 分支 | 自动推送路径 blocked → failed + 原因 | +| 10 | `transport/task/KingdeeAutoSyncRetryTask.java` | 新增:4 域 failed 重推,开关 + cron | +| 11 | 前端结算单/开票列表 | 金蝶状态筛选/展示补 `failed`(小改) | + +无新表、无 DDL、无新依赖。 + +## 6. 边界场景推演 + +| 场景 | 行为 | +|---|---| +| 推送成功但审批事务实际回滚(AFTER_COMMIT 后回滚不存在,理论不发生) | 不适用——事件只在事务成功提交后消费 | +| 推送成功、落 failed 前进程崩溃 | 下轮重推预检命中(A102-4/A103-2/A303-4/A801-3)幂等回填,不重复建单 | +| 付款审批通过但结算单应付单未生成 | A102-4 硬校验拦截 → failed("前置应付单未生成") → 重推任务续推 | +| 结算单推送失败挂 failed,下游付款/收票/开票全failed等它 | 重推任务每轮都重试各域 failed,上游成功后下游自动续上 | +| OA 审批通过的单 | OA handler 统一走 service.approve 后同样触发事件,覆盖 | +| 同一单重复 OA 回调 | approve 状态校验拒绝重复审批,不发布事件 | +| 永久性坏数据(如联行号未维护) | 每轮重推失败落原因,修数后下轮自动成功;重推无上限(见 4.5 天花板) | +| 历史存量 approved-unsynced 单 | 不自动补推,手动按钮处理 | + +## 7. 待确认默认值(按推荐默认落地,可改) + +| 默认 | 内容 | 备选 | +|---|---|---| +| D1 | AFTER_COMMIT 同步推送(同线程) | 改守护线程池异步(1 处改动,见 4.2) | +| D2 | OA handler 统一改走 service.approve(顺修 useBillBalance 绕过) | 保持 handler 直置、在各 handler 单独发事件 | +| D3 | 重推只扫 failed,存量 unsynced 不补推 | 加"存量补推"一次性开关 | +| D4 | 收票 blocked 条件在自动推送时置 failed | 保持 blockedReason 不动状态 + 重推扫 blocked | +| D5 | 重推无次数上限 | 加 retry_count 上限 |