修复在途还款被出借人维护卡死并按审批时资格登记付款后事实(09-17-company-loan-principal-repayment)
- A:出借人存在 reserved 额度占用时,身份关联与收款资料维护在出借人锁内 返回 409 loan.lender_in_flight;页面禁用并提示,修正账户弹窗误导文案 - B:回单 OCR 认领、人工回单参与人、核销按审批时资格认定,不再因原审批人 或出借员工事后离职/撤权、主数据停用而卡死;保留身份核实、版本与付款快照 一致、审批独立性及回单匹配校验;付款前环节仍校验当前资格 - 测试:隔离 PostgreSQL 合成全流程,含确定性并发交错、付款后独立性反例、 离职后经 OCR/人工复核核销、PC/手机按钮禁用;变异测试均变红 - 恢复手册:资金类数据恢复后先暂停付款,对照银行流水补齐并核对占用再开放 - design 按环节修订资格规则 注意:C(同金额回单传错批次错误核销)已复现但尚未修复,借款功能不具备 真实还款验收条件;未推送、未部署。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -35,7 +35,12 @@
|
||||
|
||||
## 4. 权限与回单
|
||||
|
||||
新台账沿财务全账读取与付款审查分离:财务管理台账,审批人只读其有权审批的申请及必要证据,普通员工无全账入口。制单人、提交人、出借收款人身份集合统一传给提交预检/批次复核/人工回单复核/登记重验;在职状态和业务权限不吃历史批准,admin通配不能替代独立身份。
|
||||
新台账沿财务全账读取与付款审查分离:财务管理台账,审批人只读其有权审批的申请及必要证据,普通员工无全账入口。制单人、提交人、出借收款人身份集合统一传给提交预检/批次复核/人工回单复核/登记重验;admin通配不能替代独立身份。
|
||||
|
||||
2026-10-05 修订(取代原“在职状态和业务权限不吃历史批准”):资格按环节区分。
|
||||
- 付款前(新建申请、批次复核、导出制单、确认银行失败):校验当前在职、当前角色/权限、出借人就绪(身份已核实、未归档、法人与银行主数据启用)及出借员工在职。
|
||||
- 付款后(回单 OCR 认领、人工回单参与人、核销登记):原审批人与出借员工的资格以审批时为准,不再复核其当前在职/角色,也不再复核法人、银行主数据当前启用状态。理由:银行已实际付出的事实不能因事后人员或主数据变化而无法登记,否则本金永久占用且未还本金多记。仍须满足:身份已核实;身份/账户版本与提交时一致(在途期间维护被 `loan.lender_in_flight` 阻止);付款快照收款资料一致;`reviewed_by/reviewed_at` 存在且审批人不是制单人、提交人或出借人关联员工(这三者之间可重合);回单的账号、金额、币种、状态与交易唯一性匹配。审批时资格的依据是复核路由当时已用当前角色校验并写入 `reviewed_by/reviewed_at`。
|
||||
- 当前操作人(上传、人工复核、执行、会计交接)始终按当前权限与独立性校验。
|
||||
|
||||
银行备注采用短本金语义,年份不一致时不生成误导性单一年份;长事由留OA。对现有建行制单模板使用显式业务用途映射,原报销用途不变。回单识别范围只有经过新来源快照和权限校验才可扩展,不能无条件对新业务调用腾讯;真实样本/云调用另批,未验证前不宣称自动支持。
|
||||
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
## 2026-10-05 复核阻断 B1 修复与回单归属复现(用户批准 A/B/D/恢复手册 + C 复现调研;本地合成,未提交、未部署)
|
||||
|
||||
背景:同模型只读复核(页面与剩余资金流程)结论 BLOCK:在途还款期间做出借人身份/账户维护会使 identity_version 变化,之后登记付款、取消、确认失败全部被拒,已付款项永久无法登记;导出后原审批人或出借员工离职同样卡死。另报回单批次归属未校验。
|
||||
|
||||
实施:
|
||||
- A(`services/companyLoans.js` `assertLenderNotInFlight`,`routes/companyLoans.js` 两个维护接口):出借人锁内只要存在 `reserved` 分配即 409 `loan.lender_in_flight`。新建还款先锁出借人再占用,故与维护互斥;导出/上传不新增占用,不需改上传锁。出借人列表新增 `hasInFlightRepayment`,页面禁用两按钮并提示;账户弹窗误导文案已改。原因码已登记三处(server registry、client 映射、内容断言夹具)。
|
||||
- B(`services/companyLoanPayments.js` `postPayment`):回单 OCR 认领、人工回单参与人、核销三处按审批时资格认定——不再复核原审批人当前在职/角色、出借员工当前在职及主数据启用;仍校验身份已核实、身份/账户版本与提交时一致、付款快照收款资料一致、`reviewed_by/reviewed_at` 存在且审批人不是制单人/提交人/出借人关联员工(这三者之间可重合)。审批时资格依据:复核路由当时已用 `activeReviewer` 校验当前角色后写入 `reviewed_by/reviewed_at`。新建、复核、导出制单、确认银行失败保持当前资格校验;当前操作人仍须有当前权限与独立性。
|
||||
- D:`scripts/qa/company-loan-http-fixture.cjs` 原“在途改身份/改账户”两段按新规则重写(改用新出借人,原出借人因负例循环留有 unknown 占用、按设计不可维护),新增:在途(导出前后)维护被拒、取消/结清后可维护;维护与新建并发 3 轮不留陈旧版本占用;导出后原审批人与出借员工离职,经 OCR 与经人工复核两条路径均从上传回单走到核销成功;付款前导出仍要求审批人当前有资格。浏览器脚本新增 PC/手机在途时两按钮禁用。
|
||||
- 恢复手册:`docs/trialrun/OPS7运维交接Runbook.md` §5 增加资金类数据恢复后先暂停付款、对照银行流水补齐、核对余额与在途占用再开放。
|
||||
|
||||
验证(Docker 隔离 PostgreSQL,`--network none`,合成数据;需 `PAYMENT_ORDER_PYTHON_BIN` 指向 BIFF8 venv):
|
||||
- `RUN_ISOLATED_LOAN_DB_TEST=1 RUN_ISOLATED_LOAN_BROWSER=1 node scripts/qa/test-company-loan-db.cjs`:73 迁移、9 组 DB 约束、HTTP 21 组、浏览器 5 组全部 PASS,清理 PASS。
|
||||
- 4 个源码变异(OCR/核销/人工复核改回当前资格、去掉在途拦截)均使测试变红,源码已恢复并逐字比对。
|
||||
- server `npm test` 226 文件 4452 通过;`prisma:validate`、client `typecheck`/`build`、UI strict、错误码合同、文档审计(0 error)、敏感扫描、`git diff --check` 通过。
|
||||
- 未覆盖:独立复核仍为同模型;真实银行/OCR 样本;生产存量(待核实)。
|
||||
|
||||
C 复现结果(合成全流程,测试只记录结果不设守卫):
|
||||
- 同一出借人两笔 7.00 在途,P 的成功回单传到 Q:`matched`,Q 执行 200 核销成功——**错误核销成立**。
|
||||
- 旧 6.00 批次确认失败并取消后新建同额批次,旧批次晚到的成功回单传到新批次:`matched`,执行 200 核销成功——**错误核销成立**。
|
||||
- 调研:BIFF8 制单每行已有唯一客户流水号 `批次号-R00n`;借款附言固定为“归还借款本金”(报销附言含单号);OCR 只提取金额、币种、双方账号、状态和银行流水号,不提取客户流水号/附言;建行回单是否保留二者无仓库证据,需获准的真实回单样本确认。修法待用户确认,未实施。
|
||||
|
||||
同模型只读复核(同日,独立上下文):本轮 A、B 修复范围 PASS_WITH_NOTES,无阻断;**借款功能整体尚未通过完整验收**(C 错误核销已复现,仍阻断真实还款)。A 互斥成立(reserved 只经 `prepareLoanRepayment` 在出借人锁后创建;幂等重放不新建;迁移触发器禁止 settled/released 回退);B 未放宽过头(`reviewed_by` 全仓唯一写入点在复核路由且已过 `activeReviewer`;出借人无归档写入;停用主数据不降低伪造回单门槛)。按其第 4、5 条补强:
|
||||
- 并发测试改为确定性交错:独立连接持出借人行锁,两请求按指定顺序排队后释放;新建在先 → 维护 409 `loan.lender_in_flight`,维护在先 → 两者 200 且新建采用新版本;两种顺序均无陈旧占用。“检查挪到加锁前”变异确定性变红。
|
||||
- 付款后独立性反例:把已匹配回单批次的 `reviewed_by` 改为制单人或出借员工后执行均 403,恢复后正常核销;“去掉 `assertApprovedIndependently`”变异变红。
|
||||
复核另记残余(未处理,计入下列阻塞):人工回单复核人在复核后、执行前离职会使执行被拒(与报销共用,推测可作废重传绕过,未验证);确认银行失败路径在出借员工离职后仍卡死,叠加 A 会长期冻结该出借人维护;付款后关闭提交人部门的付款批次灰度开关会拦执行(重新打开即可恢复)。
|
||||
|
||||
仍阻止真实还款验收:C 修复;unknown 状态人工解除、作废附件计数、晚到结果对账流程;B 未覆盖确认银行失败路径在出借员工离职后的卡死;AC5 真实样本;独立复核。
|
||||
|
||||
## 2026-09-18 共享付款回归与交接补充
|
||||
|
||||
09-06本地受管重验已覆盖报销主管/财务审批、财务同页进度、批次独立复核、回单人工更正/独立复核、付款核销及AP/票据归档两条主链;完整r6为3通过/1入口矩阵失败,矩阵另在r7通过。此证据支持共享付款旧来源回归,不代替本task真实银行样本、独立资金链审阅或公网验收。当前官方current在09-06,本task仍in_progress;上批review-candidate-r1保护保持。独立问题及真实批次/银行/K1/K2清单见retained-gates-closeout-20260917的independent-and-external-readiness.json,均未进行真实查询或操作。
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
## Current Status
|
||||
|
||||
T1–T4本地合成实施已完成,AC1–AC4、AC6–AC7有本地证据;AC5真实样本及AC8独立/恢复/发布条件保留开放。精确回执见implement顶部。未部署,不声称真实银行接受或真实批次已通知。
|
||||
T1–T4本地合成实施已完成,AC1–AC4、AC6–AC7有本地证据;AC5真实样本及AC8独立/恢复/发布条件保留开放。精确回执见implement顶部。2026-09-27 已随 `70af0c6b` 上线(借款迁移已应用);生产是否已有真实还款待核实,不声称真实银行接受或真实批次已通知。2026-10-05 同模型只读复核报出阻断 B1(在途还款被出借人维护卡死)与回单批次归属缺陷,处理进展见 implement 顶部;真实还款验收在其收口前不得进行。
|
||||
|
||||
## Planning Status(历史时点,已由顶部实施接续替代)
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户