借款登记支持上传原借入回单识别预填(09-17-company-loan-principal-repayment)
- 新增识别与保存接口:可读 PDF 本地解析,必要时有界调用现有回单 OCR;预填上下文加密绑定经办人、文件和 30 分钟 - 适配建行单位客户专用回单表格版式:按位置归行排序、竖排栏头、顶行无标签日期、大小写金额 - 拦截非最终、失败/处理中、多笔交易(含跨页无关联、编号冲突)、非人民币、公司支出和其他收款主体 - 金额、日期、编号整段校验;借款与原件同事务保存,失败回滚并清理对象;同一出借人原件唯一 - 保存绑定所选出借人版本;前端冻结核对时版本,迟到结果与切换账号不覆盖、不重发 - 测试:单测、隔离 HTTP/浏览器、变异;对账浏览器场景固定 UTC;五轮独立复核通过 - 无新增迁移 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -73,6 +73,30 @@ export function createLoanRepayment(body: { requestKey: string; lenderId: string
|
||||
allocations: Array<{ loanId: string; amount: string }> }) {
|
||||
return saveLoanAction<{ item: PaymentBatchDetail }>("/loan-repayments", body);
|
||||
}
|
||||
export type IncomingReceiptPrefill = {
|
||||
context: string; expiresAt: string; ocr: "not_needed" | "used" | "unavailable" | "failed"; pageCount: number;
|
||||
documentKind: "bank_receipt" | "unknown"; direction: "credit" | "debit" | null;
|
||||
blocks: string[]; warnings: string[];
|
||||
fieldIssues: Partial<Record<"amount" | "receivedDate" | "reference" | string, "missing" | "ambiguous" | "invalid">>;
|
||||
sources: Partial<Record<string, "pdf_text" | "ocr">>;
|
||||
fields: { originalPrincipal: string | null; receivedDate: string | null; originReference: string | null;
|
||||
referenceKind: "serial" | "voucher" | null; currency: string | null; payerName: string | null; payerAccountMasked: string | null;
|
||||
payeeName: string | null; payeeAccountMasked: string | null };
|
||||
suggestions: { legalEntityId: string | null; lenderId: string | null };
|
||||
};
|
||||
export function recognizeIncomingReceipt(file: File, signal?: AbortSignal) {
|
||||
const body = new FormData(); body.append("file", file);
|
||||
return apiRequestData<IncomingReceiptPrefill>("/api/company-loans/incoming-receipt/recognize", { method: "POST", body, signal });
|
||||
}
|
||||
export function saveLoanFromIncomingReceipt(file: File, payload: {
|
||||
context: string; requestKey: string; lenderId: string; expectedLenderIdentityVersion: number; expectedLenderAccountVersion: number;
|
||||
originReference: string; originalPrincipal: string;
|
||||
openingOutstandingPrincipal: string; receivedDate: string; balanceAsOf: string; historicalEntry: boolean;
|
||||
originalChecked: true; natureConfirmed: true;
|
||||
}, signal?: AbortSignal) {
|
||||
const body = new FormData(); body.append("payload", JSON.stringify(payload)); body.append("file", file);
|
||||
return apiRequestData<{ item: CompanyLoan; created: boolean }>("/api/company-loans/from-incoming-receipt", { method: "POST", body, signal });
|
||||
}
|
||||
export function uploadLoanEvidence(id: string, kind: string, file: File) {
|
||||
const body = new FormData(); body.append("file", file);
|
||||
return apiRequestData<LoanEvidence>(`/api/company-loans/${id}/evidences/${kind}`, { method: "POST", body });
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"loan.incoming_receipt_invalid": "请上传 5MB 以内、不超过 20 页的单份 PDF 原借入回单;文件损坏或无法安全读取时请改用手工登记。",
|
||||
"loan.incoming_receipt_blocked": "该回单不能作为本笔借入原件:可能是非最终回执、失败或处理中、公司支出、多笔交易、非人民币或收款主体不符。请更换正式入账回单或改用手工登记。",
|
||||
"loan.incoming_receipt_context_invalid": "识别结果已过期,或与当前账号、所选文件不一致,请重新上传并识别。",
|
||||
"loan.incoming_receipt_duplicate": "该原件已用于本出借人的其他借款登记,不能重复登记;请核对已有借款。",
|
||||
"loan.employee_source_incomplete": "员工姓名或收款资料不完整、不可用或银行不受支持,请在员工资料中核对维护。",
|
||||
"loan.employee_source_changed": "员工资料已变化,请重新加载并核对后再保存。",
|
||||
"loan.employee_lender_conflict": "该员工在本公司存在重复、归档或已变化的出借人关联,请核对原记录,不能另建或自动合并。",
|
||||
|
||||
在新工单中引用
屏蔽一个用户