借款还款同金额回单须经独立复核确认归属后方可核销(09-17-company-loan-principal-repayment)
- 同一出借人同金额的其他批次曾导出或上传回单(含确认失败后已取消的)时, 最终核销入口要求已复核的归属确认;OCR 匹配与人工回单复核均不能绕过, 证据不足保持待对账,本金与占用不变;存在候选时失败回单不能释放占用 - 新表 loan_receipt_attributions(迁移 20261006010000):绑定交易指纹、 回单核验与人工录入证据、当时候选集合;依据类型不含时间吻合;复核人须 当前有资格且独立于登记人、批次创建人、提交人、导出人、回单上传人、 人工录入人与出借员工;失效记录在重新登记时标为 superseded,历史只增不改 - 借款回单上传取出借人锁,与核销候选检查串行;串行只保证按当时已知候选 判断,核销之后才出现的新证据留待晚到结果对账流程处理 - 付款就绪检查反映归属要求;页面登记/复核/失效重登,写明同额分期每期需确认 - 测试:两个错误核销复现改为回归断言;证据绑定、确定性并发两种顺序、 PC 浏览器完整登记复核重登流程;变异测试均变红 注意:借款功能尚不具备真实还款验收条件(失败回单与对账流程、离职后确认 失败、真实回单样本、手机端弹窗、独立复核待完成);未推送、未部署。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -735,6 +735,8 @@ model Employee {
|
||||
loan_lender_identities LoanLender[]
|
||||
loan_evidence_uploads LoanEvidence[]
|
||||
|
||||
loan_receipt_attributions_proposed LoanReceiptAttribution[] @relation("LoanReceiptAttributionProposer")
|
||||
loan_receipt_attributions_reviewed LoanReceiptAttribution[] @relation("LoanReceiptAttributionReviewer")
|
||||
@@map("employees")
|
||||
}
|
||||
|
||||
@@ -1974,6 +1976,7 @@ model PaymentBatch {
|
||||
@@index([legal_entity_id], map: "idx_payment_batches_legal_entity")
|
||||
@@index([payment_account_id], map: "idx_payment_batches_payment_account")
|
||||
@@index([payment_order_last_exported_by], map: "idx_payment_batches_order_exported_by")
|
||||
loan_receipt_attributions LoanReceiptAttribution[]
|
||||
@@map("payment_batches")
|
||||
}
|
||||
|
||||
@@ -3979,3 +3982,34 @@ model LoanEvidence {
|
||||
@@unique([loan_id, file_sha256, kind])
|
||||
@@map("loan_evidences")
|
||||
}
|
||||
|
||||
// Independently reviewed receipt-to-batch attribution for ambiguous loan
|
||||
// repayments (same lender and amount as another exported/receipted batch).
|
||||
// Partial unique index (one proposed/approved per batch) and history trigger
|
||||
// live in migration 20261006010000_loan_receipt_attribution.
|
||||
model LoanReceiptAttribution {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
payment_batch_id String @db.Uuid
|
||||
fingerprint String @db.VarChar(64)
|
||||
key_id String @db.VarChar(64)
|
||||
verification_ids Json
|
||||
manual_proposal_ids Json
|
||||
competitor_batch_ids Json
|
||||
basis_kind String @db.VarChar(32)
|
||||
basis_note String @db.VarChar(1000)
|
||||
exclusion_note String @db.VarChar(1000)
|
||||
status String @default("proposed") @db.VarChar(16)
|
||||
proposed_by String @db.Uuid
|
||||
proposed_at DateTime @default(now())
|
||||
reviewed_by String? @db.Uuid
|
||||
reviewed_at DateTime?
|
||||
review_note String? @db.VarChar(500)
|
||||
superseded_at DateTime?
|
||||
|
||||
payment_batch PaymentBatch @relation(fields: [payment_batch_id], references: [id], onDelete: Restrict)
|
||||
proposer Employee @relation("LoanReceiptAttributionProposer", fields: [proposed_by], references: [id], onDelete: Restrict)
|
||||
reviewer Employee? @relation("LoanReceiptAttributionReviewer", fields: [reviewed_by], references: [id], onDelete: Restrict)
|
||||
|
||||
@@index([payment_batch_id, fingerprint], map: "loan_receipt_attributions_batch_fingerprint_idx")
|
||||
@@map("loan_receipt_attributions")
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户