From 1b4ed22df31bc2ec86b695c06d6fc33963e20fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Thu, 24 Sep 2026 22:15:43 +0800 Subject: [PATCH] =?UTF-8?q?:memo:=20=E9=87=91=E8=9D=B6=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3=E6=A2=B3?= =?UTF-8?q?=E7=90=86=E5=AF=B9=E9=BD=90=E5=AE=9E=E6=96=BD=EF=BC=88as-built?= =?UTF-8?q?=EF=BC=89=EF=BC=9A=E5=8B=98=E8=AF=AF=E5=89=8D=E6=8F=90=E3=80=81?= =?UTF-8?q?=E5=9B=9E=E5=A1=AB=E8=B7=AF=E5=BE=84=E3=80=81=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=E4=B8=8E=E5=AE=9E=E6=96=BD=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-09-24-kingdee-attachment-sync-design.md | 108 +++++++++++++----- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md b/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md index 33f9c552..977df069 100644 --- a/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md +++ b/doc/docs/业务流程/2026-09-24-kingdee-attachment-sync-design.md @@ -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`,`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) +syncAttachments(bizType, bizId, billPkId, List) // 主流程 +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) ## 八、验证 -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) | 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 取回回传 | +| 附件同步聚合态 | 无附件/未上传/部分未传/已上传(列表展示用,运行时聚合) | +| 重推补传 | 主单重复推送(含幂等跳过)时自动补传未成功附件,无新增交互 |