Facturas de cliente

This commit is contained in:
David Arranz 2025-09-04 12:02:24 +02:00
parent 5064494b12
commit e65524ea3c
52 changed files with 235 additions and 196 deletions

View File

@ -14,6 +14,6 @@ export const NumericStringSchema = z
// Cantidad de dinero (base): solo para la cantidad y la escala, sin moneda
export const AmountBaseSchema = z.object({
amount: NumericStringSchema,
value: NumericStringSchema,
scale: NumericStringSchema,
});

View File

@ -7,7 +7,7 @@ import { NumericStringSchema } from "./base.schemas";
*/
export const PercentageSchema = z.object({
amount: NumericStringSchema,
value: NumericStringSchema,
scale: NumericStringSchema,
});

View File

@ -7,7 +7,7 @@ import { NumericStringSchema } from "./base.schemas";
*/
export const QuantitySchema = z.object({
amount: NumericStringSchema,
value: NumericStringSchema,
scale: NumericStringSchema,
});

View File

@ -8,41 +8,38 @@ export class GetCustomerInvoiceItemsAssembler {
toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO {
const { items } = invoice;
return items.map((item, index) => ({
//id: item.
position: index,
id: item.id.toString(),
position: String(index),
description: toEmptyString(item.description, (value) => value.toPrimitive()),
quantity: item.quantity.match(
(quantity) => {
const { amount, scale } = quantity.toPrimitive();
return { amount: amount.toString(), scale: scale.toString() };
const { value, scale } = quantity.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ amount: "", scale: "" })
() => ({ value: "", scale: "" })
),
unit_price_amount: item.unitAmount.match(
(unitPrice) => {
const { amount, scale } = unitPrice.toPrimitive();
return { amount: amount.toString(), scale: scale.toString() };
unit_amount: item.unitAmount.match(
(unitAmount) => {
const { value, scale } = unitAmount.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ amount: "", scale: "" })
() => ({ value: "", scale: "" })
),
discount: item.discount.match(
(discount) => {
const { amount, scale } = discount.toPrimitive();
return { amount: amount.toString(), scale: scale.toString() };
discount_percentage: item.discountPercentage.match(
(discountPercentage) => {
const { value, scale } = discountPercentage.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ amount: "", scale: "" })
() => ({ value: "", scale: "" })
),
total_amount: item.totalPrice.match(
(discount) => {
const { amount, scale } = discount.toPrimitive();
return { amount: amount.toString(), scale: scale.toString() };
},
() => ({ amount: "", scale: "" })
),
total_amount: {
value: item.totalAmount.toPrimitive().value.toString(),
scale: item.totalAmount.toPrimitive().scale.toString(),
},
}));
}
}

View File

@ -1,13 +1,17 @@
import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
import { toEmptyString } from "@repo/rdx-ddd";
import { CustomerInvoice } from "../../../domain";
import { GetCustomerInvoiceItemsAssembler } from "./get-invoice-items.assembler";
export class GetCustomerInvoiceAssembler {
private _itemsAssembler!: GetCu;
constructor() {}
private _itemsAssembler!: GetCustomerInvoiceItemsAssembler;
constructor() {
this._itemsAssembler = new GetCustomerInvoiceItemsAssembler();
}
public toDTO(invoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
//const items = invoice.items.
const items = this._itemsAssembler.toDTO(invoice);
return {
id: invoice.id.toPrimitive(),
@ -25,6 +29,38 @@ export class GetCustomerInvoiceAssembler {
language_code: invoice.languageCode.toPrimitive(),
currency_code: invoice.currencyCode.toPrimitive(),
subtotal_amount: {
value: invoice.subtotalAmount.value.toString(),
scale: invoice.subtotalAmount.scale.toString(),
},
discount_percentage: {
value: invoice.discountPercentage.value.toString(),
scale: invoice.discountPercentage.scale.toString(),
},
discount_amount: {
value: invoice.discountAmount.value.toString(),
scale: invoice.discountAmount.scale.toString(),
},
taxable_amount: {
value: invoice.taxableAmount.value.toString(),
scale: invoice.taxableAmount.scale.toString(),
},
tax_amount: {
value: invoice.taxAmount.value.toString(),
scale: invoice.taxAmount.scale.toString(),
},
total_amount: {
value: invoice.totalAmount.value.toString(),
scale: invoice.totalAmount.scale.toString(),
},
items,
metadata: {
entity: "customer-invoices",
},

View File

@ -2,7 +2,7 @@ import { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CustomerInvoiceService } from "../../domain";
import { GetCustomerInvoiceItemsAssembler } from "./assembler";
import { GetCustomerInvoiceAssembler } from "./assembler";
type GetCustomerInvoiceUseCaseInput = {
companyId: UniqueID;
@ -13,7 +13,7 @@ export class GetCustomerInvoiceUseCase {
constructor(
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager,
private readonly assembler: GetCustomerInvoiceItemsAssembler
private readonly assembler: GetCustomerInvoiceAssembler
) {}
public execute(params: GetCustomerInvoiceUseCaseInput) {

View File

@ -37,7 +37,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
const unitPrice = extractOrPushError(
CustomerInvoiceItemUnitAmount.create({
amount: item.unitPrice.amount,
value: item.unitPrice.amount,
scale: item.unitPrice.scale,
currency_code: item.unitPrice.currency,
}),
@ -47,7 +47,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
const discount = extractOrPushError(
CustomerInvoiceItemDiscount.create({
amount: item.discount.amount,
value: item.discount.amount,
scale: item.discount.scale,
}),
path("discount"),

View File

@ -59,11 +59,11 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
return this._subtotalAmount;
}
get discount(): Maybe<CustomerInvoiceItemDiscount> {
get discountPercentage(): Maybe<CustomerInvoiceItemDiscount> {
return this.props.discount;
}
get totalPrice(): CustomerInvoiceItemTotalAmount {
get totalAmount(): CustomerInvoiceItemTotalAmount {
if (!this._totalAmount) {
this._totalAmount = this.calculateTotal();
}

View File

@ -19,7 +19,10 @@ export interface ICustomerInvoiceAddress {
phone: PhoneNumber;
}
export class CustomerInvoiceAddress extends ValueObject<ICustomerInvoiceAddressProps> implements ICustomerInvoiceAddress {
export class CustomerInvoiceAddress
extends ValueObject<ICustomerInvoiceAddressProps>
implements ICustomerInvoiceAddress
{
public static create(props: ICustomerInvoiceAddressProps) {
return Result.ok(new CustomerInvoiceAddress(props));
}
@ -62,7 +65,7 @@ export class CustomerInvoiceAddress extends ValueObject<ICustomerInvoiceAddressP
return this.props.type;
}
getValue(): ICustomerInvoiceAddressProps {
getProps(): ICustomerInvoiceAddressProps {
return this.props;
}

View File

@ -24,15 +24,15 @@ export class CustomerInvoiceAddressType extends ValueObject<ICustomerInvoiceAddr
return Result.ok(new CustomerInvoiceAddressType({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -46,15 +46,15 @@ export class CustomerInvoiceItemDescription extends ValueObject<CustomerInvoiceI
return CustomerInvoiceItemDescription.create(value).map((value) => Maybe.some(value));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -3,7 +3,7 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
export class CustomerInvoiceItemSubtotalAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ amount, currency_code, scale }: MoneyValueProps) {
static create({ value: amount, currency_code, scale }: MoneyValueProps) {
const props = {
amount: Number(amount),
scale: scale ?? MoneyValue.DEFAULT_SCALE,

View File

@ -3,9 +3,9 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
export class CustomerInvoiceItemTotalAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ amount, currency_code, scale }: MoneyValueProps) {
static create({ value, currency_code, scale }: MoneyValueProps) {
const props = {
amount: Number(amount),
value: Number(value),
scale: scale ?? MoneyValue.DEFAULT_SCALE,
currency_code,
};

View File

@ -3,7 +3,7 @@ import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
export class CustomerInvoiceItemUnitAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ amount, currency_code, scale }: MoneyValueProps) {
static create({ value: amount, currency_code, scale }: MoneyValueProps) {
const props = {
amount: Number(amount),
scale: scale ?? MoneyValue.DEFAULT_SCALE,
@ -14,7 +14,7 @@ export class CustomerInvoiceItemUnitAmount extends MoneyValue {
static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitAmount.DEFAULT_SCALE) {
const props: MoneyValueProps = {
amount: 0,
value: 0,
scale,
currency_code,
};

View File

@ -38,15 +38,15 @@ export class CustomerInvoiceNumber extends ValueObject<ICustomerInvoiceNumberPro
return Result.ok(new CustomerInvoiceNumber({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -46,15 +46,15 @@ export class CustomerInvoiceSerie extends ValueObject<ICustomerInvoiceSerieProps
return CustomerInvoiceSerie.create(value).map((value) => Maybe.some(value));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -71,12 +71,12 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.REJECTED });
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
canTransitionTo(nextStatus: string): boolean {
@ -93,6 +93,6 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
}
toString(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -60,7 +60,7 @@ export class CustomerInvoiceItemMapper
// Validación y creación de precio unitario
const unitPriceOrError = CustomerInvoiceItemUnitAmount.create({
amount: source.unit_price_amount,
value: source.unit_price_amount,
scale: source.unit_price_scale,
currency_code: sourceParent.invoice_currency,
});
@ -70,7 +70,7 @@ export class CustomerInvoiceItemMapper
// Validación y creación de descuento
const discountOrError = CustomerInvoiceItemDiscount.create({
amount: source.discount_amount || 0,
value: source.discount_amount || 0,
scale: source.discount_scale || 0,
});
if (discountOrError.isFailure) {
@ -132,8 +132,8 @@ export class CustomerInvoiceItemMapper
discount_amount: source.discount.toPrimitive().amount,
discount_scale: source.discount.toPrimitive().scale,
total_amount: source.totalPrice.toPrimitive().amount,
total_scale: source.totalPrice.toPrimitive().scale,
total_amount: source.totalAmount.toPrimitive().amount,
total_scale: source.totalAmount.toPrimitive().scale,
};
return lineData;
}

View File

@ -15,18 +15,18 @@ export const CreateCustomerInvoiceRequestSchema = z.object({
z.object({
description: z.string().min(1, "Item description is required"),
quantity: z.object({
amount: z.number().positive("Quantity amount must be positive"),
value: z.number().positive("Quantity amount must be positive"),
scale: z.number().int().nonnegative("Quantity scale must be a non-negative integer"),
}),
unit_price: z.object({
amount: z.number().positive("Unit price amount must be positive"),
unit_amount: z.object({
value: z.number().positive("Unit price amount must be positive"),
scale: z.number().int().nonnegative("Unit price scale must be a non-negative integer"),
currency_code: z
.string()
.min(3, "Unit price currency code must be at least 3 characters long"),
}),
discount: z.object({
amount: z.number().nonnegative("Discount amount cannot be negative"),
discount_percentage: z.object({
value: z.number().nonnegative("Discount amount cannot be negative"),
scale: z.number().int().nonnegative("Discount scale must be a non-negative integer"),
}),
})

View File

@ -17,13 +17,21 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({
language_code: z.string(),
currency_code: z.string(),
subtotal_amount: AmountSchema,
discount_percentage: PercentageSchema,
discount_amount: AmountSchema,
taxable_amount: AmountSchema,
tax_amount: AmountSchema,
total_amount: AmountSchema,
items: z.array(
z.object({
id: z.uuid(),
position: z.string(),
description: z.string(),
quantity: QuantitySchema,
unit_price_amount: AmountSchema,
discount: PercentageSchema,
unit_amount: AmountSchema,
discount_percentage: PercentageSchema,
total_amount: AmountSchema,
})
),

View File

@ -24,15 +24,15 @@ export class CustomerAddressType extends ValueObject<ICustomerAddressTypeProps>
return Result.ok(new CustomerAddressType({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -34,15 +34,15 @@ export class CustomerNumber extends ValueObject<ICustomerNumberProps> {
return Result.ok(new CustomerNumber({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -42,15 +42,15 @@ export class CustomerSerie extends ValueObject<ICustomerSerieProps> {
return CustomerSerie.create(value).map((value) => Maybe.some(value));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -45,12 +45,12 @@ export class CustomerStatus extends ValueObject<ICustomerStatusProps> {
return this.props.value === CUSTOMER_STATUS.ACTIVE;
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
canTransitionTo(nextStatus: string): boolean {
@ -67,6 +67,6 @@ export class CustomerStatus extends ValueObject<ICustomerStatusProps> {
}
toString(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -5,7 +5,7 @@ describe("EmailAddress Value Object", () => {
const result = EmailAddress.create("user@example.com");
expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).toBe("user@example.com");
expect(result.data.getProps()).toBe("user@example.com");
});
it("should return an error for invalid email format", () => {
@ -43,6 +43,6 @@ describe("EmailAddress Value Object", () => {
const email = EmailAddress.create("test@example.com");
expect(email.isSuccess).toBe(true);
expect(email.data.getValue()).toBe("test@example.com");
expect(email.data.getProps()).toBe("test@example.com");
});
});

View File

@ -2,50 +2,50 @@ import { MoneyValue } from "../money-value";
describe("MoneyValue", () => {
test("should correctly instantiate with amount, scale, and currency", () => {
const money = new MoneyValue({ amount: 10000, scale: 2, currency_code: "EUR" });
expect(money.amount).toBe(100);
const money = new MoneyValue({ value: 10000, scale: 2, currency_code: "EUR" });
expect(money.value).toBe(100);
expect(money.currency).toBe("EUR");
expect(money.scale).toBe(2);
});
test("should add two MoneyValue instances with the same currency", () => {
const money1 = new MoneyValue({ amount: 10000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ amount: 5000, scale: 2, currency_code: "EUR" });
const money1 = new MoneyValue({ value: 10000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ value: 5000, scale: 2, currency_code: "EUR" });
const result = money1.add(money2);
expect(result.amount).toBe(150);
expect(result.value).toBe(150);
});
test("should subtract two MoneyValue instances with the same currency", () => {
const money1 = new MoneyValue({ amount: 20000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ amount: 5000, scale: 2, currency_code: "EUR" });
const money1 = new MoneyValue({ value: 20000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ value: 5000, scale: 2, currency_code: "EUR" });
const result = money1.subtract(money2);
expect(result.amount).toBe(150);
expect(result.value).toBe(150);
});
test("should throw an error when adding different currencies", () => {
const money1 = new MoneyValue({ amount: 10000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ amount: 5000, scale: 2, currency_code: "USD" });
const money1 = new MoneyValue({ value: 10000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ value: 5000, scale: 2, currency_code: "USD" });
expect(() => money1.add(money2)).toThrow(
"You must provide a Dinero instance with the same currency"
);
});
test("should correctly convert scale", () => {
const money = new MoneyValue({ amount: 10000, scale: 2, currency_code: "EUR" });
const money = new MoneyValue({ value: 10000, scale: 2, currency_code: "EUR" });
const converted = money.convertScale(4);
expect(converted.amount).toBe(100);
expect(converted.value).toBe(100);
expect(converted.scale).toBe(4);
});
test("should format correctly according to locale", () => {
const money = new MoneyValue({ amount: 123456, scale: 2, currency_code: "EUR" });
const money = new MoneyValue({ value: 123456, scale: 2, currency_code: "EUR" });
expect(money.format("en-US")).toBe("€1,234.56");
});
test("should compare MoneyValue instances correctly", () => {
const money1 = new MoneyValue({ amount: 10000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ amount: 10000, scale: 2, currency_code: "EUR" });
const money3 = new MoneyValue({ amount: 5000, scale: 2, currency_code: "EUR" });
const money1 = new MoneyValue({ value: 10000, scale: 2, currency_code: "EUR" });
const money2 = new MoneyValue({ value: 10000, scale: 2, currency_code: "EUR" });
const money3 = new MoneyValue({ value: 5000, scale: 2, currency_code: "EUR" });
expect(money1.equalsTo(money2)).toBe(true);
expect(money1.greaterThan(money3)).toBe(true);

View File

@ -2,49 +2,49 @@ import { Percentage } from "../percentage"; // Ajusta la ruta según sea necesar
describe("Percentage Value Object", () => {
test("Debe crear un porcentaje válido con escala por defecto", () => {
const result = Percentage.create({ amount: 200 }); // 2.00%
const result = Percentage.create({ value: 200 }); // 2.00%
expect(result.isSuccess).toBe(true);
expect(result.data?.toString()).toBe("2.00%");
});
test("Debe crear un porcentaje válido con escala definida", () => {
const result = Percentage.create({ amount: 2150, scale: 2 }); // 21.50%
const result = Percentage.create({ value: 2150, scale: 2 }); // 21.50%
expect(result.isSuccess).toBe(true);
expect(result.data?.toString()).toBe("21.50%");
});
test("Debe devolver error si la cantidad supera el 100%", () => {
const result = Percentage.create({ amount: 48732000, scale: 4 });
const result = Percentage.create({ value: 48732000, scale: 4 });
expect(result.isSuccess).toBe(false);
expect(result.error.message).toBe("La escala debe estar entre 0 y 2.");
});
test("Debe devolver error si la cantidad es negativa", () => {
const result = Percentage.create({ amount: -100, scale: 2 });
const result = Percentage.create({ value: -100, scale: 2 });
expect(result.isSuccess).toBe(false);
expect(result.error.message).toContain("La cantidad no puede ser negativa.");
});
test("Debe devolver error si la escala es menor a 0", () => {
const result = Percentage.create({ amount: 100, scale: -1 });
const result = Percentage.create({ value: 100, scale: -1 });
expect(result.isSuccess).toBe(false);
expect(result.error.message).toContain("Number must be greater than or equal to 0");
});
test("Debe devolver error si la escala es mayor a 10", () => {
const result = Percentage.create({ amount: 100, scale: 11 });
const result = Percentage.create({ value: 100, scale: 11 });
expect(result.isSuccess).toBe(false);
expect(result.error.message).toContain("La escala debe estar entre 0 y 2.");
});
test("Debe representar correctamente el valor como string", () => {
const result = Percentage.create({ amount: 750, scale: 2 }); // 7.50%
const result = Percentage.create({ value: 750, scale: 2 }); // 7.50%
expect(result.isSuccess).toBe(true);
expect(result.data?.toString()).toBe("7.50%");
});
test("Debe manejar correctamente el caso de 0%", () => {
const result = Percentage.create({ amount: 0, scale: 2 });
const result = Percentage.create({ value: 0, scale: 2 });
expect(result.isSuccess).toBe(true);
expect(result.data?.toString()).toBe("0.00%");
});

View File

@ -24,7 +24,7 @@ describe("PhoneNumber", () => {
const result = PhoneNumber.create(validPhone);
expect(result.isSuccess).toBe(true);
const phoneNumber = result.data;
expect(phoneNumber?.getValue()).toBe(validPhone);
expect(phoneNumber?.getProps()).toBe(validPhone);
});
test("debe obtener el código de país del número", () => {

View File

@ -4,7 +4,7 @@ describe("Slug Value Object", () => {
test("Debe crear un Slug válido", () => {
const slugResult = Slug.create("valid-slug-123");
expect(slugResult.isSuccess).toBe(true);
expect(slugResult.data.getValue()).toBe("valid-slug-123");
expect(slugResult.data.getProps()).toBe("valid-slug-123");
});
test("Debe fallar si el Slug contiene caracteres inválidos", () => {

View File

@ -4,7 +4,7 @@ describe("TINNumber", () => {
it("debería crear un TINNumber válido", () => {
const result = TINNumber.create("12345");
expect(result.isSuccess).toBe(true);
expect(result.data.getValue()).toBe("12345");
expect(result.data.getProps()).toBe("12345");
});
it("debería fallar si el valor es demasiado corto", () => {

View File

@ -28,11 +28,11 @@ export class City extends ValueObject<CityProps> {
return Result.ok(new City({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -28,11 +28,11 @@ export class Country extends ValueObject<CountryProps> {
return Result.ok(new Country({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -38,7 +38,7 @@ export class CurrencyCode extends ValueObject<CurrencyCodeProps> {
return Result.ok(new CurrencyCode({ value: valueIsValid.data }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}

View File

@ -38,11 +38,11 @@ export class EmailAddress extends ValueObject<EmailAddressProps> {
return this.getDomain().split(".")[0];
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -38,7 +38,7 @@ export class LanguageCode extends ValueObject<LanguageCodeProps> {
return Result.ok(new LanguageCode({ value: valueIsValid.data }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}

View File

@ -19,17 +19,17 @@ export type RoundingMode =
| "DOWN";
export interface MoneyValueProps {
amount: number;
value: number;
scale?: number;
currency_code?: string;
}
export interface IMoneyValue {
amount: number;
value: number;
scale: number;
currency: Dinero.Currency;
getValue(): MoneyValueProps;
getProps(): MoneyValueProps;
convertScale(newScale: number): MoneyValue;
add(addend: MoneyValue): MoneyValue;
subtract(subtrahend: MoneyValue): MoneyValue;
@ -53,9 +53,9 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
static DEFAULT_SCALE = DEFAULT_SCALE;
static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE;
static create({ amount, currency_code, scale }: MoneyValueProps) {
static create({ value, currency_code, scale }: MoneyValueProps) {
const props = {
amount: Number(amount),
value: Number(value),
scale: scale ?? MoneyValue.DEFAULT_SCALE,
currency_code: currency_code ?? MoneyValue.DEFAULT_CURRENCY_CODE,
};
@ -64,7 +64,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
constructor(props: MoneyValueProps) {
super(props);
const { amount, scale, currency_code } = props;
const { value: amount, scale, currency_code } = props;
this.dinero = Object.freeze(
DineroFactory({
amount,
@ -74,7 +74,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
); // 🔒 Garantiza inmutabilidad
}
get amount(): number {
get value(): number {
return this.dinero.getAmount() / 10 ** this.dinero.getPrecision(); // ** => Math.pow
}
@ -86,27 +86,22 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
return this.dinero.getPrecision();
}
getValue(): MoneyValueProps {
getProps(): MoneyValueProps {
return this.props;
}
/** Serializa el VO a una cadena del tipo "EUR:123400:2" */
toPersistence(): string {
return `${this.currency}:${this.dinero.getAmount()}:${this.scale}`;
}
/** Reconstruye el VO desde la cadena persistida */
static fromPersistence(value: string): MoneyValue {
const [currencyCode, amountStr, scaleStr] = value.split(":");
const amount = Number.parseInt(amountStr, 10);
const scale = Number.parseInt(scaleStr, 10);
const currency = currencyCode;
return new MoneyValue({ amount, scale, currency_code: currency });
return new MoneyValue({ value: amount, scale, currency_code: currency });
}
toPrimitive() {
return {
amount: this.amount,
value: this.value,
scale: this.scale,
currency_code: this.currency,
};
@ -115,7 +110,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
convertScale(newScale: number, roundingMode: RoundingMode = "HALF_UP"): MoneyValue {
const _newDinero = this.dinero.convertPrecision(newScale, roundingMode);
return new MoneyValue({
amount: _newDinero.getAmount(),
value: _newDinero.getAmount(),
scale: _newDinero.getPrecision(),
currency_code: _newDinero.getCurrency(),
});
@ -123,7 +118,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
add(addend: MoneyValue): MoneyValue {
return new MoneyValue({
amount: this.dinero.add(addend.dinero).getAmount(),
value: this.dinero.add(addend.dinero).getAmount(),
scale: this.scale,
currency_code: this.currency,
});
@ -131,7 +126,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
subtract(subtrahend: MoneyValue): MoneyValue {
return new MoneyValue({
amount: this.dinero.subtract(subtrahend.dinero).getAmount(),
value: this.dinero.subtract(subtrahend.dinero).getAmount(),
scale: this.scale,
currency_code: this.currency,
});
@ -142,7 +137,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
const _newDinero = this.dinero.multiply(_multiplier, roundingMode);
return new MoneyValue({
amount: _newDinero.getAmount(),
value: _newDinero.getAmount(),
scale: _newDinero.getPrecision(),
currency_code: _newDinero.getCurrency(),
});
@ -153,7 +148,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
const _newDinero = this.dinero.divide(_divisor, roundingMode);
return new MoneyValue({
amount: _newDinero.getAmount(),
value: _newDinero.getAmount(),
scale: _newDinero.getPrecision(),
currency_code: _newDinero.getCurrency(),
});
@ -164,7 +159,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
const _newDinero = this.dinero.percentage(_percentage, roundingMode);
return new MoneyValue({
amount: _newDinero.getAmount(),
value: _newDinero.getAmount(),
scale: _newDinero.getPrecision(),
currency_code: _newDinero.getCurrency(),
});
@ -187,11 +182,11 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
}
isPositive(): boolean {
return this.amount > 0;
return this.value > 0;
}
isNegative(): boolean {
return this.amount < 0;
return this.value < 0;
}
hasSameCurrency(comparator: MoneyValue): boolean {
@ -209,7 +204,7 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
* @returns Importe formateado
*/
format(locale: string): string {
const amount = this.amount;
const value = this.value;
const currency = this.currency;
const scale = this.scale;
@ -219,6 +214,6 @@ export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyVa
minimumFractionDigits: scale,
maximumFractionDigits: scale,
useGrouping: true,
}).format(amount);
}).format(value);
}
}

View File

@ -44,11 +44,11 @@ export class Name extends ValueObject<NameProps> {
return Name.generateAcronym(this.toString());
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -11,7 +11,7 @@ const DEFAULT_MIN_SCALE = 0;
const DEFAULT_MAX_SCALE = 2;
export interface PercentageProps {
amount: number;
value: number;
scale: number;
}
@ -25,7 +25,7 @@ export class Percentage extends ValueObject<PercentageProps> {
protected static validate(values: PercentageProps) {
const schema = z.object({
amount: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
value: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
scale: z
.number()
.int()
@ -39,43 +39,43 @@ export class Percentage extends ValueObject<PercentageProps> {
return schema.safeParse(values);
}
static create(props: { amount: number; scale?: number }): Result<Percentage> {
const { amount, scale = Percentage.DEFAULT_SCALE } = props;
static create(props: { value: number; scale?: number }): Result<Percentage> {
const { value, scale = Percentage.DEFAULT_SCALE } = props;
const validationResult = Percentage.validate({ amount, scale });
const validationResult = Percentage.validate({ value, scale });
if (!validationResult.success) {
return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", ")));
}
// Cálculo del valor real del porcentaje
const realValue = amount / 10 ** scale; // ** => Math.pow
const realValue = value / 10 ** scale; // ** => Math.pow
// Validación de rango
if (realValue > Percentage.MAX_VALUE) {
return Result.fail(new Error("El porcentaje no puede ser mayor a 100%."));
}
return Result.ok(new Percentage({ amount, scale }));
return Result.ok(new Percentage({ value, scale }));
}
get amount(): number {
return this.props.amount;
get value(): number {
return this.props.value;
}
get scale(): number {
return this.props.scale;
}
getValue(): PercentageProps {
getProps(): PercentageProps {
return this.props;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
toNumber(): number {
return this.amount / 10 ** this.scale; // ** => Math.pow
return this.value / 10 ** this.scale; // ** => Math.pow
}
toString(): string {

View File

@ -51,12 +51,12 @@ export class PhoneNumber extends ValueObject<PhoneNumberProps> {
return Result.ok(new PhoneNumber({ value: valueIsValid.data }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive(): string {
return this.getValue();
return this.getProps();
}
getCountryCode(): string | undefined {

View File

@ -73,12 +73,12 @@ export class PostalAddress extends ValueObject<PostalAddressProps> {
return this.props.country;
}
getValue(): PostalAddressProps {
getProps(): PostalAddressProps {
return this.props;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
toFormat(): string {

View File

@ -36,7 +36,7 @@ export class PostalCode extends ValueObject<PostalCodeProps> {
return Result.ok(new PostalCode({ value: valueIsValid.data }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}

View File

@ -28,11 +28,11 @@ export class Province extends ValueObject<ProvinceProps> {
return Result.ok(new Province({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -7,14 +7,14 @@ const DEFAULT_MIN_SCALE = 0;
const DEFAULT_MAX_SCALE = 2;
export interface QuantityProps {
amount: number;
value: number;
scale: number;
}
export class Quantity extends ValueObject<QuantityProps> {
protected static validate(values: QuantityProps) {
const schema = z.object({
amount: z.number().int(),
value: z.number().int(),
scale: z.number().int().min(Quantity.MIN_SCALE).max(Quantity.MAX_SCALE),
});
@ -25,9 +25,9 @@ export class Quantity extends ValueObject<QuantityProps> {
static MIN_SCALE = DEFAULT_MIN_SCALE;
static MAX_SCALE = DEFAULT_MAX_SCALE;
static create({ amount, scale }: QuantityProps) {
static create({ value, scale }: QuantityProps) {
const props = {
amount: Number(amount),
value: Number(value),
scale: scale ?? Quantity.DEFAULT_SCALE,
};
const checkProps = Quantity.validate(props);
@ -38,24 +38,24 @@ export class Quantity extends ValueObject<QuantityProps> {
return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) }));
}
get amount(): number {
return this.props.amount;
get value(): number {
return this.props.value;
}
get scale(): number {
return this.props.scale;
}
getValue(): QuantityProps {
getProps(): QuantityProps {
return this.props;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
toNumber(): number {
return this.amount / 10 ** this.scale; // ** => Math.pow
return this.value / 10 ** this.scale; // ** => Math.pow
}
toString(): string {
@ -63,21 +63,21 @@ export class Quantity extends ValueObject<QuantityProps> {
}
isZero(): boolean {
return this.amount === 0;
return this.value === 0;
}
isPositive(): boolean {
return this.amount > 0;
return this.value > 0;
}
isNegative(): boolean {
return this.amount < 0;
return this.value < 0;
}
increment(anotherQuantity?: Quantity): Result<Quantity, Error> {
if (!anotherQuantity) {
return Quantity.create({
amount: Number(this.amount) + 1,
value: Number(this.value) + 1,
scale: this.scale,
});
}
@ -87,7 +87,7 @@ export class Quantity extends ValueObject<QuantityProps> {
}
return Quantity.create({
amount: Number(this.amount) + Number(anotherQuantity.amount),
value: Number(this.value) + Number(anotherQuantity.value),
scale: this.scale,
});
}
@ -95,7 +95,7 @@ export class Quantity extends ValueObject<QuantityProps> {
decrement(anotherQuantity?: Quantity): Result<Quantity, Error> {
if (!anotherQuantity) {
return Quantity.create({
amount: Number(this.amount) - 1,
value: Number(this.value) - 1,
scale: this.scale,
});
}
@ -105,7 +105,7 @@ export class Quantity extends ValueObject<QuantityProps> {
}
return Quantity.create({
amount: Number(this.amount) - Number(anotherQuantity.amount),
value: Number(this.value) - Number(anotherQuantity.value),
scale: this.scale,
});
}
@ -120,11 +120,11 @@ export class Quantity extends ValueObject<QuantityProps> {
}
const oldFactor = 10 ** this.scale; // ** => Math.pow
const value = Number(this.amount) / oldFactor;
const value = Number(this.value) / oldFactor;
const newFactor = 10 ** newScale; // ** => Math.pow
const newValue = Math.round(value * newFactor);
return Quantity.create({ amount: newValue, scale: newScale });
return Quantity.create({ value: newValue, scale: newScale });
}
}

View File

@ -32,11 +32,11 @@ export class Slug extends ValueObject<SlugProps> {
return Result.ok(new Slug({ value: valueIsValid.data! }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -28,11 +28,11 @@ export class Street extends ValueObject<StreetProps> {
return Result.ok(new Street({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -36,11 +36,11 @@ export class TaxCode extends ValueObject<TaxCodeProps> {
return Result.ok(new TaxCode({ value: valueIsValid.data! }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -29,11 +29,11 @@ export class TextValue extends ValueObject<TextValueProps> {
return Result.ok(new TextValue({ value }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive(): string {
return this.getValue();
return this.getProps();
}
}

View File

@ -33,7 +33,7 @@ export class TINNumber extends ValueObject<TINNumberProps> {
return Result.ok(new TINNumber({ value: valueIsValid.data }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}

View File

@ -32,15 +32,15 @@ export class UniqueID extends ValueObject<string> {
return Result.ok(new UniqueID(generateUUIDv4()));
}
getValue(): string {
getProps(): string {
return this.props;
}
toString(): string {
return this.getValue();
return this.getProps();
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -22,11 +22,11 @@ export class URLAddress extends ValueObject<URLAddressProps> {
return schema.safeParse(value);
}
getValue(): string {
getProps(): string {
return this.props.value;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
}

View File

@ -40,7 +40,7 @@ export class UtcDate extends ValueObject<UtcDateProps> {
return Result.ok(new UtcDate({ value: isoDateString }));
}
getValue(): string {
getProps(): string {
return this.props.value;
}

View File

@ -7,7 +7,7 @@ export abstract class ValueObject<T> {
this.props = Object.freeze(props); // 🔒 Garantiza inmutabilidad
}
abstract getValue(): any;
abstract getProps(): any;
abstract toPrimitive(): any;