📝 新增应收结算与开票申请同步金蝶设计文档及全链路业务流程文档

This commit is contained in:
刘泉佳
2026-09-21 19:14:44 +08:00
parent b454bdc6e3
commit 7964690e5d
2 changed files with 490 additions and 0 deletions
@@ -0,0 +1,255 @@
# 应收结算与开票申请同步金蝶设计(A302-2/A302-4 + A303-1/A303-5)
> 日期:2026-09-20
> 模块:tms-api(blade-transport、blade-service-api/blade-transport-api、blade-third-party-api/blade-bfi-api 零改动)
> 关联文档:`tms-api/doc/docs/业务流程/2026-09-20-应付单与付款申请同步金蝶全链路业务流程(A102-2保存-A102-4查询-A103-1下推-A103-2回写).md`
> 状态:待评审
---
## 1. 背景与目标
正式结算单的应收(receivable)链路与开票申请的金蝶同步目前未接入:应收结算单「同步金蝶」直接拦截报错;开票申请「同步金蝶」为模拟实现(生成 `K3INV+时间戳` 假单号,未真正调金蝶)。BFI 客户端侧 `saveArBill`(A302-2)、`queryArBill`(A302-4)、`saveInvoiceApply`(A303-1)、`queryInvoiceApply`(A303-4)、`queryInvoiceApplyInvoice`(A303-5) 均已实现但无调用方。
本次目标:
1. **应收结算链路**:应收正式结算单「同步金蝶」→ A302-2 财务应收单保存(前置 A302-4 幂等预检),完全对齐应付链路(A102-2/A102-4)模式。
2. **开票申请链路真实化**:开票申请「同步金蝶」替换为真实 A303-1 推送(前置校验关联应收单已生成 + A303-4 幂等预检)。
3. **定时回写扩展**:复用 `PaymentKingdeeResultTask`,新增 ② A302-4 应收勾稽纠偏、③ A303-5 开票结果回写(发票结果落新表,状态推进 `invoiced` 终态)。
## 2. 范围
**做**:上述三条链路;新表 `blade_invoice_application_invoice`;`KingdeeBfiConstant`/`BfiProperties` 常量与配置扩展;定时任务编排扩展;手动触发接口。
**不做(后续迭代)**:收票 A303-2/A302-6、收款认领 A301-1/2/3、客商风险等级与高风险管控、履约计划、附件推送、收票认领先进先出规则、应付链路任何改动、BFI 客户端任何改动。
## 3. 总体原则(与既有链路一致)
1. **先内后外**:单据先在 TMS 审批通过(approved)再推金蝶。
2. **以金蝶侧实际生成为准**:每次推送前按防重键(awrp_src_sys=WLTMS + awrp_src_code)查金蝶幂等预检,已生成则回填跳过;推送失败可断点续传。
3. **failed 纳入定时勾稽纠偏**:远端实际成功但本地回滚的单据由定时任务查到后自动纠偏,防止人工重推产生重复单据。
## 4. 业务流程
```
【环节一】正式结算单「同步金蝶」(人工,单笔)
审批通过的应收正式结算单
→ 校验:approved;settlementType=receivable(本次放行);非 synced;有结算明细
→ 组装防重键(正式结算单号_结算明细单据号…,与应付同格式)
→ A302-4 幂等预检 ──已生成──> 幂等跳过保存,直接回填已有单号
→ A302-2 财务应收单保存(明细分录逐应收货物费用行)
→ 回写结算单:kingdeeBillNo + synced + syncedTime
【环节二】开票申请「同步金蝶」(人工,替换 K3INV 模拟)
审批通过的开票申请
→ 前置校验:A302-4 校验关联应收结算单的应收单已生成(未生成拦截,提示先完成环节一)
→ A303-4 幂等预检(billno=applicationNo)──已生成──> 直接回填,视为已同步
→ A303-1 开票申请单新增(购销方/商品行分录)
→ 回写开票申请:kingdeeBillNo(金蝶真实单号)+ synced
【环节三】PaymentKingdeeResultTask 定时(每10分钟,编排三个子流程,各自 try/catch 互不影响)
① A103-2/A103-4 付款回写(现状不动)
② A302-4 应收勾稽纠偏:轮询 approved+receivable+状态∈{synced,failed},
按防重键批量查询:查到→补金蝶单号、failed纠偏为synced;查不到→不变(不推进状态机)
③ A303-5 开票结果回写:轮询 approved+kingdeeStatus=synced+金蝶单号非空的开票申请,
serialNo 批量查询(≤50/批)+ 销方税号:查到发票→逐张落新表(按发票号幂等)
→ 申请单 kingdeeStatus=invoiced(终态);查不到→保持 synced 继续轮询
```
收款由收款认领链路(A301-x)负责,不在本次范围;应收单的审核/核销状态不回写。
## 5. 状态机与防重键
### 5.1 状态机
```
应收结算单 kingdeeSyncStatus(无新状态值):
unsynced ──A302-2成功──> synced(终态)
└──推送异常──> failed ──重推 或 定时A302-4勾稽纠偏──> synced
开票申请 kingdeeStatus(新增枚举值 invoiced,varchar 无需 DDL):
unsynced ──A303-1成功──> synced ──A303-5查到发票──> invoiced(终态)
└──推送异常──> failed ──人工重推(A303-4 预检命中→幂等回填)──> synced
注:定时任务只轮询 synced(failed 无真实金蝶单号,A303-5 查询无入口,纠偏走重推预检)
```
### 5.2 防重键体系(awrp_src_sys = WLTMS)
| 单据 | awrp_src_code | 使用接口 |
| --- | --- | --- |
| 财务应收单 | 正式结算单号_结算明细单据号1_单据号2…(去重保序下划线拼接) | A302-2 / A302-4(预检+定时勾稽) |
| 开票申请单 | 开票申请单号 applicationNo(A303-1 billno 候选键) | A303-1 / A303-4(幂等)/ A303-5(serialNo) |
| 分录唯一标识 | 应收货物费用行 id(雪花 id,awrp_entry_code) | A302-2 明细 |
## 6. 接口字段映射
### 6.1 A302-2 财务应收单保存(主表)
| 接口字段 | 取数 | 备注 |
| --- | --- | --- |
| billtype_number | 常量「服务销售应收」→ arfin_sersal_BT_S | AR_BILL_BILLTYPE_MAP 已有;映射名新增常量,待金蝶确认 |
| org_number / recorg_number | 合同所属组织 deptCode | 复用 bfiOrgNumber |
| bizdate | 同步当日 yyyy-MM-dd HH:mm:ss | |
| asstacttype | bd_customer(固定客户) | |
| asstact_number | 结算单付款方 payerName → 客商档案(全称/简称)客商编码 | 未维护报错;与应付(合同乙方)对称 |
| paymode | CASH(现销) | 待金蝶确认 |
| payproperty_number | 1001(一般销售) | 与应付 2001 区分,待金蝶确认 |
| currency_number | 合同结算币种(RMB→CNY) | 复用 bfiCurrency |
| isincludetax / ispricetotal / isintax | true / true / false | 与应付一致 |
| awrp_src_sys / awrp_src_code / awrp_src_type | WLTMS / 防重键 /「TMS财务应收单」 | src_type 新常量,待金蝶确认 |
| awrp_src_bt / awrp_src_con_no | 本期不传 | 与应付一致,保留注释 |
| duedate / 结算方式 / 税率编码 | 本期不传 | 待业务确认(遗留清单) |
### 6.2 A302-2 明细分录(entry,逐应收货物费用行,不聚合)
| 接口字段 | 取数 | 备注 |
| --- | --- | --- |
| e_material_number | 货物类型→货物类型档案货物编码 | 联调期固定 SP0324(同应付) |
| e_measureunit_number | 运费计算单位(兜底数量单位) | 联调期固定 TON(同应付) |
| e_quantity | 运输数量,兜底 1 | |
| e_unitprice | 运输单价 | ispricetotal=true 仅参考 |
| e_recamount | 应收金额(含税)= 费用行调整后总金额 afterAmount | 对应应付 e_pricetaxtotal 语义 |
| e_amount | 金额(不含税)= 结算费用行不含税回链 | 兜底 0,与应付同源逻辑 |
| project_number | 联调期 999 | |
| awrp_entry_code | 应收货物费用行 id | 候选键 |
收款计划 planentity 不传,由金蝶按明细自动汇总。
### 6.3 A303-1 开票申请单新增(主表)
| 接口字段 | 取数 | 备注 |
| --- | --- | --- |
| orgid_number | 申请部门 deptCode | deptId→SysCache |
| billno | applicationNo | 候选键,天然唯一 |
| billdate | applicationDate yyyy-MM-dd HH:mm:ss | |
| billproperties | 1(正数) | |
| invoicetype | electronic_normal→10xdp、electronic_special→08xdp | 数电普票/专票 |
| taxationstyle | 0(普通征税) | |
| buyeremail | departmentEmails | 必填 |
| buyerproperty | 0(企业) | |
| buyertaxno / buyername | 申请单快照 taxpayerNo / receiverName | |
| salername / salertaxno | 申请单快照 issuerName(结算单收款方/开票单位);按开票单位查客商档案(全称/简称)取统一社会信用代码 | A303-5 的 sellerTaxpayerId 同源,不走 Nacos 配置 |
| totalamount | invoiceAmount(价税合计) | |
| invoiceamount / totaltax | 明细不含税合计 / 税额合计 | |
| customnameid_number | receiverCustomerId → 客商档案客商编码 | 未维护报错 |
| awrp_project_number | 联调期 999 | |
| awrp_orgfield_number | 申请部门 deptCode | |
| awrp_src_bt | ar_finarbill | 上游财务应收单 |
| awrp_src_con_no | 关联结算单金蝶应收单号 | 多张取第一张,拼接规则联调确认 |
| billsource / systemsource | 3(固定)/ 配置 srcSys | systemsource 待联调确认 |
| 合同号/合同金额/购方地址银行等选填 | 本期不传 | 保留注释(遗留清单) |
### 6.4 A303-1 分录(sim_original_bill_item,发票商品行平铺)
| 接口字段 | 取数 | 备注 |
| --- | --- | --- |
| goodsname / goodssimplename | line.goodsName / line.goodsCategory | 货物或服务简称 / 商品和服务分类 |
| taxratecodeid_number | line.taxClassificationCode(商品行快照,取自开票项目主数据税收分类编码) | 新增/编辑开票申请选择开票内容时随前端落库,缺失即报错提示重选 |
| num | line.quantity | |
| taxunitprice | line.amountWithTax ÷ quantity | 数量为 0 时兜底 1 |
| taxamount | line.totalAmount(价税合计) | |
| taxrate | line.taxRate ÷ 100(0-100 转小数) | |
| unit | 兜底「个」 | 行无单位字段,联调校准 |
| benchmark | 0 | |
| rowtype / awrp_src_con_eno | 待联调确认 | 先不传/示例值,见遗留清单 |
| awrp_src_type | 配置 thirdParty.bfi.invoiceApply.srcType(默认 0KzXs) | 与付款申请 payApply.srcType 对齐 |
### 6.5 A302-4 / A303-5 回写取数
- **A302-4 勾稽**:按 awrp_src_code 归并,命中即以 `billno` 回填/纠偏,其余字段不取用。
- **A303-5**:`serialNo`(金蝶开票申请单号,≤50/批逗号拼接)+ `sellerTaxpayerId`(与 A303-1 同源:按各申请单开票单位查客商档案统一社会信用代码,按税号分组分批查询,无法解析的申请单跳过并告警)。返回为票易通原文 `List<Object>`:整体 JSON 留档 `raw_json`,宽松提取发票号码/开票日期/价税合计三个字段(Jackson 泛型解析,具体字段名联调校准后收敛为常量映射)。
## 7. 代码组织与改动清单
### 7.1 blade-transport(主改动)
| # | 文件 | 改动 |
| --- | --- | --- |
| 1 | `support/KingdeeArBillSyncSupport`(新建) | 应收单防重键组装(复用 ExternalSrcCodeSupport.buildSrcCode)、A302-4 批量查询 queryArBillExistNos、应收分录解析 resolveArEntryGroups(取数源同应付:blade_receivable_payable_cargo_fee)、pushArBill(A302-4 预检 + A302-2 组装保存)。公共档案取数(bfiOrgNumber/bfiCurrency/requireContract/客商编码查询)经注入 KingdeeApBillSyncSupport 复用,不复制不修改现有类 |
| 2 | `FormalSettlementServiceImpl.syncKingdee` | 移除应收拦截;按 settlementType 路由 payable→pushApBill / receivable→pushArBill;落库逻辑共用 |
| 3 | `FormalSettlementServiceImpl.syncKingdeeArResult`(新增) | A302-4 应收勾稽纠偏(轮询+批量查+回填纠偏),接口方法+Controller 手动触发端点 `/sync-kingdee-ar-result` |
| 4 | `InvoiceApplicationServiceImpl.syncKingdee` | 模拟实现替换为真实推送:A302-4 前置校验(关联应收单已生成)→ A303-4 幂等预检 → A303-1 保存 → 回写 |
| 5 | `InvoiceApplicationServiceImpl.syncKingdeeInvoiceResult`(新增) | A303-5 回写:轮询+批量查+发票落新表+invoiced 推进;Controller 手动触发端点 `/sync-kingdee-invoice-result` |
| 6 | `task/PaymentKingdeeResultTask` | 注入 IFormalSettlementService、IInvoiceApplicationService,编排 ①付款回写 ②应收勾稽 ③开票结果 三个子流程,各自独立 try/catch,汇总日志 |
| 7 | `constant/KingdeeBfiConstant` | 新增:BFI_AR_BILL_BILLTYPE_NAME「服务销售应收」、BFI_AR_BILL_PAY_PROPERTY=1001、BFI_AR_BILL_PAY_MODE_CASH=CASH、BFI_AR_BILL_SRC_TYPE「TMS财务应收单」、KINGDEE_STATUS_INVOICED=invoiced、发票类型映射(electronic_normal→10xdp / electronic_special→08xdp) |
| 8 | `config/BfiProperties` | 新增 invoiceApply 节点:srcType(默认 0KzXs)。销方名称/税号、税收分类编码不走配置:销方名称取申请单开票单位快照、税号按开票单位查客商档案统一社会信用代码、税收分类编码取商品行开票内容快照 |
| 9 | `mapper/InvoiceApplicationInvoiceMapper`(新建) | 接口 + XML 同包 |
### 7.2 blade-transport-api
| # | 文件 | 改动 |
| --- | --- | --- |
| 10 | `pojo/entity/InvoiceApplicationInvoice`(新建) | TenantEntity:invoiceApplicationId、serialNo、invoiceNo、invoiceDate、invoiceAmount、rawJson |
| 11 | `IFormalSettlementService` / `IInvoiceApplicationService` | 新增 syncKingdeeArResult / syncKingdeeInvoiceResult 接口方法 |
| 12 | 开票申请详情 VO | 附带发票结果列表(@TableField(exist=false) 或关联查询),前端展示开票结果 |
## 8. 数据库变更
新表 `blade_invoice_application_invoice`(MySQL 版,正式提交时同步 doc/sql 全部 7 库脚本;无菜单 SQL):
```sql
CREATE TABLE `blade_invoice_application_invoice` (
`id` bigint NOT NULL COMMENT '主键',
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
`invoice_application_id` bigint NOT NULL COMMENT '开票申请id',
`serial_no` varchar(64) DEFAULT NULL COMMENT '金蝶开票申请单编号',
`invoice_no` varchar(64) NOT NULL COMMENT '发票号码',
`invoice_date` datetime DEFAULT NULL COMMENT '开票日期',
`invoice_amount` decimal(18,2) DEFAULT NULL COMMENT '价税合计',
`raw_json` text COMMENT 'A303-5返回原文JSON留档',
`create_user` bigint DEFAULT NULL COMMENT '创建人',
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_user` bigint DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`status` int DEFAULT NULL COMMENT '状态',
`is_deleted` int DEFAULT 0 COMMENT '是否已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_application_invoice` (`invoice_application_id`,`invoice_no`),
KEY `idx_iai_application_id` (`invoice_application_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='开票申请金蝶发票结果';
```
落库幂等:按(invoice_application_id, invoice_no)唯一键,已存在跳过。开票结果不反写结算单——结算单开票状态仍由收票认领链路(InvoiceReceipt)驱动,避免双链路冲突。
## 9. 配置项(Nacos thirdParty.bfi.*)
```yaml
thirdParty:
bfi:
srcSys: WLTMS # 已有
payResult:
syncEnabled: true # 已有,任务总开关(三个子流程共用)
cron: 0 */10 * * * ? # 已有
invoiceApply:
srcType: 0KzXs # 新增:分录外部单据类型,与 payApply.srcType 对齐(有默认值,一般无需配置)
```
销方名称/销方税号、税收分类编码**不再需要配置**:开票申请「新增」页选择结算单即带出开票单位(销方),销方税号按开票单位查客商档案统一社会信用代码;税收分类编码随「开票内容」选择快照到商品行(blade_invoice_application_line.tax_classification_code,增量脚本 `blade_invoice_application_line_tax_code_20260921.sql`)。
## 10. 异常与恢复场景
| # | 断点场景 | 金蝶侧实际 | 本地状态 | 恢复路径 |
| --- | --- | --- | --- | --- |
| 1 | 应收单推送前校验失败(档案缺失等) | 无应收单 | 结算单状态不变 | 补档案重新同步 |
| 2 | A302-2 成功但本地回滚 | 应收单已生成 | 未 synced | 重新同步 A302-4 命中→幂等回填;或定时任务纠偏 |
| 3 | 开票申请同步时应收单未生成 | — | 不变 | 拦截报错,先完成环节一 |
| 4 | A303-1 成功但本地回滚 | 开票申请单已生成 | 未 synced | 重推时 A303-4 命中→幂等回填 |
| 5 | 金蝶开票处理中 | 无发票 | synced | A303-5 每轮空查,直到开出票 |
| 6 | A303-5 查到发票 | 发票已开出 | synced | 落发票结果表→invoiced 终态 |
任一子流程异常只计数不中断整轮(与付款回写一致)。
## 11. 验证
1. `mvn clean compile -DskipTests` 编译通过(AGENTS.md 要求;测试由用户执行)。
2. 每个被改符号编辑前跑 GitNexus impact(upstream),提交前 detect_changes(仓库强制规范)。
3. 联调验证顺序:应收结算单同步(A302-2/4)→ 开票申请同步(A303-1/4)→ 手动触发定时回写(A302-4 勾稽 + A303-5 回写)。
## 12. 遗留与待确认清单
1. **A302-2**:paymode CASH、款项性质 1001、单据类型「服务销售应收」、awrp_src_type「TMS财务应收单」、到期日/结算方式/税率编码——均待金蝶确认。
2. **A303-1**:rowtype、awrp_src_con_eno、购方地址/银行展示、systemsource 取值、多结算单单号拼接规则、部门邮箱分隔符——联调校准。
3. **A303-5**:返回原文字段名(发票号/开票日期/金额)联调后从宽松解析收敛为固定映射。
4. ~~**销方主体**:当前走配置~~ 已改为单据取数:销方名称=开票申请开票单位快照(结算单收款方带出),销方税号=开票单位对应客商档案统一社会信用代码(要求我方开票主体在客商档案中维护)。
5. **应收结算单开票完成状态**:本次不反写结算单;如业务需要"已开票"标识,后续在结算单侧增量。
@@ -0,0 +1,235 @@
# 应收结算与开票申请同步金蝶全链路业务流程(正式结算单同步 → 开票申请同步 → 应收勾稽与开票结果定时回写)
> 适用系统:TMS(BladeX 微服务平台,服务:blade-transport)
> 对接系统:金蝶云·星瀚(业财融合 BFI,经 iPaaS 网关)
> 覆盖接口:A302-2 财务应收单保存、A302-4 财务应收单查询、A303-1 开票申请单新增、A303-4 开票申请单查询、A303-5 开票申请发票查询
> 关联文档:`2026-09-20-应收结算与开票申请同步金蝶设计.md`(设计口径与遗留清单)、`2026-09-20-应付单与付款申请同步金蝶全链路业务流程(A102-2保存-A102-4查询-A103-1下推-A103-2回写).md`(应付侧对称链路)
> 最后更新:2026-09-21(按当前代码梳理;销方名称/销方税号/税收分类编码已改为随单据取数,不再使用 Nacos 配置)
---
## 1. 整体业务链路
应收侧一笔结算到最终开出数电票,分**三个环节**,由两个页面 + 一个定时任务接力完成:
```
环节一:正式结算单「同步金蝶」(人工,正式收款单/结算单页面,单笔)
审批通过的应收类正式结算单 → 推送金蝶财务应收单(A302-2,前置 A302-4 幂等预检)
→ 回写结算单:金蝶单据编号 + 同步状态 synced
环节二:开票申请「同步金蝶」(人工,开票申请页面,单笔)
审批通过的开票申请 → 前置校验关联结算单的应收单已在金蝶生成(A302-4,缺一拦截)
→ A303-4 幂等预检(billno=开票申请单号,命中直接勾稽回填)
→ 推送金蝶开票申请单(A303-1,购销方信息 + 发票商品行分录)
→ 回写开票申请:金蝶单据编号 + 同步状态 synced
环节三:PaymentKingdeeResultTask 定时回写(自动,默认每10分钟,三子流程各自 try/catch 互不影响)
① A103-2/A103-4 付款结果回写(应付链路,本文不展开)
② A302-4 应收勾稽纠偏:轮询 approved+应收+{synced,failed},按防重键批量查询,
查到 → 补金蝶单号、failed 纠偏为 synced;查不到 → 状态不变
③ A303-5 开票结果回写:轮询 approved+synced+金蝶单号非空的开票申请,按销方税号分组
分批(≤50/批)查询,查到发票 → 逐张落结果表(发票号幂等)→ 申请单推进 invoiced(终态)
```
核心设计原则(与应付链路一致):
1. **先内后外**:单据先在 TMS 审批通过(approved)才允许推金蝶。
2. **以金蝶侧实际生成为准**:每次推送前按防重键查询确认,已生成则幂等跳过/回填勾稽,未生成才推送;推送接口真正报错才算失败。
3. **断点续传**:任一环节中断(远端成功、本地回滚)后重新触发或等定时勾稽,均从断点恢复,不产生重复单据。
4. **配置最小化**:金蝶推送所需的业务取数一律随单据快照或走档案(客商/开票项目/货物类型/部门),Nacos 仅剩外部系统编号 srcSys 与分录外部单据类型 srcType(有默认值)。
---
## 2. 五个接口在链路中的角色
| 接口 | 名称 | 方向 | 触发环节 | 业务用途 |
| --- | --- | --- | --- | --- |
| A302-2 | 财务应收单保存 | TMS → 金蝶 | 环节一 | 一张应收正式结算单生成一张金蝶财务应收单(ar_finarbill) |
| A302-4 | 财务应收单查询 | TMS → 金蝶(查询) | 环节一前置 / 环节二前置 / 环节三② | ①推送前幂等预检;②开票申请推送前校验关联应收单已生成;③定时勾稽纠偏(防重键批量查询,50/批+自动翻页) |
| A303-1 | 开票申请单新增 | TMS → 金蝶 | 环节二 | 生成金蝶开票申请单(上游勾稽财务应收单),由金蝶/票易通完成实际开票 |
| A303-4 | 开票申请单查询 | TMS → 金蝶(查询) | 环节二前置 | 按 billno=开票申请单号 幂等预检,断链重推时命中即勾稽回填 |
| A303-5 | 开票申请发票查询 | TMS → 金蝶(查询) | 环节三③ | 按开票申请单号+销方税号查已开发票,落 `blade_invoice_application_invoice` 并推进开票完成 |
收款认领(A301-x)、收票(A303-2/A302-6)、应收单核销状态回写、结算单开票完成反写均不在本链路(后续迭代)。
---
## 3. 环节一:正式结算单「同步金蝶」推送财务应收单(A302-2)
代码入口:`FormalSettlementServiceImpl.syncKingdee` → 按结算类型路由,应收走 `KingdeeArBillSyncSupport.pushArBill`(应付走 A102-2,见应付侧文档)。
### 3.1 业务流程
1. 操作入口:正式结算单页面「同步金蝶」按钮,单笔。
2. 前置校验(不满足直接报错,不调金蝶):
- 审批状态 = **approved**;
- 同步状态已是 **synced** → 幂等返回已有金蝶单号,不重复推送(failed 允许重推);
- 必须有结算明细(分录取数来源)。
3. 组装财务应收单防重键(awrp_src_code):**正式结算单号_结算明细单据号1_单据号2…**(去重保序、下划线拼接),与应付侧同规则;一张结算单只生成一张金蝶应收单。
4. 解析应收单明细分录(`resolveArEntryGroups`,逐**应收货物费用行**一条、不聚合):
- 结算明细 `sourceDetailId` 回链应收应付货物费用行(`blade_receivable_payable_cargo_fee`),无货物费用行直接报错拦截;
- 含税金额取费用行调整后总金额 `afterAmount`,不含税经结算货物费用行 `sourceFeeId` 回链(无映射兜底 0);
- 货物类型(「、/,」多类型不拆行,取第一种)必须在货物类型档案维护货物编码;计量单位取运费计算单位(兜底数量单位)。
5. **A302-4 幂等预检**:按防重键批量查询,已生成 → 幂等跳过保存、直接取已有单号回写;未生成 → 调 A302-2 保存。
6. **A302-2 保存**,主表入参来源:
| 接口字段 | 含义 | 必填 | 来源 | 前端字段(来源页面) |
| --- | --- | --- | --- | --- |
| billtype_number | 单据类型.编码 | 是 | 常量名称「服务销售应收」经 `AR_BILL_BILLTYPE_MAP` 转 `arfin_sersal_BT_S`(待金蝶确认,改常量名即可换映射) | 无(后端常量) |
| org_number / recorg_number | 结算组织/收款组织.编码 | 是 | 合同所属组织 deptCode(未维护报错) | 合同管理页面「所属组织」→ 部门编码 |
| bizdate | 单据日期 | 是 | 同步当日 yyyy-MM-dd HH:mm:ss | 无(系统生成) |
| asstacttype | 往来类型 | 是 | 固定 bd_customer(应收对方必为客户) | 无(后端常量) |
| asstact_number | 往来户.编码 | 是 | 结算单**付款方** payerName → 客商档案按全称/简称匹配取客商编码(未维护报错) | 正式结算单页面「付款方」payerName → 客商档案「客商编号」customerCode |
| paymode | 付款方式 | 是 | 常量 CASH(现销,金蝶自动 100% 推收款) | 无(后端常量,待金蝶确认) |
| payproperty_number | 款项性质.编码 | 是 | 常量 1001(一般销售,与应付 2001 区分,待金蝶确认) | 无(后端常量) |
| currency_number | 结算币别.货币代码 | 是 | 合同结算币种(RMB→CNY) | 合同管理页面「结算币种」 |
| isincludetax / ispricetotal | 录入含税价/录入总价 | 否 | 固定 true / true(免单价换算) | 无(后端常量) |
| awrp_src_sys | 外部系统编号(候选键) | 是 | 配置 thirdParty.bfi.srcSys,默认 WLTMS | 无(后端配置) |
| awrp_src_code | 外部来源单据号(候选键) | 是 | 第 3 步防重键 | 系统组装:「结算单号」+结算明细「单据号」 |
| awrp_src_type | 外部单据类型 | 是 | 常量「TMS财务应收单」(待金蝶确认) | 无(后端常量) |
| 到期日/结算方式/税率编码 | — | 否 | 本期不传(TODO 待金蝶确认后补充) | — |
明细分录(entry,逐应收货物费用行):
| 接口字段 | 含义 | 必填 | 来源 |
| --- | --- | --- | --- |
| e_material_number | 物料编码.编码 | 是 | 货物类型→货物类型档案货物编码;**联调期临时固定 SP0324**(pushArBill 内 `if (true)` 调试块覆盖,校准后移除) |
| e_measureunit_number | 计量单位.编码 | 是 | 运费计算单位(兜底数量单位,档案暂无编码传名称);**联调期临时固定 TON** |
| e_quantity / e_unitprice | 数量/单价 | 是/否 | 费用行运输数量(兜底 1)/运输单价(录入总价仅参考) |
| e_recamount | 应收金额(含税) | 是 | 费用行调整后总金额 afterAmount |
| e_amount | 金额(不含税) | 否 | 结算货物费用行不含税回链,兜底 0 |
| project_number | 项目.项目编码 | 是 | 联调期固定 999(真实来源=项目档案项目编码) |
| awrp_entry_code | 来源分录标识(候选键) | 是 | 应收货物费用行 id(雪花 id 天然唯一) |
收款计划 planentity 不传,由金蝶按明细自动汇总生成。
7. 回写落库:`result[0].number` → 结算单 `kingdeeBillNo` + `kingdeeSyncStatus=synced` + `syncedTime`,并记结算单变更历史「同步金蝶财务应收单{单号}」。失败抛业务异常,状态不变可重推。
### 3.2 状态机与勾稽纠偏
```
kingdeeSyncStatus:unsynced ──A302-2成功──> synced(终态)
└──推送异常──> failed ──人工重推 或 定时②A302-4勾稽纠偏──> synced
```
定时子流程②(`FormalSettlementServiceImpl.syncKingdeeArResult`,手动端点 `POST /formal-settlement/sync-kingdee-ar-result`):轮询 approved+receivable+{synced,failed},按防重键批量查 A302-4;查到 → 补回缺失单号、failed 纠偏为 synced;查不到 → 不动。返回汇总:待查 X 笔、回写 Y 笔(纠偏 Z 笔)。
---
## 4. 环节二:开票申请「同步金蝶」推送开票申请单(A303-1)
代码入口:`InvoiceApplicationServiceImpl.syncKingdee`;页面 `invoice-application-form.vue`(列表 `invoice-application.vue`)。
### 4.1 单据创建与审批(推送前)
1. 新增开票申请:选择**应收正式结算单**(可多张,须同合同/项目/组织/收付款方)→ 自动带出所属项目、**开票单位**(=结算单收款方,即销方)、承办部门、可开票金额;受票方从付款方客商的开票信息(`blade_customer_invoice_info`)选择,纳税人识别号/开户行/账号/注册地址随选择带出并快照到申请单。
2. 开票内容按「发票张」维护商品行:商品和服务分类 + 货物或服务简称均从**开票项目主数据**(`blade_invoice_item`)选择,选择时默认税率随带,并**快照税收分类编码**到商品行 `tax_classification_code`(A303-1 分录用,不走配置)。
3. 商品行只能整体开「运费」或整体开非运费(运费不得与其他费用并票);本次开票金额=商品行含税合计,不得超过可开票金额。
4. 状态流转:draft(草稿可编辑/删除)→ submit → reviewing → approve → approved(或 return 驳回、void 作废);仅 **approved** 允许同步金蝶。
5. 结算单开票状态(unreceived/partial/completed)由申请单分摊金额实时刷新(作废/删除联动回退),金蝶回写不反写结算单。
### 4.2 同步金蝶流程
1. 校验:approved;已 synced/invoiced 幂等返回(invoiced 终态);申请单号非空。
2. **A302-4 前置校验**:逐关联结算单组装应收单防重键批量查询,任一结算单的财务应收单未在金蝶生成 → 拦截报错「请先在正式付款单页面同步金蝶」(即先完成环节一)。
3. **A303-4 幂等预检**:billno=开票申请单号查询,金蝶已生成 → 直接回填单号 + synced(勾稽回填,防断链重推重复)。
4. 取数校验(任一不满足报错):开票单位(销方)非空;**销方税号=开票单位按全称/简称匹配客商档案的统一社会信用代码**;部门邮箱(发票推送邮箱)、购方识别号、申请部门编码、受票方客商编码均已维护;每条商品行已有税收分类编码快照(历史单据缺失时提示编辑重选开票内容)。
5. **A303-1 新增**,主表入参来源:
| 接口字段 | 含义 | 必填 | 来源 | 前端字段(来源页面) |
| --- | --- | --- | --- | --- |
| orgid_number / awrp_orgfield_number | 组织/申请部门.编码 | 是 | 承办部门 deptId → SysCache 部门编码(未维护报错) | 开票申请页「承办部门」(结算单带出) |
| billno | 业务单据编号(候选键) | 是 | 开票申请单号 applicationNo | 系统生成(TMSKV 规则单号) |
| billdate | 单据日期 | 是 | 申请日期(提交时置当日) | 页面「申请日期」 |
| billproperties | 单据性质 | 是 | 固定 1(正数) | 无 |
| invoicetype | 发票种类 | 是 | electronic_normal→10xdp(数电普票)、electronic_special→08xdp(数电专票) | 页面「发票类型」下拉 |
| taxationstyle | 征税方式 | 是 | 固定 0(普通征税) | 无 |
| buyeremail | 发票推送邮箱 | 是 | 部门邮箱(客商开票信息邮箱多选,`;` 拼接) | 页面「部门邮箱」 |
| buyerproperty / buyertaxno / buyername | 购方企业类型/税号/名称 | 是 | 固定 0(企业)/ 申请单快照 taxpayerNo / receiverName | 页面「受票方单位」选择带出 |
| salername | 销方名称 | 是 | 申请单快照开票单位 issuerName(=结算单收款方) | 页面「开票单位」(结算单带出) |
| salertaxno | 销方纳税人识别号 | 是 | 开票单位 → 客商档案统一社会信用代码(**A303-5 sellerTaxpayerId 同源**) | 客商档案「统一社会信用代码」 |
| totalamount / invoiceamount / totaltax | 价税合计/不含税合计/税额合计 | 是 | 申请单开票金额 / 商品行不含税合计 / 税额合计 | 页面「本次开票金额」、商品行自动计算 |
| customnameid_number | 客户.编码 | 是 | 受票方客商档案客商编码(未维护报错) | 客商档案「客商编号」 |
| awrp_project_number | 项目.项目编码 | 是 | 联调期 999 | — |
| billsource | 数据来源 | 是 | 固定 3 | 无 |
| awrp_src_bt / awrp_src_con_no | 上游来源单据/单号 | 是 | ar_finarbill / 关联结算单金蝶应收单号(多张取第一张,拼接规则联调确认) | 环节一回写的「金蝶单据编号」 |
| systemsource | 系统来源 | 否 | 暂不传(联调校准项) | — |
分录(sim_original_bill_item,发票张内商品行平铺):
| 接口字段 | 含义 | 必填 | 来源 |
| --- | --- | --- | --- |
| rowtype / benchmark | 行性质/基准类型 | 是 | 固定 2(商品行)/ 0(数量基准)(rowtype 联调校准) |
| goodsname / goodssimplename | 货物名称/商品和服务分类 | 是 | 行 goodsName(货物或服务简称)/ goodsCategory(分类名称) |
| taxratecodeid_number | 税收分类编码.编码 | 是 | 行快照 tax_classification_code(开票项目主数据) |
| num / unit | 数量/计量单位 | 是 | 行数量(≤0 兜底 1)/ 单位(空兜底「个」) |
| taxunitprice / taxamount | 含税单价/含税金额 | 是 | 行价税合计÷数量、行价税合计 |
| amount / unitprice / tax | 不含税金额/单价/税额 | 是 | 行不含税金额、不含税单价、税额 |
| taxrate | 税率 | 是 | 行税率 0-100 转小数字符串(13→"0.13") |
| awrp_entry_code | 来源分录标识(候选键) | 是 | 商品行 id(雪花 id 唯一) |
| awrp_src_type | 分录外部单据类型 | 是 | 配置 thirdParty.bfi.invoiceApply.srcType,默认 0KzXs(有默认值,一般无需配置) |
6. 回写落库:`result[0].number` → `kingdeeBillNo` + `kingdeeStatus=synced` + `syncedTime`,记录操作历史「同步金蝶」。
### 4.3 状态机
```
kingdeeStatus:unsynced ──A303-1成功──> synced ──A303-5查到发票──> invoiced(开票完成,终态)
└──推送异常──> failed ──人工重推(A303-4 预检命中→幂等回填)──> synced
定时③只轮询 synced(failed 无真实金蝶单号,A303-5 无查询入口)
```
---
## 5. 环节三③:开票结果定时回写(A303-5)
代码入口:`InvoiceApplicationServiceImpl.syncKingdeeInvoiceResult`,手动端点 `POST /invoice-application/sync-kingdee-invoice-result`。
1. 待回写集合:approved + synced + 金蝶单号非空,按 `kingdeeBillNo`(金蝶开票申请单号=serialNo)去重归并。
2. **销方税号解析(与 A303-1 同源)**:逐单按开票单位查客商档案统一社会信用代码,**按税号分组**后组内 ≤50/批逗号拼接调 A303-5;解析不到(开票单位为空/客商档案未维护信用代码)的单跳过并 warn 计数,不阻断整轮。
3. 返回为票易通原文 `data[]{billNo, invoiceList[]}`:按 billNo 归并回申请单,逐张宽松提取(候选键:发票号 invoiceNo/invoice_no/invoiceNumber;开票日期、价税合计同理),**原文整体 JSON 留档 `raw_json`**;字段名联调校准后收敛为固定映射。
4. 落 `blade_invoice_application_invoice`:按(invoice_application_id, invoice_no)唯一键幂等,已存在跳过;该单有新增发票 → `kingdeeStatus=invoiced`(开票完成,前端详情展示发票列表)。
5. 开票结果**不反写结算单**;结算单开票状态仍由申请单分摊驱动。
6. 汇总文案:待查 X 笔(销方税号无法解析跳过 N 笔),回写发票 Y 张(开票完成 M 笔,失败批次 K 个)。
任务调度:`PaymentKingdeeResultTask`(`thirdParty.bfi.payResult.cron` 默认每 10 分钟,总开关 `thirdParty.bfi.payResult.syncEnabled`,三子流程共用;任一子流程异常仅记日志不中断整轮)。
---
## 6. 防重键与幂等体系汇总
| 单据/对象 | 防重键/幂等依据 | 使用接口 |
| --- | --- | --- |
| 财务应收单 | awrp_src_sys=WLTMS + awrp_src_code=正式结算单号_结算明细单据号拼接 | A302-2 / A302-4(预检+定时勾稽+开票前置校验) |
| 应收单分录 | awrp_entry_code=应收货物费用行 id | A302-2 |
| 开票申请单 | billno=开票申请单号 applicationNo | A303-1 / A303-4(预检)/ A303-5(serialNo) |
| 开票申请分录 | awrp_entry_code=商品行 id | A303-1 |
| 金蝶发票结果 | 唯一键(invoice_application_id, invoice_no) | A303-5 落库 |
断点恢复场景:应收/开票推送成功但本地回滚 → 重推时 A302-4/A303-4 命中幂等回填;failed 应收单 → 定时勾稽自动纠偏;金蝶开票处理中 → ③每轮空查保持 synced,直到开出票。
---
## 7. 数据前提与上线操作
| # | 事项 | 说明 |
| --- | --- | --- |
| 1 | 建表/变更 SQL | `doc/sql/transport/blade_invoice_application_invoice_20260920.sql`(发票结果表);`blade_invoice_application_line_tax_code_20260921.sql`(商品行税收分类编码快照列,可重复执行) |
| 2 | Nacos 配置 | 仅需已有 `thirdParty.bfi.srcSys`(默认 WLTMS)与任务开关/`cron`;`invoiceApply.salerName/salerTaxNo/taxratecode` **已取消**,`invoiceApply.srcType` 有默认值无需配置 |
| 3 | 客商档案 | 我方开票单位(结算单收款方)与受票方客户均须入客商档案:我方须维护**统一社会信用代码**(销方税号),受票方须维护**客商编码**;受票方另需维护客商开票信息(税号、邮箱等) |
| 4 | 主数据 | 开票项目须维护税收分类编码(新增开票内容选择即快照);货物类型档案货物编码;部门编码 |
| 5 | 联调校准项 | A302-2:单据类型/款项性质 1001/CASH/src_type 文案、物料 SP0324+计量 TON+项目 999 临时值(`pushArBill` 调试块);A303-1:rowtype、systemsource、多结算单上游单号拼接;A303-5:原文字段名收敛 |
| 6 | 联调顺序 | 应收结算单同步(①)→ 开票申请同步(②)→ 手动触发 `sync-kingdee-ar-result` / `sync-kingdee-invoice-result` 验证回写 |
---
## 8. 关键代码索引
| 环节 | 代码位置(blade-service/blade-transport) |
| --- | --- |
| 环节一推送 | `service/impl/FormalSettlementServiceImpl.syncKingdee` + `support/KingdeeArBillSyncSupport`(pushArBill / arBillSrcCode / queryArBillExistNos / resolveArEntryGroups),档案取数复用 `support/KingdeeApBillSyncSupport` |
| 环节二推送 | `service/impl/InvoiceApplicationServiceImpl.syncKingdee / buildInvoiceApplyEntry` |
| 环节三②/③ | `FormalSettlementServiceImpl.syncKingdeeArResult` / `InvoiceApplicationServiceImpl.syncKingdeeInvoiceResult`,编排 `task/PaymentKingdeeResultTask` |
| 常量/配置 | `constant/KingdeeBfiConstant`(AR 单据类型映射、1001、CASH、TMS财务应收单、ar_finarbill、10xdp/08xdp、invoiced)、`config/BfiProperties`(thirdParty.bfi.*) |
| 手动端点 | `POST /formal-settlement/sync-kingdee-ar-result`、`POST /invoice-application/sync-kingdee-invoice-result`(网关按服务名路由至 blade-transport) |
| 前端 | 正式结算单页面(同步金蝶按钮)、`views/payment/invoice-application-form.vue`(新增开票申请:选结算单带出销方、开票内容快照税收分类编码) |