feat(recibos): split a receipt, sin fondos per receipt, keep a batch open
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m57s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m22s

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:
rmancinasandClaude Opus 5.5 committed 2026-10-12 19:20:20 -07:00
1 parent f655fcbb82
commit fd5df240bc
9 files changed
+756 -49

No files matched your search

+20
View File
@@ -1,4 +1,6 @@
import {
ArrayMaxSize,
IsArray,
IsBoolean,
IsEnum,
IsInt,
@@ -6,7 +8,9 @@ import {
IsOptional,
IsString,
MinLength,
ValidateNested,
} from "class-validator";
import { Type } from "class-transformer";
import { Currency, ServiceKind, StatementDocumentStatus } from "@jorgecuadros/database";
export class CreateStatementBatchDto {
@@ -27,6 +31,22 @@ export class ReviewDocumentDto {
@IsOptional()
@IsEnum(StatementDocumentStatus)
status?: Extract<StatementDocumentStatus, "MATCHED" | "NEEDS_REVIEW" | "CONFIRMED">;
/** Sin fondos for this receipt alone. */
@IsOptional() @IsBoolean() outstanding?: boolean;
}
export class SplitPartDto {
@IsString() @MinLength(1) customerId!: string;
@IsNumber() amount!: number;
}
/** A receipt's split; an empty list removes it. */
export class SetSplitsDto {
@IsArray()
@ArrayMaxSize(20)
@ValidateNested({ each: true })
@Type(() => SplitPartDto)
parts!: SplitPartDto[];
}
/**