fix: 增加回单核对与付款登记门禁(09-08-reimbursement-payment-closeout)
这个提交包含在:
@@ -70,6 +70,15 @@ export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"payment_batch.invalid_identifier": "付款记录标识已失效,请刷新后重新选择。",
|
||||
"payment_batch.invalid_input": "付款操作内容不完整或格式不正确,请核对页面必填项后重试。",
|
||||
"payment_batch.bank_receipt_required": "银行付款登记前必须上传有效银行回单,请先补充回单。",
|
||||
"payment_batch.receipt_retry_unavailable": "回单状态已变化或仍在识别,请刷新后查看;处理中超过15分钟仍未完成时可重试。",
|
||||
"payment_batch.receipt_independent_review_required": "回单人工核验需要独立审批人复核;制单人、核验人及报销归属人或提交人不能自审。",
|
||||
"payment_batch.receipt_transaction_reused": "回单交易已被使用或存在重复,请核对原回单;不能通过修改参考号重复登记。",
|
||||
"payment_batch.receipt_manual_state_changed": "回单或核验版本已变化,请刷新后重新查看当前状态。",
|
||||
"payment_batch.receipt_manual_verification_required": "回单信息尚不能确认与付款一致,请核对全部页、交易状态、金额、币种及收付款账号;不要直接登记付款。",
|
||||
"payment_batch.receipt_key_unavailable": "回单核验密钥不可用,请联系管理员修复;不要重复识别或登记付款。",
|
||||
"payment_batch.receipt_key_rotation_required": "回单交易记录使用的密钥版本不一致,请管理员完成受控密钥迁移;人工核验不能绕过。",
|
||||
"payment_batch.receipt_snapshot_unsupported": "付款业务或银行快照不支持回单核验,请联系管理员核实;不要重复识别。",
|
||||
"payment_batch.receipt_identity_unresolved": "无法完整确认报销参与人身份,请管理员核实人员关系后再复核;不能自行批准。",
|
||||
"payment_batch.department_unavailable": "当前单据所属部门暂不能使用付款批次,请联系管理员确认处理方式。",
|
||||
"payment_batch.source_not_found": "部分来源单据已不存在或不可用,请刷新后重新选择。",
|
||||
"payment_batch.daily_sequence_exhausted": "今日付款批次编号已用完,请联系管理员处理,勿重复提交。",
|
||||
|
||||
@@ -118,6 +118,12 @@ export type PaymentBatchCandidatesResponse = {
|
||||
};
|
||||
|
||||
export type PaymentBatchReceiptAttachment = {
|
||||
ocrState?: "legacy" | "queued" | "processing" | "ocr_failed" | "needs_review" | "matched"
|
||||
| "pending_review" | "association_ready" | "manual_approved" | "manual_rejected"
|
||||
| "system_failed" | "key_failed" | "snapshot_failed";
|
||||
ocrVersion?: number;
|
||||
ocrStartedAt?: string | null;
|
||||
ocrRetryAvailable?: boolean;
|
||||
id: string;
|
||||
originalName: string;
|
||||
mimeType: string;
|
||||
@@ -276,3 +282,65 @@ export async function voidPaymentBatchReceiptAttachment(batchId: string, attachm
|
||||
{ method: "POST", body: JSON.stringify({ reason }) },
|
||||
);
|
||||
}
|
||||
|
||||
export async function retryPaymentBatchReceiptOcr(batchId: string, attachmentId: string, expectedVersion: number) {
|
||||
return apiRequestData<{ item: { id: string; ocrState: string; ocrVersion: number } }>(
|
||||
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/ocr-retry`,
|
||||
{ method: "POST", body: JSON.stringify({ expectedVersion }) },
|
||||
);
|
||||
}
|
||||
|
||||
export type ReceiptVerificationPage = {
|
||||
pageNumber: number;
|
||||
amount: string | null;
|
||||
currency: string | null;
|
||||
payerAccountMasked: string | null;
|
||||
payeeAccountMasked: string | null;
|
||||
transactionStatus: string | null;
|
||||
reference: string | null;
|
||||
itemId?: string;
|
||||
};
|
||||
|
||||
export type ReceiptVerificationDetail = {
|
||||
canReview?: boolean;
|
||||
version: number;
|
||||
state: string;
|
||||
pagesExpected: number;
|
||||
pages: ReceiptVerificationPage[];
|
||||
proposal: null | {
|
||||
id: string; status: string; proposedBy: string; reviewedBy: string | null; reason: string;
|
||||
changedFields: { pageNumber: number; fields: string[] }[];
|
||||
pages: ReceiptVerificationPage[];
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchReceiptVerification(batchId: string, attachmentId: string, signal?: AbortSignal) {
|
||||
return apiRequestData<{ item: ReceiptVerificationDetail }>(
|
||||
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/manual-verification`, { signal },
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchReceiptReadiness(batchId: string, signal?: AbortSignal) {
|
||||
return apiRequestData<{ item: { ready: boolean } }>(
|
||||
`/api/payment-batches/${batchId}/receipt-readiness`, { signal },
|
||||
);
|
||||
}
|
||||
|
||||
export function saveReceiptVerification(batchId: string, attachmentId: string, body: {
|
||||
expectedVersion: number; reason?: string;
|
||||
pages: { pageNumber: number; itemId: string; fields?: { Name: string; Value: string }[] }[];
|
||||
}) {
|
||||
return apiRequestData<{ item: { id: string; status: string; version: number } }>(
|
||||
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/manual-verification`,
|
||||
{ method: "POST", body: JSON.stringify(body) },
|
||||
);
|
||||
}
|
||||
|
||||
export function reviewReceiptVerification(batchId: string, attachmentId: string, proposalId: string, body: {
|
||||
expectedVersion: number; decision: "approve" | "reject"; reason: string;
|
||||
}) {
|
||||
return apiRequestData<{ item: { id: string; status: string; version: number } }>(
|
||||
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/manual-verification/${proposalId}/review`,
|
||||
{ method: "POST", body: JSON.stringify(body) },
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户