import { AggregateRoot, type CurrencyCode, type LanguageCode, type UniqueID, type UtcDate, } from "@repo/rdx-ddd"; import { type Maybe, Result } from "@repo/rdx-utils"; import type { InvoicePaymentMethod, SupplierInvoiceTaxes } from "../entities"; import type { SupplierInvoiceSourceType } from "../enums"; import { type InvoiceAmount, SupplierInvoiceStatus } from "../value-objects"; export interface ISupplierInvoiceCreateProps { companyId: UniqueID; status: SupplierInvoiceStatus; supplierInvoiceCategoryId: UniqueID; supplierId: UniqueID; invoiceNumber: string; invoiceDate: UtcDate; dueDate: Maybe; description: Maybe; notes: Maybe; paymentMethod: Maybe; taxes: SupplierInvoiceTaxes; currencyCode: CurrencyCode; languageCode: LanguageCode; sourceType: SupplierInvoiceSourceType; documentId: Maybe; version?: number; } export interface ISupplierInvoiceTotals { taxableAmount: InvoiceAmount; ivaAmount: InvoiceAmount; recAmount: InvoiceAmount; retentionAmount: InvoiceAmount; taxesAmount: InvoiceAmount; transferredTaxesAmount: InvoiceAmount; netTaxesAmount: InvoiceAmount; totalAmount: InvoiceAmount; } interface ISupplierInvoice { companyId: UniqueID; status: SupplierInvoiceStatus; supplierInvoiceCategoryId: UniqueID; supplierId: UniqueID; invoiceNumber: string; invoiceDate: UtcDate; dueDate: Maybe; description: Maybe; notes: Maybe; paymentMethod: Maybe; currencyCode: CurrencyCode; languageCode: LanguageCode; sourceType: SupplierInvoiceSourceType; documentId: Maybe; version: number; taxes: SupplierInvoiceTaxes; totals(): ISupplierInvoiceTotals; } export type SupplierInvoicePatchProps = Partial< Omit >; export type InternalSupplierInvoiceProps = Omit & { version: number; }; /** * Aggregate raíz de factura de proveedor. * * Reglas MVP: * - supplierId requerido * - invoiceNumber requerido * - invoiceDate requerido * - totalAmount > 0 * - companyId requerido * - editable en cualquier estado * * Notas: * - create() aplica validaciones de negocio de alta. * - rehydrate() reconstruye desde persistencia sin revalidar. */ export class SupplierInvoice extends AggregateRoot implements ISupplierInvoice { private constructor(props: InternalSupplierInvoiceProps, id?: UniqueID) { super(props, id); } public static create( props: ISupplierInvoiceCreateProps, id?: UniqueID ): Result { const validationResult = SupplierInvoice.validateCreateProps(props); if (validationResult.isFailure) { return Result.fail(validationResult.error); } const supplierInvoice = new SupplierInvoice( { ...props, status: props.status ?? SupplierInvoiceStatus.draft(), version: props.version ?? 1, }, id ); return Result.ok(supplierInvoice); } /** * Reconstruye el agregado desde persistencia. * * Debe usarse exclusivamente desde infraestructura. */ public static rehydrate(props: InternalSupplierInvoiceProps, id: UniqueID): SupplierInvoice { return new SupplierInvoice(props, id); } public get companyId(): UniqueID { return this.props.companyId; } public get supplierId(): UniqueID { return this.props.supplierId; } public get invoiceNumber(): string { return this.props.invoiceNumber; } public get invoiceDate(): UtcDate { return this.props.invoiceDate; } public get currencyCode(): CurrencyCode { return this.props.currencyCode; } public get languageCode(): LanguageCode { return this.props.languageCode; } public get taxes(): SupplierInvoiceTaxes { return this.props.taxes; } public get status(): SupplierInvoiceStatus { return this.props.status; } public get sourceType(): SupplierInvoiceSourceType { return this.props.sourceType; } public get documentId(): Maybe { return this.props.documentId; } public get version(): number { return this.props.version; } public get description(): Maybe { return this.props.description; } public get notes(): Maybe { return this.props.notes; } public get paymentMethod(): Maybe { return this.props.paymentMethod; } public get dueDate(): Maybe { return this.props.dueDate; } public get supplierInvoiceCategoryId(): UniqueID { return this.props.supplierInvoiceCategoryId; } public totals(): ISupplierInvoiceTotals { return { taxableAmount: this.taxes.getTaxableAmount(), ivaAmount: this.taxes.getIvaAmount(), recAmount: this.taxes.getRecAmount(), retentionAmount: this.taxes.getRetentionAmount(), taxesAmount: this.taxes.getTaxesAmount(), transferredTaxesAmount: this.taxes.getTransferredTaxesAmount(), netTaxesAmount: this.taxes.getNetTaxesAmount(), totalAmount: this.taxes.getTotalAmount(), } as const; } /** * Actualiza campos editables del agregado. * * Regla MVP: * - la factura es editable en cualquier estado * * Notas: * - no permite alterar companyId, status, sourceType ni version externamente * - incrementa version tras mutación válida */ public update(patch: SupplierInvoicePatchProps): Result { const candidateProps: InternalSupplierInvoiceProps = { ...this.props, ...patch, version: this.props.version + 1, }; // Validacciones Object.assign(this.props, candidateProps); return Result.ok(); } /** * Marca la factura como confirmada. */ public confirm(): Result { if (this.props.status === SupplierInvoiceStatus.confirmed()) { return Result.ok(); } this.props.status = SupplierInvoiceStatus.confirmed(); this.incrementVersion(); return Result.ok(); } /** * Marca la factura como cancelada. */ public cancel(): Result { if (this.props.status === SupplierInvoiceStatus.cancelled()) { return Result.ok(); } this.props.status = SupplierInvoiceStatus.cancelled(); this.incrementVersion(); return Result.ok(); } private incrementVersion(): void { this.props.version += 1; } private static validateCreateProps(props: ISupplierInvoiceCreateProps): Result { if (props.dueDate.isSome() && props.dueDate.unwrap() < props.invoiceDate) { return Result.fail( new Error("La fecha de vencimiento no puede ser anterior a la fecha de factura") ); } /*if (!props.companyId?.trim()) { return Result.fail(new InvalidSupplierInvoiceCompanyError()); } if (!props.supplierId?.trim()) { return Result.fail(new InvalidSupplierInvoiceSupplierError()); } if (!props.invoiceNumber?.trim()) { return Result.fail(new InvalidSupplierInvoiceNumberError()); } if (!(props.invoiceDate instanceof Date) || Number.isNaN(props.invoiceDate.getTime())) { return Result.fail(new InvalidSupplierInvoiceDateError()); } if (props.totalAmount <= 0) { return Result.fail(new InvalidSupplierInvoiceAmountError()); }*/ return Result.ok(); } }