完善付款回单凭证核验与本地重检(09-17-company-loan-principal-repayment)
区分正式付款回单、非最终回执与未知凭证,在核验构建、人工证据重建和最终核销时使用同一份有效依据。非最终说明、入账方向、状态矛盾及重复交易不能经人工覆盖。 正式回单无字面成功状态时,允许财务按原件实际记账支出、日期及说明提出核验,由本批次适用的确认人确认;不补造银行成功文字。旧核验可显式本地重检,新版本与审计原子保存,不重新外发 OCR,不沿用旧人工批准或改写已核销历史。 本地验证:82 个迁移、借款 HTTP 116 组、关联电脑/手机 18 组、共享回单数据库与真实认证 38 组、共享报销页面限定重验 24 项、最新回单/报销定向 292 项通过;9 个资金守卫变异均被捕获。服务端全量 4554 项通过、1 项既有可选跳过,后续合成数据及零页详情改动由最新定向验证覆盖。 本次无新增迁移,新增本地 PDF 文本解析依赖。后续对本提交运行完整 verify-candidate,实际候选与结果以本轮回执为准。 限制:独立代码复核及真实还款验收待完成;历史材料预验不证明真实还本或银行提交对应。未推送、未部署、未访问生产或写入真实业务数据。
这个提交包含在:
@@ -803,6 +803,8 @@ export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"payment_batch.receipt_transaction_reused": "回单交易已被使用或存在重复,请核对原回单;不能通过修改参考号重复登记。",
|
||||
"payment_batch.receipt_manual_state_changed": "回单或核验版本已变化,请刷新后重新查看当前状态。",
|
||||
"payment_batch.receipt_manual_verification_required": "回单信息尚不能确认与付款一致,请核对全部页、交易状态、金额、币种及收付款账号;不要直接登记付款。",
|
||||
"payment_batch.receipt_document_check_required": "这份回单尚未检查凭证性质,请在核验详情中更新凭证核验后重新核对。",
|
||||
"payment_batch.receipt_final_evidence_required": "当前材料不能证明本笔付款已完成。请核对正式回单的实际付款或记账依据;非最终回执、入账方向不符或证据不明时不能登记付款。",
|
||||
"payment_batch.receipt_key_unavailable": "回单核验密钥不可用,请联系管理员修复;不要重复识别或登记付款。",
|
||||
"payment_batch.receipt_key_rotation_required": "回单交易记录使用的密钥版本不一致,请管理员完成受控密钥迁移;人工核验不能绕过。",
|
||||
"payment_batch.receipt_snapshot_unsupported": "付款业务或银行快照不支持回单核验,请联系管理员核实;不要重复识别。",
|
||||
|
||||
@@ -293,6 +293,23 @@ export async function retryPaymentBatchReceiptOcr(batchId: string, attachmentId:
|
||||
);
|
||||
}
|
||||
|
||||
export type ReceiptPaymentProof = {
|
||||
basis: "explicit_success" | "booked_debit";
|
||||
originalChecked: true;
|
||||
note: string;
|
||||
bookedOn?: string;
|
||||
};
|
||||
|
||||
export type ReceiptDocumentEvidence = {
|
||||
version: number;
|
||||
kind: "bank_receipt" | "non_final" | "unknown";
|
||||
source: "pdf_text" | "image" | "unreadable" | "manual";
|
||||
direction: "debit" | "credit" | null;
|
||||
explicitSuccess: boolean;
|
||||
issues: string[];
|
||||
proof?: ReceiptPaymentProof;
|
||||
};
|
||||
|
||||
export type ReceiptVerificationPage = {
|
||||
pageNumber: number;
|
||||
amount: string | null;
|
||||
@@ -302,10 +319,12 @@ export type ReceiptVerificationPage = {
|
||||
transactionStatus: string | null;
|
||||
reference: string | null;
|
||||
itemId?: string;
|
||||
document?: ReceiptDocumentEvidence | null;
|
||||
};
|
||||
|
||||
export type ReceiptVerificationDetail = {
|
||||
canReview?: boolean;
|
||||
documentInspectionRequired?: boolean;
|
||||
version: number;
|
||||
state: string;
|
||||
pagesExpected: number;
|
||||
@@ -331,7 +350,7 @@ export function fetchReceiptReadiness(batchId: string, signal?: AbortSignal) {
|
||||
|
||||
export function saveReceiptVerification(batchId: string, attachmentId: string, body: {
|
||||
expectedVersion: number; reason?: string;
|
||||
pages: { pageNumber: number; itemId: string; fields?: { Name: string; Value: string }[] }[];
|
||||
pages: { pageNumber: number; itemId: string; fields?: { Name: string; Value: string }[]; paymentProof?: ReceiptPaymentProof }[];
|
||||
}) {
|
||||
return apiRequestData<{ item: { id: string; status: string; version: number } }>(
|
||||
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/manual-verification`,
|
||||
@@ -339,6 +358,13 @@ export function saveReceiptVerification(batchId: string, attachmentId: string, b
|
||||
);
|
||||
}
|
||||
|
||||
export function checkReceiptDocument(batchId: string, attachmentId: string, expectedVersion: number) {
|
||||
return apiRequestData<{ item: { id: string; status: string; version: number } }>(
|
||||
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/document-check`,
|
||||
{ method: "POST", body: JSON.stringify({ expectedVersion }) },
|
||||
);
|
||||
}
|
||||
|
||||
export function reviewReceiptVerification(batchId: string, attachmentId: string, proposalId: string, body: {
|
||||
expectedVersion: number; decision: "approve" | "reject"; reason: string;
|
||||
}) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户