import { AggregateRoot, CurrencyCode, DomainValidationError, LanguageCode, Percentage, TextValue, UniqueID, UtcDate, } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerInvoiceItems, InvoicePaymentMethod } from "../entities"; import { CustomerInvoiceNumber, CustomerInvoiceSerie, CustomerInvoiceStatus, InvoiceAmount, InvoiceRecipient, } from "../value-objects"; export interface CustomerInvoiceProps { companyId: UniqueID; isProforma: boolean; status: CustomerInvoiceStatus; series: Maybe; invoiceNumber: Maybe; 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 { hasRecipient: boolean; hasPaymentMethod: boolean; getSubtotalAmount(): InvoiceAmount; getDiscountAmount(): InvoiceAmount; getTaxableAmount(): InvoiceAmount; getTaxesAmount(): InvoiceAmount; getTotalAmount(): InvoiceAmount; issueInvoice(newInvoiceNumber: CustomerInvoiceNumber): Result; } 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, }); } 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 status(): CustomerInvoiceStatus { return this.props.status; } 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 taxes() { return this.items.getTaxesAmountByTaxes(); } public get hasRecipient() { return this.recipient.isSome(); } public get hasPaymentMethod() { return this.paymentMethod.isSome(); } private _getDiscountAmount(subtotalAmount: InvoiceAmount): InvoiceAmount { return subtotalAmount.percentage(this.discountPercentage); } private _getTaxableAmount( subtotalAmount: InvoiceAmount, discountAmount: InvoiceAmount ): InvoiceAmount { return subtotalAmount.subtract(discountAmount); } private _getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount { let amount = InvoiceAmount.zero(this.currencyCode.code); for (const tax of this.taxes) { amount = amount.add(tax.taxesAmount); } 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 getDiscountAmount(): InvoiceAmount { return this._getDiscountAmount(this.getSubtotalAmount()); } public getTaxableAmount(): InvoiceAmount { return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount()); } 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 getAllAmounts() { const subtotalAmount = this.getSubtotalAmount(); const discountAmount = this._getDiscountAmount(subtotalAmount); const taxableAmount = this._getTaxableAmount(subtotalAmount, discountAmount); const taxesAmount = this._getTaxesAmount(taxableAmount); const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount); return { subtotalAmount, discountAmount, taxableAmount, taxesAmount, totalAmount, }; } public issueInvoice(newInvoiceNumber: CustomerInvoiceNumber) { return CustomerInvoice.create( { ...this.props, status: CustomerInvoiceStatus.createEmitted(), isProforma: false, invoiceNumber: Maybe.some(newInvoiceNumber), }, this.id ); } }