📝 金蝶附件同步设计文档梳理对齐实施(as-built):勘误前提、回填路径、事务边界与实施记录
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
- 日期:2026-09-24
|
||||
- 范围:后端同步链路联带 A902 附件上传(付款申请、正式结算应付/应收、收票登记、开票申请)+ 前端状态列/提示/超时调整
|
||||
- 背景:单据推送金蝶成功后,TMS 侧附件(attachmentsJson)不随单上行,金蝶单据缺影像。星瀚 A902「附件上传到指定单据」接口已具备(见《业财融合系统接口文档.xlsx》同名页签),此前未在 BFI 客户端实现
|
||||
- 前置:依赖 2026-09-24-kingdee-push-timeout-anticlick 已落地的 loading+守卫+try/finally 模式,本设计不新增前端状态机
|
||||
- 前置:原计划复用 2026-09-24-kingdee-push-timeout-anticlick 的 loading+守卫+try/finally 模式;经评审确认该前置未落地,本期**不实施防连点(后补)**,本设计仍不新增前端状态机
|
||||
|
||||
## 一、目标
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
| 开票申请单 | `sim_original_bill` | `attachmentpanel` | 本期 |
|
||||
| 客商(资质附件) | 待金蝶确认 | 待确认 | 二期 |
|
||||
|
||||
映射以常量/配置类维护(如 `KingdeeBfiConstant` 或独立 `KingdeeAttachmentEntityMapping`),二期补条目不改架构。
|
||||
映射以常量类维护:`KingdeeAttachmentConstant.ENTITY_NUMBERS`(biz_type → entityNumber),controlKey 统一 `attachmentpanel`;二期客商补条目不改架构。
|
||||
|
||||
## 三、领域模型
|
||||
|
||||
@@ -59,9 +59,15 @@
|
||||
| sync_time | 上传时间 |
|
||||
|
||||
- 唯一键 `uk_(biz_type, biz_id, attachment_key)`;同步前按 `(biz_type, biz_id)` 载入成功集合,命中即跳过(幂等)
|
||||
- 同步流水:现有同步流水新增 action=`attachment_upload`
|
||||
- 同步流水落点见「四、同步流水落点(评审定案)」
|
||||
|
||||
**单据表加列**:`kingdee_bill_id`(星瀚单据内码,取自保存响应 `BfiBillSaveResultItemVO.id`)。付款申请已有该列;正式结算按应付/应收现有 kingdeeBillNo 的存储位置对应加 id 列;收票登记加收票单 id 列;开票申请加开票申请单 id 列。
|
||||
**单据表加列**:`kingdee_bill_id`(星瀚单据内码,取自保存响应 `BfiBillSaveResultItemVO.id`)。付款申请已有该列;本期新增三列(SQL 见 `doc/sql/transport/blade_kingdee_attachment_sync_20260924.sql`):
|
||||
|
||||
| 表 | 列 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| blade_formal_settlement | kingdee_bill_id | A102-2/A302-2 保存响应返回;幂等跳过/存量缺失经 A102-4/A302-4 按防重键反查回填 |
|
||||
| blade_invoice_receipt | kingdee_received_invoice_id | A303-2 保存响应返回;A801-3 幂等预检命中/定时回写时一并回填 |
|
||||
| blade_invoice_application | kingdee_bill_id | A303-1 保存响应返回;A303-4 勾稽回填一并带回 |
|
||||
|
||||
### 附件同步状态机(单附件行级)
|
||||
|
||||
@@ -70,7 +76,8 @@
|
||||
└────上传失败──> 失败(记 fail_reason;重推时重新尝试,成功则覆盖为成功)
|
||||
```
|
||||
|
||||
列表聚合展示态(运行时聚合,非存储字段):`无附件` / `已上传` / `部分未传` / `未上传`。
|
||||
列表聚合展示态(运行时聚合,非存储字段,评审定案):`无附件` / `未上传` / `部分未传` / `已上传`。
|
||||
判定:单据 attachmentsJson 无可上传附件→无附件;同步表成功数=0→未上传;0<成功数<附件数→部分未传;成功数≥附件数→已上传。
|
||||
|
||||
### 不变式
|
||||
|
||||
@@ -84,49 +91,64 @@
|
||||
|
||||
### BFI 客户端(blade-bfi-api)
|
||||
|
||||
`IBfiClient` 新增两个方法,复用现有 token 配置(`BfiFeignClientConfig`):
|
||||
`IBfiClient` 新增一个方法,复用现有 token 配置(`BfiFeignClientConfig`):
|
||||
|
||||
- `uploadFile`:`produces = MULTIPART_FORM_DATA_VALUE`,`@RequestPart("file")` + `@RequestPart("attachmentUploadFileArgs")`(JSON 字符串),返回 `BfiOpenApiResultVO`(含 data.path)
|
||||
- `downloadFile`(A801-2):body 传 path,返回 `feign.Response`(流式读取,防大文件全量进内存)
|
||||
- `uploadAttachment`(A902):`produces = MULTIPART_FORM_DATA_VALUE`,`@RequestPart("file")`(内容为内存字节数组,经 `InMemoryMultipartFile` 适配,单文件软限制 20MB)+ `@RequestPart("attachmentUploadFileArgs")`(JSON 字符串),返回 `BfiResultVO<BfiAttachmentUploadVO>`,`data.path` 即金蝶侧文件路径
|
||||
- A801-2 下载**复用已有** `IBfiClient.invoiceDownload` / `IBfiService.downloadInvoice`(`feign.Response` 流式读取),本期仅在附件链路启用,无新增接口
|
||||
|
||||
### 附件同步支持类(blade-transport)
|
||||
|
||||
新增 `KingdeeAttachmentSyncSupport`(与 `KingdeeApBillSyncSupport` 等同包同风格),核心方法:
|
||||
新增 `KingdeeAttachmentSyncSupport`(`org.springblade.transport.support`,与 `KingdeeApBillSyncSupport` 等同包同风格):
|
||||
|
||||
```
|
||||
syncAttachments(bizType, bizId, billPkId, List<KingdeeAttachmentItem>)
|
||||
syncAttachments(bizType, bizId, billPkId, List<KingdeeAttachmentItem>) // 主流程
|
||||
syncUserAttachments(bizType, bizId, billPkId, attachmentsJson) // 用户附件快捷入口
|
||||
parseUserAttachments(json) / parseInvoicePoolFile(json) // 解析:数组项容忍 url/link/fileUrl/downloadUrl/domain/src;镜像对象取 path
|
||||
countAttachments(json) / successCountByBizId(bizType, bizIds) // 列表聚合(一次 GROUP BY,防 N+1)
|
||||
aggregateStatus(attachmentCount, successCount) // 四态判定
|
||||
```
|
||||
|
||||
流程:载入已成功集合 → 过滤已传 → 过滤超限(记 fail_reason)→ 逐个(读文件流 / A801-2 下载)→ A902 上传 → 落记录 + 流水。单附件 try/catch,异常记失败继续下一个。
|
||||
流程:载入已成功集合 → 过滤已传 → 过滤超限(记 fail_reason)→ 逐个(http(s) 下载 / A801-2 下载,流式封顶读取)→ A902 上传 → 落记录。单附件 try/catch,异常记失败继续下一个;结果汇总(`AttachmentSyncSummary`:total/success/failed/skipCount/failReasons)供接入点写操作流水与前端提示。同步表既有失败记录重试成功时覆盖为成功(复用主键 update)。
|
||||
|
||||
### 各模块接入点(主单成功、billPkId 落库之后)
|
||||
### 各模块接入点(主单成功、billPkId 落库之后,均在主单事务提交后执行)
|
||||
|
||||
| 模块 | 接入点 | entityNumber | 附件来源 |
|
||||
| --- | --- | --- | --- |
|
||||
| 付款申请 | `pushBfiPayApplyPush` / `reconcileExistPayApply` 成功后 | ap_payapply | 单据 attachmentsJson |
|
||||
| 正式结算应付面板 | A102-2 保存成功后 | ap_finapbill | 结算单 attachmentsJson |
|
||||
| 正式结算应收面板 | A302-2 保存成功后 | ar_finarbill | 结算单 attachmentsJson |
|
||||
| 收票登记 | `pushKingdeeReceivedInvoice` A303-2 成功后 | ap_invoice | 登记附件 + 镜像发票文件(A801-2 下载,key=path) |
|
||||
| 开票申请 | A303-1 保存成功后(幂等预检命中时取 A303-4 查询响应 id 回填,与付款申请勾稽同模式) | sim_original_bill | 单据 attachmentsJson |
|
||||
| 付款申请 | `PaymentApplicationServiceImpl.syncKingdee(id, context)` 包装层(`doSyncKingdee` 事务提交后) | ap_payapply | 单据 attachmentsJson |
|
||||
| 正式结算应付/应收 | `FormalSettlementServiceImpl.syncKingdee`(无事务包装)→ `doSyncKingdee`(@Transactional)提交后 | ap_finapbill / ar_finarbill | 结算单 attachmentsJson |
|
||||
| 收票登记 | `pushOneReceivedInvoice`:A303-2 落 pushed 后、A302-6 指定应付前 | ap_invoice | 登记附件 + 镜像发票文件(A801-2 下载,key=path) |
|
||||
| 开票申请 | `InvoiceApplicationServiceImpl.syncKingdee`(无事务包装)→ `doSyncKingdee` 提交后 | sim_original_bill | 单据 attachmentsJson |
|
||||
|
||||
重推补传:现有同步入口无改动——主单被幂等预检判已存在时,仍进入附件补传分支(此时 billPkId 取本地 kingdee_bill_id;本地缺失则调对应查询接口回填,如付款申请勾稽已有此能力)。
|
||||
事务边界(ADR-7):附件上传是慢远程调用,统一放在主单事务**之外**——正式结算/开票申请原 `syncKingdee` 事务体下移为 `doSyncKingdee`(经自代理调用),付款申请沿用既有包装层模式;附件失败仅在附件链路内消化,绝不回滚主单、不拉长主单持锁时间。
|
||||
|
||||
星瀚内码(billPkId)回填路径:
|
||||
|
||||
- 推送路径:保存/下推响应 `BfiBillSaveResultItemVO.id` 随单落库(`BfiApBillPushOutcome`/`ReceivedInvoicePushOutcome` 扩展携带 billId)
|
||||
- 幂等/存量路径:本地 kingdee_bill_id 缺失时反查——正式结算 A102-4/A302-4(`queryApBillExistId`/`queryArBillExistId`)、收票 A801-3 预检行/定时回写行自带 id、开票申请 A303-4 勾稽行自带 id;反查命中即回填并记 info 日志,仍未命中则跳过附件待重推补传
|
||||
|
||||
重推补传:现有同步入口无改动——主单被幂等预检判已存在时,仍进入附件补传分支(billPkId 取本地 kingdee_bill_id,缺失则按上述路径反查回填)。
|
||||
|
||||
### 同步流水落点(评审定案)
|
||||
|
||||
附件明细以 `blade_kingdee_attachment_sync` 自身为准(sync_time/fail_reason 已是完整流水);仅在**有 actionType 流水表的单据**追加 `attachment_upload` 操作记录:收票登记(`InvoiceReceiptRecord`)、开票申请(`InvoiceApplicationRecord`);付款申请/正式结算无流水表,不追加。
|
||||
|
||||
### 列表状态聚合
|
||||
|
||||
各列表 VO 增加 `attachmentSyncStatus` 聚合字段:列表查询后按 biz_ids 一次 `GROUP BY biz_type, biz_id` 查同步表,与单据附件数量(解析 attachmentsJson 计数)对比得出聚合态,避免 N+1。
|
||||
各列表 VO(PaymentApplicationVO/InvoiceApplicationVO/FormalSettlementVO/InvoiceReceiptVO)新增三个展示字段:`attachmentCount`、`attachmentSyncStatus`(none/pending/partial/done)、`attachmentSyncStatusName`(无附件/未上传/部分未传/已上传)。各 `selectPage` 转换后按本页 biz_ids 一次 `GROUP BY biz_id`(`selectMaps`)查同步表成功计数,与解析 attachmentsJson 的附件数对比得出聚合态,避免 N+1;正式结算按应付/应收拆两组业务类型各查一次。
|
||||
|
||||
## 五、前端设计
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 超时 | `paymentApplication.js` syncKingdee/syncKingdeeBatch、`invoiceApplication.js` syncKingdee、`formalSettlement.js` syncKingdee、`invoiceReceipt.js` pushKingdee 由 30s 放宽到 60s(附件联带增加耗时) |
|
||||
| 列表列 | payment-application.vue、invoice-application.vue、formal-settlement-table-panel.vue(prop 传入)、invoice-receipt.vue 增「附件同步」列:无附件/已上传/部分未传/未上传 |
|
||||
| message | 同步成功但存在附件未全部上传时,追加 warning 提示「N 单同步成功,其中 M 单附件未全部上传,可重推补传」 |
|
||||
| 交互 | loading/守卫/try-finally 模式完全复用,无新增状态;无新增按钮(重推即补传) |
|
||||
| 超时 | 实施勘误:paymentApplication/invoiceApplication/formalSettlement 的 syncKingdee 实际已是 120s(批量 300s),无需调整;唯一风险点是 `invoiceReceipt.js pushKingdee` 未设 timeout(走全局 10s 默认),已显式放宽为 120000 |
|
||||
| 列表列 | payment-application.vue、invoice-application.vue、formal-settlement.vue(option `formalSettlementTable.js`)、invoice-receipt.vue 增「附件同步」列(`attachmentSyncStatusName`):无附件/未上传/部分未传/已上传 |
|
||||
| message | 同步成功后 `await loadTable()` 刷新列表,按本次同步 ids 过滤刷新行中 `attachmentSyncStatus ∈ {pending, partial}` 的数量,追加 warning 提示「其中 N 单附件未全部上传,可重推补传」;收票页追加进推送结果弹窗文案 |
|
||||
| 交互 | 无新增按钮(重推即补传);loading/守卫/try-finally 防连点模式本期未实施(见「不做的事」) |
|
||||
|
||||
## 六、不做的事
|
||||
|
||||
- 客商档案附件推送(二期,等金蝶确认 entityNumber/controlKey;本期仅预留映射配置)
|
||||
- 前端同步按钮防连点模式(loading+守卫+try/finally):本期评审确认不实施,后补
|
||||
- 客商档案附件推送(二期,等金蝶确认 entityNumber/controlKey;本期仅预留映射配置 `KingdeeAttachmentConstant.ENTITY_NUMBERS`)
|
||||
- 附件自动重试、定时补传任务
|
||||
- attachmentsJson 结构改造(去 JSON 化属独立重构,见 ADR-3)
|
||||
- OFD/PDF 发票文件在金蝶侧的可预览性验证
|
||||
@@ -140,13 +162,14 @@ syncAttachments(bizType, bizId, billPkId, List<KingdeeAttachmentItem>)
|
||||
|
||||
## 八、验证
|
||||
|
||||
1. 后端:`mvn clean compile -DskipTests -pl`(改动模块)
|
||||
2. 前端:`pnpm run typecheck`
|
||||
1. 后端:`mvn clean compile -DskipTests -pl`(改动模块)——✅ 已通过(bfi-api / transport-api / blade-transport)
|
||||
2. 前端:`pnpm run typecheck`——⚠️ 仓库既有问题无法运行(vue-tsc 3.3.11 与 typescript@7.0.2 不兼容,TS7 移除 `./lib/tsc` 导出,与本特性无关);已用 @vue/compiler-sfc 解析 + `node --check` 校验全部改动 .vue/.js 语法通过
|
||||
3. 手动(用户执行):
|
||||
- 含附件单据(付款申请/开票申请/正式结算/收票登记)单条/批量同步 → 金蝶对应单据附件面板可见文件
|
||||
- 同步成功但附件失败(断网/超限模拟)→ 主单状态正常、列表「附件同步」列示未传、重推自动补传
|
||||
- 重推已全部上传的单据 → A902 零调用(幂等跳过)
|
||||
- 同步成功但附件失败(断网/超限模拟)→ 主单状态正常、列表「附件同步」列示未传/部分未传、重推自动补传
|
||||
- 重推已全部上传的单据 → A902 零调用(幂等跳过,日志「历史已成功 N 个」)
|
||||
- 收票登记:镜像发票文件经 A801-2 下载后出现在收票单附件面板
|
||||
- 关键日志链路:`调用BFI接口 附件上传开始/结束` → `金蝶附件上传成功` → `金蝶附件同步完成` → 各接入点「同步完成」日志
|
||||
|
||||
## 九、ADR 摘要(本次评审定案)
|
||||
|
||||
@@ -158,14 +181,39 @@ syncAttachments(bizType, bizId, billPkId, List<KingdeeAttachmentItem>)
|
||||
| ADR-4 | 本期 5 单据(开票申请 entityNumber 已确认 `sim_original_bill` 提入本期),客商预留配置 | entityNumber 是硬约束,猜错即传不上 |
|
||||
| ADR-5 | 同步接口超时 60s + 后端软限制跳过 | 大文件不拖死整批;不引入异步任务复杂度 |
|
||||
| ADR-6 | 收票含发票文件:A801-2 下载→A902 回传,key=path | path 变化(红冲重开)自动触发重传 |
|
||||
| ADR-7 | 附件上传在主单事务提交后执行(正式结算/开票申请拆 `doSyncKingdee` 事务体) | 附件是慢远程调用:放事务内会拉长持锁时间;放事务外失败只消化在附件链路,主单状态不被污染 |
|
||||
|
||||
## 十、实施记录(2026-09-24 as-built)
|
||||
|
||||
评审定案与设计的差异,均已按本节落地:
|
||||
|
||||
| # | 事项 | 设计原假设 | 实施定案 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 前端防连点模式 | 前置已落地、直接复用 | 未落地,本期不实施(后补) |
|
||||
| 2 | 前端超时 | 30s→60s 全量放宽 | 其余已 120s/300s 不动;仅 `invoiceReceipt.pushKingdee` 显式 120s(原全局 10s 默认) |
|
||||
| 3 | 同步流水落点 | 「现有同步流水」统一新增 action | 无统一流水表:新表即明细事实源;收票/开票申请有 actionType 流水表追加 `attachment_upload`,付款申请/正式结算不追加 |
|
||||
| 4 | 聚合态术语 | 正文与术语表两版冲突 | 定案:无附件/未上传/部分未传/已上传(CONTEXT.md 已落档) |
|
||||
| 5 | A801-2 客户端 | 需新增 downloadFile | 已有 `invoiceDownload`,直接复用 |
|
||||
| 6 | 应付/应收/收票推送返回 | 未明确内码获取方式 | `BfiApBillPushOutcome`/`ReceivedInvoicePushOutcome` 扩展 billId;幂等/存量经查询接口反查回填(`queryApBillExistId`/`queryArBillExistId`、A801-3/A303-4 查询行自带 id) |
|
||||
|
||||
关键产物:
|
||||
|
||||
- 后端:`IBfiClient.uploadAttachment`、`BfiAttachmentUploadVO`、`InMemoryMultipartFile`、`KingdeeAttachmentConstant`(biz_type/entityNumber/controlKey/20MB/聚合态)、`KingdeeAttachmentSync` 实体 + Mapper/Service、`KingdeeAttachmentSyncSupport`
|
||||
- 接线:PaymentApplicationServiceImpl、FormalSettlementServiceImpl、InvoiceReceiptServiceImpl、InvoiceApplicationServiceImpl、KingdeeApBillSyncSupport、KingdeeArBillSyncSupport、KingdeeReceivedInvoiceSyncSupport
|
||||
- 前端:4 option 列 + 4 页面 warning 提示 + invoiceReceipt.js 超时
|
||||
- SQL:`doc/sql/transport/blade_kingdee_attachment_sync_20260924.sql`(建表 + 3 加列,可重复执行)
|
||||
- 提交:tms-api `dee649d`(docs) → `79aaa9b`(feat) → `9f95c75`(log);tms-erp-web `aabe67e`
|
||||
|
||||
## 术语表
|
||||
|
||||
| 术语 | 含义 |
|
||||
| --- | --- |
|
||||
| A902 | 星瀚「附件上传到指定单据」接口 |
|
||||
| A902 | 星瀚「附件上传到指定单据」接口(注意与客商接口 A902-1/A902-2 是两套编号) |
|
||||
| A801-2 | 星瀚「文件下载」接口(票池发票影像) |
|
||||
| billPkId | 星瀚单据内码,保存响应 `BfiBillSaveResultItemVO.id`,A902 定位目标单据 |
|
||||
| entityNumber | 星瀚单据标识(如 ap_payapply) |
|
||||
| controlKey | 星瀚附件面板/附件字段标识,统一 attachmentpanel |
|
||||
| 附件同步聚合态 | 无附件/待上传/部分未传/已上传(列表展示用,运行时聚合) |
|
||||
| 附件幂等键 | 判定「同一附件已传过」的键:用户附件=附件 url;发票文件=A801-2 的 path(path 变化视为新文件) |
|
||||
| 镜像发票文件 | 票池镜像 attachmentsJson 保存的发票文件 {source,path,name},上传收票单时经 A801-2 取回回传 |
|
||||
| 附件同步聚合态 | 无附件/未上传/部分未传/已上传(列表展示用,运行时聚合) |
|
||||
| 重推补传 | 主单重复推送(含幂等跳过)时自动补传未成功附件,无新增交互 |
|
||||
|
||||
Reference in New Issue
Block a user