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 { ValueObject } from "@repo/rdx-ddd";
import { Percentage, ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
@ -26,6 +26,8 @@ export class Tax extends ValueObject<TaxProps> {
private static CODE_REGEX = /^[a-z0-9_:-]+$/;
private _percentage!: Percentage;
protected static validate(values: TaxProps) {
const schema = z.object({
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 {
return this.props.value;
}
@ -109,6 +119,10 @@ export class Tax extends ValueObject<TaxProps> {
return this.props.code;
}
get percentage(): Percentage {
return this._percentage;
}
getProps(): TaxProps {
return this.props;
}

View File

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

View File

@ -1,5 +1,5 @@
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 { InvoiceAmount } from "../../value-objects/invoice-amount";
@ -9,28 +9,15 @@ export interface InvoiceTaxProps {
taxesAmount: InvoiceAmount;
}
export class InvoiceTax extends ValueObject<InvoiceTaxProps> {
protected static validate(values: InvoiceTaxProps) {
return Result.ok(values);
}
export class InvoiceTax extends DomainEntity<InvoiceTaxProps> {
static create(props: InvoiceTaxProps, id?: UniqueID): Result<InvoiceTax, Error> {
const invoiceTax = new InvoiceTax(props, id);
static create(values: InvoiceTaxProps): Result<InvoiceTax, Error> {
const valueIsValid = InvoiceTax.validate(values);
// Reglas de negocio / validaciones
// ...
// ...
if (valueIsValid.isFailure) {
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);
return Result.ok(invoiceTax);
}
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,
currency_code,
};
return ItemAmount.create(props);
return ItemAmount.create(props).data;
}
}

View File

@ -14,6 +14,6 @@ export class ItemDiscount extends Percentage {
}
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() {
return ItemQuantity.create({ value: 0 });
return ItemQuantity.create({ value: 0 }).data;
}
}

View File

@ -27,7 +27,7 @@ export interface MoneyValueProps {
export interface IMoneyValue {
value: number;
scale: number;
currency: Dinero.Currency;
currencyCode: string;
getProps(): MoneyValueProps;
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
}
get currency(): CurrencyData {
return this.dinero.getCurrency();
get currencyCode(): string {
return this.dinero.getCurrency().toString();
}
get scale(): number {
@ -103,7 +103,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
return {
value: this.value,
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({
value: this.dinero.add(addend.dinero).getAmount(),
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({
value: this.dinero.subtract(subtrahend.dinero).getAmount(),
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 {
const value = this.value;
const currency = this.currency;
const currencyCode = this.currencyCode;
const scale = this.scale;
return new Intl.NumberFormat(locale, {
style: "currency",
currency: currency,
currency: currencyCode,
minimumFractionDigits: scale,
maximumFractionDigits: scale,
useGrouping: true,

View File

@ -82,6 +82,15 @@ export class Collection<T> {
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.
* @param callback - Función transformadora.