fix: 增加回单核对与付款登记门禁(09-08-reimbursement-payment-closeout)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-15 07:21:55 +08:00
父节点 229867f5fd
当前提交 8d08d46d16
共修改 33 个文件,包含 3691 行新增和 11 行删除
@@ -0,0 +1,57 @@
ALTER TABLE "payment_batch_receipt_attachments" ADD COLUMN "receipt_ocr_version" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "payment_batch_receipt_attachments"
ADD COLUMN "receipt_ocr_state" VARCHAR(32) NOT NULL DEFAULT 'legacy',
ADD COLUMN "receipt_ocr_started_at" TIMESTAMP(3);
CREATE TABLE "payment_receipt_verifications" (
"id" UUID NOT NULL,
"attachment_id" UUID NOT NULL,
"source_sha256" VARCHAR(64) NOT NULL,
"source_version" INTEGER NOT NULL,
"status" VARCHAR(32) NOT NULL,
"pages_expected" INTEGER NOT NULL,
"pages_processed" INTEGER NOT NULL,
"normalized_encrypted" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "payment_receipt_verifications_pkey" PRIMARY KEY ("id"),
CONSTRAINT "payment_receipt_verifications_pages_check" CHECK (
pages_expected >= 0 AND pages_processed BETWEEN 0 AND pages_expected
),
CONSTRAINT "payment_receipt_verifications_attachment_id_fkey" FOREIGN KEY ("attachment_id")
REFERENCES "payment_batch_receipt_attachments"("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE INDEX "payment_receipt_verifications_attachment_id_created_at_idx"
ON "payment_receipt_verifications"("attachment_id", "created_at");
CREATE TABLE "payment_receipt_matches" (
"id" UUID NOT NULL,
"verification_id" UUID NOT NULL,
"item_id" UUID NOT NULL,
"page_number" INTEGER NOT NULL CHECK (page_number BETWEEN 1 AND 20),
"fingerprint" VARCHAR(64) NOT NULL,
"key_id" VARCHAR(64) NOT NULL,
CONSTRAINT "payment_receipt_matches_pkey" PRIMARY KEY ("id"),
CONSTRAINT "payment_receipt_matches_verification_id_fkey" FOREIGN KEY ("verification_id")
REFERENCES "payment_receipt_verifications"("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "payment_receipt_matches_item_id_fkey" FOREIGN KEY ("item_id")
REFERENCES "payment_batch_items"("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE UNIQUE INDEX "payment_receipt_matches_verification_id_page_number_key"
ON "payment_receipt_matches"("verification_id", "page_number");
CREATE UNIQUE INDEX "payment_receipt_matches_verification_id_item_id_key"
ON "payment_receipt_matches"("verification_id", "item_id");
CREATE INDEX "payment_receipt_matches_item_id_idx" ON "payment_receipt_matches"("item_id");
CREATE TABLE "payment_receipt_claims" (
"fingerprint" VARCHAR(64) NOT NULL,
"key_id" VARCHAR(64) NOT NULL,
"item_id" UUID NOT NULL,
"verification_id" UUID NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "payment_receipt_claims_pkey" PRIMARY KEY ("fingerprint"),
CONSTRAINT "payment_receipt_claims_item_id_fkey" FOREIGN KEY ("item_id")
REFERENCES "payment_batch_items"("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "payment_receipt_claims_verification_id_fkey" FOREIGN KEY ("verification_id")
REFERENCES "payment_receipt_verifications"("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE UNIQUE INDEX "payment_receipt_claims_item_id_key" ON "payment_receipt_claims"("item_id");
@@ -0,0 +1,21 @@
CREATE TABLE "payment_receipt_manual_proposals" (
"id" UUID NOT NULL PRIMARY KEY,
"attachment_id" UUID NOT NULL REFERENCES "payment_batch_receipt_attachments"("id") ON DELETE RESTRICT ON UPDATE CASCADE,
"original_verification_id" UUID NOT NULL REFERENCES "payment_receipt_verifications"("id") ON DELETE RESTRICT ON UPDATE CASCADE,
"source_sha256" VARCHAR(64) NOT NULL,
"source_version" INTEGER NOT NULL CHECK ("source_version" > 0),
"status" VARCHAR(32) NOT NULL CHECK ("status" IN ('pending_review', 'association_ready', 'approved', 'rejected')),
"proposed_by" UUID NOT NULL,
"reviewed_by" UUID,
"payload_encrypted" TEXT NOT NULL,
"review_note_encrypted" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"reviewed_at" TIMESTAMP(3),
CONSTRAINT "payment_receipt_manual_review_decision_check" CHECK (
("status" IN ('pending_review', 'association_ready') AND "reviewed_by" IS NULL AND "reviewed_at" IS NULL)
OR ("status" IN ('approved', 'rejected') AND "reviewed_by" IS NOT NULL AND "reviewed_at" IS NOT NULL
AND "reviewed_by" <> "proposed_by")
)
);
CREATE UNIQUE INDEX "payment_receipt_manual_proposals_attachment_id_source_version_key"
ON "payment_receipt_manual_proposals"("attachment_id", "source_version");
@@ -0,0 +1,13 @@
ALTER TABLE "payment_receipt_claims" ADD COLUMN "manual_proposal_id" UUID;
ALTER TABLE "payment_receipt_claims" ADD CONSTRAINT "payment_receipt_claims_manual_proposal_id_fkey"
FOREIGN KEY ("manual_proposal_id") REFERENCES "payment_receipt_manual_proposals"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
CREATE TABLE "payment_receipt_transaction_aliases" (
"fingerprint" VARCHAR(64) PRIMARY KEY,
"key_id" VARCHAR(64) NOT NULL,
"claim_fingerprint" VARCHAR(64) NOT NULL REFERENCES "payment_receipt_claims"("fingerprint") ON DELETE RESTRICT ON UPDATE CASCADE,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX "payment_receipt_transaction_aliases_claim_fingerprint_idx"
ON "payment_receipt_transaction_aliases"("claim_fingerprint");
INSERT INTO "payment_receipt_transaction_aliases" ("fingerprint", "key_id", "claim_fingerprint")
SELECT "fingerprint", "key_id", "fingerprint" FROM "payment_receipt_claims";
+89
查看文件
@@ -1966,6 +1966,8 @@ model PaymentBatchItem {
payment_batch PaymentBatch @relation(fields: [payment_batch_id], references: [id], onDelete: Cascade)
@@index([payment_batch_id, status], map: "idx_payment_batch_items_batch_status")
receipt_matches PaymentReceiptMatch[]
receipt_claim PaymentReceiptClaim?
@@index([entity_id], map: "idx_payment_batch_items_entity_id")
@@map("payment_batch_items")
}
@@ -1993,6 +1995,11 @@ model PaymentBatchReceiptAttachment {
payment_batch PaymentBatch @relation(fields: [payment_batch_id], references: [id], onDelete: Cascade)
uploader Employee @relation("PaymentBatchReceiptAttachmentUploader", fields: [uploaded_by], references: [id], onDelete: Restrict)
verifications PaymentReceiptVerification[]
manual_proposals PaymentReceiptManualProposal[]
receipt_ocr_version Int @default(0)
receipt_ocr_state String @default("legacy") @db.VarChar(32)
receipt_ocr_started_at DateTime?
@@index([payment_batch_id, created_at], map: "idx_payment_batch_receipts_batch_created")
@@index([uploaded_by], map: "idx_payment_batch_receipts_uploader")
@@ -2002,6 +2009,88 @@ model PaymentBatchReceiptAttachment {
@@map("payment_batch_receipt_attachments")
}
model PaymentReceiptVerification {
id String @id @default(uuid()) @db.Uuid
attachment_id String @db.Uuid
source_sha256 String @db.VarChar(64)
source_version Int
status String @db.VarChar(32)
pages_expected Int
pages_processed Int
normalized_encrypted String
created_at DateTime @default(now())
attachment PaymentBatchReceiptAttachment @relation(fields: [attachment_id], references: [id], onDelete: Restrict)
matches PaymentReceiptMatch[]
claims PaymentReceiptClaim[]
manual_proposals PaymentReceiptManualProposal[]
@@index([attachment_id, created_at])
@@map("payment_receipt_verifications")
}
model PaymentReceiptManualProposal {
id String @id @default(uuid()) @db.Uuid
attachment_id String @db.Uuid
original_verification_id String @db.Uuid
source_sha256 String @db.VarChar(64)
source_version Int
status String @db.VarChar(32)
proposed_by String @db.Uuid
reviewed_by String? @db.Uuid
payload_encrypted String
review_note_encrypted String?
created_at DateTime @default(now())
reviewed_at DateTime?
attachment PaymentBatchReceiptAttachment @relation(fields: [attachment_id], references: [id], onDelete: Restrict)
original_verification PaymentReceiptVerification @relation(fields: [original_verification_id], references: [id], onDelete: Restrict)
claims PaymentReceiptClaim[]
@@unique([attachment_id, source_version])
@@map("payment_receipt_manual_proposals")
}
model PaymentReceiptMatch {
id String @id @default(uuid()) @db.Uuid
verification_id String @db.Uuid
item_id String @db.Uuid
page_number Int
fingerprint String @db.VarChar(64)
key_id String @db.VarChar(64)
verification PaymentReceiptVerification @relation(fields: [verification_id], references: [id], onDelete: Restrict)
item PaymentBatchItem @relation(fields: [item_id], references: [id], onDelete: Restrict)
@@unique([verification_id, page_number])
@@unique([verification_id, item_id])
@@index([item_id])
@@map("payment_receipt_matches")
}
model PaymentReceiptClaim {
fingerprint String @id @db.VarChar(64)
key_id String @db.VarChar(64)
item_id String @unique @db.Uuid
verification_id String @db.Uuid
manual_proposal_id String? @db.Uuid
created_at DateTime @default(now())
item PaymentBatchItem @relation(fields: [item_id], references: [id], onDelete: Restrict)
verification PaymentReceiptVerification @relation(fields: [verification_id], references: [id], onDelete: Restrict)
manual_proposal PaymentReceiptManualProposal? @relation(fields: [manual_proposal_id], references: [id], onDelete: Restrict)
aliases PaymentReceiptTransactionAlias[]
@@map("payment_receipt_claims")
}
model PaymentReceiptTransactionAlias {
fingerprint String @id @db.VarChar(64)
key_id String @db.VarChar(64)
claim_fingerprint String @db.VarChar(64)
created_at DateTime @default(now())
claim PaymentReceiptClaim @relation(fields: [claim_fingerprint], references: [fingerprint], onDelete: Restrict)
@@index([claim_fingerprint])
@@map("payment_receipt_transaction_aliases")
}
model PortalAnnouncement {
id String @id @default(uuid()) @db.Uuid
title String