From 52da9aab24f105583f3a2c20f9e960476bef20be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Tue, 13 Oct 2026 11:34:55 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=E9=87=91=E8=9D=B6?= =?UTF-8?q?=E7=89=A9=E6=96=99=E6=98=A0=E5=B0=84Nacos=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=A0=B7=E4=BE=8B=E4=B8=8E=E5=88=86=E5=BD=95=E6=8B=86=E5=88=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=A2=B3=E7=90=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit third-party-api.yaml 新增 thirdParty.bfi.material 物料映射段(中文key方括号写法);新增业务流程文档:取值来源总表、三级拆分规则、大类判定与税率取值、物料计量单位映射、场景算例与已知口径 --- ...¶应付应收收票单明细分录拆分与物料映射逻辑梳理.md | 177 ++++++++++++++++++ doc/nacos/third-party-api.yaml | 15 ++ 2 files changed, 192 insertions(+) create mode 100644 doc/docs/业务流程/2026-10-13-金蝶应付应收收票单明细分录拆分与物料映射逻辑梳理.md diff --git a/doc/docs/业务流程/2026-10-13-金蝶应付应收收票单明细分录拆分与物料映射逻辑梳理.md b/doc/docs/业务流程/2026-10-13-金蝶应付应收收票单明细分录拆分与物料映射逻辑梳理.md new file mode 100644 index 00000000..d6ae7d70 --- /dev/null +++ b/doc/docs/业务流程/2026-10-13-金蝶应付应收收票单明细分录拆分与物料映射逻辑梳理.md @@ -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. **装卸行计量单位**:走合同计费方案匹配(装卸费用项的计费方案规则计费单位 → 计量单位档案),装卸费用项未配计费方案规则时拦截报错,需在合同计费方案中维护。 diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml index e4317c14..a8e5320b 100644 --- a/doc/nacos/third-party-api.yaml +++ b/doc/nacos/third-party-api.yaml @@ -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 其他付款申请