借款对账 S1、S2:导出与银行提交分开登记、确认失败收紧及失败回单识别口径(09-17-company-loan-principal-repayment)
S1(迁移 20261007010000) - 借款导出逐次记录;存量批次保留已知导出次数为历史汇总,不视为真实导出,存在即拦截 - 银行提交登记(只能撤回一次)与提交清单确认;独立复核,复核后任何登记、撤回、导出即失效 - 确认失败只计未作废附件;作废附件无法证明为明确失败或确属他笔付款即阻断 - S3 前临时拦截:多次导出、多次提交、清单未复核或历史汇总均不能确认失败 - 确认失败改按付款后规则;确认人不得兼任导出、提交登记、清单确认 S2(迁移 20261007020000) - 存在同金额候选时须有已复核、覆盖提交清单的失败归属;结算与失败归属分槽 失败回单识别口径(用户同意并入) - 新增原始状态措辞字段(多字段原文不一致即为空);失败事实严格判定:单页完整、 仅“交易失败/转账失败/付款失败/支付失败”、唯一问题为交易失败、字段齐全; 已撤销、已冲正、已退汇不接受;顶层识别状态与成功匹配不变 - 测试改走真实识别与归一化链,仅替换云端 OCR 响应 验证:借款 HTTP 41 组、浏览器 7 组、76 迁移;server 4463;共享回单测试 237; 三轮同模型只读复核的阻断项均已修复。 注意:界面未完成,不能单独部署;真实回单待验。未推送、未部署。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -1,3 +1,58 @@
|
||||
## 2026-10-07 S1、S2 本地实施记录(用户 2026-10-06 批准本地实施、针对性验证与只读复核;未提交、未推送、未部署;未做 S0 生产查询)
|
||||
|
||||
S1(迁移 `20261007010000_loan_bank_submissions`):
|
||||
- 新表 `payment_order_exports`:借款批次每次导出写一条(序号、导出人、文件 SHA-256、客户方流水号、金额),只增不改。
|
||||
- 新表 `loan_legacy_export_summaries`:迁移时回填存量借款批次已知导出次数,写明未知项(逐次明细、文件摘要、银行提交对应关系)。它不是一次真实导出或提交;存在时确认失败一律 409 `loan.submission_history_unverified`。
|
||||
- 新表 `loan_bank_submissions`:财务登记实际银行提交(网银导入须关联本批次导出记录),只能撤回一次,不能改。
|
||||
- 新表 `loan_bank_submission_attestations`:财务确认“现有有效提交即全部”,独立复核人须当前有资格,且不是确认人、批次创建人、提交人、出借员工、任一导出人或提交登记人;清单变化后旧确认失效,重新确认时同事务取代。
|
||||
- 确认失败:只计未作废附件(须恰为所确认的失败回单);作废附件曾被识别为成功或处理中、曾匹配、或有未驳回的人工录入,一律 409 `loan.voided_receipt_unresolved`;临时拦截(S3 前)——存在历史汇总、多于一条导出记录或多于一次有效提交、提交清单未经复核或已变化,均拒绝;改按付款后规则(出借员工离职不阻断,确认人按当前资格)。
|
||||
|
||||
S2(迁移 `20261007020000_loan_failure_attribution`):
|
||||
- 归属确认新增 `kind`(settlement/failure)与 `covered_submission_ids`;每批次每类至多一条待复核或已通过;依据类型新增“回单载明本批次银行指令号”。
|
||||
- 存在同金额候选时,确认失败须有已复核、绑定失败回单证据与已确认提交清单的失败归属,否则 409 `loan.receipt_attribution_required`;失败归属登记前须先有已复核的提交清单。结算归属的查询全部限定 settlement 类,两类互不占位。
|
||||
|
||||
接口:`POST /payment-batches/:id/loan-bank-submissions`、`…/:submissionId/withdraw`、`…/loan-submission-attestation`、`…/:attestationId/review`;归属接口新增 `kind`、`attachmentId`;批次详情返回 `bankSubmissions`。新增 7 个原因码,三处登记。
|
||||
|
||||
页面:本步未做提交登记、清单复核和失败归属的界面(S8 页面整合)。**S1 收紧了确认失败,界面补齐前不得单独部署启用。**
|
||||
|
||||
验证与复核:
|
||||
- 首轮针对性验证:HTTP 39 组、浏览器 7 组、76 迁移 PASS;9 个资金守卫变异均变红(其中“去掉清单复核要求”首轮未变红,因缺“已登记未复核”反例,补后变红)。
|
||||
- 同模型只读复核:BLOCK,两项,均经副本实验复现。已修:
|
||||
- B1 作废回单判定过宽(作废时尚未 OCR、状态不可读、状态歧义都会放行):改为“无法证明就拦截”。作废附件只有在当前版本识别为明确失败,或可读且确定不是本笔付款(金额/账户与本批次不符、无处理中或歧义标记)时才忽略;没有核验记录、未识别、状态不明、匹配过或有未驳回人工录入,一律 409。代价:无法识别的误传文件也会阻断,须以可读证据处理(留待 S3 作废复核或线下补证)。
|
||||
- B2 清单批准后“登记再撤回”会让旧批准重新生效:清单确认新增 `all_submission_ids`,绑定全部提交行(含已撤回)及全部导出,复核后的任何登记、撤回、导出都使批准失效,须重新确认并复核。
|
||||
- 同时补:确认失败人与失败归属复核人另排除全部导出人、提交登记人与清单确认人;清单复核人另排除已撤回提交的登记人;失败归属数据库层限定恰一条核验记录。
|
||||
- 修复后:HTTP 40 组、浏览器 7 组、76 迁移 PASS;修复对应的 5 个变异均变红(“忽略歧义状态标记”首轮未变红,补“标记失败但带处理中”反例后变红);server 4463、prisma validate、client typecheck/build、UI strict、错误码合同、敏感扫描、diff 检查通过。
|
||||
- 第二轮同模型只读复核:PASS_WITH_NOTES,无阻断,B1、B2 修复成立。按其意见加固:作废回单要判为“确定不是本笔”,须匹配结果明确不符、金额大于 0,且付款与收款账号不能都与本批次一致(同账号仅金额不同可能是本笔误读,继续阻断);作废回单逐页核对页数完整;补数据库层反例(清单确认的全部提交、导出集合不可改,已撤回提交不能恢复)。“同账号放行”变异变红。最终:HTTP 40 组、浏览器 7 组、76 迁移,server 4463 等全部通过。
|
||||
- **既有问题(第二轮复核发现,自 09-06 即存在,方向为拦截;已按下条口径修复)**:真实 OCR 只要页面带任何问题标记就整体判为 `needs_review`,失败回单必带 `transaction_failed`,而 `failedReceiptFact` 要求 `recognized`。因此用真实 OCR 时“确认失败”永远返回 409,作废失败回单的放行分支也是死代码。测试夹具伪造了真实识别器不会产出的“已识别且带失败标记”结果,正向用例没有覆盖真实路径。修正需定义失败回单的识别口径(如仅允许唯一问题为 `transaction_failed`),属资金规则,待用户决定;在此之前生产中任何银行失败都无法在系统内确认。
|
||||
- 失败回单识别口径(用户 2026-10-07 同意并入 S1/S2,口径为 4 种失败措辞):
|
||||
- 归一化新增 `transactionStatusRaw`(银行原始状态措辞),随核验结果存储;匹配逻辑不变,报销与成功核销不受影响(相关共享单测 237 项通过)。
|
||||
- 不改顶层识别状态。失败事实另用严格判定 `isClearBankFailure`:单页且页数完整;状态为失败且原始措辞属于“交易失败、转账失败、付款失败、支付失败”;唯一问题标记为 `transaction_failed`;金额、币种、双方账号、流水号齐全;再经与冻结快照匹配。它只用于确认失败,不能用于成功核销,也不绕过提交清单、失败归属与独立复核。“已撤销”“已冲正”“已退汇”不接受,分别留给 S3 指令终态与后续退款对账流程。作废回单中的失败页也按同一判定,否则继续阻断。
|
||||
- 测试改走真实处理链:只替换云端 `BankSlipOCR` 响应,从合成银行字段经 PDF 解析、`recognizePaymentReceipt`、`normalizeBankSlip`、核验存储、匹配与认领。仅两个纵深防御用例显式使用合成归一化结果(作废路径“失败但带处理中标记”、确认路径“失败但另带账号歧义标记”),二者真实归一化无法产出。新增用例:“转账失败”可确认;“已撤销/已冲正/已退汇”、缺流水号、两页回单、金额不符、带额外问题标记均拒绝;作废的“已撤销”回单继续阻断。
|
||||
- 变异:接受任意失败措辞、要求顶层 recognized、作废失败页不按严格判定三者变红;“忽略多余问题标记”“跳过字段完整性”未变红——下游 `matchBankSlip` 对任何剩余问题或缺失字段都会判不匹配,二者为纵深防御,测试无法单独区分。
|
||||
- 结果:HTTP 41 组、浏览器 7 组、76 迁移,server 4463、client typecheck/build、UI strict、错误码合同、敏感扫描、diff、治理检查均通过。真实建行回单接受性仍待验。
|
||||
- 只读复核(同模型):BLOCK 一项——同页两个状态字段(如“交易失败”与“已撤销”,都归一化为失败)不被判歧义,原始措辞只取首个字段,按 OCR 字段顺序可绕过白名单(复核实验证实)。已修:仅当所有状态字段原文完全相同时才记录 `transactionStatusRaw`,否则为空即拒绝;`transactionStatus` 与问题标记不变,报销与成功匹配不受影响。补用例:两种字段顺序的“失败+已撤销”均拒绝、作废的“失败+已撤销”回单继续阻断、“付款失败”“支付失败”正向用例。其余检查(原文可靠性、严格判定、成功路径未放宽、测试走真实链)复核成立。
|
||||
- 复核其余非阻断(留待 S3/S8):导出记录未被手工提交关联时仍可确认失败(S3 须落实每条导出都关联已终止提交或人工确认未提交);失败归属状态与提交登记、清单复核无界面;部分反例未覆盖(导出失败时导出记录回滚、回填 SQL 在有存量时的效果、报销导出不写导出记录)。
|
||||
|
||||
## 2026-10-06 对账方案实施拆分(依据 design 第 3 版;待用户批准,未实施)
|
||||
|
||||
通用规则:
|
||||
- 每步只做新增型迁移,均涉及资金规则,完成后必须只读复核。
|
||||
- 每步可单独验证、单独提交,但**不代表可以单独部署启用**。部署时机另行批准,提交须单独确认。
|
||||
- 验证按改动范围安排:相关的数据库、HTTP、权限与并发回归,加上针对资金守卫的变异测试;改到共享付款路径时扩大回归;阶段完成后再跑完整前后端测试。
|
||||
- 完整的释放条件在 S3 之前尚未实现,所以 S1、S2 不得放宽任何释放结果,只能修正误判并保留拦截(见 S1 第 3 条)。
|
||||
|
||||
| 步骤 | 内容 | 对应 | 依赖 |
|
||||
|---|---|---|---|
|
||||
| S0 前提核实(不写代码) | ① 生产有无真实还款与存量批次(只读查询须用户当轮授权);② 是否至少有 2 名与单据无关、当前有资格的经理或总经理;③ 建设银行能否出具“指令查询”官方 PDF,含查询无结果的情况(用户线下确认)。三项均不默认为已满足;不阻塞本地合成开发,但②③决定 S4 能否启用,①为上线前必须完成的存量核实 | AC16、AC12 | — |
|
||||
| S1 导出与提交记录、确认失败修正 | 1. 新增制单导出记录表(序号、时间、导出人、文件摘要、客户方流水号、金额、快照摘要),每次导出写入。<br>2. 新增银行提交尝试登记(财务登记,关联导出记录与银行指令号),提交清单须经独立复核。<br>3. 确认失败:只计未作废附件,但作废的成功或处理中回单仍阻断;**同时保留拦截**——批次存在多于一条导出记录、提交清单未经复核、或清单中不止一次提交时,一律拒绝确认失败,留待 S3 的逐次终态规则处理。<br>4. 确认失败改按付款后规则:核对冻结快照,不要求出借员工在职,当前操作人按当前资格。<br>5. 存量:保留已知的 `payment_order_export_count`,新增“历史导出汇总”标明逐次明细、文件与银行提交对应关系待核实;该汇总不是一次真实导出或提交,存在它的批次在确认失败与释放时按“提交未明”拦截 | R2、R4、AC9、AC16 | — |
|
||||
| S2 失败归属 | 归属确认新增 `failure` 类,须写明覆盖的银行指令并绑定失败回单证据;存在同金额候选时,确认失败须有已复核的失败归属,否则保持占用;S1 第 3 条的拦截继续有效 | R3、AC10 | S1 |
|
||||
| S3 银行终态确认与释放 | 新增 `terminated` 类与 `confirmed_not_paid` 状态(含触发器);释放条件:已复核提交清单中每一次提交都有覆盖它的终态或失败证据,每条导出记录都关联已终止的提交(或有 S4 的人工确认),且无未解决的成功或处理中事实;之后取代 S1 第 3 条的临时拦截;释放后禁止再导出,页面提示原制单文件不得再导入网银 | R1(银行确认)、AC11 | S1、S2 |
|
||||
| S4 人工确认未提交(Q6) | 新增 `not_submitted` 类与 `confirmed_not_submitted` 状态;全部证据加两名不同的独立复核人;记录与页面标为人工确认;人员不足或材料缺失时继续占用 | R1(人工例外)、AC12 | S3;能否启用取决于 S0 ②③ |
|
||||
| S5 冻结原因与解冻 | 新增冻结原因表(类型、来源、状态、关闭依据);冻结拦截该出借人的新申请,以及其他在途申请的复核与导出,放行事实登记;误报原因经独立复核关闭;全部原因关闭才解冻 | AC13 | —(S6 的前置) |
|
||||
| S6 晚到成功补记 | 晚到事实只增不改、标记原结论被推翻;登记即产生冻结原因;按冻结快照与历史审批核对;逐笔先处理占用冲突,再按差额结清,余额生成待追回原因;补记还款不生成付款批次、不可导出;覆盖已释放与已确认失败未释放两种情况 | R5、AC14 | S3、S5 |
|
||||
| S7 追回与解冻 | 凭到账回单经独立复核登记追回,支持部分追回,全额后关闭对应原因;本期不支持冲抵 | AC15 | S6 |
|
||||
| S8 收口 | 页面整合与手机端走查;恢复手册补充;完整前后端回归;把 AC9–AC16 的验证结果与剩余缺口回填到 PRD 与本文件 | 全部 | S1–S7 |
|
||||
|
||||
## 2026-10-06 C 回单批次归属保守拦截(用户批准实施;本地合成,未提交、未部署)
|
||||
|
||||
规则:同一出借人、同金额的其他借款批次只要曾导出制单或上传回单(含确认失败后已取消的),本批次的匹配回单就不能证明归属。
|
||||
|
||||
在新工单中引用
屏蔽一个用户