完善借款、编号、付款与预算并发及验收(09-06-release-gate-integrity)

汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。
统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。
纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。

关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。
预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。
完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
这个提交包含在:
2026-09-19 13:11:04 +08:00
父节点 729d90576f
当前提交 1f8d5b658a
共修改 367 个文件,包含 21079 行新增和 12287 行删除
+170
查看文件
@@ -273,6 +273,7 @@ enum PaymentBatchStatus {
enum PaymentBatchEntityType {
reimbursement
travel_order
loan_repayment
}
enum BudgetControlRuleEntityType {
@@ -731,6 +732,9 @@ model Employee {
@@index([legal_entity_id], map: "idx_employees_legal_entity")
@@index([bank_institution_id], map: "idx_employees_bank_institution")
@@index([direct_manager_id])
loan_lender_identities LoanLender[]
loan_evidence_uploads LoanEvidence[]
@@map("employees")
}
@@ -949,6 +953,26 @@ model ReimbursementTemplate {
@@map("reimbursement_templates")
}
// Absent policy means legacy numbering. Migration never activates v2.
model BusinessNumberPolicy {
version Int @id
effective_at DateTime
enabled Boolean @default(false)
updated_at DateTime @default(now()) @updatedAt
updated_by String @db.Uuid
reference String
@@map("business_number_policies")
}
model BusinessNumberCounter {
key String @id
current_number BigInt @default(0)
updated_at DateTime @default(now()) @updatedAt
@@map("business_number_counters")
}
model DocumentSequence {
document_type String @id
prefix String
@@ -997,6 +1021,8 @@ model LegalEntity {
@@index([enabled, archived_at], map: "idx_legal_entities_status")
@@index([default_holiday_calendar_id], map: "idx_legal_entities_default_calendar")
loan_lenders LoanLender[]
@@map("legal_entities")
}
@@ -1016,6 +1042,8 @@ model BankInstitution {
payment_accounts PaymentAccount[]
@@index([enabled, archived_at], map: "idx_bank_institutions_status")
loan_lenders LoanLender[]
@@map("bank_institutions")
}
@@ -1168,6 +1196,7 @@ model Supplier {
}
model PurchaseRequest {
numbering_version Int @default(1)
id String @id @default(uuid()) @db.Uuid
request_no String? @unique
applicant_id String @db.Uuid
@@ -1901,6 +1930,8 @@ model EntityRiskFlag {
}
model PaymentBatch {
loan_repayment_id String? @db.Uuid
loan_repayment LoanRepayment? @relation(fields: [loan_repayment_id], references: [id], onDelete: Restrict)
id String @id @default(uuid()) @db.Uuid
batch_no String @unique @db.VarChar(64)
entity_type PaymentBatchEntityType
@@ -3809,3 +3840,142 @@ model ApprovalTransfer {
@@index([to_approver_id])
@@map("approval_transfers")
}
model LoanLender {
id String @id @default(uuid()) @db.Uuid
legal_entity_id String @db.Uuid
name String @db.VarChar(120)
employee_id String? @db.Uuid
identity_status String @default("unverified") @db.VarChar(16)
identity_version Int @default(1)
account_version Int @default(1)
identity_confirmed_by String? @db.Uuid
identity_confirmed_at DateTime?
identity_confirmation String?
bank_institution_id String @db.Uuid
bank_name String @db.VarChar(200)
bank_account_encrypted String
account_name_encrypted String
archived_at DateTime?
created_at DateTime @default(now())
created_by String @db.Uuid
updated_at DateTime @updatedAt
updated_by String @db.Uuid
request_key String @db.Uuid
legal_entity LegalEntity @relation(fields: [legal_entity_id], references: [id], onDelete: Restrict)
employee Employee? @relation(fields: [employee_id], references: [id], onDelete: Restrict)
bank_institution BankInstitution @relation(fields: [bank_institution_id], references: [id], onDelete: Restrict)
loans CompanyLoan[]
repayments LoanRepayment[]
@@unique([legal_entity_id, id])
@@unique([created_by, request_key])
@@index([employee_id])
@@map("loan_lenders")
}
model CompanyLoan {
id String @id @default(uuid()) @db.Uuid
loan_no String @unique @db.VarChar(64)
origin_reference String @db.VarChar(200)
legal_entity_id String @db.Uuid
lender_id String @db.Uuid
currency_code String @default("CNY") @db.VarChar(8)
original_principal Decimal @db.Decimal(14, 2)
opening_outstanding_principal Decimal @db.Decimal(14, 2)
received_date DateTime @db.Date
balance_as_of DateTime @db.Date
historical_entry Boolean
confirmation_status String @default("unconfirmed") @db.VarChar(16)
version Int @default(1)
confirmed_by String? @db.Uuid
confirmed_at DateTime?
accounting_confirmation_ref String? @db.VarChar(200)
created_at DateTime @default(now())
created_by String @db.Uuid
updated_at DateTime @updatedAt
updated_by String @db.Uuid
request_key String @db.Uuid
lender LoanLender @relation(fields: [legal_entity_id, lender_id], references: [legal_entity_id, id], onDelete: Restrict)
allocations LoanRepaymentAllocation[]
evidences LoanEvidence[]
@@unique([created_by, request_key])
@@unique([legal_entity_id, lender_id, origin_reference])
@@index([lender_id, confirmation_status, received_date])
@@map("company_loans")
}
model LoanRepayment {
id String @id @default(uuid()) @db.Uuid
repayment_no String @unique @db.VarChar(64)
legal_entity_id String @db.Uuid
lender_id String @db.Uuid
currency_code String @default("CNY") @db.VarChar(8)
reason String @db.VarChar(500)
amount Decimal @db.Decimal(14, 2)
version Int @default(1)
submitted_by String @db.Uuid
submitted_at DateTime @default(now())
lender_identity_version Int
lender_account_version Int
payee_snapshot_json Json
bank_result_state String @default("not_sent") @db.VarChar(24)
bank_result_ref String? @db.VarChar(500)
accounting_handoff_by String? @db.Uuid
accounting_handoff_at DateTime?
accounting_handoff_ref String? @db.VarChar(200)
request_key String @db.Uuid
lender LoanLender @relation(fields: [legal_entity_id, lender_id], references: [legal_entity_id, id], onDelete: Restrict)
allocations LoanRepaymentAllocation[]
batches PaymentBatch[]
@@unique([submitted_by, request_key])
@@index([lender_id, submitted_at])
@@map("loan_repayments")
}
model LoanRepaymentAllocation {
id String @id @default(uuid()) @db.Uuid
repayment_id String @db.Uuid
loan_id String @db.Uuid
amount Decimal @db.Decimal(14, 2)
loan_version Int
reservation_state String @default("reserved") @db.VarChar(16)
settled_at DateTime?
released_at DateTime?
release_reason String? @db.VarChar(500)
repayment LoanRepayment @relation(fields: [repayment_id], references: [id], onDelete: Restrict)
loan CompanyLoan @relation(fields: [loan_id], references: [id], onDelete: Restrict)
@@unique([repayment_id, loan_id])
@@index([loan_id, reservation_state])
@@map("loan_repayment_allocations")
}
model LoanEvidence {
id String @id @default(uuid()) @db.Uuid
loan_id String @db.Uuid
kind String @db.VarChar(32)
original_name String @db.VarChar(255)
storage_path String
mime_type String @db.VarChar(100)
file_size Int
file_sha256 String @db.VarChar(64)
governance_status String @default("pending") @db.VarChar(16)
uploaded_by String @db.Uuid
uploaded_at DateTime @default(now())
archived_at DateTime?
archive_reason String? @db.VarChar(500)
loan CompanyLoan @relation(fields: [loan_id], references: [id], onDelete: Restrict)
uploader Employee @relation(fields: [uploaded_by], references: [id], onDelete: Restrict)
@@unique([loan_id, file_sha256, kind])
@@map("loan_evidences")
}