补齐人工回单验收用例的原件依据(09-17-company-loan-principal-repayment)

报销付款主链路手工录入回单字段时,显式提交合成正式回单的原件依据和已核对声明,不再依赖受限本地解析一定成功。保留拒绝自审、独立复核、最终付款和下游收口断言。

记录安全的凭证类别、来源及失败业务原因,避免只留下通用 VALID_001。首轮完整候选的 371 项浏览器通过及 1 项失败原样保留;因原记录缺少具体原因,不将解析超时追认为已确认根因。

验证:回单服务端定向 93 项通过;受管隔离环境的报销付款主链路通过并完成核销、下游收口及自有资源清理;任务、流程、文档、敏感和差异检查通过。

仅修改测试 helper、规约和任务记录,应用资金规则、解析限时、数据库及正式门禁范围未改。后续对本提交重新运行完整 verify-candidate。

独立代码复核及真实还款验收待完成;未推送、未部署、未访问生产或写入真实业务数据。
这个提交包含在:
2026-10-09 07:24:16 +08:00
父节点 5a7a13524e
当前提交 18315d1183
共修改 4 个文件,包含 37 行新增和 4 行删除
@@ -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<Detail>(api, base)).item.state, {timeout: 60_000}).toBe("ocr_failed");
const { item: detail } = await apiGetData<Detail>(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: "合成独立复核"};