diff --git a/doc/docs/业务流程/2026-10-18-正式结算单预付整合设计(进度预付+项目预付核销·金额口径与记录流转).md b/doc/docs/业务流程/2026-10-18-正式结算单预付整合设计(进度预付+项目预付核销·金额口径与记录流转).md new file mode 100644 index 00000000..6c3805c7 --- /dev/null +++ b/doc/docs/业务流程/2026-10-18-正式结算单预付整合设计(进度预付+项目预付核销·金额口径与记录流转).md @@ -0,0 +1,284 @@ +# 正式结算单预付整合设计(进度预付 + 项目预付核销) + +> 关联文档:同目录《2026-10-18-预付完整流程梳理(进度预付-项目预付-正式结算单-付款-金蝶回写).md》(改造前现状梳理) +> 设计日期:2026-10-18。范围:正式结算单(FormalSettlement)对两类预付的金额整合与付款记录关联显示。 +> **落地后全链路逻辑见同目录《2026-10-18-预付与正式结算单全链路逻辑梳理(进度预付-项目预付核销·前端到后端各环节).md》。** +> **v2 修订**:纳入评审意见——核销计入口径改为"仅已付款预付可核销"(待业务确认)、新增核销行迁移设计(预结算转正式 / 调整重推)、作废联动删行补全(含结算单作废/金蝶删单回调/预结算作废)、调整单校验与刷新、列表页 N+1 规避、对账与调整完成判定影响面、细节修正。见文末修订记录。 + +## 一、背景与目标 + +两条预付线的"单据流"已经打通(进度预付经预结算单继承、项目预付经核销组件落库),但**金额与记录**没有整合进正式结算单: + +| # | 差距 | 现状锚点 | +|---|---|---| +| 1 | 结算合计区域看不到预付抵扣:原金额、已付金额(含预付/核销)、剩余未付金额 | `formal-settlement-editor.vue` 结算合计区只渲染费用明细合计(L99-180) | +| 2 | 项目预付核销金额不计入结算单 `appliedPaymentAmount / paidAmount / remainingPayableAmount / paymentStatus` | `calculatePaymentSummary` payable 分支只加来源预结算的预付快照(`FormalSettlementServiceImpl` L1881-1890) | +| 3 | 付款记录只显示尾款(`settlement_payment`)申请,进度预付/项目预付的已付款记录不关联显示 | `detail()` 查询付款申请固定 `paymentType=结算`(L339) | +| 4 | 核销行没有"流转"设计:预结算转正式、调整重推时核销行丢失或额度双占 | `cloneFormalSettlement` L641-708 不克隆核销行;编辑器新建固定重置 `[]`(L1410) | +| 5 | 各类作废不清理核销行,详情残留、批量刷新会把作废单核销计入已付 | `FormalSettlementServiceImpl.voidBill` L600-609、`voidByKingdee` L613-637、`PreSettlementServiceImpl.voidBill` L443 均无删行 | + +**零表结构变更**:所需字段均已存在(`FormalSettlementSource` 预付快照、`SettlementProjectAdvanceWriteOff` 核销行、`PreSettlementAdvance` 预付台账),本设计只做计算口径、记录流转与展示整合。 + +## 二、已确认决策 + +| 决策 | 结论 | 状态 | +|---|---|---| +| D1 结算合计"扣除"的呈现 | `settlementAmount` **保持全额**(费用合计,开票匹配上限、金蝶 A102-2 应付单金额、结算调整单基线都不变)。结算合计卡片下增加汇总栏:**结算合计(原金额)/ 已付金额(含预付、核销)/ 剩余未付金额**,编辑态与只读态都显示。 | ✅ 已确认 | +| D2 核销计入口径(修订) | **仅已付款的项目预付可核销**:`listOptions` 选单过滤与 `replaceWriteOffs` 保存校验都增加 `kingdeeStatus='paid'` 条件(当前只要求 `approved`,L157)。核销时点钱已付出,核销金额同时计入 `appliedPaymentAmount` 与 `paidAmount` 属实付口径,不存在"审批未付却推成 paid"的失真,与付款记录行展示的底层实付一致。 | ✅ 已确认(方案②) | +| D3 付款记录关联显示 | 后端 detail 新增统一预付付款记录列表 `advancePayments`;前端在只读"付款信息"表格中**合并成一张表**,以"付款类型"列区分:进度预付 / 项目预付核销 / 结算尾款。 | ✅ 已确认 | +| D4 范围 | 只做正式结算单(FORMAL)侧汇总;金蝶预付抵扣字段(`y_billtype/y_billid/y_settleamt`)维持本期不传;应收(receivable)侧不动。**但 PRE→FORMAL 的核销行搬运在范围内**(见 D5)。 | ✅ 已确认 | +| D5 核销行流转原则 | **同一笔预付的核销在链路上只存在一份**:预结算转正式时来源 PRE 核销行转移为本单 FORMAL 行;调整重推时原单核销行迁移到新单。转移 = 删源行 + 落新行,同一事务。 | ✅ 已确认 | +| D6 作废联动删行 | 付款申请作废删其核销行(既有"作废联动"模式);正式结算单作废(`voidBill`/`voidByKingdee`)与预结算单作废(`voidBill`)补 `deleteBySettlement`,消除详情残留与汇总污染。 | ✅ 已确认 | +| D7 结算调整单兜底 | 调整落库(`SettlementAdjustmentServiceImpl` L384-388)后:①校验**核销合计 ≤ 新结算金额**,超出则拒绝并提示先调减核销;②调用 `refreshPaymentSummary` 重算汇总(含核销)。 | ✅ 已确认 | +| D8 影响面确认点 | ① 对账单 `copyHeader` 复制 `paidAmount`(`TransportReconciliationServiceImpl` L1325)→ 对账单已付口径将含核销,**按可接受设计**;② `isSettlementCompleted`(`SettlementAdjustmentServiceImpl` L468-471)判定 → 全额核销的结算单 `paymentStatus=paid`,将**不能再发起结算调整**,按"全额抵扣即结清"设计。历史数据问题明确不考虑。 | ✅ 已确认 | + +## 三、金额口径 + +前提(D2 修订):核销行的底层付款申请必为 `kingdeeStatus='paid'`。 + +``` +结算金额 settlementAmount = Σ 结算合计费用项 settlementAmount(不变) +已付金额 paidAmount = 尾款已付(直挂+关系表, approved) + + Σ 来源预结算 advancePaidAmount(进度预付,已有) + + Σ 本单核销行 writeOffAmount(项目预付,本次新增;行必对应已付款预付) +已申请付款 appliedPaymentAmount = 尾款申请中(直挂+关系表, reviewing) + + Σ 来源预结算 advanceAppliedAmount(已有) + + Σ 本单核销行 writeOffAmount(本次新增) +剩余可付 remainingPayableAmount = max(settlementAmount − paidAmount, 0)(已有公式,输入变了) +付款状态 paymentStatus = unpaid / partial / paid(已有公式,输入变了) +``` + +> 与进度预付口径的准确表述:进度预付在结算单上是"申请(advanceAppliedAmount)/ 实付(advancePaidAmount)"两条快照分开跟踪;项目预付在核销时点已完成实付(D2),故核销额计入实付。付款记录行的"已付款金额"(底层实付)与汇总栏"已付金额"必然一致,不存在同屏矛盾。 + +## 四、后端改动(tms-api / blade-transport) + +### 4.1 汇总计入核销金额 + +**`FormalSettlementServiceImpl.calculatePaymentSummary(settlement, data)`**(L1864-1901): + +`payable` 分支(L1881-1890)在累加来源预付快照之后追加: + +```java +// 项目预付核销计入已付/已申请(D2:核销行底层预付必已付款,核销额即实付抵扣) +BigDecimal writeOffAmount = data.writeOffAmounts() != null + ? data.writeOffAmounts().getOrDefault(settlement.getId(), BigDecimal.ZERO) + : this.projectAdvanceWriteOffService.sumBySettlement( + TransportBusiConstant.SETTLEMENT_BILL_FORMAL, settlement.getId()); +appliedAmount = appliedAmount.add(this.money(writeOffAmount)); +paidAmount = paidAmount.add(this.money(writeOffAmount)); +``` + +**`PaymentSummaryData` record**(L1926-1929)新增组件: + +```java +@param writeOffAmounts 结算单id → 核销金额合计(仅应付结算单;null 表示由纯计算按需查询) +..., BigDecimal receiptClaimPaid, Map writeOffAmounts +``` + +同步修改两处构造调用: +- 批量版 `refreshPaymentSummaries`(L1544-1627):`hasPayable` 时按 `settledIds` **一次**查询核销行分组求和(4.5 新增方法),按结算单分发,避免 N+1; +- 单笔版包装方法(L1839-1858):传 `null` 走按需查询。 + +> 说明:`saveDraft` 保存核销后由 `refreshSettlementAmount`(内部 L1524 刷新)+ 新增的一次 `refreshPaymentSummary` **连续两次完整刷新是有意为之**——核销校验依赖 `refreshSettlementAmount` 重算后的最终结算金额,故 `replaceWriteOffs` 只能排在其后,二次刷新不能合并省略。后续优化前先读此注释。 + +### 4.2 保存核销后刷新汇总 + +**`FormalSettlementServiceImpl.saveDraft`**(L546-553):`replaceWriteOffs` 保持在 `refreshSettlementAmount`(L546)之后(校验用最终结算金额),其后追加: + +```java +projectAdvanceWriteOffService.replaceWriteOffs(...); // L548-553 原位置不动 +this.refreshPaymentSummary(settlement.getId()); // 新增:核销行计入汇总 +``` + +### 4.3 核销行流转(D5) + +**通用删行方法**(`ISettlementProjectAdvanceWriteOffService` 新增,事务内逻辑删除): + +```java +/** 删除指定结算单上的核销行(配合转移/作废场景),返回被删行数 */ +int deleteBySettlementIds(String settlementType, Collection settlementIds); +/** 按付款申请删除核销行,返回受影响的正式结算单id(settlementType=formal,供汇总刷新) */ +List deleteByPaymentApplicationIds(Collection paymentApplicationIds); +``` + +**预结算转正式(`saveDraft` 完整建单路径)**: +1. 前端:编辑器 `confirmCandidates` 选定来源预结算后,对每个新选 `preSettlementId` 复用**既有** `preSettlement.getDetail(id)` 取 `projectAdvanceWriteOffs` 预填进本单核销组件(去重、金额 Number 化),用户可在弹窗中增删改; +2. 后端 `saveDraft`:在 `replaceWriteOffs`(L548)**之前**执行 `deleteBySettlementIds("pre", request.getSourcePreSettlementIds())`——先释放来源占用,`replaceWriteOffs` 的"不超可核销余额"校验才能通过预填金额;随后 `replaceWriteOffs` 将预填行落为本单 FORMAL 行。同一事务,失败整体回滚。 +3. 轻量标记路径(`PreSettlementServiceImpl.formalSettlement(id)`,L550):不产生正式单实体,PRE 核销行原地保留(预结算实体仍是载体),待后续真正建正式单时按上述规则转移。 + +**调整重推(`cloneFormalSettlement`,L641-708)**:与既有"新单付款口径归零"哲学(L658 注释)冲突时以**额度正确性优先**—— +1. 在来源克隆循环后,克隆原单活跃核销行到新单(`settlementId` 换新、`resetAuditFields`); +2. `deleteBySettlementIds("formal", List.of(sourceSettlementId))` 删原单核销行(原单 `adjustStatus=adjusted` 为历史存档,已付归零以新单为准,文档明示); +3. `refreshPaymentSummaries(List.of(原单id, 新单id))` 一次批量刷新两单(新单头 L662-664 置零的 applied/paid 由重算纠正为核销额)。 + +> 新单由克隆直接 `approved`(L647),无草稿编辑机会,故必须迁移而非"让用户重新核销"。 + +### 4.4 作废联动删行(D6) + +| 触发点 | 改动 | +|---|---| +| `PaymentApplicationServiceImpl.voidBill`(L446-460,L458 `voidPreSettlementAdvance` 后) | `deleteByPaymentApplicationIds(List.of(entity.getId()))` → 返回的 formal ids 非空则 `formalSettlementService.refreshPaymentSummaries(ids)` | +| `FormalSettlementServiceImpl.voidBill`(L600-609) | `deleteBySettlementIds("formal", List.of(id))` | +| `FormalSettlementServiceImpl.voidByKingdee`(L613-637) | 同上 | +| `PreSettlementServiceImpl.voidBill`(L443) | `deleteBySettlementIds("pre", List.of(id))`(预结算作废前置"先作废预付"校验不受影响,核销行不阻断) | + +金蝶反审核自动解锁置 `failed`(未作废)的场景**不删行**:预付申请仍是 `approved`,已核销行继续计入;人工作废后经上表路径回落。 + +存量数据提示:v2 之前保存的核销行可能对应"approved 未 paid"的预付,本次不做数据修正,如需口径清洗另出脚本(`replaceWriteOffs` 校验只约束新保存)。 + +### 4.5 核销服务与 Mapper + +**`ISettlementProjectAdvanceWriteOffService` / `SettlementProjectAdvanceWriteOffServiceImpl`**: +- 4.3 两个删除方法(见上); +- `Map sumBySettlementIds(String settlementType, Collection settlementIds)`——一次 group-by,供批量刷新与列表页(4.7)使用。 + +**`SettlementProjectAdvanceWriteOffMapper.java`(接口)+ `SettlementProjectAdvanceWriteOffMapper.xml`**:新增 `sumWriteOffBySettlementIds`(`is_deleted=0` + `settlement_type` + `settlement_id in (...)`,`GROUP BY settlement_id`),与既有 `sumWriteOffBySettlement`(XML L27-33)口径一致。 + +**选单与校验加 paid 条件(D2)**: +- `listOptions`(ServiceImpl L48-81):查询条件追加 `kingdeeStatus='paid'`; +- `replaceWriteOffs`(L124-193):在 `approvalStatus=approved` 校验(L157-159)后追加 `kingdeeStatus='paid'` 校验,提示文案"项目预付已付款后方可核销"。 + +### 4.6 detail 返回统一预付付款记录 + +**新 VO `SettlementAdvancePaymentVO`**(`blade-transport-api/pojo/vo/`,仿 `ProjectAdvanceWriteOffOptionVO` 风格,含 BladeX 许可头): + +| 字段 | 说明 | +|---|---| +| `recordType` | `progress_advance` 进度预付 / `project_advance` 项目预付核销 | +| `paymentNo` | 进度预付 = `PreSettlementAdvance.advanceNo`(FKD 单号);项目预付 = 核销行 `paymentNo` | +| `appliedAmount` | 进度预付 = `appliedAmount`;项目预付 = 核销行 `writeOffAmount`(**本次核销金额**,非付款申请金额) | +| `paidAmount` | 进度预付 = `paidAmount`;项目预付 = 底层付款申请 `paidAmount`(D2 下 = 核销额,同屏一致) | +| `billStatus` | 进度预付 = 台账 `billStatus`;项目预付 = 付款申请 `approvalStatus` | +| `kingdeeBillNo` | 进度预付 = `kingdeeAdvanceNo`;项目预付 = 核销行 `kingdeeBillNo` | +| `createUserName` / `createTime` | `UserCache.getUserRealName(createUser)`(AGENTS.md 6.9);**createTime 统一取底层付款申请创建时间**:项目行用核销行已回填的 `paymentCreateTime`(`listBySettlement` 现成),进度行按 `advanceNo in (...)` 批量反查 `PaymentApplication.createTime`(一次 in 查询)。列表排序与展示均用该时间 | + +**`FormalSettlementVO`** 新增 `@TableField(exist = false) private List advancePayments;`(L74 `paymentApplications` 旁)。 + +**`FormalSettlementServiceImpl.detail`**(L307-352)在加载 `projectAdvanceWriteOffs`(L323-324)后组装: + +```java +vo.setAdvancePayments(this.listAdvancePayments(sources, vo.getProjectAdvanceWriteOffs())); +``` + +`listAdvancePayments` 私有方法: +1. **进度预付**:`PreSettlementAdvance` 按 `preSettlementId in 来源预结算ids`、`billStatus != 'voided'` 查询(注入 `PreSettlementAdvanceMapper`,与同类 `sourceMapper` 直连模式一致); +2. **项目预付**:遍历核销行,`paymentApplicationMapper.selectBatchIds` 批量取底层付款申请回填已付金额/状态; +3. 合并后按"付款申请创建时间"降序(列表默认排序规范)。 + +尾款记录(`paymentApplications`)查询不动。 + +### 4.7 列表页 N+1 规避(D8/评审#5) + +`selectPage` L209-210 逐行 `toVO`,而 `toVO`(L1827-1837)内部 `calculatePaymentSummary` 已是每行 3-4 次查询,**不能再叠加每行一次核销求和**: + +- 新增重载 `toVO(FormalSettlement entity, Map writeOffAmounts)`,把 map 置入 `PaymentSummaryData`;单参版委托 `toVO(entity, null)`(按需查询,`detail` 等单笔场景不受影响); +- `selectPage`:本页 `payable` 结算单 ids 一次 `sumBySettlementIds` 得 map,`result.convert(item -> this.toVO(item, writeOffMap))`。 + +### 4.8 结算调整单兜底(D7) + +**`SettlementAdjustmentServiceImpl`**(L384-388 直改金额处): +1. 落库前校验:`projectAdvanceWriteOffService.sumBySettlement("formal", formal.getId())` ≤ 新 `settlementAmount`,超出抛"核销合计超过调整后结算金额,请先调减项目预付核销"; +2. 落库后:`formalSettlementService.refreshPaymentSummary(formal.getId())`(注入 `IFormalSettlementService`,当前仅有 `formalMapper`)。 + +### 4.9 后端改动文件清单 + +| 文件 | 改动 | +|---|---| +| `service/impl/FormalSettlementServiceImpl.java` | `calculatePaymentSummary` 计入核销;`PaymentSummaryData` 加组件;`saveDraft` 补刷新 + 转移来源 PRE 行;`cloneFormalSettlement` 克隆/迁移核销行;`voidBill`/`voidByKingdee` 删行;`detail` 补 `advancePayments` + 新私有 `listAdvancePayments`;`toVO` 加 map 重载;`selectPage` 批量供数;注入 `PreSettlementAdvanceMapper` | +| `service/ISettlementProjectAdvanceWriteOffService.java` + `impl` | 新增 `deleteBySettlementIds`、`deleteByPaymentApplicationIds`、`sumBySettlementIds`;`listOptions`/`replaceWriteOffs` 加 `kingdeeStatus='paid'` 条件 | +| `mapper/SettlementProjectAdvanceWriteOffMapper.java` + `.xml` | 新增 `sumWriteOffBySettlementIds`(接口 + XML 成对) | +| `service/impl/PaymentApplicationServiceImpl.java` | `voidBill` 联动删核销行 + 刷新汇总 | +| `service/impl/PreSettlementServiceImpl.java` | `voidBill` 删 PRE 核销行 | +| `service/impl/SettlementAdjustmentServiceImpl.java` | 调整落库前核销合计校验 + 落库后刷新汇总;注入 `IFormalSettlementService` | +| `pojo/vo/SettlementAdvancePaymentVO.java`(新增) | 统一预付付款记录 VO | +| `pojo/vo/FormalSettlementVO.java` | 加 `advancePayments` | + +## 五、前端改动(tms-erp-web) + +### 5.1 结算合计汇总栏 + +**`formal-settlement-editor.vue`**:结算合计 section-card(L99-180)表格下方加汇总条(仅 `form.settlementType === 'payable'` 显示): + +``` +结算合计(原金额):form.settlementAmount +已付金额(含预付、核销):见下 +剩余未付金额:max(结算合计 − 已付, 0) +``` + +- 编辑态:`Σ sources.advancePaidAmount + Σ projectAdvanceWriteOffs.writeOffAmount`(选预结算 `confirmCandidates` 与勾选核销行时实时联动;草稿态无尾款申请,公式完备;D2 下核销行必对应已付预付,计入实付口径成立); +- 只读态:`form.paidAmount`(已含尾款 + 进度预付 + 核销的落库值)。 + +### 5.2 付款记录合并表 + +只读"付款信息"应付表格(L580-612)数据源改为合并计算属性: + +```js +paymentRecords() { + const advance = (this.advancePayments || []).map(r => ({ ...r, typeName: this.recordTypeName(r.recordType) })); + const tail = (this.paymentApplications || []).map(r => ({ ...r, typeName: r.paymentTypeName })); + return [...advance, ...tail]; +} +// recordTypeName: progress_advance→'进度预付',project_advance→'项目预付核销' +``` + +- 列:付款类型(`typeName`,尾款行兼容原 `paymentTypeName`)/ 单据号 / **申请/核销金额**(预付行的"申请金额"实为核销金额,列名合并以免误导)/ 已付款金额 / 单据状态 / 金蝶单据号 / 创建人 / 创建时间(= 付款申请创建时间,见 4.6)。原"付款方式"列预付行无值,显示 `-`; +- 单据状态名映射**提取公共工具**(与 `pre-settlement-editor.vue` L2879 `advanceStatusName` 去重):新建 `src/utils/settlementStatus.js` 导出 `settlementStatusName(value)`(reviewing 审核中、approved 已审批、paid 已付款、returned 已驳回、voided 已作废、draft 草稿),两个编辑器共用,避免状态名不一致; +- `loadFormalDetail`(L1360-1379)回填 `this.advancePayments = data.advancePayments || []`,`readonly` 重置时清空。 + +### 5.3 转正式预填核销行 + +`confirmCandidates`(L1729-1752)选定来源预结算后:对每个新选 `preSettlementId` 调用既有 `preSettlement` detail 接口,取 `projectAdvanceWriteOffs` 预填进本单核销组件(与已有行按 `paymentApplicationId` 去重、金额 Number 化);保存时随 payload 提交,后端按 4.3 转移来源行。 + +### 5.4 前端不动的部分 + +- `project-advance-write-off.vue`:选单过滤、本次核销金额校验链路已完整(L198-288),"已付款"过滤条件由后端 `listOptions` 落实,组件不改; +- 列表页 `formal-settlement.vue`:"申请付款金额(含预付)/已收已付合计(含预付)/剩余可付金额"列直接受益于后端口径修正,不改; +- 尾款申请默认可付金额 `payableOfFormalSettlement` 已扣核销额(`PaymentApplicationServiceImpl` L2115-2122),不改。 + +## 六、边界场景 + +| 场景 | 行为 | +|---|---| +| 草稿保存核销 | 已付/已申请/剩余可付/付款状态立即生效(D2,行必对应已付预付),列表可见 | +| 底层项目预付申请作废 | 联动删核销行 + 批量刷新 → 已付回落(4.4) | +| 底层预付金蝶推送 `failed` / 反审核解锁 | 不删行,核销继续计入(预付申请仍 approved);人工作废后回落 | +| 金蝶付款完成回写(进度预付) | `syncOnePreSettlementAdvance` 刷新来源快照 → `refreshPaymentSummariesForPreSettlement` → 批量版自动计入核销(4.1 供数) | +| 正式结算单作废 / 金蝶删单回调 | 删除本单核销行(4.4),额度释放、详情无残留 | +| 预结算单作废 | 删除 PRE 核销行(4.4) | +| 预结算转正式(尾款结算) | 前端预填 + 后端先删来源 PRE 行再落 FORMAL 行(4.3),额度单份占用 | +| 轻量标记转正式(无在途预付) | PRE 核销行原地保留,待建正式单时转移(4.3) | +| 结算调整重推 | 核销行迁移到新单,原单删行并以新单为准(4.3) | +| 结算调整单调低金额致核销超限 | 落库前拦截,提示先调减核销(4.8) | +| 全额核销后发起调整 | `isSettlementCompleted` 拦截(现状逻辑),业务含义=已结清(D8 待确认) | +| 对账单生成 | 复制的 `paidAmount` 含核销口径(D8 待确认) | +| 核销合计 + 尾款申请超结算金额 | 双向封顶:核销合计 ≤ 结算金额(`replaceWriteOffs` L184-186);尾款默认额已扣核销额;`remainingPayableAmount` min 0 | +| 开票 | `settlementAmount` 全额不变,发票匹配上限(≤结算金额)不受影响(D1) | +| 应收(receivable)结算单 | 走收款认领分支,payable 分支的新增逻辑不触达 | + +## 七、测试要点 + +1. `saveDraft` 带核销行 → `appliedPaymentAmount / paidAmount / remainingPayableAmount / paymentStatus` 正确;编辑重复保存不重复累计(全删重插)。 +2. D2:`approved` 未 `paid` 的项目预付不出现在选单(`listOptions`),手工提交被 `replaceWriteOffs` 拒绝。 +3. 底层预付作废 → 核销行删除、汇总回落、列表金额一致;作废的正式结算单不再出现在批量刷新的已付统计中。 +4. 预结算转正式:预填行保存成功(先删 PRE 行再校验余额的顺序);同一预付在 PRE/FORMAL 侧无双份核销。 +5. 调整重推:新单含核销行且汇总正确(头字段从置零被重算纠正),原单行删除、汇总归零。 +6. 结算调整单:调低金额低于核销合计被拒;合法调整后 `refreshPaymentSummary` 生效。 +7. `detail` 返回 `advancePayments`:进度预付行(排除 voided)+ 项目预付行(回填底层实付),按付款申请创建时间倒序。 +8. 列表页 SQL 次数:本页核销求和恒定 1 次(不随行数增长)。 +9. 应收结算单、无预付结算单回归:汇总与现状一致。 +10. 前端:编辑态汇总栏随选预结算/核销行实时变化;只读态合并付款记录表三类型展示、状态名与预结算页一致;应收侧页面不出现汇总栏。 + +## 八、范围外(明确不做) + +- 预结算(PRE)侧自身收付汇总计入核销(本次仅 FORMAL); +- 金蝶预付抵扣字段 `y_billtype / y_billid / y_settleamt`(文档 TODO,本期不传); +- 应收侧预收/收款认领汇总; +- 存量"approved 未 paid 核销行"的数据修正脚本(如需另出); +- 遗留接口 `/pre-settlement/apply-advance` 等下线评估(见《2026-10-18-预付完整流程梳理》第五节)。 + +## 九、修订记录 + +| 版本 | 日期 | 内容 | +|---|---|---| +| v1 | 2026-10-18 | 初版:汇总计入、作废联动(仅付款申请侧)、detail 预付记录、前后端展示。 | +| v2 | 2026-10-18 | 评审修订:D2 改为"仅已付款可核销"(待确认);新增 D5 核销行流转(预结算转正式预填+转移、调整重推迁移)、D6 作废删行补全(结算单作废/金蝶回调/预结算作废)、D7 调整单校验+刷新、D8 对账与调整完成判定影响面(待确认);列表页 N+1 规避;文件清单补 Mapper 接口;合并表列名"申请/核销金额";状态名公共工具;createTime 统一为付款申请创建时间;点明 saveDraft 双刷新为有意设计。 | diff --git a/doc/docs/业务流程/2026-10-18-预付与正式结算单全链路逻辑梳理(进度预付-项目预付核销·前端到后端各环节).md b/doc/docs/业务流程/2026-10-18-预付与正式结算单全链路逻辑梳理(进度预付-项目预付核销·前端到后端各环节).md new file mode 100644 index 00000000..25cf41f8 --- /dev/null +++ b/doc/docs/业务流程/2026-10-18-预付与正式结算单全链路逻辑梳理(进度预付-项目预付核销·前端到后端各环节).md @@ -0,0 +1,129 @@ +# 改造后全链路逻辑梳理(预付 × 正式结算单) + +> 关联文档:同目录《2026-10-18-预付完整流程梳理(进度预付-项目预付-正式结算单-付款-金蝶回写).md》(改造前现状梳理)、同目录《2026-10-18-正式结算单预付整合设计(进度预付+项目预付核销·金额口径与记录流转).md》(v2 设计与决策) +> 梳理日期:2026-10-18。本文描述**改造落地后**的完整逻辑,前端到后端各环节。 + +## 一、全景 + +``` +【线A 进度预付】付款申请(progress_advance) ─A103-3→ 金蝶付款 ─回写→ 预结算预付汇总 + └→ 尾款结算建正式单:继承预付快照 → 结算合计(全额)+汇总栏(已付含预付) → 审批 ─A102-2→ 财务应付单 + └→ 尾款付款(默认额已扣核销) ─A103-1→ 回写paid → 付款记录合并表 + +【线B 项目预付】付款申请(project_advance) ─A103-3→ 金蝶付款 ─回写→ kingdeeStatus=paid + └→ 正式结算单「核销」(仅paid可选) → 核销计入已付 → 审批 ─A102-2→ 财务应付单(全额) + └→ 尾款付剩余 → 后续与正式结算单流程完全一致 +``` + +**核心口径**(`FormalSettlementServiceImpl.calculatePaymentSummary` payable 分支,全系统唯一): + +``` +结算金额 settlementAmount = Σ 结算合计费用项(全额,不扣预付/核销——开票与金蝶应付基线) +已申请 appliedPaymentAmount = 尾款reviewing(直挂+关系表) + Σ来源预付快照 + Σ核销 +已付 paidAmount = 尾款approved实付 + Σ来源预付快照 + Σ核销 +剩余可付 remainingPayableAmount = max(结算金额 − 已付, 0) +付款状态 paymentStatus = unpaid / partial / paid +``` + +D2 决策:仅**金蝶已付款**(`kingdeeStatus='paid'`)的项目预付可核销,核销额计入已付属实付口径。 + +--- + +## 二、线A:进度预付(挂预结算单) + +### A1 预付与付款(改造前已有) + +- 前端 `pre-settlement.vue`「预付申请」→ `payment-application-form?paymentType=progress_advance` +- 后端 `PaymentApplicationServiceImpl`:审批 → AFTER_COMMIT `KingdeeApprovalSyncListener` → A103-3 直推金蝶 → 提前建档 `PreSettlementAdvance`(advanceNo=FKD 单号) +- 金蝶付款后 `PaymentKingdeeResultTask`(A103-2/A103-4)回写 paid +- 联动:`syncOnePreSettlementAdvance` 刷新预结算预付汇总(`advanceAppliedAmount/advancePaidAmount`)→ 同步刷新引用它的 `FormalSettlementSource` 快照 → `refreshPaymentSummariesForPreSettlement`(正式单已付跟着涨) + +### A2 尾款结算 → 正式结算单表单(前端) + +- `applyInitialSources`(列表跳转带入)/ `confirmCandidates`(弹窗追加):拉每张预结算 detail,合并明细与费用项 → `summaryTotal` → `form.settlementAmount`(**全额费用合计,不扣预付**) +- **汇总栏(新增)**:仅应付显示;编辑态实时 `已付 = Σ来源预结算.advancePaidAmount + Σ核销行`,`剩余 = 原金额 − 已付`;只读态 `已付 = form.paidAmount`(落库值,含尾款) + +### A3 保存 `saveDraft`(后端,同一事务) + +1. 来源合并(同合同同币种校验)→ `FormalSettlementSource` 快照(含预付汇总) +2. `refreshSettlementAmount`:settlementAmount=Σ费用项 → 内部刷一次汇总 +3. **新增**:`deleteBySettlementIds(PRE, 来源预结算ids)`——先转移来源预结算的项目预付核销行(释放额度) +4. `replaceWriteOffs`(服务端复校验:paid、同合同、≤可核销余额、合计≤结算金额)→ **新增** `refreshPaymentSummary`(核销落库后必须再刷一次,第 2 步的刷新覆盖不到核销行) + +### A4 审批 → 同步金蝶 + +- `approve` → `KingdeeApprovalSyncListener` → **A102-2 财务应付单,金额=全额结算金额** → lockBusiData 锁单 + A902 附件 + +### A5 只读详情(付款记录关联显示,本次核心新增) + +- 后端 `detail` 新增 `advancePayments`:进度预付行 = 来源预结算的 `PreSettlementAdvance` 台账(排除 voided;appliedAmount=申请预付、paidAmount=实付;createTime 统一取**付款申请创建时间**,按 advanceNo 反查) +- 前端 `paymentRecords` 合并表:**进度预付 / 项目预付核销 / 结算尾款** 三类型一张表;状态名走公共 `src/utils/settlementStatus.js` + +### A6 尾款付款 + +- 前端 `formal-settlement.vue`「付款申请」→ `referenceAmount` 默认可付 = **结算金额 − 累计尾款申请 − 核销** +- `settlement_payment` → 前置校验已有财务应付单 → A103-1 应付下推 → 回写 paid → `FormalSettlementPayment(final)` + 汇总刷新 → `paymentStatus=paid` + +--- + +## 三、线B:项目预付(挂项目+承运商合同) + +| 环节 | 逻辑 | +|---|---| +| B1 申请 | 付款申请 `project_advance`:选项目+承运商合同、不挂结算单;`validateQuota` 校验项目额度(fundLimit+临时额度)与客户额度(maxCreditLimit) | +| B2 付款 | 审批 → A103-3 直推 → 金蝶付款 → A103-2 回写 **`kingdeeStatus='paid'`**(核销前置条件) | +| B3 核销·选单(编辑器) | 全新单:核销组件「选择预付单」→ `project-advance-write-off-options`(后端过滤:同合同 + `approved` + **`paid`** + 有余额);**预结算转正式入口(新增)**:`confirmCandidates`/`applyInitialSources` 选定来源后自动预填来源预结算已保存的核销行,可增删改 | +| B3 核销·录额(前端) | 手录本次核销金额:>0、≤可核销余额、合计≤结算金额(`project-advance-write-off.vue` 既有校验链) | +| B4 保存 | 同 A3 第 3-4 步:先转移源 PRE 行 → `replaceWriteOffs` 复校验落 FORMAL 行 → 刷新汇总,核销计入已付 | +| B5 审批 | A102-2 按全额结算金额生成财务应付单(金蝶预付抵扣字段 `y_billtype/y_billid/y_settleamt` 本期不传) | +| B6 付款记录 | 合并表显示项目预付核销行:`申请/核销金额`=本次核销额,`已付款金额`=底层付款申请实付(paid 前置下两者相等,同屏一致) | +| B7 尾款 | 默认可付已扣核销 → 已核销部分不会被重复支付;剩余走正常尾款流程 | +| B8 差异 | 后续金额差异按既有规则走**结算调整单**(见横切 5) | + +--- + +## 四、横切环节 + +| # | 环节 | 逻辑 | +|---|---|---| +| 1 | 列表页 | `selectPage` 本页 payable 一次 `sumWriteOffBySettlementIds`(group-by)→ `toVO(map)` 重算 → 列「申请付款金额(含预付)/已收已付合计(含预付)/剩余可付/收付款状态」直接反映核销;SQL 不随行数增长 | +| 2 | 汇总刷新触发点 | 保存、审批、金蝶付款回写、预结算预付快照联动、作废、调整——全部收敛到 `calculatePaymentSummary`,口径唯一 | +| 3 | 作废联动(新增) | 付款申请作废 → 删其核销行 + 刷新受影响正式单汇总(已付回落);正式单作废(`voidBill`)与金蝶删单回调(`voidByKingdee`)→ 删本单核销行(额度释放);预结算作废 → 删 PRE 核销行 | +| 4 | 核销行流转(新增) | 预结算转正式 = 先删源 PRE 行再落 FORMAL 行(同事务,同一预付的核销全链路只存在一份);调整重推 = 核销行克隆到新单 + 删原单行 + `refreshPaymentSummaries(两单)`(原单为调整存档,收付口径以新单为准) | +| 5 | 结算调整单(新增兜底) | 调整落库前校验**核销合计 ≤ 新结算金额**(超出拒绝并提示先调减核销);落库后 `refreshPaymentSummary` 重算 | +| 6 | 对账单 | `copyHeader` 复制 `paidAmount` → 对账单已付口径含核销(已确认接受) | +| 7 | 全额核销=结清 | `paymentStatus=paid` → `isSettlementCompleted` 拦截,不允许再发起结算调整(已确认接受) | +| 8 | 开票 | 始终按**全额 settlementAmount** 匹配(Σ匹配 ≤ 结算金额),预付/核销不影响发票 | +| 9 | 金蝶预付抵扣 | `y_billtype / y_billid / y_settleamt` 本期不传(见《2026-10-18-预付完整流程梳理》TODO) | +| 10 | 历史数据 | 本次不考虑存量"approved 未 paid"核销行的清洗 | + +--- + +## 五、改动文件索引 + +**后端(tms-api)** + +| 文件 | 内容 | +|---|---| +| `FormalSettlementServiceImpl` | 汇总计入核销(单笔/批量/toVO 重载)、saveDraft 转移+刷新、cloneFormalSettlement 迁移、voidBill/voidByKingdee 删行、detail.advancePayments、selectPage 批量供数 | +| `SettlementProjectAdvanceWriteOffServiceImpl` | listOptions/replaceWriteOffs 加 paid 条件;sumBySettlementIds / deleteBySettlementIds / deleteByPaymentApplicationIds | +| `SettlementProjectAdvanceWriteOffMapper.java/.xml` | sumWriteOffBySettlementIds | +| `PaymentApplicationServiceImpl` | voidBill 联动删核销行+刷新 | +| `PreSettlementServiceImpl` | voidBill 删 PRE 核销行 | +| `SettlementAdjustmentServiceImpl` | 调整前核销合计校验 + 调整后刷新汇总 | +| `SettlementAdvancePaymentVO`(新)/ `FormalSettlementVO` | 统一预付付款记录 VO / advancePayments 字段 | + +**前端(tms-erp-web)** + +| 文件 | 内容 | +|---|---| +| `formal-settlement-editor.vue` | 结算合计汇总栏;付款记录合并表;confirmCandidates/applyInitialSources 预填核销行 | +| `src/utils/settlementStatus.js`(新) | 状态名/记录类型名公共映射(预结算编辑器同步使用) | + +--- + +## 六、已知口径与风险 + +1. 尾款默认可付额(`payableOfFormalSettlement`)= 结算金额 − 累计尾款申请 − 核销,**不扣进度预付已付部分**(改造前既有口径,本次未动);进度预付占大头时默认额可能偏大,提交后由审批环节把关。 +2. 正式结算单 `paidAmount` 现含核销,下游消费该字段的报表/对账口径随之变化(对账已确认接受)。 +3. 金蝶侧预付抵扣字段未传,金蝶报表与本系统"已付含核销"存在时间差。 diff --git a/doc/docs/业务流程/2026-10-18-预付完整流程梳理(进度预付-项目预付-正式结算单-付款-金蝶回写).md b/doc/docs/业务流程/2026-10-18-预付完整流程梳理(进度预付-项目预付-正式结算单-付款-金蝶回写).md new file mode 100644 index 00000000..63ac715b --- /dev/null +++ b/doc/docs/业务流程/2026-10-18-预付完整流程梳理(进度预付-项目预付-正式结算单-付款-金蝶回写).md @@ -0,0 +1,160 @@ +# 预付完整流程梳理 + +> 范围:预付(进度预付 / 项目预付)→ 正式结算单 → 付款 → 金蝶回写等后续流程,前端到后端全链路。 +> 依据代码现状梳理(tms-api / tms-erp-web),梳理日期:2026-10-18。 + +## 一、总览 + +预付有**两条线**,最终在金蝶(星瀚 BFI)汇合: + +``` +【进度预付线】 【项目预付线】 +预结算单(审批通过,应付) 付款申请-项目预付(挂项目/合同,不挂结算单) + └─「预付申请」→ 付款申请(progress_advance) └─ 校验项目额度/客户额度 → A103-3 → 金蝶付款 + └─ 审批 → A103-3 直推金蝶付款申请单 └─ 事后在预结算/正式结算单上「项目预付核销」 + └─ 提前建档 PreSettlementAdvance(approved) + └─ 定时回写 A103-2/A103-4 → paid + └─ 预付记录置 paid + 预结算单预付汇总刷新 + └─「尾款结算」→ 正式结算单(继承预付汇总) → 审批 → A102-2 财务应付单 + └─「申请付款」→ 付款申请(settlement_payment) → A103-1 应付下推 + └─ 回写 paid → FormalSettlementPayment + 结算单付款汇总(unpaid→partial→paid) +``` + +### 金蝶接口速查 + +| 接口 | 用途 | 触发方 | +|---|---|---| +| A103-3 | 预付款直接保存金蝶付款申请单(不绑应付单) | 付款申请(progress_advance / project_advance)同步金蝶 | +| A103-1 | 应付下推生成金蝶付款申请单 | 付款申请(settlement_payment)同步金蝶 | +| A103-2 | 付款申请单查询(幂等预检 + 结果回写) | 推送前预检、定时回写任务 | +| A103-4 | 付款处理查询(回填实付款记录) | 定时回写任务 | +| A102-2 | 财务应付单保存 | 正式结算单「同步金蝶」 | +| A102-4 | 财务应付单查询(防重预检 / 反查) | 推送前预检、下推反查付款计划分录 | +| A902 | 附件上传 | 主单推送成功后联带上传 | +| lockBusiData | 金蝶单据锁定 | 推送成功事务提交后 | + +## 二、涉及的主要代码与数据表 + +### 后端(tms-api / blade-transport) + +| 内容 | 位置 | +|---|---| +| 预结算单 | `controller/PreSettlementController.java`、`service/impl/PreSettlementServiceImpl.java` | +| 付款申请 | `controller/PaymentApplicationController.java`、`service/impl/PaymentApplicationServiceImpl.java` | +| 正式结算单 | `controller/FormalSettlementController.java`、`service/impl/FormalSettlementServiceImpl.java` | +| 项目预付核销 | `service/impl/SettlementProjectAdvanceWriteOffServiceImpl.java` | +| 金蝶同步支撑 | `support/KingdeeApBillSyncSupport.java`(A102-2/A102-4)、`service/impl/KingdeeBillLockServiceImpl.java`(锁单)、`KingdeeAttachmentSyncServiceImpl.java`(附件) | +| 审批后自动推送 | `listener/KingdeeApprovalSyncListener.java`(AFTER_COMMIT)+ `event/KingdeeBillApprovedEvent.java` | +| 定时任务 | `task/PaymentKingdeeResultTask.java`(付款结果回写,每 10 分钟)、`task/KingdeeAutoSyncRetryTask.java`(失败重推,每 10 分钟)、`task/KingdeeBillLockRetryTask.java`(补锁) | + +### 前端(tms-erp-web) + +| 页面 | 位置 | +|---|---| +| 预结算单列表/表单 | `src/views/settlement/pre-settlement.vue`、`pre-settlement-form.vue`、`components/pre-settlement-editor.vue` | +| 正式结算单列表/表单 | `src/views/settlement/formal-settlement.vue`、`formal-settlement-form.vue`、`components/formal-settlement-editor.vue` | +| 项目预付核销组件 | `src/views/settlement/components/project-advance-write-off.vue` | +| 付款申请列表/表单 | `src/views/payment/payment-application.vue`、`payment-application-form.vue` | +| API 层 | `src/api/settlement/preSettlement.js`、`src/api/settlement/formalSettlement.js`、`src/api/payment/paymentApplication.js` | + +### 核心数据表(实体) + +| 表/实体 | 作用 | +|---|---| +| `PreSettlement`(预结算单) | 含预付汇总字段 `advanceNo / advanceAppliedAmount / advancePaidAmount`、`formalSettlementNo` | +| `PreSettlementAdvance`(预结算预付记录) | 预付台账:`advanceNo`(=付款申请单号)、`appliedAmount / paidAmount / billStatus(reviewing/approved/paid/voided) / kingdeeAdvanceNo` | +| `PaymentApplication`(付款申请) | `paymentType`:`progress_advance` 进度预付 / `project_advance` 项目预付 / `settlement_payment` 尾款付款;`kingdeeStatus`:`unsynced/synced/paying/paid/closed/failed` | +| `PaymentApplicationSettlement` | 付款申请 ↔ 结算单(正式/预结算)多选关系及分摊金额 | +| `PaymentApplicationRecord` | 付款记录(金蝶 A103-4 回填,`voucherJson` 留档凭证号/银行流水/电子回单) | +| `FormalSettlement`(正式结算单) | 含 `appliedPaymentAmount / paidAmount / remainingPayableAmount / paymentStatus / kingdeeSyncStatus` | +| `FormalSettlementSource` | 正式结算单来源预结算单/明细快照,冗余预付汇总 | +| `FormalSettlementPayment` | 正式结算单付款记录(尾款 `paymentType=final`) | +| `SettlementProjectAdvanceWriteOff` | 项目预付核销记录(`settlementType`:PRE/FORMAL) | + +## 三、阶段详解 + +### 阶段一:前置 —— 预结算单审批通过 + +- 前端:预结算列表新建/编辑 → 保存草稿 → 提交 → 审批通过(`pre-settlement.vue`,API `/blade-transport/pre-settlement/save|submit|approve`)。 +- 后端:`PreSettlementController` → `PreSettlementServiceImpl.saveDraft / submit / approve`。 +- 审批通过(`approved`)后,**应付(payable)**预结算单即可发起预付。 + +### 阶段二:发起预付(进度预付) + +1. **前端入口**:预结算列表勾选 →「预付申请」按钮。前端校验:同一合同、`approved`、`payable`、未转正式结算(`pre-settlement.vue` `handleAdvanceApplication`)。 +2. **跳转付款申请表单** `/payment/payment-application/form?paymentType=progress_advance&preSettlementIds=...`(并非直接建预付单)。 +3. **表单**(`payment-application-form.vue`): + - 进度预付仅可关联预结算单;不展示发票信息与付款记录; + - 可付金额实时调 `/payment-application/reference-amount` = 结算金额 − 累计已申请 − 项目预付核销额。 +4. **后端**(`PaymentApplicationServiceImpl`): + - `saveDraft`:生成 `FKD+日期+序号` 单号;`fillReference` 回填预结算关联;保存 `PaymentApplicationSettlement` 关系表; + - `submit`:`draft/returned → reviewing`(财务审核); + - `approve`:`→ approved`,刷新关联结算单付款汇总,**发布 `KingdeeBillApprovedEvent`**。 + +> 说明:`PreSettlementController` 上的 `/apply-advance`、`/update-advance-paid`、`/void-advance` 为遗留接口,前端已不再调用;`PreSettlementAdvance` 台账现由付款申请流程联动维护(见阶段三/四)。 + +### 阶段三:同步金蝶(A103-3 预付款直推) + +- 审批事务 **AFTER_COMMIT** → `KingdeeApprovalSyncListener.pushPayment`(Nacos 开关 `thirdParty.bfi.autoSync.payment`)→ `paymentApplicationService.syncKingdee`。 +- `doSyncKingdee`:预付款走 **A103-3 直接保存金蝶付款申请单(不绑应付单)**;推送前 A103-2 幂等预检防重复;成功后落 `kingdeeStatus=synced` + `kingdeeBillId` → **lockBusiData 锁单** → A902 附件上传。 +- **提前建档**:`syncPreSettlementAdvance(entity, approved)` —— 按付款申请号在 `PreSettlementAdvance` 生成预付记录(`advanceNo = FKD 单号`),刷新预结算单预付汇总(`advanceNo / advanceAppliedAmount / advancePaidAmount`),并联动刷新关联正式结算单付款汇总(预付信息两边页面即时可见)。 +- **失败兜底**:`markKingdeeSyncFailed` 落 `failed`;`KingdeeAutoSyncRetryTask` 每 10 分钟自动重推;页面「同步金蝶」按钮手动兜底。审批接口通过 `pollSyncFailure()` 将推送失败原因以警告文案带回前端(审批结果不受推送影响)。 + +### 阶段四:金蝶付款 + 结果回写(付款之后) + +定时任务 `PaymentKingdeeResultTask`(每 10 分钟,分布式锁 single-flight)→ `syncKingdeePaymentResult`: + +1. **A103-2 付款申请单查询**:按 `paymentNo`(防重键)分批查询,回填金蝶单号、状态(`synced → paying → paid/closed`)、已付金额(分录 `e_paidamt` 汇总)。 +2. **付款完成(paid)联动**:`syncPreSettlementAdvance(paid, 全额)` 置预付记录为已付。 +3. **A103-4 付款处理查询**:回填付款记录 `PaymentApplicationRecord`——实付金额、付款日期、金蝶付款单号;凭证号/银行流水/电子回单整体存 `voucherJson` 留档追溯。 +4. **异常自愈**:A103-2 连续两轮查不到已推送单据(Redis 计数)→ 判定金蝶侧已反审核删除 → `unlockBill` 置 `failed`、清空金蝶单号/内码、**联动作废未付的预付记录**(已付事实不回退);重推后重新建档。 +5. 前端可手动触发:付款申请页「回写金蝶付款结果」按钮 `/sync-kingdee-result`。 + +### 阶段五:预结算 → 正式结算单 + +- **前端**:预结算列表「尾款结算」→ 校验同项目、`approved` → 跳 `/settlement/formal-settlement/form` 携带预结算数据(`handleFormalSettlement`)。 +- **后端两条路**: + 1. **完整建单**:`FormalSettlementServiceImpl.saveDraft` —— 合并多张预结算单(`FormalSettlementSource`)或应收应付明细;**继承预付汇总**(`advanceAppliedAmount / advancePaidAmount`);支持**项目预付核销** `replaceWriteOffs`(校验:同合同、审批通过、不超可核销余额、合计不超结算单金额);单号 `ZJS...`。 + 2. **轻量标记**:`PreSettlementController /formal-settlement` → `formalSettlement(id)` —— 预结算单回写 `formalSettlementNo`、源明细置 `formal_settled`;**前提:无在途预付申请**(存在 `reviewing/approved` 预付则拦截)。 +- **正式结算单审批**:草稿 → 提交 → `approve` → 事件 → `syncKingdee` → **A102-2 财务应付单**(A102-4 预检/反查防重,一张结算单一张应付单)→ 回写 `kingdeeSyncStatus` → 锁单 + 附件。 +- 列表展示「申请付款金额(含预付)/ 已收已付合计(含预付)」。 + +### 阶段六:尾款付款(正式结算单之后) + +1. **前端**:正式结算单列表勾选(`approved`、`payable`、同合同)→「申请付款」→ 付款申请表单 `paymentType=settlement_payment`,默认取剩余可付金额(`openPaymentDialog`)。 +2. **后端**:`saveDraft` 支持一单挂多张正式结算单(同合同,按可付金额比例分摊);`submit` 时 `validateNoTailPayment` 防重复尾款申请。 +3. **同步金蝶路由不同**:结算付款走 **A103-1 应付下推**——前置校验每张正式结算单已在金蝶生成财务应付单(A102-4 预检,未生成则提示"请先在正式付款单页面同步金蝶"),再下推生成金蝶付款申请单;之后锁单、附件同前。 +4. **回写后**:paid 时 `syncFormalSettlementPayments` 按关系表分摊生成 `FormalSettlementPayment(paymentType=final)` → `refreshPaymentSummary` 重算结算单 `appliedPaymentAmount / paidAmount / remainingPayableAmount / paymentStatus`(unpaid → partial → paid)。金额后续差异走**结算调整单**。 + +### 项目预付线(project_advance) + +- 付款申请页「预付申请」按钮 → `project_advance`:选**项目 + 承运商合同**(仅承运商合同),不挂结算单、无发票/付款记录。 +- `validateQuota` 校验:项目额度(`fundLimit` + 有效期内审批通过的临时额度)、客户额度(`maxCreditLimit`)。 +- 审批 → A103-3 直推 → 金蝶付款 → 回写 paid。 +- **后续核销**:预结算单/正式结算单表单中「项目预付核销」组件 → 保存时 `replaceWriteOffs` 写 `SettlementProjectAdvanceWriteOff`;可付金额计算统一扣减核销额(`referenceAmount`)。 +- 金蝶侧预付抵扣字段(`y_billtype / y_billid / y_settleamt`)**TODO 本期不传**(`KingdeeApBillSyncSupport`,待业务确认后迭代)。 + +## 四、逆向流程 + +| 操作 | 规则 | +|---|---| +| 付款申请作废(仅 approved) | 金蝶锁单中禁止作废(`checkEditable`);汇票付款释放票据余额;**联动作废未付的预付记录**(已付事实不回退);刷新结算单汇总 | +| 预结算单作废 | 存在关联预付申请需先作废预付 | +| 预付记录作废 | 已付款的不能作废 | +| 金蝶反审核(解锁) | A103-2 连续两轮消失自动解锁置 `failed`,人工核实后重推;解锁时原金蝶单号/内码留档清空,重推生成新单后重新关联附件与预付记录 | + +## 五、遗留接口说明 + +`PreSettlementController` 的以下接口前端已不再调用(前端统一走付款申请单流程),`PreSettlementAdvance` 表改由付款申请联动维护,保留兼容: + +- `POST /pre-settlement/apply-advance`(发起预付申请) +- `POST /pre-settlement/update-advance-paid`(回写预付付款金额) +- `POST /pre-settlement/void-advance`(作废预付申请) + +如需下线,需另行评估权限与存量数据兼容性。 + +## 六、风险与未覆盖范围 + +- 应收侧预收/收款认领(ReceiptClaim)流程未在本篇展开。 +- 汇票付款(BillPayment)仅提及票据余额占用/释放联动。 +- 金蝶 A102/A103 接口报文字段细节未逐项列出,参见 `KingdeeApBillSyncSupport`、`PaymentApplicationServiceImpl.pushBfiPayApply / pushBfiPayApplyPush` 源码注释。