补齐人工回单验收用例的原件依据(09-17-company-loan-principal-repayment)
报销付款主链路手工录入回单字段时,显式提交合成正式回单的原件依据和已核对声明,不再依赖受限本地解析一定成功。保留拒绝自审、独立复核、最终付款和下游收口断言。 记录安全的凭证类别、来源及失败业务原因,避免只留下通用 VALID_001。首轮完整候选的 371 项浏览器通过及 1 项失败原样保留;因原记录缺少具体原因,不将解析超时追认为已确认根因。 验证:回单服务端定向 93 项通过;受管隔离环境的报销付款主链路通过并完成核销、下游收口及自有资源清理;任务、流程、文档、敏感和差异检查通过。 仅修改测试 helper、规约和任务记录,应用资金规则、解析限时、数据库及正式门禁范围未改。后续对本提交重新运行完整 verify-candidate。 独立代码复核及真实还款验收待完成;未推送、未部署、未访问生产或写入真实业务数据。
这个提交包含在:
@@ -142,6 +142,10 @@ QA script patterns over new abstractions.
|
|||||||
wording. New proof always needs the batch's applicable confirmation mode.
|
wording. New proof always needs the batch's applicable confirmation mode.
|
||||||
Non-final disclaimers, incoming direction and known contradictory facts cannot
|
Non-final disclaimers, incoming direction and known contradictory facts cannot
|
||||||
be removed by manual corrections. Manual-source evidence is not automatic OCR.
|
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.
|
- Old unconsumed evidence without document inspection cannot default to final.
|
||||||
Finance may explicitly POST `receipt-attachments/:attachmentId/document-check`
|
Finance may explicitly POST `receipt-attachments/:attachmentId/document-check`
|
||||||
with `expectedVersion`; use the existing batch/claim/attachment locks, scope and
|
with `expectedVersion`; use the existing batch/claim/attachment locks, scope and
|
||||||
|
|||||||
@@ -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 本地提交与新候选验证(独立代码复核待完成)
|
## 2026-10-09 N3 本地提交与新候选验证(独立代码复核待完成)
|
||||||
|
|
||||||
用户在 N1–N2 完成回执后明确“继续下一步”,本轮按已说明的 N3 接续本地提交和新候选验证。提交范围为已核对的 31 项改动;源码、测试及规约与上一轮最终清单逐项摘要一致,本轮仅补本节和 PRD 接续记录。提交、验证的实际结果以本轮回执及 Git 记录为准,不提前记为通过。
|
用户在 N1–N2 完成回执后明确“继续下一步”,本轮按已说明的 N3 接续本地提交和新候选验证。提交范围为已核对的 31 项改动;源码、测试及规约与上一轮最终清单逐项摘要一致,本轮仅补本节和 PRD 接续记录。提交、验证的实际结果以本轮回执及 Git 记录为准,不提前记为通过。
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
## 2026-10-09 N3 本地提交及新候选验证接续
|
## 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 等开放条件不提前勾选。
|
当前 inline 指令禁止派 implement/check 子代理;独立代码复核与真实还款验收继续待完成。提交和候选验证不代表生产启用,未授权推送、部署、生产访问或真实业务写入。下方 N1–N2“未提交”为上一轮历史时点;整体任务保持 `in_progress`,AC5、AC8、AC27 等开放条件不提前勾选。
|
||||||
|
|
||||||
|
|||||||
@@ -40,14 +40,27 @@ async function prepareBankFacts(api: APIRequestContext, employeeId: string) {
|
|||||||
|
|
||||||
async function manualReceipt(api: APIRequestContext, reviewer: APIRequestContext, batchId: string, itemId: string, attachmentId: 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`;
|
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<Detail>(api, base)).item.state, {timeout: 60_000}).toBe("ocr_failed");
|
await expect.poll(async () => (await apiGetData<Detail>(api, base)).item.state, {timeout: 60_000}).toBe("ocr_failed");
|
||||||
const { item: detail } = await apiGetData<Detail>(api, base);
|
const { item: detail } = await apiGetData<Detail>(api, base);
|
||||||
expect(detail.pagesExpected).toBe(1);
|
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,
|
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}))}],
|
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");
|
expect(proposal.status).toBe("pending_review");
|
||||||
const reviewPath = `${base}/${proposal.id}/review`;
|
const reviewPath = `${base}/${proposal.id}/review`;
|
||||||
const body = {expectedVersion: proposal.version, decision: "approve", reason: "合成独立复核"};
|
const body = {expectedVersion: proposal.version, decision: "approve", reason: "合成独立复核"};
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户