import { ArrayMaxSize, ArrayMinSize, IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString, MinLength, ValidateNested, } from "class-validator"; import { Type } from "class-transformer"; import { Currency, TransactionDomain } from "@jorgecuadros/database"; /** * A new ledger movement. `amount` is signed: negative = cargo (charge), * positive = abono (credit) — the module's sign convention. Booked movements * are never edited; a mistake is corrected by voiding and re-capturing. */ export class CreateMovementDto { @IsString() @MinLength(1) customerId!: string; @IsEnum(TransactionDomain) domain!: TransactionDomain; @IsNumber() amount!: number; @IsString() @MinLength(1) transactionDate!: string; @IsOptional() @IsEnum(Currency) currency?: Currency; @IsOptional() @IsString() typeId?: string; @IsOptional() @IsString() period?: string; @IsOptional() @IsString() reference?: string; @IsOptional() @IsString() checkNumber?: string; @IsOptional() @IsString() message?: string; /** * Legacy "NOPAGO": the bill was captured but not actually paid (no funds). * The row posts normally and stays visible, but is kept out of every balance * aggregate until resolved — see BillingService's NOT_OUTSTANDING. */ @IsOptional() @IsBoolean() outstanding?: boolean; } /** * Resolving an outstanding row: the check finally got cut, so the movement * takes the resolution date and check number and starts counting toward the * balance. Legacy behavior: "se actualiza registro con fecha del día y el * cheque a pagar y quitas outstanding". */ export class ResolveOutstandingDto { @IsString() @MinLength(1) checkNumber!: string; @IsString() @MinLength(1) resolvedDate!: string; } /** One customer's line within a batch; check-level fields live on the parent. */ export class BatchLineDto { @IsString() @MinLength(1) customerId!: string; @IsNumber() amount!: number; @IsOptional() @IsString() reference?: string; @IsOptional() @IsString() period?: string; @IsOptional() @IsString() message?: string; @IsOptional() @IsBoolean() outstanding?: boolean; } /** * Batch capture by check — the legacy "Editor" flow: key many customers' * receipts against one check, then reconcile the captured total against the * physical check. Deliberately NOT a persisted batch entity: `checkNumber` is * already a column, and grouping by it answers every legacy by-check query. */ export class BatchCreateDto { @IsEnum(TransactionDomain) domain!: TransactionDomain; @IsString() @MinLength(1) transactionDate!: string; @IsString() @MinLength(1) checkNumber!: string; @IsOptional() @IsEnum(Currency) currency?: Currency; @IsOptional() @IsString() typeId?: string; // Capped so one request can't open a transaction over an unbounded row set; // a physical check batch is tens of lines, not thousands. @IsArray() @ArrayMinSize(1) @ArrayMaxSize(500) @ValidateNested({ each: true }) @Type(() => BatchLineDto) lines!: BatchLineDto[]; }