借款对账 SR-A/B:补证恢复与复核后释放(09-17-company-loan-principal-repayment)

保留原银行终态及依据,以恢复轮次绑定材料、候选和独立性;最终取消重验并原子消费批准、释放占用和记录审计。新增增量迁移、HTTP 回归及数据库保护。

验证:78 个迁移、HTTP 72 组、既有浏览器 7 组、数据库约束 9 组通过;7 个变异均被抓住。服务端首跑 4470 通过、6 失败、1 跳过,补齐 BIFF8 测试依赖后相关 146 项通过,未重跑完整套件。

限制:inline 主会话自查不等于独立复核,独立复核待完成;界面未完成,不能单独部署;真实回单待验。新恢复界面留待 S8,S4 另批,整体借款功能尚未完成验收。本次仅本地提交,不推送、不部署、不查询生产。
这个提交包含在:
2026-10-07 20:07:57 +08:00
父节点 14aee82d5c
当前提交 2e00d30fd0
共修改 15 个文件,包含 960 行新增和 9 行删除
+64
查看文件
@@ -745,6 +745,12 @@ model Employee {
loan_terminal_facts_proposed LoanBankTerminalFact[] @relation("LoanTerminalFactProposer")
loan_terminal_facts_reviewed LoanBankTerminalFact[] @relation("LoanTerminalFactReviewer")
@@map("employees")
loan_recoveries_started LoanReleaseRecovery[] @relation("LoanReleaseStarter")
loan_recoveries_submitted LoanReleaseRecovery[] @relation("LoanReleaseSubmitter")
loan_recoveries_reviewed LoanReleaseRecovery[] @relation("LoanReleaseReviewer")
loan_recoveries_consumed LoanReleaseRecovery[] @relation("LoanReleaseConsumer")
loan_recovery_events LoanReleaseRecoveryEvent[]
}
model PasswordResetCode {
@@ -1992,6 +1998,9 @@ model PaymentBatch {
loan_terminal_facts LoanBankTerminalFact[]
loan_failure_evidence_uses LoanFailureEvidenceUse[]
@@map("payment_batches")
loan_release_recoveries LoanReleaseRecovery[]
loan_material_revision LoanReleaseMaterialRevision?
}
model PaymentBatchItem {
@@ -4180,3 +4189,58 @@ model LoanFailureEvidenceUse {
@@map("loan_failure_evidence_uses")
}
// SR: original bank conclusions remain immutable; recovery controls release only.
model LoanReleaseRecovery {
id String @id @default(uuid()) @db.Uuid
payment_batch_id String @db.Uuid
sequence Int
previous_id String? @db.Uuid
request_key String @db.Uuid
original_state String @db.VarChar(32)
original_ref String
reason String @db.VarChar(1000)
started_by String @db.Uuid
started_at DateTime @default(now())
status String @default("collecting") @db.VarChar(16)
submitted_by String? @db.Uuid
submitted_at DateTime?
snapshot_json Json?
reviewed_by String? @db.Uuid
reviewed_at DateTime?
review_note String? @db.VarChar(500)
ended_at DateTime?
cancel_request_key String? @db.Uuid
consumed_by String? @db.Uuid
payment_batch PaymentBatch @relation(fields: [payment_batch_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
starter Employee @relation("LoanReleaseStarter", fields: [started_by], references: [id], onDelete: Restrict, onUpdate: NoAction)
submitter Employee? @relation("LoanReleaseSubmitter", fields: [submitted_by], references: [id], onDelete: Restrict, onUpdate: NoAction)
reviewer Employee? @relation("LoanReleaseReviewer", fields: [reviewed_by], references: [id], onDelete: Restrict, onUpdate: NoAction)
consumer Employee? @relation("LoanReleaseConsumer", fields: [consumed_by], references: [id], onDelete: Restrict, onUpdate: NoAction)
previous LoanReleaseRecovery? @relation("LoanRecoveryPrevious", fields: [previous_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
next LoanReleaseRecovery[] @relation("LoanRecoveryPrevious")
events LoanReleaseRecoveryEvent[]
@@unique([payment_batch_id, sequence])
@@unique([payment_batch_id, request_key])
@@map("loan_release_recoveries")
}
model LoanReleaseRecoveryEvent {
id String @id @default(uuid()) @db.Uuid
recovery_id String @db.Uuid
action String @db.VarChar(32)
actor_id String @db.Uuid
note String @db.VarChar(1000)
created_at DateTime @default(now())
recovery LoanReleaseRecovery @relation(fields: [recovery_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
actor Employee @relation(fields: [actor_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
@@index([recovery_id])
@@map("loan_release_recovery_events")
}
model LoanReleaseMaterialRevision {
payment_batch_id String @id @db.Uuid
revision BigInt @default(0)
payment_batch PaymentBatch @relation(fields: [payment_batch_id], references: [id], onDelete: Restrict, onUpdate: NoAction)
@@map("loan_release_material_revisions")
}