314 lines
7.3 KiB
TypeScript
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();
|
|
}
|
|
}
|