Uecko_ERP/modules/supplier-invoices/src/api/domain/aggregates/supplier-invoice.aggregate.ts

314 lines
7.3 KiB
TypeScript

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<UtcDate>;
description: Maybe<string>;
notes: Maybe<string>;
paymentMethod: Maybe<InvoicePaymentMethod>;
taxes: SupplierInvoiceTaxes;
currencyCode: CurrencyCode;
languageCode: LanguageCode;
sourceType: SupplierInvoiceSourceType;
documentId: Maybe<UniqueID>;
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<UtcDate>;
description: Maybe<string>;
notes: Maybe<string>;
paymentMethod: Maybe<InvoicePaymentMethod>;
currencyCode: CurrencyCode;
languageCode: LanguageCode;
sourceType: SupplierInvoiceSourceType;
documentId: Maybe<UniqueID>;
version: number;
taxes: SupplierInvoiceTaxes;
totals(): ISupplierInvoiceTotals;
}
export type SupplierInvoicePatchProps = Partial<
Omit<ISupplierInvoiceCreateProps, "companyId" | "status" | "sourceType" | "version">
>;
export type InternalSupplierInvoiceProps = Omit<ISupplierInvoiceCreateProps, "version"> & {
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<InternalSupplierInvoiceProps>
implements ISupplierInvoice
{
private constructor(props: InternalSupplierInvoiceProps, id?: UniqueID) {
super(props, id);
}
public static create(
props: ISupplierInvoiceCreateProps,
id?: UniqueID
): Result<SupplierInvoice, Error> {
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<UniqueID> {
return this.props.documentId;
}
public get version(): number {
return this.props.version;
}
public get description(): Maybe<string> {
return this.props.description;
}
public get notes(): Maybe<string> {
return this.props.notes;
}
public get paymentMethod(): Maybe<InvoicePaymentMethod> {
return this.props.paymentMethod;
}
public get dueDate(): Maybe<UtcDate> {
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<void, Error> {
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<void, Error> {
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<void, Error> {
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<void, Error> {
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();
}
}