Transactions and the bank register become append-only with a void (reversal) action — never edited or hard-deleted. This is the API half of phase 5; the capture/void web UI is the remaining piece. Schema: - Transaction and BankTransaction gain voidedAt + voidedById. A non-null voidedAt reverses the row. Pushed to dev. Correctness (the high-stakes part): - Every aggregate excludes voided rows: billing movements totals, the raw balances SQL, stats (groupBy + the sides/crossLine raw subqueries + first/last), facets (types/sources/years); the statement's running balance freezes on a voided row and its per-currency/per-domain/per-type summaries skip them; customers.detail and property owner-ledger groupBy; and every bank total (totalsFor, stats counts/bounds, facets + summary raw SQL). List views still return voided rows with a `voided` flag so the UI can strike them through. - Bank's legacy zero-amount "void" cheques are unchanged and distinct from app voids (voidedAt). API: - POST /billing + POST /billing/:id/void (ledger:create / ledger:void); POST /bank + POST /bank/:id/void (bank:create / bank:void). Create needs STAFF+, void needs MANAGER+. Double-void -> 400, unknown id -> 404, bad date -> 400. Mutations audited. DTOs added. Verified against dev end-to-end: a -500 MXN charge moved a customer balance 31082.08 -> 30582.08, and voiding it returned it to 31082.08 to the cent; a +1234.56 bank ingreso moved net 899375.77 -> 900610.33 and voiding returned it to 899375.77. VIEWER create/void both 403, double-void 400. API compiles clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 lines
917 B
TypeScript
28 lines
917 B
TypeScript
import {
|
|
IsEnum,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
MinLength,
|
|
} from "class-validator";
|
|
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;
|
|
}
|