✨ 新增金蝶付款结果定时回写任务(A103-2/A103-4)并抽取BFI分页通用翻页工具

This commit is contained in:
刘泉佳
2026-09-17 16:40:01 +08:00
parent e18c7dcfda
commit bdd1d7571f
15 changed files with 860 additions and 21 deletions
@@ -0,0 +1,101 @@
# BFI 分页查询通用翻页工具(BfiUtil.queryAllPages)技术文档
> 适用系统:TMS(BladeX 微服务平台,模块:`blade-third-party-api/blade-bfi-api`)
> 组件类型:通用工具类(非业务流程),服务业财融合全部 `BfiPageVO` 分页查询接口
> 关联文档:`2026-09-17-付款管理金蝶付款结果定时回写(A103-2付款申请单查询+A103-4付款处理查询)技术文档.md`
> 最后更新:2026-09-17
---
## 1. 背景与范围
`IBfiService` 中共有 9 个查询接口返回 `BfiPageVO<T>` 分页结构,各接口的分页翻页逻辑完全一致(页码递增、空页/末页终止、防死循环保护)。原实现在 `PaymentApplicationServiceImpl` 中以私有方法 `queryPage` 承载,无法被其他查询场景复用,现抽取为 `BfiUtil.queryAllPages` 通用工具。
设计目标:
- **一处实现,全部查询复用**:付款结果回写(A103-2/A103-4)之外,后续 A102-4 应付勾稽、A302-4 应收核对、A303-4 开票结果回写、A801-3/A301-1/A301-2 收票收款认领等场景接入时零成本复用
- **调用方只关心"取第 N 页"**:翻页推进、终止判断、全量汇总由工具类负责
- **防死循环**:金蝶侧 `lastPage` 异常(恒为 false)时按最大页数保护终止
## 2. 适用接口清单
| 接口编号 | 名称 | `IBfiService` 方法 | 返回结构 |
| --- | --- | --- | --- |
| A103-2 | 付款申请单查询 | `queryPayApply` | `BfiPageVO<BfiPayApplyVO>` |
| A103-4 | 付款处理查询 | `queryPayBill` | `BfiPageVO<BfiPayBillVO>` |
| A102-4 | 财务应付单查询 | `queryApBill` | `BfiPageVO<BfiApBillVO>` |
| A302-4 | 财务应收单查询 | `queryArBill` | `BfiPageVO<BfiArBillVO>` |
| A303-4 | 开票申请单查询 | `queryInvoiceApply` | `BfiPageVO<BfiInvoiceApplyVO>` |
| A902-1 | 客户查询 | `queryCustomer` | `BfiPageVO<BfiCustomerVO>` |
| A801-3 | 收票单查询 | `queryReceivedInvoice` | `BfiPageVO<BfiReceivedInvoiceVO>` |
| A301-1 | 收款单查询 | `queryRecBill` | `BfiPageVO<BfiRecBillVO>` |
| A301-2 | 收款认领公告查询 | `queryClaimAnnounce` | `BfiPageVO<BfiClaimAnnounceVO>` |
不适用:`queryInvoicePool`(A801-1,直接返回 List)、`queryExchangeRate`(A801-4,单页调用由 `BfiServiceImpl` 内部处理)。
## 3. API 定义
```java
// 默认最大页数保护(50 页)
public static <T> List<T> queryAllPages(IntFunction<BfiPageVO<T>> pageFetcher)
// 自定义最大页数保护
public static <T> List<T> queryAllPages(IntFunction<BfiPageVO<T>> pageFetcher, int maxPages)
```
| 参数 | 说明 |
| --- | --- |
| `pageFetcher` | 页码 → 分页结果函数。lambda 内负责按传入页码设置请求参数(页码必设,页大小按各接口限制设置)并调用 `IBfiService` 对应查询方法 |
| `maxPages` | 最大页数保护,防止金蝶侧 `lastPage` 异常导致死循环;默认 `BfiUtil.DEFAULT_MAX_PAGES = 50` |
| 返回值 | 全量数据行汇总列表(按页序追加);无数据时返回空列表 |
## 4. 翻页终止条件(按判断顺序)
| 序 | 条件 | 说明 |
| --- | --- | --- |
| 1 | 返回页为 null 或数据行为空 | 本页无数据即止 |
| 2 | `lastPage = true` | 金蝶标识已最后一页 |
| 3 | `rows.size() < 响应体 pageSize` | 末页数据不满一页,无需继续;**pageSize 取响应体返回值**(`BfiPageVO.pageSize`),调用方无需额外传入,响应体未返回 pageSize 时自动退化为依赖条件 2 与最大页数保护 |
| 4 | 页码超过 `maxPages` | 防御性终止,异常场景兜底 |
## 5. 用法示例
```java
// A103-2 付款申请单查询:来源单据号批量查询
private List<BfiPayApplyVO> queryPayApplyPages(List<String> awrpSrcCodes) {
BfiPayApplyQueryDTO param = new BfiPayApplyQueryDTO();
param.setAwrpSrcCode(awrpSrcCodes);
param.setAwrpSrcSys(bfiPayApplySrcSys);
return BfiUtil.queryAllPages(pageNo -> {
param.setPageNo(pageNo);
param.setPageSize(PAGE_SIZE);
return bfiService.queryPayApply(param);
});
}
```
lambda 内通过闭包复用同一参数对象,仅需覆盖 `pageNo`;请求参数的各接口约束(如 A103-4 付款组织编码必填、时间范围不超过 1 个月)由调用方在构造参数时保证。
## 6. 设计说明
| 决策 | 说明 |
| --- | --- |
| 抽取为 `IntFunction<BfiPageVO<T>>` 而非传入 DTO | 各查询接口的入参 DTO、Feign 方法签名各不相同,仅"按页码取结果"语义一致;函数式入参对调用方约束最小,DTO 字段设置留在调用方(编译期类型安全) |
| 不再需要 `rowsGetter` 参数 | 全部查询统一返回 `BfiPageVO`,`getRows` 内聚到工具类;早期版本传入 `BfiPageVO::getRows` 属冗余 |
| pageSize 判满取响应体 | 早期版本要求调用方传入请求 pageSize,两处易失配;响应体 `pageSize` 为金蝶实际执行值,更可靠 |
| 空列表而非 null | 无数据返回空列表,调用方无需判空 |
## 7. 迁移记录
| 变更 | 内容 |
| --- | --- |
| 新增 | `BfiUtil.queryAllPages` 两个重载 + `DEFAULT_MAX_PAGES` 常量 |
| 删除 | `PaymentApplicationServiceImpl` 私有方法 `queryPage` 及 `PAY_RESULT_MAX_PAGES` 常量 |
| 替换 | 付款结果回写 `queryPayApplyPages` / `queryPayBillPages` 改调 `BfiUtil.queryAllPages` |
## 8. 涉及代码位置
| 内容 | 位置 |
| --- | --- |
| 工具类 | `blade-third-party-api/blade-bfi-api/src/main/java/org/springblade/thirdparty/bfi/util/BfiUtil.java` |
| 当前唯一使用方 | `blade-service/blade-transport/.../service/impl/PaymentApplicationServiceImpl.java`(queryPayApplyPages / queryPayBillPages) |
@@ -0,0 +1,167 @@
# 付款管理金蝶付款结果定时回写(A103-2 付款申请单查询 + A103-4 付款处理查询)技术文档
> 适用系统:TMS(BladeX 微服务平台,服务:blade-transport)
> 对接系统:金蝶云·星瀚(业财融合 BFI,经 iPaaS 网关)
> 关联文档:`doc/docs/TMS业财融合-集成流程图文字版与业务流程梳理.md`(4.3 节步骤 11)、`付款申请同步金蝶(星瀚)技术文档.md`、`2026-09-16-付款管理正式付款同步金蝶(财务应付单+付款申请下推)技术文档.md`、`2026-09-17-BFI分页查询通用翻页工具(BfiUtil-queryAllPages)技术文档.md`
> 最后更新:2026-09-17
---
## 1. 背景与范围
付款申请推送金蝶(A103-1/A103-3)成功后,金蝶侧依次完成:财务共享审批(黑盒,机器人处理)→ 付款处理 → 付款完成。原实现将"付款完成"整段模拟(`paidAmount = 申请金额`、`MOCK-PAY-` 前缀付款记录、履约付款数据直接置 `paid`),本文档建设真实回写链路并停用模拟:
- 触发入口:
- **定时任务**:`PaymentKingdeeResultTask`,`@Scheduled` 默认每 10 分钟(`thirdParty.bfi.payResult.cron`),开关 `thirdParty.bfi.payResult.syncEnabled`
- **手动触发**:付款管理页面"同步付款结果"按钮(权限码复用 `payment_application_sync`)→ `POST /payment-application/sync-kingdee-result`
- 对接接口:
- **A103-2 付款申请单查询** `POST /ierp/kapi/v2/awrp/ap/ap_payapply/query`:按 `awrp_src_code`(=paymentNo)批量查询,回填金蝶单据编号(billno)、单据状态、付款状态、已付金额
- **A103-4 付款处理查询** `POST /ierp/kapi/v2/awrp/cas/cas_paybill/query`:按付款人(公司)编码 + 业务日期窗口查询,回填付款记录(付款单号、付款日期、付款金额、整体报文留档)
- 联动更新:付款完成(paystatus=Alreadypay)时复用既有逻辑联动履约付款数据(预结算预付记录 `blade_pre_settlement_advance`、正式结算付款记录 `blade_formal_settlement_payment`)
## 2. 业务流程
### 2.1 集成链路(对应方案文档 4.3 节步骤 11)
```
付款申请已推送金蝶(kingdeeStatus = synced)
→ 定时任务(每10分钟)
→ A103-2 付款申请单查询(awrp_src_code 批量,50单/批)
回填 kingdee_bill_id / kingdee_bill_no(billno)/ kingdeeStatus / paid_amount
→ A103-4 付款处理查询(付款中/已付款单据,按付款组织分组 + 最近1个月窗口)
回填付款记录 blade_payment_application_record(并清理历史 MOCK-PAY- 模拟记录)
→ 付款完成(Alreadypay):联动履约付款数据(预结算预付、正式结算付款记录)
→ (后续环节)付款附件推送 A902、合同系统履约同步 C106-2,待建设
```
### 2.2 定时回写时序
```
PaymentKingdeeResultTask PaymentApplicationServiceImpl 星瀚(BFI)
│ @Scheduled 每10分钟 │ │
│ syncKingdeePaymentResult() ──────>│ │
│ │ ①查本地 approved + synced/paying │
│ │ ②awrp_src_code 分批(50) ─────────────────>│ A103-2
│ │ <── billno/billstatus/paystatus/entry ────│
│ │ 逐单独立事务:回填单号/状态/已付金额 │
│ │ Alreadypay → 联动履约付款数据 │
│ │ ③付款组织分组 + 最近1个月窗口 ──────────────>│ A103-4
│ │ <── 付款处理单(sourcebillid/sourcebillnumber)│
│ │ 勾稽匹配 → 回填付款记录(幂等) │
│ <── 执行摘要日志 ──────────────────│ │
```
### 2.3 查询范围与幂等约定
| 约定 | 说明 |
| --- | --- |
| A103-2 查询范围 | `approvalStatus = approved` 且 `kingdeeStatus IN (synced, paying)`;`paid/closed` 为终态不再轮询 |
| A103-2 防重键 | `awrp_src_code`(=paymentNo)+ `awrp_src_sys`(=WLTMS)批量查询,50 单/批,自动翻页(页大小 200,最大 50 页保护) |
| 同号多单 | 同一 `awrp_src_code` 返回多条时取 `modifytime` 最新的一条 |
| A103-4 查询范围 | 付款中/已付款单据(本轮 A103-2 查到 `paystatus ≠ Unpaid` 的)+ 已付款但尚无真实付款记录的单据(补偿轮询) |
| A103-4 入参约束 | 付款人(公司)编码必填(优先取 A103-2 返回 `payorg_number`,兜底按合同所属组织解析);业务日期窗口取最近 1 个月(接口限制不超过 1 个月) |
| A103-4 勾稽键 | `sourcebillid`(=付款申请单星瀚id `kingdee_bill_id`)优先,`sourcebillnumber`(=付款申请单编号 `kingdee_bill_no`)兜底 |
| 付款记录幂等 | 按 `payment_application_id + payment_no`(=付款处理单号)判重,存在则更新;回填时清理该申请单下 `MOCK-PAY-` 前缀的历史模拟记录 |
| 逐单隔离 | 每笔申请通过 `SpringUtil` 自代理走独立事务(`applyPayApplyResult` / `applyPayBillRecord`),单条失败不中断批次,异常记录日志后继续 |
### 2.4 状态映射(kingdeeStatus 扩展)
| A103-2 返回 | 本地 kingdeeStatus | 展示名 | 是否终态 |
| --- | --- | --- | --- |
| `paystatus = Alreadypay` | `paid` | 已付款 | 是(触发履约联动) |
| `billstatus = D`(已关闭)且未付款 | `closed` | 已关闭 | 是 |
| `paystatus = Inpayment` | `paying` | 付款中 | 否(继续轮询) |
| `billstatus = A暂存/B已提交/C已审核` + `paystatus = Unpaid` | `synced` | 已生成 | 否(继续轮询) |
既有状态保持不变:`unsynced` 未生成、`failed` 生成失败(可重推)。
## 3. 字段映射关系
### 3.1 A103-2 回写(`BfiPayApplyVO` → `PaymentApplication`)
| 星瀚字段 | 含义 | 落库字段 | 说明 |
| --- | --- | --- | --- |
| id | 星瀚单据内部主键 | `kingdee_bill_id` | A902 附件上传等后续环节使用 |
| billno | 单据编号(FKSQ-xxxx) | `kingdee_bill_no` | 展示用金蝶单据号;A103-4 勾稽键之一 |
| billstatus | 单据状态 A/B/C/D | `kingdee_status` | 见 2.4 状态映射 |
| paystatus | 付款状态 | `kingdee_status` | 见 2.4 状态映射 |
| entry[].e_paidamt | 分录已付金额(汇总) | `paid_amount` | 分录缺失且已付款时兜底取申请金额 |
| payorg_number | 付款组织编码 | —(内存) | A103-4 付款人(公司)编码入参 |
### 3.2 A103-4 回填(`BfiPayBillVO` → `PaymentApplicationRecord`)
| 星瀚字段 | 含义 | 落库字段 | 说明 |
| --- | --- | --- | --- |
| billno | 付款处理单号 | `payment_no` / `kingdee_bill_no` | 幂等判重键 |
| paydate | 付款日期 | `paid_date` | 取前 10 位解析 yyyy-MM-dd,解析失败置空并告警 |
| actpayamt | 付款金额 | `paid_amount` | 2 位小数 |
| 整体报文 | 付款处理单全量 JSON | `voucher_json` | 凭证号、银行流水、电子回单等信息留档追溯 |
## 4. 付款完成联动(履约付款数据)
付款状态变为 `paid` 时复用既有逻辑(与原模拟阶段一致,数据源改为真实回写值):
| 联动项 | 逻辑 | 位置 |
| --- | --- | --- |
| 预结算预付记录 | `syncPreSettlementAdvance`:`paid_amount` = 申请金额、`bill_status = paid`、`kingdee_advance_no` = 金蝶单据号,刷新预结算单预付汇总与正式结算来源预付统计 | PaymentApplicationServiceImpl |
| 正式结算付款记录 | `syncFormalSettlementPayments`:按申请单-结算单关联分摊已付金额,创建/更新 `blade_formal_settlement_payment`(`bill_status = paid`、金蝶单据号),刷新结算单付款汇总 | 同上 |
部分付款(`paying`)仅更新付款申请 `paid_amount` 与付款记录,**不**触发履约联动(待付款完成一次性联动)。
## 5. 模拟逻辑停用说明
- `mockPaymentRecords()`(`MOCK-PAY-` 付款记录模拟)与 `syncKingdee` 内的付款完成模拟代码段**保留方法体、注释调用处**并备注原因:联调环境无金蝶付款数据时可恢复注释模拟使用。
- `syncKingdee` 现仅落 `kingdee_bill_id` + `kingdee_status = synced`;`kingdee_bill_no`、`paid_amount`、付款记录、履约付款数据全部由定时回写驱动。
- 存量数据兼容:
- 已 `synced` 单据(`kingdee_bill_no` 存的是旧语义星瀚id)由任务首轮 A103-2 按 paymentNo 查询自动修正为 billno;
- 手动重推幂等返回兼容旧数据(`kingdee_bill_id` 为空时回退返回 `kingdee_bill_no`)。
## 6. 数据库变更
| 变更 | 内容 | 脚本 |
| --- | --- | --- |
| 新增列 | `blade_payment_application.kingdee_bill_id` varchar(32):金蝶单据id(星瀚内部主键) | `doc/sql/transport/blade_payment_application_kingdee_bill_id_20260917.sql`(幂等可重复执行) |
| 语义调整 | `kingdee_bill_no`:星瀚id → **金蝶单据编号 billno**(由回写任务回填) | 无 DDL |
| 建表模板 | `blade_payment_application_20260821.sql` 已同步包含新列 | 新装环境直接执行 |
## 7. 配置项(Nacos:`thirdParty.bfi.payResult.*`)
模板见 `doc/nacos/blade-dev.yaml`、`blade-prod.yaml`、`third-party-api.yaml`:
| 配置键 | 环境变量 | 默认值 | 说明 |
| --- | --- | --- | --- |
| thirdParty.bfi.payResult.syncEnabled | BFI_PAY_RESULT_SYNC_ENABLED | true | 回写任务开关 |
| thirdParty.bfi.payResult.cron | BFI_PAY_RESULT_CRON | 0 */10 * * * ? | 调度表达式(默认每10分钟) |
调度启用:`TransportApplication` 增加 `@EnableScheduling`。
## 8. 异常处理
| 场景 | 行为 |
| --- | --- |
| A103-2/A103-4 接口级失败(status=false / errorCode≠0) | `BfiServiceImpl.commonCall` 抛 `BfiException`,当前批次终止,下轮任务自动重试 |
| 单笔回写异常 | 记录 error 日志(含 paymentNo 与堆栈),计数后继续下一单 |
| A103-4 付款组织编码获取失败(无 A103-2 数据且合同缺失/组织编码为空) | warn 日志跳过该单,下轮重试 |
| 付款日期解析失败 | warn 日志,`paid_date` 置空,其余字段正常回填 |
| 手动触发与定时任务并发 | 同单以 `updateById` 主键更新兜底,最终一致;付款记录按单号判重不重复插入 |
## 9. 涉及代码位置
| 内容 | 位置 |
| --- | --- |
| 定时任务入口 | `blade-service/blade-transport/.../task/PaymentKingdeeResultTask.java` |
| 回写编排 | `blade-service/blade-transport/.../service/impl/PaymentApplicationServiceImpl.java`(syncKingdeePaymentResult :409 / applyPayApplyResult :512 / applyPayBillRecord :538 / queryPayApplyPages / queryPayBillPages / kingdeeResultStatus / matchPayBill) |
| 手动触发接口 | `blade-service/blade-transport/.../controller/PaymentApplicationController.java`(POST /sync-kingdee-result) |
| 状态常量 | `blade-service/blade-transport/.../constant/KingdeeBfiConstant.java`(KINGDEE_STATUS_* / BFI_PAY_STATUS_* / BFI_BILL_STATUS_CLOSED) |
| 实体/翻译 | `blade-transport-api/.../pojo/entity/PaymentApplication.java`(kingdeeBillId);`transport/wrapper/PaymentApplicationWrapper.java`(状态翻译) |
| BFI 客户端 | `blade-third-party-api/blade-bfi-api/.../service/IBfiService.java`(queryPayApply / queryPayBill,本次无改动);分页通用翻页 `util/BfiUtil.java`(queryAllPages,A102-4/A302-4/A303-4 等分页查询可复用) |
| 前端 | `tms-erp-web/src/views/payment/payment-application.vue`(同步付款结果按钮);`src/api/payment/paymentApplication.js`(syncKingdeeResult / kingdeeStatusOptions) |
| SQL | `doc/sql/transport/blade_payment_application_kingdee_bill_id_20260917.sql`;建表模板 `blade_payment_application_20260821.sql` |
| Nacos 配置模板 | `doc/nacos/blade-dev.yaml`、`blade-prod.yaml`、`third-party-api.yaml`(thirdParty.bfi.payResult.*) |
## 10. 遗留事项与联调确认
1. **A103-4 勾稽键语义待联调确认**:以 `sourcebillid = 付款申请单星瀚id`、`sourcebillnumber = 付款申请单编号(billno)` 假设实现,联调时需用真实付款处理单验证;若金蝶侧源单指向不同,仅需调整 `matchPayBill` 匹配逻辑。
2. **付款日期格式**:`paydate` 按前 10 位 yyyy-MM-dd 兼容解析,金蝶若返回纯时间戳需调整 `parseKingdeeDate`。
3. **A103-2 分页翻页**:`awrp_src_code` 批量查询理论单页返回(批次 50 < 页大小 200),翻页保护仅防御异常场景。
4. **后续环节待建设**:付款附件推送 A902(凭 `kingdee_bill_id`)、合同系统履约同步 C106-2、付款申请反审核异常回收(B901 解锁)。