支持财务经办与公司代表关联方还款确认(09-17-company-loan-principal-repayment)
公司只有财务和出借收款经理两名办理人,新增限定公司、出借人和借款范围的关联方确认模式。 仅豁免指定公司代表的收款人关联;经办、自审、证据、交易归属、资金占用及 Q6 限制保留。 贯通付款、人工回单、银行对账、补证恢复、晚到补记与追回;电脑和手机显示实际确认身份。 授权版本、付款与对账停用事件及历史决定只增不改,新增迁移 20261009010000_loan_related_approval。 本地合成验证:82 迁移、HTTP 109 组、浏览器 33 组、数据库约束 9 组通过;13 个有效资金守卫变异均捕获。 已有服务端全量 4519 项通过、1 项原有跳过,后续定向 77 项通过;提示收尾另有 908 项合同测试及前端构建通过。 按用户授权提交后对干净候选运行正式 verify-candidate,不沿用旧候选回执。 限制:独立代码复核待完成(inline 模式);真实回单待验。本次仅本地提交,不推送、不部署。 生成关联决定后不能直接降回不理解该模式的旧代码;真实授权、录入、生产核实和付款仍须明确批准。
这个提交包含在:
@@ -18,6 +18,14 @@ export type CompanyLoan = {
|
||||
evidences: LoanEvidence[]; accountingConfirmationRef: string | null;
|
||||
};
|
||||
export type LoanRepayment = LoanReconciliation & {
|
||||
approvalMode?: "independent" | "related_party"; authorizedApproverId?: string | null;
|
||||
paymentDecision?: "approve" | "reject" | null;
|
||||
authorizationId?: string | null; authorizationVersion?: number | null;
|
||||
actionCapabilities?: null | {
|
||||
canPrepare: boolean; canRecordFacts: boolean; canApprovePayment: boolean; canExport: boolean;
|
||||
canReviewReconciliation: boolean; canRelease: boolean; canReadPayeeAccount: boolean;
|
||||
canUseNotSubmitted: boolean; paymentAuthorized: boolean; reconciliationAuthorized: boolean;
|
||||
};
|
||||
id: string; repaymentNo: string; lenderId: string; lenderName: string; amount: string; reason: string;
|
||||
bankResultState: string; paymentBatchId: string; status: string;
|
||||
accountingHandoffRef: string | null; accountingHandoffAt: string | null;
|
||||
@@ -58,8 +66,23 @@ 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 });
|
||||
}
|
||||
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 function downloadLoanEvidence(id: string, evidenceId: string, reason: string, paymentBatchId?: string, authorizationId?: string) {
|
||||
return apiRequestBlob(`/api/company-loans/${id}/evidences/${evidenceId}/download`, { method: "POST", body: JSON.stringify({ reason, paymentBatchId, authorizationId }) });
|
||||
}
|
||||
|
||||
export type LoanRelatedAuthorization = {
|
||||
id: string; lenderId: string; legalEntityId: string; approverId: string; version: number;
|
||||
basis: string; proposedBy: string; proposedAt: string; loanIds: string[];
|
||||
confirmed: boolean; current: boolean; paymentEnabled: boolean; reconcileEnabled: boolean;
|
||||
lenderName: string; legalEntityName: string; approverName: string;
|
||||
actions: { confirm: boolean; stopPayment: boolean; stopReconcile: boolean };
|
||||
events: Array<{ id: string; action: string; actorId: string; reason: string; createdAt: string }>;
|
||||
};
|
||||
export function fetchLoanRelatedAuthorizations(page: number, signal?: AbortSignal) {
|
||||
return apiRequestData<{ items: LoanRelatedAuthorization[]; total: number }>(`/api/loan-related-authorizations?page=${page}&pageSize=30`, { signal });
|
||||
}
|
||||
export function fetchLoanRelatedAuthorization(id: string, signal?: AbortSignal) {
|
||||
return apiRequestData<LoanRelatedAuthorization & { loans: CompanyLoan[] }>(`/api/loan-related-authorizations/${id}`, { signal });
|
||||
}
|
||||
|
||||
export type LoanReconciliation = {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"loan.bank_result_reconcile_required": "银行对账事实、归属或证据尚不满足条件,请补齐材料并独立复核;占用或冻结继续保留。",
|
||||
"loan.related_authorization_required": "本次操作没有有效的关联方公司授权,请核对授权范围和当前付款或对账权限。",
|
||||
"loan.related_scope_invalid": "关联方授权、身份、借款范围或历史决定不一致,请刷新并核对;不能转换原审批模式。",
|
||||
"loan.related_actor_conflict": "关联方确认须由指定公司有权人完成,且不能兼任本批经办、上传、录入或提案人。",
|
||||
"loan.related_q6_unavailable": "关联方模式不支持人工确认未提交;没有符合要求的银行终态证据时,额度继续占用。",
|
||||
"loan.bank_result_reconcile_required": "银行对账事实、归属或证据尚不满足条件,请补齐材料并按本批次规则完成确认;占用或冻结继续保留。",
|
||||
"loan.lender_frozen": "该出借人存在尚未处理的银行对账事项,暂停新还款、复核和导出;仍可登记银行事实并对账。",
|
||||
"loan.not_submitted_invalid": "人工未提交的证据、清单或双人复核不满足要求,请补齐后重新登记复核。",
|
||||
"loan.recovery_required": "释放依据已进入补证流程,须完成最新一轮独立复核后才能取消释放。",
|
||||
"loan.recovery_required": "释放依据已进入补证流程,须按本批次规则完成最新一轮确认后才能取消释放。",
|
||||
"loan.recovery_unavailable": "当前批次或恢复轮次不支持此操作;已释放、已核销的还款不能重新开启。",
|
||||
"loan.recovery_exists": "已有补证中或有效的恢复复核,请继续处理当前轮次。",
|
||||
"loan.recovery_stale": "补证材料、提交清单或候选批次已变化,请重新发起恢复并独立复核。",
|
||||
"loan.recovery_stale": "补证材料、提交清单或候选批次已变化,请重新发起恢复并按本批次规则完成确认。",
|
||||
|
||||
"integration.erp_retired": "ERP 集成已停用,历史记录仅供查询,不能重新投递。",
|
||||
"loan.invalid_input": "借款信息不符合要求,请检查必填项、金额和日期。",
|
||||
@@ -21,17 +25,17 @@ export const ERROR_REASON_MESSAGES: Record<string, string> = {
|
||||
"loan.balance_unconfirmed": "借款余额尚未核实,不能申请归还本金。",
|
||||
"loan.allocation_invalid": "分配金额超过可还本金,或借款不属于同一公司和出借人,请重新核对。",
|
||||
"loan.reviewer_required": "没有符合独立复核要求的经理或总经理;制单人、提交人和收款人不能复核。",
|
||||
"loan.independent_review_required": "本次操作须由具备业务权限的独立人员完成,制单人、提交人或收款人不能自审。",
|
||||
"loan.independent_review_required": "当前人员不符合本批次确认规则,请核对业务权限、确认模式及经办身份冲突。",
|
||||
"loan.bank_state_unsafe": "银行付款状态尚未明确,请先查证;当前不能释放额度或再次付款。",
|
||||
"loan.handoff_not_ready": "本金付款尚未成功登记,或已完成会计交接,请核对付款状态。",
|
||||
"loan.payment_not_ready": "还款申请或付款状态已变化,请刷新核对后继续。",
|
||||
"loan.lender_in_flight": "该出借人有还款正在办理,处理完成或安全取消后才能修改身份关联和收款资料。",
|
||||
"loan.receipt_attribution_required": "该回单可能属于同一出借人的其他同金额还款,须登记归属依据并经独立复核,确认前不能核销或释放额度。",
|
||||
"loan.receipt_attribution_required": "该回单可能属于同一出借人的其他同金额还款,须登记归属依据并按本批次规则完成确认,确认前不能核销或释放额度。",
|
||||
"loan.receipt_attribution_not_required": "本还款没有同金额的其他候选批次,无需确认回单归属。",
|
||||
"loan.receipt_attribution_unavailable": "尚无可用于确认归属的已核验银行回单,请先完成回单识别或人工核对。",
|
||||
"loan.receipt_attribution_exists": "本还款已有待复核或已通过的回单归属确认,请勿重复提交。",
|
||||
"loan.submission_required": "须先登记本批次实际向银行提交的指令,才能确认提交清单。",
|
||||
"loan.submission_list_unverified": "银行提交清单尚未经独立复核,或复核后已有变化,须先完成提交清单复核。",
|
||||
"loan.submission_list_unverified": "银行提交清单尚未完成有效确认,或确认后已有变化,须按本批次规则重新确认清单。",
|
||||
"loan.submission_history_unverified": "该批次为历史批次,导出明细及与银行提交的对应关系待核实,当前不能确认失败或释放额度。",
|
||||
"loan.multiple_submissions_unresolved": "该批次有多次导出或多次银行提交,须逐次取得终态证据,当前不能确认失败。",
|
||||
"loan.voided_receipt_unresolved": "已作废的回单曾显示付款成功或处理中,须先查明该笔付款结果,当前不能确认失败。",
|
||||
|
||||
@@ -51,11 +51,12 @@ export type PaymentBatch = {
|
||||
thresholdAmount: string;
|
||||
requiredReviewCount: number;
|
||||
completedReviewCount: number;
|
||||
nextStep: "first_review" | "ready_for_execute";
|
||||
nextStep: "first_review" | "ready_for_execute" | "returned";
|
||||
makerCheckerRequired: boolean;
|
||||
reviewerPermission: string;
|
||||
};
|
||||
reviewComment: string | null;
|
||||
relatedPaymentDecision?: "approve" | "reject" | null;
|
||||
secondaryReviewComment: string | null;
|
||||
executionSummaryJson: Record<string, unknown> | null;
|
||||
receiptAttachmentCount?: number;
|
||||
@@ -202,7 +203,7 @@ export async function updatePaymentBatchItem(batchId: string, itemId: string, pa
|
||||
);
|
||||
}
|
||||
|
||||
export async function reviewPaymentBatch(batchId: string, payload: { comment?: string; resolveRiskFlagIds?: string[] } = {}) {
|
||||
export async function reviewPaymentBatch(batchId: string, payload: { decision?: "approve" | "reject"; comment?: string; resolveRiskFlagIds?: string[] } = {}) {
|
||||
return apiRequestData<{ item: PaymentBatch }>(`/api/payment-batches/${batchId}/review`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
@@ -310,7 +311,7 @@ export type ReceiptVerificationDetail = {
|
||||
pagesExpected: number;
|
||||
pages: ReceiptVerificationPage[];
|
||||
proposal: null | {
|
||||
id: string; status: string; proposedBy: string; reviewedBy: string | null; reason: string;
|
||||
id: string; status: string; proposedBy: string; reviewedBy: string | null; reason: string; approvalMode?: "independent" | "related_party";
|
||||
changedFields: { pageNumber: number; fields: string[] }[];
|
||||
pages: ReceiptVerificationPage[];
|
||||
};
|
||||
|
||||
在新工单中引用
屏蔽一个用户