diff --git a/doc/docs/业务流程/2026-10-09-金蝶单据锁定与解锁业务流程(lockBusiData锁定-unLockNotifyBusiData回调-失败补偿-删单还原).md b/doc/docs/业务流程/2026-10-09-金蝶单据锁定与解锁业务流程(lockBusiData锁定-unLockNotifyBusiData回调-失败补偿-删单还原).md new file mode 100644 index 00000000..356569e7 --- /dev/null +++ b/doc/docs/业务流程/2026-10-09-金蝶单据锁定与解锁业务流程(lockBusiData锁定-unLockNotifyBusiData回调-失败补偿-删单还原).md @@ -0,0 +1,128 @@ +# 金蝶单据锁定与解锁业务流程(lockBusiData 锁定 - unLockNotifyBusiData 回调 - 失败补偿 - 删单还原) + +- 日期:2026-10-09 +- 范围:正式结算单(应付 A102-2 / 应收 A302-2)、开票申请单(A303-1)推送金蝶成功后的单据锁定、金蝶解锁/删单回调、锁定失败定时补偿 +- 设计文档:`docs/superpowers/specs/2026-10-09-kingdee-bill-lock-unlock-design.md` +- 锁记录表:`blade_kingdee_bill_lock`(一行 = 一张金蝶目标单) + +## 一、涉及单据与触发链路 + +| 业务单据 | 推送接口 | 推送入口 | 金蝶目标单 | 锁定触发 | +| --- | --- | --- | --- | --- | +| 正式结算单(应付) | A102-2 财务应付单保存 | 结算单页面「同步金蝶」 | ApFin_other_BT_S | 推送成功后 | +| 正式结算单(应收) | A302-2 财务应收单保存 | 同上 | arfin_other_BT_S | 推送成功后 | +| 开票申请单 | A303-1 开票申请新增 | 开票申请页面「同步金蝶」 | cdm_paybill_bank_elect_BT_S(每张发票张次一张) | 推送成功后 | + +- lockBusiData:WLTMS 调金蝶,源系统 WLTMS、目标系统 XH、lockAction=LOCK +- unLockNotifyBusiData:金蝶调 WLTMS,源系统 XH、目标系统 WLTMS、lockAction=UNLOCK +- 批量约定:多张单据编号英文逗号拼接(sourceBillNo 与 targetBillNo 顺序一一对应),同一源单的多张金蝶单合并一次调用 +- 出入参同构,应答为 `{updateCount, updateResultList}` 裸结构(逐单 1-成功/0-失败) + +## 二、主线流程 + +### 1. 推送并锁定(WLTMS → 金蝶) + +``` +用户点「同步金蝶」 + ├─ doSyncKingdee(单据级事务):幂等预检 → A102-2/A302-2/A303-1 保存 → 回写 kingdeeBillNo/同步状态=synced + │ └─ 事务提交 + ├─ 锁定(lockAfterPush): + │ ├─ 每张金蝶目标单一行锁记录,先落 failed 占位(保证补偿扫表无遗漏) + │ ├─ 同源单多张目标单合并一次调用:LOCK + SAVE,编号逗号拼接 + │ ├─ 应答 updateCount>0 → 全部翻 locked ──→ 单据冻结 + │ └─ 失败/超时 → 保持 failed,tryCount+1,失败原因落 fail_reason(主单同步状态不受影响) + └─ 附件联带上传(A902,原有动作,后置不受影响) +``` + +**锁定后的效果**: + +- 开票申请:`作废`入口被 `checkEditable` 拦截(提示"该单据已被金蝶锁定,不可修改") +- 结算单:synced 状态本身已拦截编辑/调整/作废,锁定期被同一组守卫覆盖 +- 重复点「同步金蝶」:推送幂等命中直接返回已有单号,已 locked 的锁记录跳过,不会重复锁 + +### 2. 金蝶侧解锁回调(金蝶 → WLTMS) + +金蝶在星瀚侧对单据解锁或删单后,回调 `POST /kingdee-bfi/unLockNotifyBusiData`(`UNLOCK` + 逗号拼接批量): + +``` +校验:sourceSystem=XH、targetSystem=WLTMS、lockAction=UNLOCK(否则应答失败) + │ + ├─ operationType=SAVE(仅解锁,没删单) + │ └─ 锁记录 → unlocked,记录回调操作人/时间,解锁动作留痕 + │ + └─ operationType=DELETE(金蝶把单删了) + ├─ 锁记录 → unlocked(同上) + └─ 按 targetBillType 反查 WLTMS 单据类型,分发还原: + ├─ SETTLEMENT → 结算单作废(当前节点→已作废), + │ 同步信息还原推送前状态:kingdeeSyncStatus→unsynced、 + │ 清 kingdeeBillNo/kingdeeBillId/syncedTime + │ 变更流水记"金蝶删单回调自动废除",操作人=金蝶报文 operator + └─ INVOICE_APPLY → 开票申请作废,kingdeeStatus→unsynced、清单据编号, + 刷新关联结算单开票状态,动作流水记 kingdee_delete +应答:{updateCount: 处理张数, updateResultList: [1]} +``` + +重复回调幂等:单据已作废或锁记录已 unlocked → 该张跳过,不重复还原。 + +### 3. 锁定失败补偿(定时兜底,不依赖人工) + +``` +KingdeeBillLockRetryTask(thirdParty.bfi.billLock.retryEnabled,默认每5分钟) + └─ 扫 lock_status=failed 且 try_count<5 的锁记录 + ├─ 按源单分组,组内合并一次 lockBusiData 重锁 + ├─ 成功 → 翻 locked,进入正常锁定态 + └─ 失败 → try_count+1,超过 5 次停试,留 fail_reason 待人工排查 +``` + +## 三、状态视角 + +**锁记录状态机**: + +``` +failed(锁定失败,待补偿)→ locked(金蝶锁定中)→ unlocked(终态) +``` + +同单重新推送生成新锁记录,走新一轮循环。 + +**业务单据生命周期**(以结算单为例): + +``` +草稿 → 提交 → 审批通过 ──同步金蝶──→ synced(金蝶单已生成) + │ │ + │ ├─ 锁定成功:locked(冻结,不可动) + │ │ │ + │ │ ├─ 金蝶解锁(SAVE):unlocked,流水留痕 + │ │ └─ 金蝶删单(DELETE):作废 + 同步信息还原 unsynced + │ │ (结算明细/付款/开票关联的后续动作 + │ │ 由各自前置校验自然拦截重推) + │ └─ 锁定失败:failed,定时补偿重锁 + └─ 推送失败:failed(可重推) +``` + +## 四、关键设计点 + +1. **先占位再锁**:锁记录落库先于远程调用,补偿任务扫 `failed` 就是完整的待重试集合,不漏单 +2. **锁失败不回滚推送**:金蝶单已生成,本地回滚只会制造断链;代价是短暂"已推未锁"窗口,由补偿任务闭合 +3. **批量同成败**:接口应答只有聚合 updateCount,无逐单成败,整批一起翻状态,最简且不错账 +4. **回调幂等**:按 targetBillType+targetBillNo 找最新 locked 记录,找不到/已 unlocked 直接跳过应答成功,防金蝶重试风暴 +5. **DELETE 还原口径**:作废源单 + 清金蝶同步信息 + 关联状态刷新;作废是终态,重推需新单重走流程(口径待业务联调确认) + +## 五、代码落点速查 + +| 内容 | 位置 | +| --- | --- | +| 锁单 Feign/DTO/应答 VO | `blade-bfi-api`:`BfiBillLockDTO`、`BfiBillLockResultVO`、`IBfiClient.lockBusiData`、`IBfiService.lockBusiData` | +| 锁类型常量与映射 | `transport/constant/KingdeeLockConstant` | +| 锁记录实体/服务 | `KingdeeBillLock`、`IKingdeeBillLockService` + `KingdeeBillLockServiceImpl` | +| 回调端点 | `KingdeeBfiPublicController`:`POST /kingdee-bfi/unLockNotifyBusiData`(PERMIT_ALL + TenantIgnore) | +| 补偿定时任务 | `KingdeeBillLockRetryTask`(`thirdParty.bfi.billLock.retryEnabled`,默认每5分钟) | +| 推送后锁定触发 | `FormalSettlementServiceImpl.syncKingdee`、`InvoiceApplicationServiceImpl.syncKingdee`(事务提交后) | +| 删单还原 | `FormalSettlementServiceImpl.voidByKingdee`、`InvoiceApplicationServiceImpl.voidByKingdee` | +| 建表脚本 | `doc/sql/transport/blade_kingdee_bill_lock_20261009.sql` | + +## 六、联调前配置 + +1. Nacos 配置 `thirdParty.bfi.billLockUrl`:lockBusiData 真实路径(代码默认占位 `/ierp/kapi/app/billLock/lockBusiData`,以金蝶最终提供为准) +2. Nacos 开启补偿:`thirdParty.bfi.billLock.retryEnabled: true`(按环境) +3. 向金蝶提供回调地址:网关地址 + transport 服务路由 + `/kingdee-bfi/unLockNotifyBusiData` +4. 与金蝶确认 WLTMS 单据类型编码(SETTLEMENT/INVOICE_APPLY)与 DELETE 还原口径