import type { Tax } from "@erp/core/api"; import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd"; import { Collection } from "@repo/rdx-utils"; import { ItemAmount } from "../../value-objects"; import type { CustomerInvoiceItem } from "./customer-invoice-item"; export interface CustomerInvoiceItemsProps { items?: CustomerInvoiceItem[]; languageCode: LanguageCode; currencyCode: CurrencyCode; } export class CustomerInvoiceItems extends Collection { private _languageCode!: LanguageCode; private _currencyCode!: CurrencyCode; constructor(props: CustomerInvoiceItemsProps) { super(props.items ?? []); this._languageCode = props.languageCode; this._currencyCode = props.currencyCode; } public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems { return new CustomerInvoiceItems(props); } /** * @summary Añade un nuevo ítem a la colección. * @param item - El ítem de factura a añadir. * @returns `true` si el ítem fue añadido correctamente; `false` si fue rechazado. * @remarks * Sólo se aceptan ítems cuyo `LanguageCode` y `CurrencyCode` coincidan con * los de la colección. Si no coinciden, el método devuelve `false` sin modificar * la colección. */ add(item: CustomerInvoiceItem): boolean { // Antes de añadir un nuevo item, debo comprobar que el item a añadir // tiene el mismo "currencyCode" y "languageCode" que la colección de items. if ( !( this._languageCode.equals(item.languageCode) && this._currencyCode.equals(item.currencyCode) ) ) { return false; } return super.add(item); } /** * @summary Calcula el subtotal de todos los ítems sin descuentos ni impuestos. * @returns Un `ItemAmount` con el subtotal total de la colección en su moneda. */ public getSubtotalAmount(): ItemAmount { return this.getAll().reduce( (total, item) => total.add(item.getSubtotalAmount()), ItemAmount.zero(this._currencyCode.code) ); } /** * @summary Calcula el importe total de descuentos aplicados a todos los ítems. * @returns Un `ItemAmount` con el importe total de descuentos. */ public getDiscountAmount(): ItemAmount { return this.getAll().reduce( (total, item) => total.add(item.getDiscountAmount()), ItemAmount.zero(this._currencyCode.code) ); } /** * @summary Calcula el importe imponible total (base antes de impuestos). * @returns Un `ItemAmount` con el total imponible. */ public getTaxableAmount(): ItemAmount { return this.getAll().reduce( (total, item) => total.add(item.getTaxableAmount()), ItemAmount.zero(this._currencyCode.code) ); } /** * @summary Calcula el importe total de impuestos de todos los ítems. * @returns Un `ItemAmount` con la suma de todos los impuestos aplicados. */ public getTaxesAmount(): ItemAmount { return this.getAll().reduce( (total, item) => total.add(item.getTaxesAmount()), ItemAmount.zero(this._currencyCode.code) ); } /** * @summary Calcula el importe total final de todos los ítems (subtotal -descuentos + impuestos). * @returns Un `ItemAmount` con el total global de la colección. */ public getTotalAmount(): ItemAmount { return this.getAll().reduce( (total, item) => total.add(item.getTotalAmount()), ItemAmount.zero(this._currencyCode.code) ); } /* totales de iva/rec/ret a nivel factura */ public getAggregatedTaxesByType() { let iva = ItemAmount.zero(this._currencyCode.code); let rec = ItemAmount.zero(this._currencyCode.code); let retention = ItemAmount.zero(this._currencyCode.code); for (const item of this.getAll()) { const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts(); iva = iva.add(ivaAmount); rec = rec.add(recAmount); retention = retention.add(retentionAmount); } return { iva, rec, retention }; } /* agrupación por código fiscal → usado para customer_invoice_taxes */ public getAggregatedTaxesByCode() { const map = new Map(); for (const item of this.getAll()) { const taxable = item.getTaxableAmount(); const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts(); item.taxes.iva.match( (iva) => { const prev = map.get(iva.code) ?? { taxable: ItemAmount.zero(taxable.currencyCode), total: ItemAmount.zero(taxable.currencyCode), }; map.set(iva.code, { tax: iva, taxable: prev.taxable.add(taxable), total: prev.total.add(ivaAmount), }); }, () => { // } ); item.taxes.rec.match( (rec) => { const prev = map.get(rec.code) ?? { taxable: ItemAmount.zero(taxable.currencyCode), total: ItemAmount.zero(taxable.currencyCode), }; map.set(rec.code, { tax: rec, taxable: prev.taxable.add(taxable), total: prev.total.add(recAmount), }); }, () => { // } ); item.taxes.retention.match( (retention) => { const prev = map.get(retention.code) ?? { taxable: ItemAmount.zero(taxable.currencyCode), total: ItemAmount.zero(taxable.currencyCode), }; map.set(retention.code, { tax: retention, taxable: prev.taxable.add(taxable), total: prev.total.add(retentionAmount), }); }, () => { // } ); } return map; } }