Facturas de cliente

This commit is contained in:
David Arranz 2025-09-10 13:57:15 +02:00
parent fa56473a00
commit 8c867eb7f3
14 changed files with 167 additions and 90 deletions

View File

@ -1,5 +1,5 @@
import { TaxCatalogProvider } from "@erp/core"; import { TaxCatalogProvider } from "@erp/core";
import { ValueObject } from "@repo/rdx-ddd"; import { Percentage, ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4"; import * as z from "zod/v4";
@ -26,6 +26,8 @@ export class Tax extends ValueObject<TaxProps> {
private static CODE_REGEX = /^[a-z0-9_:-]+$/; private static CODE_REGEX = /^[a-z0-9_:-]+$/;
private _percentage!: Percentage;
protected static validate(values: TaxProps) { protected static validate(values: TaxProps) {
const schema = z.object({ const schema = z.object({
value: z value: z
@ -96,6 +98,14 @@ export class Tax extends ValueObject<TaxProps> {
}); });
} }
protected constructor(props: TaxProps) {
super(props);
this._percentage = Percentage.create({
value: this.props.value,
scale: this.props.scale,
}).data;
}
get value(): number { get value(): number {
return this.props.value; return this.props.value;
} }
@ -109,6 +119,10 @@ export class Tax extends ValueObject<TaxProps> {
return this.props.code; return this.props.code;
} }
get percentage(): Percentage {
return this._percentage;
}
getProps(): TaxProps { getProps(): TaxProps {
return this.props; return this.props;
} }

View File

@ -1,12 +1,4 @@
import { import { CurrencyCode, DomainEntity, LanguageCode, Percentage, UniqueID } from "@repo/rdx-ddd";
CurrencyCode,
DomainEntity,
LanguageCode,
MoneyValue,
Percentage,
Taxes,
UniqueID,
} from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils";
import { import {
CustomerInvoiceItemDescription, CustomerInvoiceItemDescription,
@ -14,14 +6,16 @@ import {
ItemDiscount, ItemDiscount,
ItemQuantity, ItemQuantity,
} from "../../value-objects"; } from "../../value-objects";
import { ItemTaxes } from "../item-taxes";
export interface CustomerInvoiceItemProps { export interface CustomerInvoiceItemProps {
description: Maybe<CustomerInvoiceItemDescription>; description: Maybe<CustomerInvoiceItemDescription>;
quantity: Maybe<ItemQuantity>; // Cantidad de unidades quantity: Maybe<ItemQuantity>; // Cantidad de unidades
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
discountPercentage: Maybe<ItemDiscount>; // % descuento discountPercentage: Maybe<ItemDiscount>; // % descuento
taxes: Taxes; taxes: ItemTaxes;
languageCode: LanguageCode; languageCode: LanguageCode;
currencyCode: CurrencyCode; currencyCode: CurrencyCode;
@ -32,31 +26,26 @@ export interface ICustomerInvoiceItem {
quantity: Maybe<ItemQuantity>; // Cantidad de unidades quantity: Maybe<ItemQuantity>; // Cantidad de unidades
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
subtotalAmount: ItemAmount;
discountPercentage: Maybe<ItemDiscount>; // % descuento discountPercentage: Maybe<ItemDiscount>; // % descuento
discountAmount: Maybe<ItemAmount>;
taxableAmount: ItemAmount; taxes: ItemTaxes;
taxesAmount: ItemAmount;
totalAmount: ItemAmount;
languageCode: LanguageCode; languageCode: LanguageCode;
currencyCode: CurrencyCode; currencyCode: CurrencyCode;
calculateSubtotal(): ItemAmount; getSubtotalAmount(): ItemAmount;
getDiscountAmount(): ItemAmount;
calculateTotal(): ItemAmount; getTaxableAmount(): ItemAmount;
getTaxesAmount(): ItemAmount;
getTotalAmount(): ItemAmount;
} }
export class CustomerInvoiceItem export class CustomerInvoiceItem
extends DomainEntity<CustomerInvoiceItemProps> extends DomainEntity<CustomerInvoiceItemProps>
implements ICustomerInvoiceItem implements ICustomerInvoiceItem
{ {
private _subtotalAmount!: ItemAmount;
private _totalAmount!: ItemAmount;
public static create( public static create(
props: CustomerInvoiceItemProps, props: CustomerInvoiceItemProps,
id?: UniqueID id?: UniqueID
@ -67,10 +56,6 @@ export class CustomerInvoiceItem
// ... // ...
// ... // ...
// 🔹 Disparar evento de dominio "CustomerInvoiceItemCreatedEvent"
//const { customerInvoice } = props;
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
return Result.ok(item); return Result.ok(item);
} }
@ -86,38 +71,22 @@ export class CustomerInvoiceItem
return this.props.unitAmount; return this.props.unitAmount;
} }
get subtotalAmount(): ItemAmount {
throw new Error("Not implemented");
}
get discountPercentage(): Maybe<Percentage> { get discountPercentage(): Maybe<Percentage> {
return this.props.discountPercentage; return this.props.discountPercentage;
} }
get discountAmount(): Maybe<MoneyValue> { get languageCode(): LanguageCode {
throw new Error("Not implemented");
}
get taxableAmount(): ItemAmount {
throw new Error("Not implemented");
}
get taxesAmount(): ItemAmount {
throw new Error("Not implemented");
}
get totalAmount(): ItemAmount {
throw new Error("Not implemented");
}
public get languageCode(): LanguageCode {
return this.props.languageCode; return this.props.languageCode;
} }
public get currencyCode(): CurrencyCode { get currencyCode(): CurrencyCode {
return this.props.currencyCode; return this.props.currencyCode;
} }
get taxes(): ItemTaxes {
return this.props.taxes;
}
getProps(): CustomerInvoiceItemProps { getProps(): CustomerInvoiceItemProps {
return this.props; return this.props;
} }
@ -126,17 +95,52 @@ export class CustomerInvoiceItem
return this.getProps(); return this.getProps();
} }
calculateSubtotal(): ItemAmount { public getSubtotalAmount(): ItemAmount {
throw new Error("Not implemented"); const curCode = this.currencyCode.code;
const quantity = this.quantity.match(
(quantity) => quantity,
() => ItemQuantity.zero()
);
const unitAmount = this.unitAmount.match(
(unitAmount) => unitAmount,
() => ItemAmount.zero(curCode)
);
/*const unitPrice = this.unitPrice.isSome() return unitAmount.multiply(quantity);
? this.unitPrice.unwrap()
: CustomerInvoiceItemUnitPrice.zero();
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/
} }
calculateTotal(): ItemAmount { public getDiscountAmount(): ItemAmount {
throw new Error("Not implemented"); const discount = this.discountPercentage.match(
//return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber())); (percentage) => percentage,
() => ItemDiscount.zero()
);
return this.getSubtotalAmount().percentage(discount);
}
public getTaxableAmount(): ItemAmount {
return this.getSubtotalAmount().subtract(this.getDiscountAmount());
}
public getTaxesAmount(): ItemAmount {
return this._getTaxesAmount(this.getTaxableAmount());
}
public getTotalAmount(): ItemAmount {
const taxableAmount = this.getTaxableAmount();
return taxableAmount.add(this._getTaxesAmount(taxableAmount));
}
public getAllAmounts() {
return {
subtotalAmount: this.getSubtotalAmount(),
discountAmount: this.getDiscountAmount(),
taxableAmount: this.getTaxableAmount(),
taxesAmount: this.getTaxesAmount(),
totalAmount: this.getTotalAmount(),
};
}
private _getTaxesAmount(_taxableAmount: ItemAmount): ItemAmount {
return this.props.taxes.getTaxesAmount(_taxableAmount);
} }
} }

View File

@ -1,3 +1,3 @@
export * from "./customer-invoice-items"; export * from "./customer-invoice-items";
export * from "./invoice-recipient";
export * from "./invoice-taxes"; export * from "./invoice-taxes";
export * from "./item-taxes";

View File

@ -1,5 +1,5 @@
import { Tax } from "@erp/core/api"; import { Tax } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd"; import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { InvoiceAmount } from "../../value-objects/invoice-amount"; import { InvoiceAmount } from "../../value-objects/invoice-amount";
@ -9,28 +9,15 @@ export interface InvoiceTaxProps {
taxesAmount: InvoiceAmount; taxesAmount: InvoiceAmount;
} }
export class InvoiceTax extends ValueObject<InvoiceTaxProps> { export class InvoiceTax extends DomainEntity<InvoiceTaxProps> {
protected static validate(values: InvoiceTaxProps) { static create(props: InvoiceTaxProps, id?: UniqueID): Result<InvoiceTax, Error> {
return Result.ok(values); const invoiceTax = new InvoiceTax(props, id);
}
static create(values: InvoiceTaxProps): Result<InvoiceTax, Error> { // Reglas de negocio / validaciones
const valueIsValid = InvoiceTax.validate(values); // ...
// ...
if (valueIsValid.isFailure) { return Result.ok(invoiceTax);
return Result.fail(valueIsValid.error);
}
return Result.ok(new InvoiceTax(values));
}
public update(partial: Partial<InvoiceTaxProps>): Result<InvoiceTax, Error> {
const updatedProps = {
...this.props,
...partial,
} as InvoiceTaxProps;
return InvoiceTax.create(updatedProps);
} }
public get tax(): Tax { public get tax(): Tax {

View File

@ -0,0 +1,2 @@
export * from "./item-tax";
export * from "./item-taxes";

View File

@ -0,0 +1,36 @@
import { Tax } from "@erp/core/api";
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { ItemAmount } from "../../value-objects";
export interface ItemTaxProps {
tax: Tax;
}
export class ItemTax extends DomainEntity<ItemTaxProps> {
static create(props: ItemTaxProps, id?: UniqueID): Result<ItemTax, Error> {
const invoiceTax = new ItemTax(props, id);
// Reglas de negocio / validaciones
// ...
// ...
return Result.ok(invoiceTax);
}
public get tax(): Tax {
return this.props.tax;
}
getProps(): ItemTaxProps {
return this.props;
}
toPrimitive() {
return this.getProps();
}
public getTaxAmount(taxableAmount: ItemAmount): ItemAmount {
return taxableAmount.percentage(this.tax.percentage);
}
}

View File

@ -0,0 +1,25 @@
import { Collection } from "@repo/rdx-utils";
import { ItemAmount } from "../../value-objects";
import { ItemTax } from "./item-tax";
export interface ItemTaxesProps {
items?: ItemTax[];
}
export class ItemTaxes extends Collection<ItemTax> {
constructor(props: ItemTaxesProps) {
const { items = [] } = props;
super(items);
}
public static create(props: ItemTaxesProps): ItemTaxes {
return new ItemTaxes(props);
}
public getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
return this.getAll().reduce(
(total, tax) => total.add(tax.getTaxAmount(taxableAmount)),
ItemAmount.zero(taxableAmount.currencyCode)
);
}
}

View File

@ -19,6 +19,6 @@ export class ItemAmount extends MoneyValue {
value: 0, value: 0,
currency_code, currency_code,
}; };
return ItemAmount.create(props); return ItemAmount.create(props).data;
} }
} }

View File

@ -14,6 +14,6 @@ export class ItemDiscount extends Percentage {
} }
static zero() { static zero() {
return ItemDiscount.create({ value: 0 }); return ItemDiscount.create({ value: 0 }).data;
} }
} }

View File

@ -13,6 +13,6 @@ export class ItemQuantity extends Quantity {
} }
static zero() { static zero() {
return ItemQuantity.create({ value: 0 }); return ItemQuantity.create({ value: 0 }).data;
} }
} }

View File

@ -27,7 +27,7 @@ export interface MoneyValueProps {
export interface IMoneyValue { export interface IMoneyValue {
value: number; value: number;
scale: number; scale: number;
currency: Dinero.Currency; currencyCode: string;
getProps(): MoneyValueProps; getProps(): MoneyValueProps;
convertScale(newScale: number): MoneyValue; convertScale(newScale: number): MoneyValue;
@ -78,8 +78,8 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
return this.dinero.getAmount() / 10 ** this.dinero.getPrecision(); // ** => Math.pow return this.dinero.getAmount() / 10 ** this.dinero.getPrecision(); // ** => Math.pow
} }
get currency(): CurrencyData { get currencyCode(): string {
return this.dinero.getCurrency(); return this.dinero.getCurrency().toString();
} }
get scale(): number { get scale(): number {
@ -103,7 +103,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
return { return {
value: this.value, value: this.value,
scale: this.scale, scale: this.scale,
currency_code: this.currency, currency_code: this.currencyCode,
}; };
} }
@ -120,7 +120,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
return new MoneyValue({ return new MoneyValue({
value: this.dinero.add(addend.dinero).getAmount(), value: this.dinero.add(addend.dinero).getAmount(),
scale: this.scale, scale: this.scale,
currency_code: this.currency, currency_code: this.currencyCode,
}); });
} }
@ -128,7 +128,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
return new MoneyValue({ return new MoneyValue({
value: this.dinero.subtract(subtrahend.dinero).getAmount(), value: this.dinero.subtract(subtrahend.dinero).getAmount(),
scale: this.scale, scale: this.scale,
currency_code: this.currency, currency_code: this.currencyCode,
}); });
} }
@ -205,12 +205,12 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
*/ */
format(locale: string): string { format(locale: string): string {
const value = this.value; const value = this.value;
const currency = this.currency; const currencyCode = this.currencyCode;
const scale = this.scale; const scale = this.scale;
return new Intl.NumberFormat(locale, { return new Intl.NumberFormat(locale, {
style: "currency", style: "currency",
currency: currency, currency: currencyCode,
minimumFractionDigits: scale, minimumFractionDigits: scale,
maximumFractionDigits: scale, maximumFractionDigits: scale,
useGrouping: true, useGrouping: true,

View File

@ -82,6 +82,15 @@ export class Collection<T> {
return this.totalItems; return this.totalItems;
} }
/**
* Performs the specified action for each element in an array.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach<U>(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void {
this.items.forEach(callbackfn);
}
/** /**
* Aplica una función a cada elemento y devuelve un nuevo array con los resultados. * Aplica una función a cada elemento y devuelve un nuevo array con los resultados.
* @param callback - Función transformadora. * @param callback - Función transformadora.