diff --git a/modules/doc-numbering/src/api/domain/aggregates/doc-number.ts b/modules/doc-numbering/src/api/domain/aggregates/doc-number.ts index ed1655ae..b398448b 100644 --- a/modules/doc-numbering/src/api/domain/aggregates/doc-number.ts +++ b/modules/doc-numbering/src/api/domain/aggregates/doc-number.ts @@ -2,6 +2,48 @@ import { AggregateRoot, UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { DocType } from "../value-objects"; +/** + * + * + * const num1 = DocumentNumber.create(7, "2025", "{series}/{number:0003}"); + * console.log(num1.isSuccess, num1.data.getFormatted()); // true, "2025/007" + * + * const num2 = DocumentNumber.create(7, undefined, "{series}/{number:0003}"); + * console.log(num2.isFailure, num2.error.message); // true, error porque falta {series} + * + *. Número simple sin padding + * DocumentNumber.create(123, undefined, "{number}") + * // → "123" + * + * 2. Número con padding + * DocumentNumber.create(7, undefined, "{number:0005}") + * // → "00007" + * + * 3. Serie + número con padding + * DocumentNumber.create(45, "2025", "{series}/{number:0003}") + * // → "2025/045" + * + * 4. Serie + número sin padding + * DocumentNumber.create(987, "Sucursal-01", "{series}-{number}") + * // → "Sucursal-01-987" + * + * 5. Año + mes + día + número + * + * (Suponiendo fecha 2025-09-26 y rawValue=12) + * + * DocumentNumber.create(12, "2025", "{year}/{month}/{day}-{number:0002}") + * // → "2025/09/26-12" + * + * 6. Escapes de llaves + * DocumentNumber.create(33, "2025", "Factura N° {{{number}}}") + * // → "Factura N° {33}" + * + * 7. Patrón sin número (no muy común, pero válido) + * DocumentNumber.create(77, "2025", "SERIE-{series}") + * // → "SERIE-2025" + * + */ + export interface DocNumberProps { companyId: UniqueID; year: number; @@ -22,8 +64,8 @@ export class DocNumber extends AggregateRoot { // ... // 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent" - //const { contact } = props; - //user.addDomainEvent(new CustomerAuthenticatedEvent(id, contact.toString())); + // ... + // ... return Result.ok(docNumber); } @@ -35,8 +77,59 @@ export class DocNumber extends AggregateRoot { } private applyFormat(): string { - // Sustituye {series}, {number:000000}, {year}, etc. - return ""; + // 🔹 Preprocesar escapes de llaves + const pattern = this.props.formatPattern + .replace(/{{/g, "__LBRACE__") + .replace(/}}/g, "__RBRACE__"); + + const date = new Date(); + + // 🔹 Expresión regular para tokens {token} o {token:0000} + const tokenRegex = /{([a-zA-Z]+)(?::([0]+))?}/g; + + const formatted = pattern.replace(tokenRegex, (_match, token, pad) => { + const name = token.toLowerCase(); + + switch (name) { + case "number": { + const raw = String(this.props.currentValue); + if (pad) { + // validar que el especificador son sólo ceros + if (!/^0+$/.test(pad)) { + throw new Error( + `DocumentNumber: especificador de padding inválido en patrón '${pad}'` + ); + } + return raw.padStart(pad.length, "0"); + } + return raw; + } + + case "series": { + if (!this.props.series) { + throw new Error( + "DocumentNumber: el patrón requiere {series} pero no se proporcionó serie" + ); + } + return this.props.series; + } + + case "year": + return String(date.getFullYear()); + + case "month": + return String(date.getMonth() + 1).padStart(2, "0"); + + case "day": + return String(date.getDate()).padStart(2, "0"); + + default: + throw new Error(`DocumentNumber: token desconocido {${token}}`); + } + }); + + // 🔹 Restaurar escapes + return formatted.replace(/LBRACE/g, "{").replace(/RBRACE/g, "}"); } getFormatted(): string {