245 lines
7.0 KiB
TypeScript
245 lines
7.0 KiB
TypeScript
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<CustomerInvoiceItemDescription>;
|
||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
||
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
||
|
||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
||
|
||
taxes: ItemTaxGroup;
|
||
|
||
languageCode: LanguageCode;
|
||
currencyCode: CurrencyCode;
|
||
}
|
||
|
||
export interface ICustomerInvoiceItem {
|
||
isValued: boolean;
|
||
|
||
description: Maybe<CustomerInvoiceItemDescription>;
|
||
|
||
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
|
||
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
|
||
|
||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
||
|
||
taxes: ItemTaxGroup;
|
||
|
||
languageCode: LanguageCode;
|
||
currencyCode: CurrencyCode;
|
||
|
||
getSubtotalAmount(): ItemAmount;
|
||
getDiscountAmount(): ItemAmount;
|
||
|
||
getTaxableAmount(): ItemAmount;
|
||
getTaxesAmount(): ItemAmount;
|
||
getTotalAmount(): ItemAmount;
|
||
}
|
||
|
||
export class CustomerInvoiceItem
|
||
extends DomainEntity<CustomerInvoiceItemProps>
|
||
implements ICustomerInvoiceItem
|
||
{
|
||
protected _isValued!: boolean;
|
||
|
||
public static create(
|
||
props: CustomerInvoiceItemProps,
|
||
id?: UniqueID
|
||
): Result<CustomerInvoiceItem, Error> {
|
||
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,
|
||
};
|
||
}
|
||
}
|