借款对账 S4–S8:人工未提交、晚到补记、追回与界面收口(09-17-company-loan-principal-repayment)
新增三类证据与双人复核的人工未提交、逐原因冻结、晚到付款逐借款补记和实际退款追回;补齐对账与补证恢复界面,保护已消费依据、最终归属重验及全局交易唯一性。 验证:81 迁移、HTTP 95 组、浏览器 13 组、数据库约束 9 组;13 项有效变异被捕获,部分由下游或数据库保护检出。构建、类型、合同和治理检查通过;修复后同模型只读静态复核通过。 真实回单待验、整体业务验收未通过;S0 存量、人员、银行材料与业务独立验收未完成。3 个新增迁移须纳入获批发布清单。本次仅本地提交,不推送、不部署。
这个提交包含在:
@@ -17,7 +17,7 @@ export type CompanyLoan = {
|
||||
outstanding: string; available: string; reserved: string; settled: string;
|
||||
evidences: LoanEvidence[]; accountingConfirmationRef: string | null;
|
||||
};
|
||||
export type LoanRepayment = {
|
||||
export type LoanRepayment = LoanReconciliation & {
|
||||
id: string; repaymentNo: string; lenderId: string; lenderName: string; amount: string; reason: string;
|
||||
bankResultState: string; paymentBatchId: string; status: string;
|
||||
accountingHandoffRef: string | null; accountingHandoffAt: string | null;
|
||||
@@ -61,3 +61,43 @@ export function uploadLoanEvidence(id: string, kind: string, file: File) {
|
||||
export function downloadLoanEvidence(id: string, evidenceId: string, reason: string, paymentBatchId?: string) {
|
||||
return apiRequestBlob(`/api/company-loans/${id}/evidences/${evidenceId}/download`, { method: "POST", body: JSON.stringify({ reason, paymentBatchId }) });
|
||||
}
|
||||
|
||||
export type LoanReconciliation = {
|
||||
failureAttributions?: Array<{ id: string; status: string; proposedBy: string; basisNote: string; exclusionNote: string; coveredSubmissionIds: string[]; failureScope: string; receiptAttachmentIds: string[] }>;
|
||||
bankSubmissions?: {
|
||||
exports: Array<{ id: string; sequence: number; exportedAt: string; exportedBy: string; customerSerials: string[] }>;
|
||||
legacy: { knownExportCount: number; unknownFacts: string[] } | null;
|
||||
submissions: Array<{ id: string; exportId: string | null; bankInstructionRef: string; status: string; submittedAt: string; registeredBy: string; note: string }>;
|
||||
attestation: { id: string; status: string; proposedBy: string; stale: boolean } | null;
|
||||
};
|
||||
terminal?: {
|
||||
evidences: Array<{ id: string; originalName: string; evidenceKind: string; voided: boolean }>;
|
||||
facts: Array<{ id: string; submissionId: string; status: string; proposedBy: string; kind: string; note: string; terminalStatus: string; evidenceId: string | null; receiptAttachmentId: string | null; resolvesAttachmentIds: string[] }>;
|
||||
};
|
||||
releaseRecovery?: {
|
||||
current: { id: string; sequence: number; status: string; stale: boolean } | null;
|
||||
history: Array<{ id: string; sequence: number; status: string; startedBy: string; reason: string }>;
|
||||
};
|
||||
notSubmitted?: { history: Array<{ id: string; sequence: number; status: string; proposedBy: string; stale: boolean;
|
||||
payload: { exportIds: string[]; queryEvidenceId: string; statementEvidenceId: string; exporterStatements: Array<{ exporterId: string; evidenceId: string }>; coverageFrom: string; coverageThrough: string; note: string }; reviews: Array<{ reviewedBy: string; decision: string; reason: string }> }> };
|
||||
freeze?: { frozen: boolean; reasons: Array<{ id: string; kind: string; status: string; paymentBatchId: string }> };
|
||||
bankResults?: { events: Array<{ id: string; kind: string; parentId: string | null; evidenceId: string; status: string; amount: string;
|
||||
proposedBy: string; reviewedBy: string | null; reviewNote: string | null; outstandingReturn: string | null;
|
||||
settlements: Array<{ loanId: string; bankAmount: string; settledAmount: string; receivableAmount: string }> }> };
|
||||
};
|
||||
export function uploadLoanBankEvidence(batchId: string, kind: string, file: File) {
|
||||
const body = new FormData(); body.append("file", file);
|
||||
return apiRequestData<{ id: string }>(`/api/payment-batches/${batchId}/loan-terminal-evidences?kind=${encodeURIComponent(kind)}`, { method: "POST", body });
|
||||
}
|
||||
export function downloadLoanBankEvidence(batchId: string, id: string, reason: string) {
|
||||
return apiRequestBlob(`/api/payment-batches/${batchId}/loan-terminal-evidences/${id}/file`, { headers: { "x-access-reason": encodeURIComponent(reason) } });
|
||||
}
|
||||
|
||||
export function readLoanBankResultDetails(batchId: string, id: string, reason: string) {
|
||||
return apiRequestData<{ fields: Array<{ Name: string; Value: string }>; attributionNote: string; exclusionNote: string }>(`/api/payment-batches/${batchId}/loan-bank-results/${id}/details`, { headers: { "x-access-reason": encodeURIComponent(reason) } });
|
||||
}
|
||||
|
||||
export function downloadLoanReconciliationReceipt(batchId: string, attachmentId: string, reason: string) {
|
||||
const query = new URLSearchParams({ purpose: "loan_reconciliation", reason });
|
||||
return apiRequestBlob(`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/file?${query}`);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"loan.bank_result_reconcile_required": "银行对账事实、归属或证据尚不满足条件,请补齐材料并独立复核;占用或冻结继续保留。",
|
||||
"loan.lender_frozen": "该出借人存在尚未处理的银行对账事项,暂停新还款、复核和导出;仍可登记银行事实并对账。",
|
||||
"loan.not_submitted_invalid": "人工未提交的证据、清单或双人复核不满足要求,请补齐后重新登记复核。",
|
||||
"loan.recovery_required": "释放依据已进入补证流程,须完成最新一轮独立复核后才能取消释放。",
|
||||
"loan.recovery_unavailable": "当前批次或恢复轮次不支持此操作;已释放、已核销的还款不能重新开启。",
|
||||
"loan.recovery_exists": "已有补证中或有效的恢复复核,请继续处理当前轮次。",
|
||||
|
||||
在新工单中引用
屏蔽一个用户