diff --git a/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md b/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md new file mode 100644 index 00000000..33f9c552 --- /dev/null +++ b/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md @@ -0,0 +1,171 @@ +# 金蝶附件同步(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` | 本期 | +| 客商(资质附件) | 待金蝶确认 | 待确认 | 二期 | + +映射以常量/配置类维护(如 `KingdeeBfiConstant` 或独立 `KingdeeAttachmentEntityMapping`),二期补条目不改架构。 + +## 三、领域模型 + +### 实体 + +**业务附件(不动)**:各单据 `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)` 载入成功集合,命中即跳过(幂等) +- 同步流水:现有同步流水新增 action=`attachment_upload` + +**单据表加列**:`kingdee_bill_id`(星瀚单据内码,取自保存响应 `BfiBillSaveResultItemVO.id`)。付款申请已有该列;正式结算按应付/应收现有 kingdeeBillNo 的存储位置对应加 id 列;收票登记加收票单 id 列;开票申请加开票申请单 id 列。 + +### 附件同步状态机(单附件行级) + +``` +(无记录) ──上传成功──> 成功(终态,幂等跳过) + └────上传失败──> 失败(记 fail_reason;重推时重新尝试,成功则覆盖为成功) +``` + +列表聚合展示态(运行时聚合,非存储字段):`无附件` / `已上传` / `部分未传` / `未上传`。 + +### 不变式 + +1. 附件上传必须晚于主单推送成功、拿到并落库 billPkId 之后 +2. 附件失败永不回滚、不污染主单同步状态(逐附件独立 try/catch) +3. 同 `(biz_type, biz_id, attachment_key)` 最多一条成功记录 +4. 不自动重试;重推主单时自动补传(主单被幂等预检跳过不影响附件补传分支) +5. 单文件软限制 20MB(常量,待金蝶确认后调整);超限/下载失败记原因跳过,不阻塞同单其余附件,更不阻塞同批其他单据 + +## 四、后端设计 + +### BFI 客户端(blade-bfi-api) + +`IBfiClient` 新增两个方法,复用现有 token 配置(`BfiFeignClientConfig`): + +- `uploadFile`:`produces = MULTIPART_FORM_DATA_VALUE`,`@RequestPart("file")` + `@RequestPart("attachmentUploadFileArgs")`(JSON 字符串),返回 `BfiOpenApiResultVO`(含 data.path) +- `downloadFile`(A801-2):body 传 path,返回 `feign.Response`(流式读取,防大文件全量进内存) + +### 附件同步支持类(blade-transport) + +新增 `KingdeeAttachmentSyncSupport`(与 `KingdeeApBillSyncSupport` 等同包同风格),核心方法: + +``` +syncAttachments(bizType, bizId, billPkId, List) +``` + +流程:载入已成功集合 → 过滤已传 → 过滤超限(记 fail_reason)→ 逐个(读文件流 / A801-2 下载)→ A902 上传 → 落记录 + 流水。单附件 try/catch,异常记失败继续下一个。 + +### 各模块接入点(主单成功、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 | + +重推补传:现有同步入口无改动——主单被幂等预检判已存在时,仍进入附件补传分支(此时 billPkId 取本地 kingdee_bill_id;本地缺失则调对应查询接口回填,如付款申请勾稽已有此能力)。 + +### 列表状态聚合 + +各列表 VO 增加 `attachmentSyncStatus` 聚合字段:列表查询后按 biz_ids 一次 `GROUP BY biz_type, biz_id` 查同步表,与单据附件数量(解析 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 模式完全复用,无新增状态;无新增按钮(重推即补传) | + +## 六、不做的事 + +- 客商档案附件推送(二期,等金蝶确认 entityNumber/controlKey;本期仅预留映射配置) +- 附件自动重试、定时补传任务 +- 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`(改动模块) +2. 前端:`pnpm run typecheck` +3. 手动(用户执行): + - 含附件单据(付款申请/开票申请/正式结算/收票登记)单条/批量同步 → 金蝶对应单据附件面板可见文件 + - 同步成功但附件失败(断网/超限模拟)→ 主单状态正常、列表「附件同步」列示未传、重推自动补传 + - 重推已全部上传的单据 → A902 零调用(幂等跳过) + - 收票登记:镜像发票文件经 A801-2 下载后出现在收票单附件面板 + +## 九、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 变化(红冲重开)自动触发重传 | + +## 术语表 + +| 术语 | 含义 | +| --- | --- | +| A902 | 星瀚「附件上传到指定单据」接口 | +| A801-2 | 星瀚「文件下载」接口(票池发票影像) | +| billPkId | 星瀚单据内码,保存响应 `BfiBillSaveResultItemVO.id`,A902 定位目标单据 | +| entityNumber | 星瀚单据标识(如 ap_payapply) | +| controlKey | 星瀚附件面板/附件字段标识,统一 attachmentpanel | +| 附件同步聚合态 | 无附件/待上传/部分未传/已上传(列表展示用,运行时聚合) |