feat(recibos): split a receipt, sin fondos per receipt, keep a batch open
Three asks from the 2026-10-08 meeting, all on the OCR receipt queue. - Split: one receipt paid by several customers (411 + 523 sharing water 3014057) posts one charge per part. statement_document_splits holds free amounts that must sum to the receipt in cents, checked on save and again at confirm; "partes iguales" fills them in. Each part's row carries captureRef <docId>#<n>, so the double-post guard still holds. - Sin fondos per receipt: StatementDocument.outstanding, toggled in review. At confirm a line is outstanding if its receipt or the whole check is. - Open batch: POST /statements/batches/:id/files appends scans, numbered after the existing pages; a COMPLETED batch reopens. The confirm card prefills the check the batch was last posted against. Verified on dev against a real batch: 1,892.00 split 946/946 across two customers, a sibling receipt posted outstanding, second confirm refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f655fcbb82
commit
fd5df240bc
9 files changed
+756
-49
No files matched your search
+30
@@ -0,0 +1,30 @@
|
||||
-- Two asks from the 2026-10-08 meeting, both on the OCR receipt queue:
|
||||
-- * sin fondos per receipt, not only per check (`outstanding`);
|
||||
-- * one receipt paid by several customers (411 + 523 sharing a water bill),
|
||||
-- posted as one charge per part.
|
||||
-- AlterTable
|
||||
ALTER TABLE `statement_documents` ADD COLUMN `outstanding` BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `statement_document_splits` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`documentId` VARCHAR(191) NOT NULL,
|
||||
`sequence` INTEGER NOT NULL,
|
||||
`customerId` VARCHAR(191) NOT NULL,
|
||||
`amount` DECIMAL(12, 2) NOT NULL,
|
||||
`postedTransactionId` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `statement_document_splits_postedTransactionId_key`(`postedTransactionId`),
|
||||
UNIQUE INDEX `statement_document_splits_documentId_sequence_key`(`documentId`, `sequence`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `statement_document_splits` ADD CONSTRAINT `statement_document_splits_documentId_fkey` FOREIGN KEY (`documentId`) REFERENCES `statement_documents`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `statement_document_splits` ADD CONSTRAINT `statement_document_splits_customerId_fkey` FOREIGN KEY (`customerId`) REFERENCES `customers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `statement_document_splits` ADD CONSTRAINT `statement_document_splits_postedTransactionId_fkey` FOREIGN KEY (`postedTransactionId`) REFERENCES `transactions`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
@@ -130,7 +130,8 @@ model Customer {
|
||||
transactions Transaction[]
|
||||
|
||||
statementDocuments StatementDocument[]
|
||||
policyOcrDocuments PolicyOcrDocument[] @relation("PolicyOcrDocumentCustomer")
|
||||
statementSplits StatementDocumentSplit[]
|
||||
policyOcrDocuments PolicyOcrDocument[] @relation("PolicyOcrDocumentCustomer")
|
||||
|
||||
emailNotificationLogs EmailNotificationLog[]
|
||||
accountStatusHistory AccountStatusHistory[]
|
||||
@@ -730,6 +731,26 @@ model StatementBatch {
|
||||
}
|
||||
|
||||
/// One statement — one customer, one period — after splitting the batch.
|
||||
/// One customer's share of a split receipt — a water bill two neighbours
|
||||
/// share, an HOA fee spread across a building. Free amounts, no stored
|
||||
/// percentages: the office splits unevenly as often as not.
|
||||
model StatementDocumentSplit {
|
||||
id String @id @default(uuid())
|
||||
documentId String
|
||||
document StatementDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||
/// 1-based; also the `#n` in the posted row's captureRef.
|
||||
sequence Int
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
amount Decimal @db.Decimal(12, 2)
|
||||
|
||||
postedTransactionId String? @unique
|
||||
postedTransaction Transaction? @relation("StatementDocumentSplitTransaction", fields: [postedTransactionId], references: [id])
|
||||
|
||||
@@unique([documentId, sequence])
|
||||
@@map("statement_document_splits")
|
||||
}
|
||||
|
||||
model StatementDocument {
|
||||
id String @id @default(uuid())
|
||||
batchId String
|
||||
@@ -775,6 +796,15 @@ model StatementDocument {
|
||||
postedTransactionId String? @unique
|
||||
postedTransaction Transaction? @relation(fields: [postedTransactionId], references: [id])
|
||||
|
||||
/// Sin fondos for this receipt alone: posted as outstanding even when the
|
||||
/// rest of the check is funded.
|
||||
outstanding Boolean @default(false)
|
||||
|
||||
/// When set, the receipt is paid for by several customers and posts one
|
||||
/// charge per part instead of one to `matchedCustomer`. The parts always sum
|
||||
/// to `extractedAmount`.
|
||||
splits StatementDocumentSplit[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([batchId, pageNumber])
|
||||
@@ -853,7 +883,8 @@ model Transaction {
|
||||
|
||||
/// Set only on OCR-posted rows — the statement page this came from.
|
||||
statementDocument StatementDocument?
|
||||
policyOcrDocument PolicyOcrDocument? @relation("PolicyOcrDocumentTransaction")
|
||||
statementSplit StatementDocumentSplit? @relation("StatementDocumentSplitTransaction")
|
||||
policyOcrDocument PolicyOcrDocument? @relation("PolicyOcrDocumentTransaction")
|
||||
|
||||
@@unique([legacySourceDb, legacySourceTable, legacyId])
|
||||
@@index([customerId, transactionDate])
|
||||
|
||||
Reference in new issue
Block a user