2025-11-16 19:30:11 +00:00
|
|
|
import type { Tax } from "@erp/core/api";
|
|
|
|
|
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
2025-09-03 10:41:12 +00:00
|
|
|
import { Collection } from "@repo/rdx-utils";
|
2025-11-16 19:30:11 +00:00
|
|
|
|
2025-09-10 18:14:19 +00:00
|
|
|
import { ItemAmount } from "../../value-objects";
|
2025-11-16 19:30:11 +00:00
|
|
|
|
|
|
|
|
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
2025-09-03 10:41:12 +00:00
|
|
|
|
|
|
|
|
export interface CustomerInvoiceItemsProps {
|
|
|
|
|
items?: CustomerInvoiceItem[];
|
|
|
|
|
languageCode: LanguageCode;
|
|
|
|
|
currencyCode: CurrencyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|
|
|
|
private _languageCode!: LanguageCode;
|
|
|
|
|
private _currencyCode!: CurrencyCode;
|
|
|
|
|
|
|
|
|
|
constructor(props: CustomerInvoiceItemsProps) {
|
2025-11-23 21:44:31 +00:00
|
|
|
super(props.items ?? []);
|
|
|
|
|
this._languageCode = props.languageCode;
|
|
|
|
|
this._currencyCode = props.currencyCode;
|
2025-09-03 10:41:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
|
|
|
|
return new CustomerInvoiceItems(props);
|
|
|
|
|
}
|
2025-09-07 19:55:12 +00:00
|
|
|
|
2025-10-12 09:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* @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.
|
|
|
|
|
*/
|
2025-09-07 19:55:12 +00:00
|
|
|
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.
|
2025-09-10 18:14:19 +00:00
|
|
|
if (
|
2025-11-16 19:30:11 +00:00
|
|
|
!(
|
|
|
|
|
this._languageCode.equals(item.languageCode) && this._currencyCode.equals(item.currencyCode)
|
|
|
|
|
)
|
2025-09-10 18:14:19 +00:00
|
|
|
) {
|
2025-09-07 19:55:12 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2025-09-10 18:14:19 +00:00
|
|
|
return super.add(item);
|
2025-09-07 19:55:12 +00:00
|
|
|
}
|
|
|
|
|
|
2025-10-12 09:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* @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.
|
|
|
|
|
*/
|
2025-09-17 17:37:41 +00:00
|
|
|
public getSubtotalAmount(): ItemAmount {
|
|
|
|
|
return this.getAll().reduce(
|
2025-11-06 16:16:18 +00:00
|
|
|
(total, item) => total.add(item.getSubtotalAmount()),
|
2025-09-17 17:37:41 +00:00
|
|
|
ItemAmount.zero(this._currencyCode.code)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 09:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* @summary Calcula el importe total de descuentos aplicados a todos los ítems.
|
|
|
|
|
* @returns Un `ItemAmount` con el importe total de descuentos.
|
|
|
|
|
*/
|
2025-09-17 17:37:41 +00:00
|
|
|
public getDiscountAmount(): ItemAmount {
|
|
|
|
|
return this.getAll().reduce(
|
|
|
|
|
(total, item) => total.add(item.getDiscountAmount()),
|
|
|
|
|
ItemAmount.zero(this._currencyCode.code)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 09:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* @summary Calcula el importe imponible total (base antes de impuestos).
|
|
|
|
|
* @returns Un `ItemAmount` con el total imponible.
|
|
|
|
|
*/
|
2025-09-17 17:37:41 +00:00
|
|
|
public getTaxableAmount(): ItemAmount {
|
|
|
|
|
return this.getAll().reduce(
|
|
|
|
|
(total, item) => total.add(item.getTaxableAmount()),
|
|
|
|
|
ItemAmount.zero(this._currencyCode.code)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 09:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* @summary Calcula el importe total de impuestos de todos los ítems.
|
|
|
|
|
* @returns Un `ItemAmount` con la suma de todos los impuestos aplicados.
|
|
|
|
|
*/
|
2025-09-17 17:37:41 +00:00
|
|
|
public getTaxesAmount(): ItemAmount {
|
|
|
|
|
return this.getAll().reduce(
|
|
|
|
|
(total, item) => total.add(item.getTaxesAmount()),
|
|
|
|
|
ItemAmount.zero(this._currencyCode.code)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 09:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* @summary Calcula el importe total final de todos los ítems (subtotal -descuentos + impuestos).
|
|
|
|
|
* @returns Un `ItemAmount` con el total global de la colección.
|
|
|
|
|
*/
|
2025-09-10 18:14:19 +00:00
|
|
|
public getTotalAmount(): ItemAmount {
|
|
|
|
|
return this.getAll().reduce(
|
2025-09-17 17:37:41 +00:00
|
|
|
(total, item) => total.add(item.getTotalAmount()),
|
2025-09-10 18:14:19 +00:00
|
|
|
ItemAmount.zero(this._currencyCode.code)
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-26 15:00:11 +00:00
|
|
|
|
2025-11-23 21:44:31 +00:00
|
|
|
/* 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);
|
2025-09-29 07:32:13 +00:00
|
|
|
|
2025-09-26 15:00:11 +00:00
|
|
|
for (const item of this.getAll()) {
|
2025-11-23 21:44:31 +00:00
|
|
|
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
|
|
|
|
|
|
|
|
|
iva = iva.add(ivaAmount);
|
|
|
|
|
rec = rec.add(recAmount);
|
|
|
|
|
retention = retention.add(retentionAmount);
|
2025-09-26 15:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-23 21:44:31 +00:00
|
|
|
return { iva, rec, retention };
|
2025-09-26 15:00:11 +00:00
|
|
|
}
|
2025-10-12 09:14:33 +00:00
|
|
|
|
2025-11-23 21:44:31 +00:00
|
|
|
/* agrupación por código fiscal → usado para customer_invoice_taxes */
|
|
|
|
|
public getAggregatedTaxesByCode() {
|
|
|
|
|
const map = new Map<string, { tax: Tax; taxable: ItemAmount; total: ItemAmount }>();
|
|
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
() => {
|
|
|
|
|
//
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-10-12 09:14:33 +00:00
|
|
|
|
2025-11-23 21:44:31 +00:00
|
|
|
return map;
|
2025-10-12 09:14:33 +00:00
|
|
|
}
|
2025-09-03 10:41:12 +00:00
|
|
|
}
|