Files
tms-erp-api/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md
T

220 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 金蝶附件同步(A902)设计文档
- 日期:2026-09-24
- 范围:后端同步链路联带 A902 附件上传(付款申请、正式结算应付/应收、收票登记、开票申请)+ 前端状态列/提示/超时调整
- 背景:单据推送金蝶成功后,TMS 侧附件(attachmentsJson)不随单上行,金蝶单据缺影像。星瀚 A902「附件上传到指定单据」接口已具备(见《业财融合系统接口文档.xlsx》同名页签),此前未在 BFI 客户端实现
- 前置:原计划复用 2026-09-24-kingdee-push-timeout-anticlick 的 loading+守卫+try/finally 模式;经评审确认该前置未落地,本期**不实施防连点(后补)**,本设计仍不新增前端状态机
## 一、目标
1. 五类单据推送金蝶成功后,后端联带逐个上传附件至金蝶对应单据附件面板(A902)
2. 收票登记含发票文件:票池镜像附件 path → A801-2 下载 → A902 回传收票单
3. 附件同步独立状态、失败可补传(重推自动补);前端列表列 + message 可见
4. 客商档案预留 entityNumber 映射配置,二期接入(开票申请 entityNumber 已确认为 `sim_original_bill`,提入本期)
## 二、A902 / A801-2 契约
### A902 附件上传到指定单据
- `POST /ierp/kapi/v2/frame/attachment/uploadFile`,multipart/form-data
- Header:`access_token`(复用现有 getToken/token 缓存)
- Body:`file`(文件)+ `attachmentUploadFileArgs`(JSON):`{"entityNumber":"ap_payapply","billPkId":2386455983883835392,"controlKey":"attachmentpanel"}`
- 返回:`{"data":{"path":"211e6ce5c9805c00"},"errorCode":"0","status":true}`,`data.path` 即金蝶侧文件路径
### A801-2 文件下载(收票发票文件用)
- `POST /ierp/kapi/v2/awrp/bosext/attachment/download`,body `path`(镜像 attachmentsJson 的 path)→ 返回文件流
### entityNumber / controlKey 映射
| 业务单据 | entityNumber | controlKey | 期次 |
| --- | --- | --- | --- |
| 付款申请单 | `ap_payapply` | `attachmentpanel` | 本期 |
| 财务应付单(正式结算应付面板) | `ap_finapbill` | `attachmentpanel` | 本期 |
| 财务应收单(正式结算应收面板) | `ar_finarbill` | `attachmentpanel` | 本期 |
| 收票单 | `ap_invoice` | `attachmentpanel` | 本期 |
| 开票申请单 | `sim_original_bill` | `attachmentpanel` | 本期 |
| 客商(资质附件) | 待金蝶确认 | 待确认 | 二期 |
映射以常量类维护:`KingdeeAttachmentConstant.ENTITY_NUMBERS`(biz_type → entityNumber),controlKey 统一 `attachmentpanel`;二期客商补条目不改架构。
## 三、领域模型
### 实体
**业务附件(不动)**:各单据 `attachmentsJson` JSON 列,是「单据有哪些附件」的业务事实,随表单编辑、随审批快照。本设计不读写其结构(只读 url/name)。
**附件同步表 `blade_kingdee_attachment_sync`(新增)**:
| 字段 | 说明 |
| --- | --- |
| id / tenant_id / 审计字段 | 标准结构 |
| biz_type | `pay_application` / `formal_settlement_ap` / `formal_settlement_ar` / `invoice_receipt` / `invoice_application` |
| biz_id | 单据 id |
| attachment_key | 幂等键:用户附件=附件 url;发票文件=A801-2 的 path(path 变化视为新文件可重传) |
| attachment_name | 附件名(展示/排查用) |
| kingdee_path | A902 返回的 data.path |
| sync_status | 1 成功 / 0 失败 |
| fail_reason | 失败原因(超限/下载失败/金蝶报错) |
| sync_time | 上传时间 |
- 唯一键 `uk_(biz_type, biz_id, attachment_key)`;同步前按 `(biz_type, biz_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 勾稽回填一并带回 |
### 附件同步状态机(单附件行级)
```
(无记录) ──上传成功──> 成功(终态,幂等跳过)
└────上传失败──> 失败(记 fail_reason;重推时重新尝试,成功则覆盖为成功)
```
列表聚合展示态(运行时聚合,非存储字段,评审定案):`无附件` / `未上传` / `部分未传` / `已上传`。
判定:单据 attachmentsJson 无可上传附件→无附件;同步表成功数=0→未上传;0<成功数<附件数→部分未传;成功数≥附件数→已上传。
### 不变式
1. 附件上传必须晚于主单推送成功、拿到并落库 billPkId 之后
2. 附件失败永不回滚、不污染主单同步状态(逐附件独立 try/catch)
3. 同 `(biz_type, biz_id, attachment_key)` 最多一条成功记录
4. 不自动重试;重推主单时自动补传(主单被幂等预检跳过不影响附件补传分支)
5. 单文件软限制 20MB(常量,待金蝶确认后调整);超限/下载失败记原因跳过,不阻塞同单其余附件,更不阻塞同批其他单据
## 四、后端设计
### BFI 客户端(blade-bfi-api)
`IBfiClient` 新增一个方法,复用现有 token 配置(`BfiFeignClientConfig`):
- `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`(`org.springblade.transport.support`,与 `KingdeeApBillSyncSupport` 等同包同风格):
```
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)→ 逐个(http(s) 下载 / A801-2 下载,流式封顶读取)→ A902 上传 → 落记录。单附件 try/catch,异常记失败继续下一个;结果汇总(`AttachmentSyncSummary`:total/success/failed/skipCount/failReasons)供接入点写操作流水与前端提示。同步表既有失败记录重试成功时覆盖为成功(复用主键 update)。
### 各模块接入点(主单成功、billPkId 落库之后,均在主单事务提交后执行)
| 模块 | 接入点 | entityNumber | 附件来源 |
| --- | --- | --- | --- |
| 付款申请 | `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 |
事务边界(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(PaymentApplicationVO/InvoiceApplicationVO/FormalSettlementVO/InvoiceReceiptVO)新增三个展示字段:`attachmentCount`、`attachmentSyncStatus`(none/pending/partial/done)、`attachmentSyncStatusName`(无附件/未上传/部分未传/已上传)。各 `selectPage` 转换后按本页 biz_ids 一次 `GROUP BY biz_id`(`selectMaps`)查同步表成功计数,与解析 attachmentsJson 的附件数对比得出聚合态,避免 N+1;正式结算按应付/应收拆两组业务类型各查一次。
## 五、前端设计
| 项 | 内容 |
| --- | --- |
| 超时 | 实施勘误: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 防连点模式本期未实施(见「不做的事」) |
## 六、不做的事
- 前端同步按钮防连点模式(loading+守卫+try/finally):本期评审确认不实施,后补
- 客商档案附件推送(二期,等金蝶确认 entityNumber/controlKey;本期仅预留映射配置 `KingdeeAttachmentConstant.ENTITY_NUMBERS`)
- 附件自动重试、定时补传任务
- attachmentsJson 结构改造(去 JSON 化属独立重构,见 ADR-3)
- OFD/PDF 发票文件在金蝶侧的可预览性验证
## 七、待确认(金蝶侧)
1. A902 单文件大小上限、允许格式(当前后端软限制 20MB 常量)
2. 客商的 entityNumber/controlKey
3. A902 调用频率限制(批量串行是否需间隔)
4. OFD 数电票文件上传收票单附件面板是否可用(主文档 641 行注明「金蝶不支持直接推送数电票 OFD 附件」,需验证 A902 通道例外与否)
## 八、验证
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 零调用(幂等跳过,日志「历史已成功 N 个」)
- 收票登记:镜像发票文件经 A801-2 下载后出现在收票单附件面板
- 关键日志链路:`调用BFI接口 附件上传开始/结束` → `金蝶附件上传成功` → `金蝶附件同步完成` → 各接入点「同步完成」日志
## 九、ADR 摘要(本次评审定案)
| # | 决策 | 备选与理由 |
| --- | --- | --- |
| ADR-1 | 后端联带自动传附件 | 前端两段式/独立按钮被否:状态机复杂、易漏传 |
| ADR-2 | 附件独立状态+失败可补传,不自动重试 | 主单状态机不被污染;重推即补传零新增交互 |
| ADR-3 | 幂等落独立同步表;**保留 attachmentsJson** | 附件业务事实(编辑/审批快照)与集成状态(幂等/path/原因)职责分离,url 幂等键是必要冗余而非数据冗余;推送期间单据锁定,无并发写 JSON 风险;去 JSON 化若要做属独立重构 |
| 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-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 取回回传 |
| 附件同步聚合态 | 无附件/未上传/部分未传/已上传(列表展示用,运行时聚合) |
| 重推补传 | 主单重复推送(含幂等跳过)时自动补传未成功附件,无新增交互 |