import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd"; import { type Maybe, Result } from "@repo/rdx-utils"; import { type CustomerInvoiceItemDescription, ItemAmount, ItemDiscount, ItemQuantity, } from "../../value-objects"; import type { ItemTaxGroup } from "../../value-objects/item-tax-group"; export interface CustomerInvoiceItemProps { description: Maybe; quantity: Maybe; // Cantidad de unidades unitAmount: Maybe; // Precio unitario en la moneda de la factura discountPercentage: Maybe; // % descuento taxes: ItemTaxGroup; 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: ItemTaxGroup; 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() { return this.props.description; } get quantity() { return this.props.quantity; } get unitAmount() { return this.props.unitAmount; } get discountPercentage() { return this.props.discountPercentage; } get languageCode() { return this.props.languageCode; } get currencyCode() { return this.props.currencyCode; } get taxes() { 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( (discount) => discount, () => 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); } /* importes individuales: iva / rec / ret */ private _getIndividualTaxAmounts(taxableAmount: ItemAmount) { return this.props.taxes.calculateAmounts(taxableAmount); } /** * @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 { const { ivaAmount, recAmount, retentionAmount } = this._getIndividualTaxAmounts(taxableAmount); return ivaAmount.add(recAmount).add(retentionAmount); // retención ya es negativa } /** * @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 qty = this.quantity.match( (quantity) => quantity, () => ItemQuantity.zero() ); const unit = this.unitAmount.match( (unitAmount) => unitAmount, () => ItemAmount.zero(this.currencyCode.code) ); return unit.multiply(qty); } /** * @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()); } /* importes individuales: iva / rec / ret */ public getIndividualTaxAmounts() { return this._getIndividualTaxAmounts(this.getTaxableAmount()); } /** * @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 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, }; } }