import { AggregateRoot, CurrencyCode, DomainValidationError, LanguageCode, Percentage, TextValue, UniqueID, UtcDate, } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerInvoiceItems, InvoicePaymentMethod, InvoiceTaxTotal } from "../entities"; import { CustomerInvoiceNumber, CustomerInvoiceSerie, CustomerInvoiceStatus, InvoiceAmount, InvoiceRecipient, } from "../value-objects"; export interface CustomerInvoiceProps { companyId: UniqueID; isProforma: boolean; status: CustomerInvoiceStatus; proformaId: Maybe; series: Maybe; invoiceNumber: CustomerInvoiceNumber; invoiceDate: UtcDate; operationDate: Maybe; customerId: UniqueID; recipient: Maybe; reference: Maybe; description: Maybe; notes: Maybe; languageCode: LanguageCode; currencyCode: CurrencyCode; items: CustomerInvoiceItems; paymentMethod: Maybe; discountPercentage: Percentage; /*verifactu_qr: string; verifactu_url: string; verifactu_status: string;*/ } export type CustomerInvoicePatchProps = Partial< Omit > & { items?: CustomerInvoiceItems; }; export interface ICustomerInvoice { canTransitionTo(nextStatus: string): boolean; hasRecipient: boolean; hasPaymentMethod: boolean; getTaxes(): InvoiceTaxTotal[]; getProps(): CustomerInvoiceProps; } export class CustomerInvoice extends AggregateRoot implements ICustomerInvoice { private _items!: CustomerInvoiceItems; protected constructor(props: CustomerInvoiceProps, id?: UniqueID) { super(props, id); this._items = props.items || CustomerInvoiceItems.create({ languageCode: props.languageCode, currencyCode: props.currencyCode, }); } getHeaderDiscountAmount(): InvoiceAmount { throw new Error("Method not implemented."); } getTaxableAmount(): InvoiceAmount { throw new Error("Method not implemented."); } getTaxesAmount(): InvoiceAmount { throw new Error("Method not implemented."); } getTotalAmount(): InvoiceAmount { throw new Error("Method not implemented."); } static create(props: CustomerInvoiceProps, id?: UniqueID): Result { const customerInvoice = new CustomerInvoice(props, id); // Reglas de negocio / validaciones if (!customerInvoice.isProforma && !customerInvoice.hasRecipient) { return Result.fail( new DomainValidationError( "MISSING_CUSTOMER_DATA", "recipient", "Customer data must be provided for non-proforma invoices" ) ); } // 🔹 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent" //const { customerInvoice } = props; //user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString())); return Result.ok(customerInvoice); } public update(partialInvoice: CustomerInvoicePatchProps): Result { const { items, ...rest } = partialInvoice; const updatedProps = { ...this.props, ...rest, } as CustomerInvoiceProps; /*if (partialAddress) { const updatedAddressOrError = this.address.update(partialAddress); if (updatedAddressOrError.isFailure) { return Result.fail(updatedAddressOrError.error); } updatedProps.address = updatedAddressOrError.data; }*/ return CustomerInvoice.create(updatedProps, this.id); } public get companyId(): UniqueID { return this.props.companyId; } public get customerId(): UniqueID { return this.props.customerId; } public get isProforma(): boolean { return this.props.isProforma; } public get proformaId(): Maybe { return this.props.proformaId; } public get status(): CustomerInvoiceStatus { return this.props.status; } canTransitionTo(nextStatus: string): boolean { return this.props.status.canTransitionTo(nextStatus); } public get series(): Maybe { return this.props.series; } public get invoiceNumber() { return this.props.invoiceNumber; } public get invoiceDate(): UtcDate { return this.props.invoiceDate; } public get operationDate(): Maybe { return this.props.operationDate; } public get reference(): Maybe { return this.props.reference; } public get description(): Maybe { return this.props.description; } public get notes(): Maybe { return this.props.notes; } public get recipient(): Maybe { return this.props.recipient; } public get paymentMethod(): Maybe { return this.props.paymentMethod; } public get languageCode(): LanguageCode { return this.props.languageCode; } public get currencyCode(): CurrencyCode { return this.props.currencyCode; } public get discountPercentage(): Percentage { return this.props.discountPercentage; } // Method to get the complete list of line items public get items(): CustomerInvoiceItems { return this._items; } public get hasRecipient() { return this.recipient.isSome(); } public get hasPaymentMethod() { return this.paymentMethod.isSome(); } private _getHeaderDiscountAmount( subtotalAmount: InvoiceAmount, itemsDiscountAmount: InvoiceAmount ): InvoiceAmount { return subtotalAmount.subtract(itemsDiscountAmount).percentage(this.discountPercentage); } private _getTaxableAmount( subtotalAmount: InvoiceAmount, itemsDiscountAmount: InvoiceAmount, headerDiscountAmount: InvoiceAmount ): InvoiceAmount { return subtotalAmount.subtract(itemsDiscountAmount).subtract(headerDiscountAmount); } private _getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount { let amount = InvoiceAmount.zero(this.currencyCode.code); const itemTaxes = this.items.getTaxesAmountByTaxes(); for (const taxItem of itemTaxes) { amount = amount.add( InvoiceAmount.create({ value: taxItem.taxesAmount.convertScale(2).value, currency_code: this.currencyCode.code, }).data ); } return amount; } private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount { return taxableAmount.add(taxesAmount); } public _getSubtotalAmount(): InvoiceAmount { const itemsSubtotal = this.items.getSubtotalAmount().convertScale(2); return InvoiceAmount.create({ value: itemsSubtotal.value, currency_code: this.currencyCode.code, }).data as InvoiceAmount; } public _getItemsDiscountAmount(): InvoiceAmount { const itemsDiscountAmount = this.items.getDiscountAmount().convertScale(2); return InvoiceAmount.create({ value: itemsDiscountAmount.value, currency_code: this.currencyCode.code, }).data as InvoiceAmount; } /*public getHeaderDiscountAmount(): InvoiceAmount { return this._getHeaderDiscountAmount(this.getSubtotalAmount()); } public getTaxableAmount(): InvoiceAmount { return this._getTaxableAmount(this.getSubtotalAmount(), this.getHeaderDiscountAmount()); } public getTaxesAmount(): InvoiceAmount { return this._getTaxesAmount(this.getTaxableAmount()); } public getTotalAmount(): InvoiceAmount { const taxableAmount = this.getTaxableAmount(); const taxesAmount = this._getTaxesAmount(taxableAmount); return this._getTotalAmount(taxableAmount, taxesAmount); }*/ public getTaxes(): InvoiceTaxTotal[] { const itemTaxes = this.items.getTaxesAmountByTaxes(); return itemTaxes.map((item) => { return { tax: item.tax, taxableAmount: InvoiceAmount.create({ value: item.taxableAmount.convertScale(2).value, currency_code: this.currencyCode.code, }).data, taxesAmount: InvoiceAmount.create({ value: item.taxesAmount.convertScale(2).value, currency_code: this.currencyCode.code, }).data, }; }); } public getAllAmounts() { const subtotalAmount = this._getSubtotalAmount(); // Sin IVA ni dtos de línea const itemDiscountAmount = this._getItemsDiscountAmount(); // Suma de los Importes de descuentos de linea const headerDiscountAmount = this._getHeaderDiscountAmount(subtotalAmount, itemDiscountAmount); // Importe de descuento de cabecera const taxableAmount = this._getTaxableAmount( subtotalAmount, itemDiscountAmount, headerDiscountAmount ); // const taxesAmount = this._getTaxesAmount(taxableAmount); const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount); return { subtotalAmount, itemDiscountAmount, headerDiscountAmount, taxableAmount, taxesAmount, totalAmount, }; } public getProps(): CustomerInvoiceProps { return this.props; } }