docs: 补充金蝶物料映射Nacos配置样例与分录拆分逻辑梳理文档
third-party-api.yaml 新增 thirdParty.bfi.material 物料映射段(中文key方括号写法);新增业务流程文档:取值来源总表、三级拆分规则、大类判定与税率取值、物料计量单位映射、场景算例与已知口径
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
# 金蝶应付/应收/收票单明细分录拆分与物料映射逻辑梳理(含取值口径)
|
||||
|
||||
> 更新日期:2026-10-13
|
||||
> 适用链路:财务应付单(A102-2)、财务应收单(A302-2)、收票单(A303-2)明细分录
|
||||
> 核心代码:`blade-transport` 模块 `org.springblade.transport.support.KingdeeApBillSyncSupport`(共享解析)
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体设计
|
||||
|
||||
应付单、应收单、收票单三个金蝶单据的明细分录**共用同一份解析结果**(`KingdeeApBillSyncSupport.BfiEntryGroup`),拆分规则、物料映射、计量单位、税率分组只在应付支撑组件解析一次,三个消费端(`pushApBill` / `pushArBill` / `pushReceivedInvoice`)直接逐条转 DTO,保证三单明细同源同口径、分录标识同键、金蝶侧逐行勾稽。
|
||||
|
||||
```
|
||||
正式结算单(blade_formal_settlement)
|
||||
├── 结算明细行(blade_formal_settlement_detail,一个运单/一个车牌)
|
||||
│ └── 货物费用行(blade_formal_settlement_detail_fee,一车多货按货物拆)
|
||||
└── 结算合计费用项(blade_formal_settlement_summary_fee,仅取手动添加 manualFlag=1)
|
||||
↓ 解析(KingdeeApBillSyncSupport.resolveEntryGroups)
|
||||
BfiEntryGroup 列表(货物 → 费用大类 → 税率 三级拆分后)
|
||||
↓ 同一份结果
|
||||
应付单 A102-2 / 应收单 A302-2 / 收票单 A303-2 明细分录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 取值来源总表
|
||||
|
||||
| 数据 | 来源表/配置 | 字段 | 用途 |
|
||||
|---|---|---|---|
|
||||
| 结算明细行 | `blade_formal_settlement_detail` | `document_no` 单据号、`vehicle_no` 车牌号、`transport_type` 运输方式、`cargo_name` 货物名称、`transport_quantity` 运输量、`fee_items_json` 费用项JSON、`settlement_amount_tax/no_tax` 结算金额(含税/不含税) | 无货物费用行时的唯一货物行;车号/运输方式/数量取数 |
|
||||
| 货物费用行 | `blade_formal_settlement_detail_fee` | `cargo_name`、`transport_quantity`、`fee_items_json`、`settlement_amount_tax/no_tax`、`line_no` | 一车多货按货物拆行的金额与数量来源 |
|
||||
| 结算合计费用项 | `blade_formal_settlement_summary_fee` | `fee_type` 费用类型、`fee_item` 费用项、`settlement_amount`、`manual_flag` | 手动添加费用(manualFlag=1)每行单独一条分录 |
|
||||
| 合同 | `blade_contract_manage` | `billing_plan_json` 计费方案JSON(规则含 `feeType`/`feeItem`/`billingUnit`/`taxRate`) | 税率分组、计量单位匹配 |
|
||||
| 计量单位档案 | system 服务 `getMeasurementUnits()` | 单位名称 → `unit_code` | 计费单位 → 金蝶计量单位编码 |
|
||||
| 费用项档案 | `blade_fee_item`(system 服务 `getFeeItems()`) | `name` 费用项名、`fee_category` 费用类型编码、`tax_rate` 税率(备用) | 费用大类判定 |
|
||||
| 业务字典 | 字典 `fee_category` | 编码 → 名称(如"装卸"、"物流配送") | 费用类型编码翻译成名称后做大类判定 |
|
||||
| 项目 | `ProjectApply`(`project_id` 关联) | `business_mode` 业务模式、`business_type` 业务类型 | 国内/国际运输判定(含"国际"即国际) |
|
||||
| 物料/计量单位映射 | Nacos `thirdparty.bfi.material.*`(`BfiProperties.Material`) | 见第 6 节 | 物料编码、计量单位编码映射 |
|
||||
|
||||
`fee_items_json` 结构:`{"费用项名": 金额, ...}`(如 `{"运输费": 3120, "装卸费": 800}`),保持行序解析。
|
||||
|
||||
---
|
||||
|
||||
## 3. 明细拆分规则(核心逻辑)
|
||||
|
||||
### 3.1 三级拆分:货物 → 费用大类 → 税率
|
||||
|
||||
对每条结算明细行(一个运单,同一车牌号就是一个车):
|
||||
|
||||
1. **按货物拆**(一车多货):存在货物费用行(`blade_formal_settlement_detail_fee`)时,每个货物费用行单独拆;无货物费用行时结算明细行本身即唯一货物。
|
||||
2. **按费用大类拆**:货物行的 `fee_items_json` 里逐个费用项判定大类(见第 4 节)——装卸大类、运输大类(非装卸全部归运输类)。
|
||||
3. **按税率拆**:同大类内按计费方案税率分组,**税率不同分开多行**;费用项未配置税率时归入该大类"无税率组"(不拆)。
|
||||
|
||||
### 3.2 各字段取值
|
||||
|
||||
| 分录字段 | 运输大类行 | 装卸大类行 | 结算合计手动费用行 |
|
||||
|---|---|---|---|
|
||||
| 物料编码 | 按项目业务模式/运输方式映射(第 6 节) | `material.loading` 装卸物料 | 非运费类→`material.loading`;运费类→同运输行映射 |
|
||||
| 计量单位 | 费用项/费用类型匹配合同计费方案计费单位 → 匹配计量单位档案编码 | 同左 | 同左 |
|
||||
| 数量 | **运单运输量**(`transport_quantity`,未维护兜底 1),按税率拆多行时**每行都取运输量** | 1 | 1 |
|
||||
| 含税金额 | 组内费用项金额合计 | 组内费用项金额合计 | 合计行 `settlement_amount` |
|
||||
| 单价 | 金额 ÷ 数量(2 位小数反算) | 金额 ÷ 1 = 金额 | 金额 ÷ 1 = 金额 |
|
||||
| 不含税金额 | **仅该货物行第一条运输类分录携带**整行 `settlement_amount_no_tax`(拆分行无分类不含税金额,保证不含税合计勾稽),其余行不传 | 不传 | 不传 |
|
||||
| 运输货物 awrp_transport | 货物名称 | 货物名称 | 不传 |
|
||||
| 车号 awrp_lpn | 结算明细车号(整单一个车) | 结算明细车号 | 不传 |
|
||||
| 分录标识 awrp_entry_code | `单据号-货物序号-运输费[-税率]` | `单据号-货物序号-装卸[-税率]` | `结算单号-费用类型[-费用项]`(不变) |
|
||||
|
||||
- 货物序号:货物费用行按行序 1、2、3…;无费用行时为 1。单据号为空兜底明细行 id。
|
||||
- 税率后缀:`-9`、`-6.5` 格式(去尾零);税率未配置不拼。
|
||||
- entryCode 是金蝶候选键,收票单与应付单分录同键勾稽。
|
||||
|
||||
### 3.3 残差勾稽
|
||||
|
||||
货物行结算金额(含税)与费用项金额合计的**差额(残差)并入运输类无税率组**,保证:同一货物行拆出的全部分录含税金额合计 = 该行结算金额。典型场景:历史数据 `fee_items_json` 未含"运输费"项(运费金额在 `freight_amount` 字段)时,自动补一条运输类无税率分录承接差额。
|
||||
|
||||
### 3.4 三个标准场景
|
||||
|
||||
**场景 1:同费用大类、多税率(运输费存在 2 种税率)**
|
||||
运输费 2000 元@9% + 附加运输补贴 1120 元@6%,运单 10 吨(水路-国内)→ 拆 2 行,每行数量均取运输量 10:
|
||||
- 行 1:物料=水路运输编码,数量 10,金额 2000,单价 200
|
||||
- 行 2:物料=水路运输编码,数量 10,金额 1120,单价 112
|
||||
|
||||
**场景 2:包含装卸大类**
|
||||
运输费 3120 元@9% + 装卸费 800 元@6%,10 吨 → 拆 2 行:
|
||||
- 运输行:物料=水路运输编码,数量 10,单价 312,金额 3120
|
||||
- 装卸行:物料=装卸编码(XDWL11002004),数量 1,单价 800,金额 800
|
||||
|
||||
**场景 3:结算单手动添加费用**
|
||||
手动新增仓储服务费 500 元(非运费类)→ 单独一行:物料=装卸编码,数量 1,单价 500,金额 500。
|
||||
|
||||
---
|
||||
|
||||
## 4. 费用大类判定(档案 + 字典优先,关键字兜底)
|
||||
|
||||
判定链路(`isLoadingCategory` / `isFreightCategory`):
|
||||
|
||||
```
|
||||
费用项名 → blade_fee_item.fee_category(费用类型编码)
|
||||
→ 字典 fee_category 翻译成费用类型名称
|
||||
→ 名称含"装卸" → 装卸大类;否则运输大类
|
||||
费用项不在档案 → 回退名称关键字判定(含"装卸"=装卸类;含"运费/运输费"=运费类)
|
||||
```
|
||||
|
||||
- 判定对象从"费用项名"升级为"档案翻译后的费用类型名称":费用项叫"吊装作业"但档案归到"装卸"类型,也会正确映射装卸物料。
|
||||
- 结算合计手动费用行的运费判定:档案分类名称 / `fee_type` / `fee_item` 任一含"运费"或"运输费" → 运费类映射运输物料;否则装卸物料(场景 3)。
|
||||
- 档案读取失败直接拦截推送(与计量单位档案口径一致);分类映射缓存在 `KingdeeSyncContext.feeCategoryByName`,批量同步只加载一次。
|
||||
|
||||
---
|
||||
|
||||
## 5. 税率取值
|
||||
|
||||
- **来源**:合同计费方案 JSON(`blade_contract_manage.billing_plan_json`),每条规则的 `taxRate` 字段(百分数文本,"9" = 9%),按规则的 `feeItem`/`feeType` 建 Map(先到先得)。合同保存时已强制校验税率 0~100、最多 2 位小数。
|
||||
- **用途**:仅用于**拆行分组**和分录标识后缀,**不传金蝶**——分录 DTO 的 `taxrateid_number` 为待金蝶确认字段,暂不送。
|
||||
- **匹配方式**:按费用项名精确匹配;未配置税率的费用项归入无税率组。
|
||||
- 结算合计手动费用行不取税率(每行单独一条,不分组)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 物料编码映射(Nacos 可配)
|
||||
|
||||
配置前缀 `thirdparty.bfi.material.*`(`BfiProperties.Material`,代码带默认值,Nacos 覆盖即改,无需改代码):
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|---|---|---|
|
||||
| `domestic."["公路运输"]"` | XDWL11010001 | 国内道路货物运输服务—公路运输 |
|
||||
| `domestic."["铁路运输"]"` | XDWL11010002 | 国内道路货物运输服务—铁路运输 |
|
||||
| `domestic."["水路运输"]"` | XDWL11010003 | 国内道路货物运输服务—水路运输 |
|
||||
| `domestic."["航空运输"]"` | Item-00001082 | 国内道路货物运输服务—航空运输 |
|
||||
| `international` | Item-00001081 | 国际货运代理服务费(业务模式/业务类型含"国际") |
|
||||
| `loading` | XDWL11002004 | 装卸(装卸大类 + 手动添加非运费杂费) |
|
||||
|
||||
映射优先级(运输类):装卸大类 → `loading`;国际业务 → `international`;国内 → `domestic[运输方式]`,**运输方式未配置映射直接拦截报错(无兜底)**。物料编码缺失直接拦截报错。
|
||||
|
||||
**计量单位取值链路**:费用项/费用类型匹配合同计费方案 `billingUnit` → 按计量单位档案(单位名称匹配)取 `unit_code`;任一环节缺失拦截报错。
|
||||
|
||||
**⚠️ Nacos 配置坑**:`domestic` 的中文 key **必须用方括号写法** `"[公路运输]": xxx`,裸写中文 key 会导致 Spring Boot 绑定失败、服务无法启动(`No converter found from String to Map`)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 消费链路与同源关系
|
||||
|
||||
| 单据 | 组件/方法 | 分录取数 |
|
||||
|---|---|---|
|
||||
| 财务应付单 A102-2 | `KingdeeApBillSyncSupport.pushApBill` | `entryGroups` 逐条转 `BfiApBillSaveEntryDTO` |
|
||||
| 财务应收单 A302-2 | `KingdeeArBillSyncSupport.pushArBill` | 同一解析(`resolveArEntryGroups` 复用应付解析),逐条转 `BfiArBillSaveEntryDTO` |
|
||||
| 收票单 A303-2 | `KingdeeReceivedInvoiceSyncSupport.pushReceivedInvoice` | 分摊结算单的同一解析结果,逐条转 `BfiReceivedInvoiceSaveEntryDTO`(无计量单位字段) |
|
||||
| 付款申请 A103-1/A103-3 | 付款链路 | 同源解析,分录携带 `formalSettlementId` 按结算单勾稽各自应付单 |
|
||||
|
||||
幂等与防重:防重键 `awrp_src_code`(正式结算单号_结算明细单据号拼接)不变;分录标识 `awrp_entry_code` 因拆行规则变化,仅适用于新推送数据。
|
||||
|
||||
---
|
||||
|
||||
## 8. 代码位置索引
|
||||
|
||||
| 逻辑 | 位置 |
|
||||
|---|---|
|
||||
| 分录解析入口(多结算单/单结算单) | `KingdeeApBillSyncSupport.resolveEntryGroups` |
|
||||
| 货物行拆分(货物→大类→税率) | `appendDetailEntries` / `appendCargoLines` |
|
||||
| 费用项分组纯函数 | `splitFeeItems`(含残差勾稽,单测 `KingdeeEntrySplitTest`) |
|
||||
| 费用大类判定 | `isLoadingCategory` / `isFreightCategory` / `feeCategoryByName` |
|
||||
| 物料映射 | `resolveMaterialNumber`(读 `bfiProperties.getMaterial()`) |
|
||||
| 计量单位解析 | `resolveEntryMeasureunit`(YAML 优先)→ `resolveMeasureunitNumber`(计费方案→档案) |
|
||||
| 税率/计费单位解析 | `billingRuleUnits`(解析 `billing_plan_json` 一次取两样) |
|
||||
| 结算合计手动费用行 | `appendSummaryEntry` |
|
||||
| 配置类 | `org.springblade.transport.config.BfiProperties.Material` |
|
||||
| Nacos 配置样例 | `doc/nacos/third-party-api.yaml`(thirdParty.bfi.material 段) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 已知口径与限制
|
||||
|
||||
1. **运输大类内部多税率**:靠费用项级税率分组实现;同大类多税率时每个税率组一行、每行数量都取运输量。未配置税率的费用项合入无税率组不拆。
|
||||
2. **非装卸的杂项费用**(运单自带,如补贴、其他费用):归运输大类、随运输行映射运输物料;金额残差兜底并入运输类无税率组。
|
||||
3. **不含税金额**:拆分后仅运输类首行携带整行不含税金额(合计勾稽正确,单行口径不精确);装卸行/手动费用行不传(`e_amount` 非必传)。
|
||||
4. **税率不传金蝶**:`taxrateid_number` 待金蝶确认编码规则(如 V13)后再补。
|
||||
5. **规格型号**:金蝶保存 DTO 无该字段;车号/货物已有 `awrp_lpn`/`awrp_transport` 传参口径。
|
||||
6. **装卸行计量单位**:走合同计费方案匹配(装卸费用项的计费方案规则计费单位 → 计量单位档案),装卸费用项未配计费方案规则时拦截报错,需在合同计费方案中维护。
|
||||
@@ -49,6 +49,21 @@ thirdParty:
|
||||
language: ${BFI_LANGUAGE:zh_CN}
|
||||
# 数据来源系统编码(推送金蝶公共参数:客商、付款申请等统一使用),默认 WLTMS(运输TMS)
|
||||
srcSys: ${BFI_SRC_SYS:WLTMS}
|
||||
# 应付/应收/收票单分录物料映射(A102-2/A302-2/A303-2 共用,未配置使用代码默认值):
|
||||
# 运输类按项目业务模式映射——国内运输按运单运输方式映射、国际运输映射国际货代服务;
|
||||
# 装卸大类及手动添加的非运费杂费映射装卸物料
|
||||
# 注意:Map 的中文 key 必须用 ["中文"] 方括号写法,否则 Spring Boot 绑定失败、服务启动报错
|
||||
material:
|
||||
# 国内运输:运单运输方式 -> 金蝶物料编码(公路运输/铁路运输/水路运输/航空运输四种)
|
||||
domestic:
|
||||
"[公路运输]": ${BFI_MATERIAL_DOMESTIC_ROAD:XDWL11010001}
|
||||
"[铁路运输]": ${BFI_MATERIAL_DOMESTIC_RAIL:XDWL11010002}
|
||||
"[水路运输]": ${BFI_MATERIAL_DOMESTIC_WATER:XDWL11010003}
|
||||
"[航空运输]": ${BFI_MATERIAL_DOMESTIC_AIR:Item-00001082}
|
||||
# 国际运输业务模式物料编码(国际货运代理服务)
|
||||
international: ${BFI_MATERIAL_INTERNATIONAL:Item-00001081}
|
||||
# 装卸大类、手动添加非运费杂费物料编码(装卸)
|
||||
loading: ${BFI_MATERIAL_LOADING:XDWL11002004}
|
||||
# 付款申请推送配置(A103-3 付款申请保存)
|
||||
payApply:
|
||||
# 单据类型编码:ap_payapply_BT_S 采购付款申请、ap_payapply_oth_BT_S 其他付款申请
|
||||
|
||||
Reference in New Issue
Block a user