Uecko_ERP/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-items.ts

186 lines
5.7 KiB
TypeScript
Raw Normal View History

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) {
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(
(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
/* 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()) {
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
}
return { iva, rec, retention };
2025-09-26 15:00:11 +00:00
}
2025-10-12 09:14:33 +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
return map;
2025-10-12 09:14:33 +00:00
}
2025-09-03 10:41:12 +00:00
}