From 9a82ed2eafb8b60b58417e6320d3eb0ff5251989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Mon, 21 Sep 2026 13:19:10 +0800 Subject: [PATCH] =?UTF-8?q?:memo:=20=E6=96=B0=E5=A2=9E=E6=94=B6=E7=A5=A8?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=90=8C=E6=AD=A5=E9=87=91=E8=9D=B6=E5=85=A8?= =?UTF-8?q?=E7=A5=A8=E6=B1=A0=E5=8F=91=E7=A5=A8=EF=BC=88A801-1=20=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2+=E6=89=B9=E9=87=8F=E5=90=8C=E6=AD=A5+=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E7=BB=91=E5=AE=9A=E5=85=B3=E8=81=94=EF=BC=89=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E6=B5=81=E7=A8=8B=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...票池发票(A801-1查询+批量同步+定时绑定关联)业务流程.md | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 doc/docs/业务流程/2026-09-20-收票管理同步金蝶全票池发票(A801-1查询+批量同步+定时绑定关联)业务流程.md diff --git a/doc/docs/业务流程/2026-09-20-收票管理同步金蝶全票池发票(A801-1查询+批量同步+定时绑定关联)业务流程.md b/doc/docs/业务流程/2026-09-20-收票管理同步金蝶全票池发票(A801-1查询+批量同步+定时绑定关联)业务流程.md new file mode 100644 index 00000000..9b6fe56b --- /dev/null +++ b/doc/docs/业务流程/2026-09-20-收票管理同步金蝶全票池发票(A801-1查询+批量同步+定时绑定关联)业务流程.md @@ -0,0 +1,206 @@ +# 收票管理同步金蝶全票池发票(A801-1 查询 + 批量同步 + 定时绑定关联)业务流程 + +> 适用系统:TMS(BladeX 微服务平台,服务:blade-transport) +> 对接系统:金蝶发票云旗舰版·票易通(piaozone)全票池,经业财融合 BFI 网关(rim 契约)透传 +> 关联文档:`doc/docs/TMS业财融合-集成流程图文字版与业务流程梳理.md`(进项收票 A801-1 章节)、`2026-09-17-BFI分页查询通用翻页工具(BfiUtil-queryAllPages)技术文档.md`、`2026-09-17-付款管理金蝶付款结果定时回写(A103-2付款申请单查询+A103-4付款处理查询)技术文档.md`、`2026-09-17-客商档案同步金蝶(A902-1查询回填+A902-2审批推送)技术文档.md` +> 最后更新:2026-09-20 + +--- + +## 1. 背景与范围 + +收票管理(`/payment/invoice-receipt`)当前依赖本地镜像表 `blade_kingdee_invoice_pool`(金蝶进项发票票据池镜像)选票登记,镜像数据无自动采集来源;「批量同步」按钮(权限码 `invoice_receipt_sync`)调用的 `syncKingdee` 仅回读本地镜像复制 `kingdeeBillNo/kingdeeStatus`,**并未真正请求金蝶**。本文档建设两条真实同步链路: + +- **链路一(手动批量同步)**:收票管理列表勾选「审批通过」的收票登记 → 批量同步 → 后端逐单按发票号码调用 **A801-1 查询全票池发票数据** → 刷新票据池镜像并绑定/回填同步状态。 +- **链路二(定时任务)**:新增定时任务分页拉取 A801-1 全票池数据(增量窗口)→ 按 `invoiceNo` upsert 镜像 → **与收票登记做绑定关联**(按发票号码回填 `kingdeeInvoicePoolId`、金蝶流水号与同步状态),覆盖金蝶侧采票滞后于收票登记的场景。 + +不在本期范围:A801-2 发票附件文件下载(`attachmentsJson` 仅记录 `downloadUrl` 备用)、收票单推送金蝶(A303-2)、收票认领结果与正式结算单自动认领。 + +## 2. 对接接口(A801-1) + +| 项 | 说明 | +| --- | --- | +| 接口 | A801-1 查询全票池发票数据(票易通 rim 契约,经 BFI 网关 `/kapi/app/rim/message` 透传) | +| 既有能力 | `IBfiService.queryInvoicePool(BfiInvoicePoolQueryDTO)` 单页查询,本次**无改动复用** | +| 请求体 | `{ messageType: "invoiceQuery", messageId: <服务端填充>, queryParam: { pageNo, pageSize, ...筛选 } }`,`pageSize` 上限 500,`pageNo` 从 1 开始 | +| 响应 | `{ data: [发票...], errorCode, message, status }`,**不返回总数、无末页标识**;成功码 0000 由 `BfiServiceImpl.commonCall` 统一处理 | +| 关键查询条件 | `invoiceNo`(发票号码,手动同步按此精确查询)、`updateTime/updateTimeEnd`(更新时间窗口,定时任务增量拉取)、`buyerTaxNo/salerTaxNo`(税号)、`expenseStatus`(单据使用状态)、`invoiceDateStart/End`(开票日期区间) | +| 关键返回字段 | `invoiceNo` 发票号码、`serialNo` 发票流水号(**票池唯一标识**)、`invoiceDate`、`invoiceType`、`totalAmount` 价税合计、`taxAmount`、`taxRate`、`buyerName/buyerTaxNo`、`salerName/salerTaxNo`、`checkStatus` 查验状态、`delete` 是否作废删除、`downloadUrl` | + +## 3. 业务流程 + +### 3.1 集成链路总览 + +``` +金蝶发票云全票池(A801-1 查询全票池发票数据) + │ + ├── 链路一:手动批量同步(收票管理列表「批量同步」按钮) + │ 勾选 approvalStatus = approved 的收票登记(前端已限定) + │ → 逐单 POST /invoice-receipt/sync-kingdee + │ → 后端按登记发票号码精确查询 A801-1 + │ → 命中:upsert 镜像 + 绑定 kingdeeInvoicePoolId + 回填金蝶流水号/同步状态 + │ → 未命中:置 unsynced,流水记录原因「金蝶票池未查询到」 + │ + └── 链路二:定时任务(InvoiceReceiptKingdeeSyncTask,默认关闭) + → A801-1 按 updateTime 增量窗口分页拉取(pageSize 500,最大 50 页保护) + → 按 invoiceNo upsert 镜像 blade_kingdee_invoice_pool + → 绑定关联:未绑定镜像的收票登记按 invoiceNo 匹配回填 + → 已绑定登记:跟随镜像刷新金蝶流水号/同步状态(delete=1 → failed) +``` + +### 3.2 链路一:手动批量同步时序 + +``` +invoice-receipt.vue InvoiceReceiptController InvoiceReceiptServiceImpl 金蝶(BFI网关) + │ 批量同步按钮 │ │ │ + │ 勾选 approved 行(前端校验) │ │ │ + │ 逐单 syncKingdee(id) ─────────>│ POST /invoice-receipt/ │ │ + │ │ sync-kingdee?id=xxx │ │ + │ │ ──────────────────────────────>│ ①校验 approvalStatus=approved │ + │ │ │ ②按 invoiceNo ───────────────>│ A801-1 + │ │ │ <── 票池发票(0/1条)──────────│ + │ │ │ ③命中:upsert 镜像(按 invoiceNo)│ + │ │ │ 绑定 kingdeeInvoicePoolId │ + │ │ │ 回填 kingdeeBillNo=serialNo │ + │ │ │ 刷新 kingdeeStatus;写 sync 流水│ + │ │ │ ④未命中:kingdeeStatus=unsynced │ + │ │ │ 写 sync 流水(原因) │ + │ <── 同步结果(金蝶流水号)──────────────────────────────────────│ │ +``` + +前端 `handleBatchSync` 现有逻辑(`invoice-receipt.vue:366`)保持不变:仅允许勾选「审批通过」行、`Promise.all` 逐单调单条接口,失败由 axios 拦截器统一提示。 + +### 3.3 链路二:定时绑定时序 + +``` +InvoiceReceiptKingdeeSyncTask InvoiceReceiptServiceImpl 金蝶(BFI网关) + │ @Scheduled cron(可配) │ │ + │ syncEnabled 开关校验 │ │ + │ syncKingdeeInvoicePool() ───────>│ ①增量窗口(滚动近N天)+ 发票种类/客商税号白名单 │ + │ │ ②A801-1 分页拉取(pageSize 500,最大50页)────>│ + │ │ <── 票池发票列表 ────────────────────────────│ + │ │ ③白名单过滤(购销双方税号须命中客商档案)后按号批量 upsert 镜像│ + │ │ ④绑定回填批量落库(页级事务,批量失败整页跳过) │ + │ │ ⑤补偿绑定:未绑定登记按发票号码精确补查(不限窗口)──>│ + │ │ 单轮限量、批量落库,直到绑定成功为止 │ + │ <── 执行摘要日志(拉取/过滤/镜像/绑定/刷新/失败/补偿统计)│ │ +``` + +- 任务类仿 `PaymentKingdeeResultTask` / `CustomerKingdeeSyncTask`:业务逻辑收敛在 `IInvoiceReceiptService.syncKingdeeInvoicePool()`,任务类只做开关与调度,便于手动触发联调。 +- 增量窗口约定:**每轮固定滚动近 N 天**(可配,建议 30 天),不依赖"上次成功时间点"——单轮失败不会漏,同一批数据会被后续每轮反复幂等覆盖。 +- **客商税号白名单(购、销双方)**:每轮加载客商档案有效税号(`unifiedCreditCode`,status=1),窗口拉回的发票须**购方税号与销方税号同时命中**该集合才入镜像、参与绑定,白名单外发票不入镜像不绑定;**集合为空不限制**(档案未维护时兼容全量)。`buyerTaxNo`/`salerTaxNo` 均为 rim 契约单值参数,无法按税号集合远程查询,故采用窗口拉取 + 内存过滤的等价实现。 +- **防漏绑补偿**:滚动窗口唯一的漏绑缝隙是「持续失败超过 N 天(窗口滑出 updateTime)」,第 ⑤ 步兜底——仍为未绑定(`kingdeeInvoicePoolId` 为空)的登记按发票号码精确补查 A801-1(优先复用本地镜像,缺失才远程),单轮限量(`compensateLimit`,默认 200,按创建时间先进先出),绑定成功即移出补查队列;金蝶票池暂无该发票时跳过不写库、待后续轮次。**手动同步与补偿绑定不受税号白名单约束**(票由登记侧发起,属我方票)。 +- 提供手动触发端点 `POST /invoice-receipt/sync-invoice-pool`(权限码复用 `invoice_receipt_sync`),返回执行摘要,供联调与补偿。 + +### 3.4 匹配与幂等规则 + +| 约定 | 说明 | +| --- | --- | +| 匹配键 | `invoiceNo`(发票号码)。收票登记侧 `assertInvoiceUnused` 已保证一张发票仅可登记一次,镜像侧按 `invoiceNo` upsert 天然幂等 | +| 发票种类白名单 | `thirdParty.bfi.invoiceReceipt.invoiceTypes`(逗号分隔票易通编码,如 `26,27`)控制可同步的发票种类:白名单外发票**不落镜像不绑定**(手动同步记「发票类型不在同步范围」流水,定时拉取在类型过滤统计中体现);配置为空不限制。定时拉取翻页终止仍按原始页条数判断,不受过滤影响 | +| 客商税号白名单 | 客商档案有效税号(`unifiedCreditCode`,status=1)为白名单,窗口拉回的发票须**购方税号与销方税号同时命中**才入镜像、参与绑定;**集合为空不限制**。与发票种类白名单叠加生效;手动同步与补偿绑定不受此约束 | +| 镜像批量写库 | 同页镜像**一次批量查询**既有记录(`invoiceNo IN 同页号码`)+ `Db.saveBatch` / `Db.updateBatchById` **批量写入**(避免逐条查改 N+1);批量写失败中断本轮,由任务层记录异常、下轮重试(upsert 幂等) | +| 绑定批量落库 | 绑定回填先在**内存**完成(登记实体按页批量查出,票面字段不覆盖),再以**页级事务**批量落库:`updateBatchById` 登记绑定 + `Db.saveBatch` 同步流水(`applyInvoicePoolSyncBatch`,JDBC batch)。批量失败**整页跳过**计入失败统计,下轮任务幂等重试,**无逐单降级路径**(`applyInvoicePoolSyncResult` 仅手动单条同步使用) | +| 防漏绑补偿 | 增量窗口为滚动近 N 天,单轮失败不漏;唯一缝隙是「持续失败超 N 天窗口滑出」。补偿绑定步骤对未绑定登记(`kingdeeInvoicePoolId` 为空、非作废)批量补绑:本地镜像**一次批量查询**复用(主流程本轮已落库镜像命中免远程),缺失才按发票号码**精确补查** A801-1(rim 契约无批量号码参数,仅此处逐号远程),新增镜像批量写入 + 绑定页级事务批量落库;单轮限量 `compensateLimit`(默认 200)先进先出,绑定成功即出队;票池暂无则跳过待下轮,不写库不刷流水 | +| 手动同步查询 | A801-1 `invoiceNo` 精确单查(rim 契约单页,返回 0/1 条);多条理论上不存在,取第一条并 warn 日志 | +| 定时绑定范围 | `approvalStatus IN (draft, reviewing, approved, returned)` 且(`kingdeeInvoicePoolId` 为空 或 指向镜像不存在的记录);`voided` 不参与绑定 | +| 绑定回填范围 | 仅回填**绑定关系与金蝶侧信息**:`kingdeeInvoicePoolId`、`kingdeeBillNo`(=serialNo)、`kingdeeStatus`;**不覆盖**登记已保存的票面字段(发票号、金额、税额、收开票单位等),避免冲掉人工登记信息 | +| 镜像刷新口径 | 镜像表字段一律以 A801-1 返回为准(票池是唯一事实源);镜像存在即可被新登记选票 | +| 金额校验 | 绑定时若票池价税合计与登记 `invoiceAmount` 不一致仅记 warn 日志(跨系统开票金额可能变更),不阻断绑定 | +| 已绑定状态刷新 | 跟随镜像刷新 `kingdeeStatus`;票池 `delete=1`(作废删除)置 `failed`,流水记录原因,**不自动作废**收票登记,人工确认处理 | +| 逐单隔离 | 定时链路无逐单路径:镜像批量查改 + 绑定页级批量落库;仅批量落库异常时整页跳过、下轮幂等重试 | +| 关键日志 | 任务开始(增量窗口 + 发票种类 + 客商税号白名单个数)、每页拉取条数、镜像批量写入条数(新增/更新)、逐条绑定成功(发票号码 + 登记id + 金蝶流水号)、绑定异常(含堆栈)、结束汇总摘要(拉取/类型过滤/客商过滤/镜像新增更新/绑定/状态刷新/失败/补偿绑定) | + +### 3.5 状态映射(kingdeeStatus 语义) + +| 场景 | 本地 kingdeeStatus | 展示名 | 说明 | +| --- | --- | --- | --- | +| A801-1 命中且 `delete=0` | `synced` | 已同步 | 票池匹配成功,`kingdeeBillNo` 回填 `serialNo` | +| A801-1 未命中 | `unsynced` | 未同步 | 金蝶票池未查询到(可能未采集/发票号不符),流水记录原因 | +| A801-1 命中且 `delete=1` | `failed` | 同步失败 | 发票在金蝶侧已作废/删除,流水记录原因,人工确认 | +| 同步过程异常(接口失败) | 保持原状态 | — | 本轮终止或该单跳过,下轮重试 | + +## 4. 字段映射关系 + +### 4.1 A801-1 → 镜像表 `blade_kingdee_invoice_pool` + +| A801-1 字段 | 含义 | 落库字段 | 说明 | +| --- | --- | --- | --- | +| invoiceNo | 发票号码 | `invoice_no` | upsert 匹配键 | +| serialNo | 发票流水号 | `serial_no`(**新增列**) | 票池唯一标识 | +| invoiceDate | 开票日期 | `invoice_date` | yyyy-MM-dd | +| invoiceType | 发票类型编码 | `invoice_type` | 经 `BfiConstant.INVOICE_TYPE_NAMES` 编码转名称落库(与正式结算单认领口径一致) | +| taxRate | 税率(小数) | `tax_rate` | 小数转百分数(0.13 → 13),顶层为空回退首条明细 | +| totalAmount | 价税合计 | `invoice_amount` | 与正式结算单认领同口径(价税合计即发票金额) | +| taxAmount | 税额 | `tax_amount` | 2 位小数 | +| buyerName | 购方名称 | `receiver_name` | 受票单位 | +| buyerTaxNo | 购方税号 | `buyer_tax_no`(**新增列**) | 便于按组织税号过滤 | +| salerName | 销方名称 | `issuer_name` | 开票单位 | +| salerTaxNo | 销方税号 | `saler_tax_no`(**新增列**) | 便于按销方过滤 | +| checkStatus | 查验状态 | `check_status`(**新增列**) | 0 未查验 / 1 通过 / 2 不通过 | +| delete | 是否作废删除 | —(映射为登记侧 `failed`) | 不在镜像冗余存储,随查随判 | +| downloadUrl | 文件下载地址 | `attachments_json` | `{"source":"piaozone","path":"","name":"<发票号码>.pdf"}`,A801-2 接入备用 | +| updateTime/billCreateTime | 票池更新时间 | `source_updated_time` | 镜像排序字段 | + +### 4.2 镜像/票池 → 收票登记绑定回填 + +| 来源 | 回填字段 | 说明 | +| --- | --- | --- | +| 镜像主键 | `kingdee_invoice_pool_id` | 绑定关系,绑定后登记详情/编辑可关联票池 | +| 镜像 `serial_no` | `kingdee_bill_no` | 金蝶侧唯一流水号(A801-1 无单据号概念,以流水号作金蝶标识) | +| 匹配结果 | `kingdee_status` | 见 3.5 状态映射 | +| — | `blade_invoice_receipt_record` | 每次同步写 `actionType=sync` 流水(成功/未匹配/作废原因),定时任务操作人以「定时任务」名义记录 | + +## 5. 数据库变更 + +| 变更 | 内容 | 说明 | +| --- | --- | --- | +| 新增列 | `blade_kingdee_invoice_pool`:`serial_no` varchar(64)、`saler_tax_no` varchar(32)、`buyer_tax_no` varchar(32)、`check_status` varchar(8) | 独立幂等脚本 `doc/sql/transport/blade_kingdee_invoice_pool_a8011_20260920.sql`(参照 `blade_payment_application_kingdee_bill_id_20260917.sql` 风格,**不改动**建表模板);排序规则 `utf8mb4_general_ci` | +| 索引 | 无需新增 | 建表模板已有唯一索引 `uk_kingdee_invoice_pool_no (tenant_id, invoice_no)`,覆盖 upsert 与绑定匹配 | +| 收票表 | `blade_invoice_receipt` 无结构变更 | 复用既有 `kingdee_invoice_pool_id/kingdee_bill_no/kingdee_status` | + +## 6. 配置项(Nacos:`thirdParty.bfi.invoiceReceipt.*`) + +模板同步 `doc/nacos/blade-dev.yaml`、`blade-prod.yaml`、`third-party-api.yaml`: + +| 配置键 | 环境变量 | 默认值 | 说明 | +| --- | --- | --- | --- | +| thirdParty.bfi.invoiceReceipt.syncEnabled | BFI_INVOICE_RECEIPT_SYNC_ENABLED | false | 定时绑定任务开关(上线初期关闭,联调稳定后开启) | +| thirdParty.bfi.invoiceReceipt.cron | BFI_INVOICE_RECEIPT_CRON | 0 */30 * * * ? | 调度表达式(默认每 30 分钟) | +| thirdParty.bfi.invoiceReceipt.incrementDays | BFI_INVOICE_RECEIPT_INCREMENT_DAYS | 30 | 首轮/兜底增量窗口天数 | +| thirdParty.bfi.invoiceReceipt.invoiceTypes | BFI_INVOICE_RECEIPT_INVOICE_TYPES | 空 | 同步发票种类白名单(逗号分隔票易通编码,如 26,27 数电普票/专票),为空不限制 | +| thirdParty.bfi.invoiceReceipt.compensateLimit | BFI_INVOICE_RECEIPT_COMPENSATE_LIMIT | 200 | 补偿绑定单轮限量(未绑定登记按发票号码精确补查条数上限) | + +## 7. 异常处理 + +| 场景 | 行为 | +| --- | --- | +| A801-1 接口级失败(status=false / errorCode≠0000) | `BfiServiceImpl.commonCall` 抛 `BfiException`:链路一该单同步失败并提示;链路二当前页终止,下轮任务自动重试 | +| 手动同步按发票号未查到 | 登记 `kingdeeStatus=unsynced`,流水记录「金蝶票池未查询到」,不抛异常中断批量 | +| 绑定时金额不一致 | warn 日志(发票号 + 双方金额),仍按 3.4 口径绑定 | +| 单条绑定/状态刷新异常 | 记录 error 日志(含发票号与堆栈),计数后继续下一单 | +| 定时任务与手动同步并发 | 镜像按 invoiceNo upsert、登记按主键 `updateById`、绑定判重幂等,最终一致 | +| A801-1 翻页异常(rim 无总数) | 以「本页条数 < pageSize 或空页」终止,最大 50 页保护,防御死循环 | + +## 8. 涉及代码位置 + +| 内容 | 位置 | 改动 | +| --- | --- | --- | +| 前端页面 | `tms-erp-web/src/views/payment/invoice-receipt.vue`(handleBatchSync)、`src/api/payment/invoiceReceipt.js`(syncKingdee) | 无需改动(复用现有单条同步接口与按钮) | +| 同步入口改造 | `blade-service/blade-transport/.../service/impl/InvoiceReceiptServiceImpl.java`(syncKingdee 接入 A801-1 查询 + 镜像 upsert + 绑定回填) | 修改 | +| 定时绑定编排 | 同上 `syncKingdeeInvoicePool()`(增量窗口分页拉取 + upsert + 绑定)、`applyInvoicePoolSyncResult()`(逐单独立事务:绑定回填 + 同步流水) | 新增 | +| Service 接口 | `.../service/IInvoiceReceiptService.java`(syncKingdeeInvoicePool) | 新增 | +| 定时任务 | `blade-service/blade-transport/.../task/InvoiceReceiptKingdeeSyncTask.java` | 新增(仿 PaymentKingdeeResultTask) | +| 手动触发端点 | `.../controller/InvoiceReceiptController.java`(POST /invoice-receipt/sync-invoice-pool) | 新增(可选) | +| 镜像实体 | `blade-transport-api/.../pojo/entity/KingdeeInvoicePool.java`(serialNo/salerTaxNo/buyerTaxNo/checkStatus) | 修改 | +| BFI 客户端 | `blade-third-party-api/blade-bfi-api/.../service/IBfiService.java`(queryInvoicePool)、`pojo/dto/BfiInvoicePoolQueryDataDTO.java`、`pojo/vo/BfiInvoicePoolVO.java` | 无改动复用 | +| SQL | `doc/sql/transport/`(镜像表新增列 + 索引幂等脚本) | 新增 | +| Nacos 配置模板 | `doc/nacos/blade-dev.yaml`、`blade-prod.yaml`、`third-party-api.yaml`(thirdParty.bfi.invoiceReceipt.*) | 新增 | + +## 9. 遗留事项与联调确认 + +1. **A801-1 增量窗口口径**:`updateTime/updateTimeEnd`(yyyy-MM-dd HH:mm:ss)为票池更新时间,联调时需用真实票池数据验证窗口过滤有效性;无效则退化为按 `invoiceDateStart/End`(开票日期)滚动窗口全量比对(upsert 幂等,代价可接受)。 +2. **`serialNo` 作金蝶标识**:A801-1 无金蝶单据号概念,以发票流水号回填 `kingdee_bill_no`;后续如接 A303-2 收票单推送,金蝶侧单据号语义另行扩展,避免混用。 +3. **镜像存量数据**:镜像表存量记录 `serial_no` 为空,由首轮定时任务/手动同步按 invoiceNo 命中后补齐;未命中的存量数据保持原状不清理。 +4. **A801-2 附件下载**:`attachmentsJson.path` 仅记录备用,查看/下载附件待 A801-2 接入后建设。 +5. **票池作废联动**:`delete=1` 仅置 `failed` 提示人工确认,是否自动作废收票登记待业务确认后调整。