From 18315d1183f8d9e998fafe169be612af6d52d33c Mon Sep 17 00:00:00 2001 From: shunli Date: Fri, 9 Oct 2026 07:24:16 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E4=BA=BA=E5=B7=A5=E5=9B=9E?= =?UTF-8?q?=E5=8D=95=E9=AA=8C=E6=94=B6=E7=94=A8=E4=BE=8B=E7=9A=84=E5=8E=9F?= =?UTF-8?q?=E4=BB=B6=E4=BE=9D=E6=8D=AE=EF=BC=8809-17-company-loan-principa?= =?UTF-8?q?l-repayment=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 报销付款主链路手工录入回单字段时,显式提交合成正式回单的原件依据和已核对声明,不再依赖受限本地解析一定成功。保留拒绝自审、独立复核、最终付款和下游收口断言。 记录安全的凭证类别、来源及失败业务原因,避免只留下通用 VALID_001。首轮完整候选的 371 项浏览器通过及 1 项失败原样保留;因原记录缺少具体原因,不将解析超时追认为已确认根因。 验证:回单服务端定向 93 项通过;受管隔离环境的报销付款主链路通过并完成核销、下游收口及自有资源清理;任务、流程、文档、敏感和差异检查通过。 仅修改测试 helper、规约和任务记录,应用资金规则、解析限时、数据库及正式门禁范围未改。后续对本提交重新运行完整 verify-candidate。 独立代码复核及真实还款验收待完成;未推送、未部署、未访问生产或写入真实业务数据。 --- .../spec/hyb-oa/backend-fastify-prisma.md | 4 ++++ .../implement.md | 14 ++++++++++++++ .../prd.md | 4 +++- .../e2e/helpers/reimbursementPaymentFlow.ts | 19 ++++++++++++++++--- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/.trellis/spec/hyb-oa/backend-fastify-prisma.md b/.trellis/spec/hyb-oa/backend-fastify-prisma.md index 3b529615..fd224f4e 100644 --- a/.trellis/spec/hyb-oa/backend-fastify-prisma.md +++ b/.trellis/spec/hyb-oa/backend-fastify-prisma.md @@ -142,6 +142,10 @@ QA script patterns over new abstractions. wording. New proof always needs the batch's applicable confirmation mode. Non-final disclaimers, incoming direction and known contradictory facts cannot be removed by manual corrections. Manual-source evidence is not automatic OCR. + Browser fixtures entering synthetic receipt facts manually must supply the + checked-original proof supported by that fixture's actual contents; bounded + parsing may legitimately stay unknown. Report only safe document categories + and allowlisted business reason codes on failure, never raw banking fields. - Old unconsumed evidence without document inspection cannot default to final. Finance may explicitly POST `receipt-attachments/:attachmentId/document-check` with `expectedVersion`; use the existing batch/claim/attachment locks, scope and diff --git a/.trellis/tasks/09-17-company-loan-principal-repayment/implement.md b/.trellis/tasks/09-17-company-loan-principal-repayment/implement.md index de2ba16a..16911f3c 100644 --- a/.trellis/tasks/09-17-company-loan-principal-repayment/implement.md +++ b/.trellis/tasks/09-17-company-loan-principal-repayment/implement.md @@ -1,3 +1,17 @@ +## 2026-10-09 N3 首轮结果及人工核验测试补充 + +本地提交 `5a7a1352` 包含 N1–N2 的 31 个文件。首轮 `verify-candidate` 已完成且结果为 **FAIL**:static、82 迁移、服务端 4558 通过/1 项既有可选跳过、业务回归通过;浏览器 371 通过、1 失败、无跳过或重试。失败为报销付款批次主链路在人工核验提交时收到 HTTP 409、`VALID_001`,尚未执行付款。首轮完整回执和环境清理记录保留在 `artifacts/release-gate/loan-receipt-n3-20261009/`。 + +- 原测试的错误包装只保留通用文案,没有留下 `reasonCode` 或凭证分类;不能事后将本地解析超时写成已确认根因。失败摘要见准备目录的 `first-failure-summary.json`。 +- 已确认的测试缺口:该链路人工录入全部付款字段,却未提交 `paymentProof`,依赖自动凭证分类恰好成功。现有服务端确定性回归证明:凭证未知时缺少明确原件依据必须拒绝,补充有效依据后仍须复核;本地受限解析允许返回未知。 +- 本轮补全合成正式回单实际具有的“原件明确成功”依据和原件已核对声明,同时记录凭证类别/来源及失败时的允许列举业务原因。保留字段对应、拒绝自审、独立复核、最终执行和下游收口断言,不修改应用资金规则、解析限时或正式门禁范围。 +- 修正后的受管限定重验通过:报销付款主链路 1 项,实际完成核销及下游收口,环境及自有资源清理 PASS;回单服务端 3 文件、93 项通过。本次实际解析类别为 `bank_receipt/pdf_text`,未知凭证分支由既有确定性服务端反例证明,不凭这次定向通过倒推首轮解析结果。证据为准备目录中的 `retained-manual-proof/`、`retained-manual-proof.log` 和 `guard-targeted.json`。 +- 修复提交范围为 4 个文件:人工核验测试 helper、后端测试规约及 PRD/implement;应用服务、页面、数据库及门禁脚本均未更改。保持本地提交,不推送;新候选与完整复验结果见下述 r2 回执及其 `local-followup.json`。 +- 先在受管新数据库限定重验报销付款主链路,并复用/核对已有未知凭证与不可覆盖限制的服务端反例;通过后形成新的本地修复提交,不 amend 原提交。随后对干净的新候选重新运行完整 `verify-candidate`,本轮复验回执固定为 `artifacts/release-gate/loan-receipt-n3-20261009-r2/`,结果以该回执为准,不提前写 PASS。 +- 独立代码复核仍受当前 inline 指令限制而待完成;真实还款及生产启用未验,未推送、未部署、未访问生产或外发银行原件。验证期间不修改正在验证的输入。 + +下方 N3 安排及 N1–N2 回执保留各自历史时点;最新候选、修复和复验结论由本节所列回执记录。 + ## 2026-10-09 N3 本地提交与新候选验证(独立代码复核待完成) 用户在 N1–N2 完成回执后明确“继续下一步”,本轮按已说明的 N3 接续本地提交和新候选验证。提交范围为已核对的 31 项改动;源码、测试及规约与上一轮最终清单逐项摘要一致,本轮仅补本节和 PRD 接续记录。提交、验证的实际结果以本轮回执及 Git 记录为准,不提前记为通过。 diff --git a/.trellis/tasks/09-17-company-loan-principal-repayment/prd.md b/.trellis/tasks/09-17-company-loan-principal-repayment/prd.md index 726950b6..02696712 100644 --- a/.trellis/tasks/09-17-company-loan-principal-repayment/prd.md +++ b/.trellis/tasks/09-17-company-loan-principal-repayment/prd.md @@ -2,7 +2,9 @@ ## 2026-10-09 N3 本地提交及新候选验证接续 -用户明确“继续下一步”,沿已列明的 N3 处理 N1–N2 的 31 项本地改动:核对上一轮验证清单、补任务记录、单独本地提交并对干净的新候选运行 `verify-candidate`。范围、命令及失败处置见 implement 顶部;本轮实际候选及结果以 `artifacts/release-gate/loan-receipt-n3-20261009/` 为准,不沿用 `ce11dea9` 的通过结论。 +N1–N2 已本地提交为 `5a7a1352`。首轮完整候选验证为 FAIL:服务端、构建及业务回归通过,浏览器 371 通过/1 失败;人工核验测试提交返回 409,未执行付款。原测试未记录具体业务原因,不能追认解析超时为根因;现已补其确定缺失的人工原件依据及安全诊断,保留全部资金断言。复验安排见 implement 顶部,后续新候选实际结果以 `artifacts/release-gate/loan-receipt-n3-20261009-r2/` 为准,首轮失败回执不覆盖、不改写。 + +用户明确“继续下一步”,沿已列明的 N3 处理 N1–N2 的 31 项本地改动:核对上一轮验证清单、补任务记录、单独本地提交并对干净的新候选运行 `verify-candidate`。范围、命令及失败处置见 implement 顶部;首轮实际候选及结果以 `artifacts/release-gate/loan-receipt-n3-20261009/` 为准,不沿用 `ce11dea9` 的通过结论。 当前 inline 指令禁止派 implement/check 子代理;独立代码复核与真实还款验收继续待完成。提交和候选验证不代表生产启用,未授权推送、部署、生产访问或真实业务写入。下方 N1–N2“未提交”为上一轮历史时点;整体任务保持 `in_progress`,AC5、AC8、AC27 等开放条件不提前勾选。 diff --git a/client/tests/e2e/helpers/reimbursementPaymentFlow.ts b/client/tests/e2e/helpers/reimbursementPaymentFlow.ts index 22c40080..8ad03c47 100644 --- a/client/tests/e2e/helpers/reimbursementPaymentFlow.ts +++ b/client/tests/e2e/helpers/reimbursementPaymentFlow.ts @@ -40,14 +40,27 @@ async function prepareBankFacts(api: APIRequestContext, employeeId: string) { async function manualReceipt(api: APIRequestContext, reviewer: APIRequestContext, batchId: string, itemId: string, attachmentId: string) { const base = `/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/manual-verification`; - type Detail = { item: {version: number; state: string; pagesExpected: number} }; + type Detail = { item: {version: number; state: string; pagesExpected: number; + pages: Array<{document?: {kind: "bank_receipt" | "non_final" | "unknown"; source: "pdf_text" | "image" | "unreadable" | "manual"} | null}>} }; await expect.poll(async () => (await apiGetData(api, base)).item.state, {timeout: 60_000}).toBe("ocr_failed"); const { item: detail } = await apiGetData(api, base); expect(detail.pagesExpected).toBe(1); - const { item: proposal } = await apiPostData<{item: {id: string; version: number; status: string}}>(api, base, { + console.info("[reimbursement-payment-flow]", JSON.stringify({stage: "receipt-manual-source", + state: detail.state, pagesExpected: detail.pagesExpected, + documentKind: detail.pages[0]?.document?.kind ?? null, documentSource: detail.pages[0]?.document?.source ?? null})); + const response = await api.post(base, {data: { expectedVersion: detail.version, reason: "合成回单逐项核对,不代表真实银行付款", pages: [{pageNumber: 1, itemId, + // This fixture owns a formal success receipt and manually enters its facts. + // A bounded local parser may remain unknown; record the actual manual basis + // rather than depend on a successful automatic document classification. + paymentProof: {basis: "explicit_success", originalChecked: true, note: "合成正式回单明确显示交易成功,已核对完整原件与付款字段"}, fields: Object.entries({小写金额: "10.00", 币别: "CNY", 付款人账号: payer, 收款人账号: payee, 凭证号: `SYNTHETIC-${batchId}`, 单据状态: "交易成功"}).map(([Name, Value]) => ({Name, Value}))}], - }); + }}); + const result = await response.json(); + const reason = result.error?.details?.reasonCode; + const safeReason = typeof reason === "string" && /^payment_batch\.[a-z_]{1,100}$/.test(reason) ? reason : "unavailable"; + expect(response.ok(), `synthetic manual receipt HTTP ${response.status()} reason=${safeReason}`).toBe(true); + const proposal = result.data.item as {id: string; version: number; status: string}; expect(proposal.status).toBe("pending_review"); const reviewPath = `${base}/${proposal.id}/review`; const body = {expectedVersion: proposal.version, decision: "approve", reason: "合成独立复核"};