import { CurrencyCode, DomainEntity, LanguageCode, Percentage, UniqueID } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerInvoiceItemDescription, ItemAmount, ItemDiscount, ItemQuantity, } from "../../value-objects"; import { ItemTaxes, ItemTaxTotal } from "../item-taxes"; export interface CustomerInvoiceItemProps { description: Maybe; quantity: Maybe; // Cantidad de unidades unitAmount: Maybe; // Precio unitario en la moneda de la factura discountPercentage: Maybe; // % descuento taxes: ItemTaxes; languageCode: LanguageCode; currencyCode: CurrencyCode; } export interface ICustomerInvoiceItem { isValued: boolean; description: Maybe; quantity: Maybe; // Cantidad de unidades unitAmount: Maybe; // Precio unitario en la moneda de la factura discountPercentage: Maybe; // % descuento taxes: ItemTaxes; languageCode: LanguageCode; currencyCode: CurrencyCode; getSubtotalAmount(): ItemAmount; getDiscountAmount(): ItemAmount; getTaxableAmount(): ItemAmount; getTaxesAmount(): ItemAmount; getTotalAmount(): ItemAmount; } export class CustomerInvoiceItem extends DomainEntity implements ICustomerInvoiceItem { protected _isValued!: boolean; public static create( props: CustomerInvoiceItemProps, id?: UniqueID ): Result { const item = new CustomerInvoiceItem(props, id); // Reglas de negocio / validaciones // ... // ... return Result.ok(item); } protected constructor(props: CustomerInvoiceItemProps, id?: UniqueID) { super(props, id); this._isValued = this.quantity.isSome() || this.unitAmount.isSome(); } get isValued(): boolean { return this._isValued; } get description(): Maybe { return this.props.description; } get quantity(): Maybe { return this.props.quantity; } get unitAmount(): Maybe { return this.props.unitAmount; } get discountPercentage(): Maybe { return this.props.discountPercentage; } get languageCode(): LanguageCode { return this.props.languageCode; } get currencyCode(): CurrencyCode { return this.props.currencyCode; } get taxes(): ItemTaxes { return this.props.taxes; } getProps(): CustomerInvoiceItemProps { return this.props; } toPrimitive() { return this.getProps(); } /** * @private * @summary Calcula el importe de descuento a partir del subtotal y el porcentaje. * @param subtotalAmount - Importe subtotal. * @returns El importe de descuento calculado. */ private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount { const discount = this.discountPercentage.match( (percentage) => percentage, () => ItemDiscount.zero() ); return subtotalAmount.percentage(discount); } /** * @private * @summary Calcula el importe imponible restando el descuento al subtotal. * @param subtotalAmount - Importe subtotal. * @param discountAmount - Importe de descuento. * @returns El importe imponible resultante. */ private _getTaxableAmount(subtotalAmount: ItemAmount, discountAmount: ItemAmount): ItemAmount { return subtotalAmount.subtract(discountAmount); } /** * @private * @summary Calcula el importe total de impuestos sobre la base imponible. * @param taxableAmount - Importe imponible. * @returns El importe de impuestos calculado. */ private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount { return this.props.taxes.getTaxesAmount(taxableAmount); } /** * @private * @summary Calcula el importe total sumando base imponible e impuestos. * @param taxableAmount - Importe imponible. * @param taxesAmount - Importe de impuestos. * @returns El importe total del ítem. */ private _getTotalAmount(taxableAmount: ItemAmount, taxesAmount: ItemAmount): ItemAmount { return taxableAmount.add(taxesAmount); } /** * @summary Calcula el subtotal del ítem (cantidad × importe unitario). * @returns Un `ItemAmount` con el subtotal del ítem. * @remarks * Si la cantidad o el importe unitario no están definidos, se asumen valores cero. */ public getSubtotalAmount(): ItemAmount { const curCode = this.currencyCode.code; const quantity = this.quantity.match( (quantity) => quantity, () => ItemQuantity.zero() ); const unitAmount = this.unitAmount.match( (unitAmount) => unitAmount, () => ItemAmount.zero(curCode) ); return unitAmount.multiply(quantity); } /** * @summary Calcula el importe total de descuento del ítem. * @returns Un `ItemAmount` con el importe descontado. */ public getDiscountAmount(): ItemAmount { return this._getDiscountAmount(this.getSubtotalAmount()); } /** * @summary Calcula el importe imponible (subtotal − descuento). * @returns Un `ItemAmount` con la base imponible del ítem. */ public getTaxableAmount(): ItemAmount { return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount()); } /** * @summary Calcula el importe total de impuestos aplicados al ítem. * @returns Un `ItemAmount` con el total de impuestos. */ public getTaxesAmount(): ItemAmount { return this._getTaxesAmount(this.getTaxableAmount()); } /** * @summary Calcula el importe total final del ítem (base imponible + impuestos). * @returns Un `ItemAmount` con el importe total. */ public getTotalAmount(): ItemAmount { const taxableAmount = this.getTaxableAmount(); const taxesAmount = this._getTaxesAmount(taxableAmount); return this._getTotalAmount(taxableAmount, taxesAmount); } /** * @summary Obtiene los importes de impuestos agrupados por tipo de impuesto. * @returns Una colección con la base imponible y el importe de impuestos por cada tipo. */ public getTaxesAmountByTaxes(): ItemTaxTotal[] { return this.taxes.getTaxesAmountByTaxes(this.getTaxableAmount()); } /** * @summary Devuelve todos los importes calculados del ítem en un único objeto. * @returns Un objeto con las propiedades: * - `subtotalAmount` * - `discountAmount` * - `taxableAmount` * - `taxesAmount` * - `totalAmount` * @remarks * Este método es útil para mostrar todos los cálculos en la interfaz de usuario * o serializar el ítem con sus valores calculados. */ 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, }; } }