From 01cc9f7f37c9a71a13f93be7ab9eeceee95cdd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B3=89=E4=BD=B3?= <1107186916@qq.com> Date: Wed, 4 Nov 2026 16:01:06 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E8=B4=A2=E5=8A=A1?= =?UTF-8?q?=E5=BA=94=E4=BB=98=E5=8D=95preentry=E9=A2=84=E4=BB=98=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E6=8E=A8=E9=80=81=E9=87=91=E8=9D=B6=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...金蝶设计(A102-2预付抵扣-y_billid落库-拦截规则与分摊).md | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 doc/docs/业务流程/2026-11-04-财务应付单preentry预付信息推送金蝶设计(A102-2预付抵扣-y_billid落库-拦截规则与分摊).md diff --git a/doc/docs/业务流程/2026-11-04-财务应付单preentry预付信息推送金蝶设计(A102-2预付抵扣-y_billid落库-拦截规则与分摊).md b/doc/docs/业务流程/2026-11-04-财务应付单preentry预付信息推送金蝶设计(A102-2预付抵扣-y_billid落库-拦截规则与分摊).md new file mode 100644 index 00000000..8783f30b --- /dev/null +++ b/doc/docs/业务流程/2026-11-04-财务应付单preentry预付信息推送金蝶设计(A102-2预付抵扣-y_billid落库-拦截规则与分摊).md @@ -0,0 +1,195 @@ +# 财务应付单预付信息 preentry 推送金蝶设计 + +> 范围:正式结算单「同步金蝶」推送财务应付单(A102-2)时,携带预付信息 `preentry`(`y_billtype / y_billid / y_settleamt`)的取数口径、每笔计算规则、拦截规则与落地改动。 +> 背景:`KingdeeApBillSyncSupport.pushApBill` 现有 TODO"存在预付款抵扣时是否需传预付信息 preentry,本期不传、后续迭代",本文为该迭代的实现设计。 +> 关联文档:`2026-10-18-预付完整流程梳理(进度预付-项目预付-正式结算单-付款-金蝶回写).md`、`2026-09-16-付款管理正式付款同步金蝶(财务应付单+付款申请下推)技术文档.md`、`2026-09-17-付款管理金蝶付款结果定时回写(A103-2付款申请单查询+A103-4付款处理查询)技术文档.md`。 +> 设计日期:2026-11-04。 + +--- + +## 一、字段语义与结论 + +`preentry` 是 A102-2 财务应付单的预付信息分录,**支持多条**,用于登记本张应付单抵扣了哪些已付款的预付单。每条 `preentry` 对应**一张金蝶付款处理单(`cas_paybill`)**: + +```json +"preentry": [ + { "y_billtype": "cas_paybill", "y_billid": "付款处理单1 id", "y_settleamt": 10000.00 }, + { "y_billtype": "cas_paybill", "y_billid": "付款处理单2 id", "y_settleamt": 5000.00 } +] +``` + +### 字段取值 + +| 字段 | 必填 | 取值 | 说明 | +| --- | --- | --- | --- | +| `y_billtype` | 是 | 固定 `cas_paybill`(`BfiConstant.PREPAY_BILL_TYPE_PAY_BILL`) | TMS 预付均经 A103-3 付款申请 → 金蝶付款处理,不产生期初预付单(`ap_paidbill` 不使用) | +| `y_billid` | 是 | 金蝶**付款处理单** `id`(A103-4 `BfiPayBillVO.id`) | **不能**用 `blade_payment_application.kingdee_bill_id`(那是 A103-2 返回的付款申请单 id,类型不对) | +| `y_settleamt` | 否 | 本次应付单实际抵扣该预付的金额,2 位小数 | 进度预付取 `PreSettlementAdvance.paid_amount`,项目预付取核销行 `write_off_amount` | + +### 两个易混淆字段(不在本次改造范围) + +| 字段 | 取值 | 现状 | +| --- | --- | --- | +| `awrp_src_type`(A102-2 应付单头) | `TMS财务应付单` | `KingdeeBfiConstant.BFI_AP_BILL_SRC_TYPE`,保持不变,**不要**改成 `0KzXs` | +| `awrp_src_type`(A103-1/A103-3 付款申请头/分录) | 固定 `0KzXs` | Nacos `thirdParty.bfi.payApply.srcType` 配置,已是正确值,无需改 | + +--- + +## 二、现状核查 + +### 1. preentry 目前未送 + +`KingdeeApBillSyncSupport.pushApBill`(A102-2 保存)明细分录之后保留 TODO,`setPreentry` 全项目无调用;DTO `BfiApBillSavePreEntryDTO`(`y_billtype / y_billid / y_settleamt`)已就绪,`@JsonInclude(NON_NULL)`——**为空时保持 `null`,不要设置空数组**(避免报文送 `preentry: []`)。 + +### 2. y_billid 需要的内码未落库 + +| 数据 | 实际值 | 可否作 `y_billid` | +| --- | --- | --- | +| `blade_payment_application.kingdee_bill_id` | A103-2 返回的付款申请单 id(`ap_payapply`) | ❌ 类型不对 | +| `blade_payment_application_record.kingdee_bill_no` | A103-4 返回的付款处理单号(`cas_paybill.billno`) | ❌ 只有单号 | +| `blade_payment_application_record.voucher_json` | A103-4 付款处理单整单 JSON,含 `id` | ⚠️ 仅 JSON 内有,无独立字段 | +| `blade_pre_settlement_advance.kingdee_advance_no` | 付款申请单号 | ❌ | +| `blade_settlement_project_advance_write_off.kingdee_bill_no` | 付款申请单号 | ❌ | + +**结论:必须先补存 `cas_paybill.id`**,否则 preentry 无值可送。 + +--- + +## 三、每笔 preentry 的计算规则 + +### 粒度与合并 + +- 一条 `preentry` = 一张金蝶付款处理单(按 `y_billid` 唯一)。 +- 同一 `y_billid` 被多行业务数据引用(如一张付款申请跨多个预结算分摊后落到同一张付款处理单)时,**按 `y_billid` 汇总金额只出一条**,避免向金蝶送重复键。 + +### 项目预付 + +数据源 `blade_settlement_project_advance_write_off`(本身就是"本结算单核销了某张项目预付多少"): + +``` +遍历核销行(settlement_type='formal' AND settlement_id=本单id) + → payment_application_id → 付款申请 + → 付款处理记录(blade_payment_application_record,kingdee_bill_id 非空) + → 应抵扣金额 = write_off_amount,按付款处理单实付分摊 + → 每条记录生成一条 preentry +``` + +### 进度预付 + +数据源 `blade_formal_settlement_source` + `blade_pre_settlement_advance`(`syncOnePreSettlementAdvance` 已把付款申请按预结算关联行拆分,`paid_amount` 即本预结算分摊额): + +``` +遍历正式结算来源(formal_settlement_id=本单id) + → pre_settlement_id + → 预付记录(pre_settlement_id= ? AND paid_amount>0 AND is_deleted=0) + → advance_no(=付款申请单号)→ 付款申请 + → 付款处理记录(kingdee_bill_id 非空) + → 应抵扣金额 = advance.paid_amount,按付款处理单实付分摊 + → 每条记录生成一条 preentry +``` + +### 多付款处理单分摊(部分付款兜底) + +一张付款申请可能对应多张付款处理单(A103-4 多条回写): + +1. 按各记录 `paid_amount` 占比分摊应抵扣金额; +2. 金额 `setScale(2, HALF_UP)`; +3. 最后一条取"总额 − 已分摊"补差,保证合计不差分; +4. 单条 `y_settleamt` 不超过该记录 `paid_amount`; +5. 应抵扣金额超过付款处理单实付合计时封顶为实付合计(正常业务不会出现,防御性处理)。 + +--- + +## 四、拦截规则(已确认:拦截应付单推送) + +拦截点在 **A102-4 幂等预检(确认金蝶无本单应付单)之后、`saveApBill` 之前**;预检命中"已存在"直接幂等返回,不做校验(已推单据无法补 preentry)。 + +任一笔应抵扣预付不满足以下条件即抛 `ServiceException`,整张应付单不推: + +| # | 校验 | 不满足提示 | +| --- | --- | --- | +| 1 | 底层付款申请存在 | 预付单[XXX]不存在付款申请 | +| 2 | 付款申请已付款(`kingdee_status='paid'` / 已有付款处理记录) | 预付单[XXX]尚未付款完成,预付须金蝶付款完成后方可抵扣 | +| 3 | `kingdee_bill_id` 非空(至少一条付款处理记录带回内码) | 预付单[XXX]金蝶付款处理单内码未回写,请先在付款管理执行【同步付款结果】后再推送应付单 | +| 4 | 付款处理单实付合计 ≥ 本次抵扣金额 | 预付单[XXX]付款处理单实付金额不足 | + +提示信息**汇总所有不满足的预付单**,最多列 3 笔 + "等 N 笔",控制在 `kingdee_fail_reason` 的 500 字符内。 + +失败后的自动流转(复用现有机制,无需新增代码): + +``` +doSyncKingdee 抛 ServiceException + → syncKingdee 捕获 → markKingdeeSyncFailed(failed + 失败原因留档) + → retryKingdeeFailed 定时重推 + → A103-4 回写 kingdee_bill_id 后,下一轮重推自动成功 +``` + +--- + +## 五、落地改动清单 + +### 1. 数据表与实体 + +- `blade_payment_application_record` 增加 `kingdee_bill_id varchar(64) DEFAULT NULL COMMENT '金蝶付款处理单id(cas_paybill.id,A103-4回写)'`; +- `PaymentApplicationRecord` 增加 `kingdeeBillId` 字段; +- SQL 放 `doc/sql/transport/lqj/temp/`,**可重复执行**,含存量回填: + +```sql +ALTER TABLE `blade_payment_application_record` + ADD COLUMN `kingdee_bill_id` varchar(64) DEFAULT NULL COMMENT '金蝶付款处理单id(cas_paybill.id,A103-4回写)' AFTER `kingdee_bill_no`; + +UPDATE `blade_payment_application_record` +SET `kingdee_bill_id` = JSON_UNQUOTE(JSON_EXTRACT(`voucher_json`, '$.id')) +WHERE `kingdee_bill_id` IS NULL AND `voucher_json` IS NOT NULL; +``` + +### 2. A103-4 回写 + +`PaymentApplicationServiceImpl.applyPayBillRecord` 补一行: + +```java +record.setKingdeeBillId(payBill.getId() == null ? null : String.valueOf(payBill.getId())); +``` + +### 3. preentry 解析(KingdeeApBillSyncSupport) + +- `KingdeeApBillSyncSupport` 追加注入 5 个 Mapper:`FormalSettlementSourceMapper`、`PreSettlementAdvanceMapper`、`SettlementProjectAdvanceWriteOffMapper`、`PaymentApplicationMapper`、`PaymentApplicationRecordMapper`; +- 新增私有方法 `resolvePreEntries(FormalSettlement settlement)`:按第三章两条链路取数 → 逐笔校验(第四章拦截规则)→ 分摊 → 按 `y_billid` 合并,返回 `List`; +- `pushApBill` 中 A102-4 预检通过后、保存前调用: + +```java +List preEntries = this.resolvePreEntries(settlement); +if (!preEntries.isEmpty()) { + param.setPreentry(preEntries); +} +``` + +同时删除原 TODO 注释。 + +### 4. 日志 + +每笔输出:来源(进度预付/项目预付)、`y_billid`、`y_settleamt`;跳过/封顶/拦截输出原因。日志禁用中文标点。 + +--- + +## 六、边界与风险 + +| # | 边界 | 处理 | +| --- | --- | --- | +| 1 | 历史已推送的应付单 | A102-4 预检命中幂等返回,**不会补 preentry**(现有设计,不处理) | +| 2 | 预付未付款(synced/paying)但有抵扣金额 | 按拦截规则 #2 阻断,等付款完成后重推自动成功 | +| 3 | 进度预付部分付款 | 按已付金额(`paid_amount`)送,未付部分不抵扣;如业务后续要求"全额付清才可推应付单",在拦截规则追加 `paid_amount >= applied_amount` | +| 4 | 存量付款记录无 `kingdee_bill_id` 且 `voucher_json` 缺失 | 拦截规则 #3 阻断,走付款管理【同步付款结果】重新回写 | +| 5 | preentry 合计超应付单金额 | 正常业务不会出现(核销/预付均封顶于结算金额);防御性不额外校验,依赖金蝶侧报错 | +| 6 | `ap_paidbill`(期初预付单) | TMS 不产生,不在本期范围;如有手工核销需求由金蝶侧人工处理 | + +--- + +## 七、验证要点 + +1. 无预付抵扣的应付单:报文无 `preentry` 字段(非空数组)。 +2. 单笔项目预付核销 + 已付款:一条 preentry,`y_settleamt` = 核销额。 +3. 进度预付跨多个预结算分摊:同 `y_billid` 合并为一条,金额为各行之和。 +4. 预付未付款时推应付单:拦截、落 `failed` 与原因;付款完成后定时重推成功且带 preentry。 +5. 部分付款(多条付款处理记录):分摊合计 = 应抵扣金额,末条补差无分差。 +6. 存量数据回填:SQL 重复执行幂等,回填后无需重新同步付款结果。