From d40c2279bd1561625d5de092b6cd0ae92e05715e Mon Sep 17 00:00:00 2001 From: david Date: Fri, 5 Sep 2025 13:23:45 +0200 Subject: [PATCH] Facturas de cliente --- .../invoice-repository.interface.ts | 2 +- .../server/domain/services/invoice.service.ts | 8 +- .../database/transaction-manager.ts | 98 ++++++++++++++++++- .../errors/infrastructure-repository-error.ts | 10 +- ...ap-dto-to-create-customer-invoice-props.ts | 10 +- .../delete-customer-invoice.use-case.ts | 29 ++++-- ...map-dto-to-customer-invoice-items-props.ts | 8 +- .../customer-invoice-item.ts | 80 ++++++++++----- .../services/customer-invoice.service.ts | 2 +- .../customer-invoice-item-discount.ts | 3 - .../customer-invoice-item-subtotal-amount.ts | 14 --- .../customer-invoice-item-total-amount.ts | 14 --- .../customer-invoice-item-unit-amount.ts | 23 ----- .../src/api/domain/value-objects/index.ts | 6 +- .../api/domain/value-objects/item-amount.ts | 24 +++++ .../api/domain/value-objects/item-discount.ts | 19 ++++ .../api/domain/value-objects/item-quantity.ts | 15 ++- .../mappers/customer-invoice-item.mapper.ts | 97 ++++++++++-------- .../mappers/customer-invoice.mapper.ts | 46 ++++++--- .../customer-invoice-item-taxes.model.ts | 6 +- .../sequelize/customer-invoice-item.model.ts | 5 +- .../sequelize/customer-invoice-taxes.model.ts | 4 +- .../src/api/infrastructure/sequelize/index.ts | 14 ++- .../src/value-objects/currency-code.ts | 4 + .../src/value-objects/language-code.ts | 4 + .../rdx-ddd/src/value-objects/text-value.ts | 2 +- .../rdx-ddd/src/value-objects/unique-id.ts | 4 +- 27 files changed, 362 insertions(+), 189 deletions(-) delete mode 100644 modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-discount.ts delete mode 100644 modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts delete mode 100644 modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts delete mode 100644 modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts create mode 100644 modules/customer-invoices/src/api/domain/value-objects/item-amount.ts create mode 100644 modules/customer-invoices/src/api/domain/value-objects/item-discount.ts diff --git a/modules.bak/invoices/src/server/domain/repositories/invoice-repository.interface.ts b/modules.bak/invoices/src/server/domain/repositories/invoice-repository.interface.ts index 3a4732e0..e448078f 100644 --- a/modules.bak/invoices/src/server/domain/repositories/invoice-repository.interface.ts +++ b/modules.bak/invoices/src/server/domain/repositories/invoice-repository.interface.ts @@ -5,7 +5,7 @@ import { Invoice } from "../aggregates"; export interface IInvoiceRepository { findAll(transaction?: any): Promise, Error>>; getById(id: UniqueID, transaction?: any): Promise>; - deleteById(id: UniqueID, transaction?: any): Promise>; + deleteByIdInCompany(id: UniqueID, transaction?: any): Promise>; create(invoice: Invoice, transaction?: any): Promise; update(invoice: Invoice, transaction?: any): Promise; diff --git a/modules.bak/invoices/src/server/domain/services/invoice.service.ts b/modules.bak/invoices/src/server/domain/services/invoice.service.ts index a9598f15..306d963a 100644 --- a/modules.bak/invoices/src/server/domain/services/invoice.service.ts +++ b/modules.bak/invoices/src/server/domain/services/invoice.service.ts @@ -7,6 +7,9 @@ import { IInvoiceService } from "./invoice-service.interface"; export class InvoiceService implements IInvoiceService { constructor(private readonly repo: IInvoiceRepository) {} + deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise> { + throw new Error("Method not implemented."); + } async findInvoices(transaction?: Transaction): Promise, Error>> { const invoicesOrError = await this.repo.findAll(transaction); @@ -71,10 +74,11 @@ export class InvoiceService implements IInvoiceService { return Result.ok(newInvoice); } - async deleteInvoiceById( + async deleteInvoiceByIdInCompnay( + companyId: UniqueID, invoiceId: UniqueID, transaction?: Transaction ): Promise> { - return this.repo.deleteById(invoiceId, transaction); + return this.repo.deleteByIdInCompany(companyId, invoiceId, transaction); } } diff --git a/modules/core/src/api/infrastructure/database/transaction-manager.ts b/modules/core/src/api/infrastructure/database/transaction-manager.ts index c22cd847..ccad266a 100644 --- a/modules/core/src/api/infrastructure/database/transaction-manager.ts +++ b/modules/core/src/api/infrastructure/database/transaction-manager.ts @@ -1,15 +1,35 @@ +import { Result } from "@repo/rdx-utils"; +import { ILogger } from "../../logger"; import { ITransactionManager } from "./transaction-manager.interface"; export abstract class TransactionManager implements ITransactionManager { protected _transaction: any | null = null; + protected _isCompleted = false; + protected readonly logger!: ILogger; + + constructor(logger?: ILogger) { + // Si no hay logger, usa console adaptado + this.logger = logger ?? { + info: (msg, meta) => console.info(msg, meta), + warn: (msg, meta) => console.warn(msg, meta), + error: (msg, err) => console.error(msg, err), + debug: (msg, meta) => console.debug(msg, meta), + }; + } /** * 🔹 Inicia una transacción si no hay una activa */ async start(): Promise { - if (!this._transaction) { - this._transaction = await this._startTransaction(); + if (this._transaction) { + this.logger.error("❌ Transaction already started. Nested transactions are not allowed.", { + label: "TransactionManager.start", + }); + throw new Error("A transaction is already active. Nested transactions are not allowed."); } + this._transaction = await this._startTransaction(); + this._isCompleted = false; + this.logger.debug("Transaction started", { label: "TransactionManager.start" }); } /** @@ -17,8 +37,16 @@ export abstract class TransactionManager implements ITransactionManager { */ getTransaction(): any { if (!this._transaction) { + this.logger.error("❌ No active transaction. Call start() first.", { + label: "TransactionManager.getTransaction", + }); throw new Error("No active transaction. Call start() first."); } + if (this._isCompleted) { + this.logger.error("❌ Transaction already completed."); + throw new Error("Transaction already completed."); + } + return this._transaction; } @@ -26,13 +54,30 @@ export abstract class TransactionManager implements ITransactionManager { * 🔹 Ejecuta una función dentro de una transacción */ async complete(work: (transaction: any) => Promise): Promise { + if (this._transaction) { + this.logger.error( + "❌ Cannot start a new transaction inside another. Nested transactions are not allowed.", + { label: "TransactionManager.complete" } + ); + + throw new Error("A transaction is already active. Nested transactions are not allowed."); + } + await this.start(); try { const result = await work(this.getTransaction()); + if (result instanceof Result && result.isFailure) { + throw result.error; + } await this.commit(); return result; - } catch (error) { + } catch (err) { await this.rollback(); + const error = err as Error; + this.logger.error(`❌ Transaction rolled back due to error: ${error.message}`, { + stack: error.stack, + label: "TransactionManager.start", + }); throw error; } } @@ -45,16 +90,59 @@ export abstract class TransactionManager implements ITransactionManager { protected abstract _rollbackTransaction(): Promise; async commit(): Promise { - if (this._transaction) { + if (!this._transaction) { + this.logger.error("❌ No transaction to commit.", { label: "TransactionManager.commit" }); + throw new Error("No transaction to commit."); + } + if (this._isCompleted) { + this.logger.error("❌ Transaction already completed. Cannot commit again.", { + label: "TransactionManager.commit", + }); + throw new Error("Transaction already completed."); + } + + try { await this._commitTransaction(); + this.logger.info("Transaction committed.", { label: "TransactionManager.commit" }); + } catch (err) { + const error = err as Error; + this.logger.error(`❌ Error during commit: ${error.message}`, { + stack: error.stack, + label: "TransactionManager.commit", + }); + throw error; + } finally { + // Siempre limpia el estado interno this._transaction = null; + this._isCompleted = true; } } async rollback(): Promise { - if (this._transaction) { + if (!this._transaction) { + this.logger.error("❌ No transaction to rollback.", { label: "TransactionManager.rollback" }); + throw new Error("No transaction to rollback."); + } + if (this._isCompleted) { + this.logger.error("❌ Transaction already completed. Cannot rollback again.", { + label: "TransactionManager.rollback", + }); + throw new Error("Transaction already completed."); + } + + try { await this._rollbackTransaction(); + this.logger.info("Transaction rolled back."); + } catch (err) { + const error = err as Error; + this.logger.error(`❌ Error during rollback: ${error.message}`, { + stack: error.stack, + label: "TransactionManager.rollback", + }); + throw error; + } finally { this._transaction = null; + this._isCompleted = true; } } } diff --git a/modules/core/src/api/infrastructure/errors/infrastructure-repository-error.ts b/modules/core/src/api/infrastructure/errors/infrastructure-repository-error.ts index abb018e4..3d8bd20f 100644 --- a/modules/core/src/api/infrastructure/errors/infrastructure-repository-error.ts +++ b/modules/core/src/api/infrastructure/errors/infrastructure-repository-error.ts @@ -1,9 +1,17 @@ +import { DatabaseError } from "sequelize"; import { InfrastructureError } from "./infrastructure-errors"; export class InfrastructureRepositoryError extends InfrastructureError { public readonly code = "REPOSITORY_ERROR" as const; constructor(message = "Repository operation failed", options?: ErrorOptions) { - super(message, options); + const { cause } = options || {}; + let _message = message; + + if (cause && cause instanceof DatabaseError) { + _message = `${message}: ${(cause as DatabaseError).message}`; + } + + super(_message, options); } } diff --git a/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts b/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts index d0ca73c7..fb93a593 100644 --- a/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts +++ b/modules/customer-invoices/src/api/application/create-customer-invoice/map-dto-to-create-customer-invoice-props.ts @@ -17,14 +17,14 @@ import { Result } from "@repo/rdx-utils"; import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto"; import { CustomerInvoiceItemDescription, - CustomerInvoiceItemDiscount, CustomerInvoiceItemProps, CustomerInvoiceItemQuantity, - CustomerInvoiceItemUnitAmount, CustomerInvoiceNumber, CustomerInvoiceProps, CustomerInvoiceSerie, CustomerInvoiceStatus, + ItemAmount, + ItemDiscount, } from "../../domain"; /** @@ -162,15 +162,13 @@ function mapDTOToCreateCustomerInvoiceItemsProps( ); const unitAmount = extractOrPushError( - maybeFromNullableVO(item.unit_amount, (value) => CustomerInvoiceItemUnitAmount.create(value)), + maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)), "unit_amount", errors ); const discountPercentage = extractOrPushError( - maybeFromNullableVO(item.discount_percentage, (value) => - CustomerInvoiceItemDiscount.create(value) - ), + maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)), "discount_percentage", errors ); diff --git a/modules/customer-invoices/src/api/application/delete-customer-invoice/delete-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/delete-customer-invoice/delete-customer-invoice.use-case.ts index 48642ba1..f7d2ea3c 100644 --- a/modules/customer-invoices/src/api/application/delete-customer-invoice/delete-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/delete-customer-invoice/delete-customer-invoice.use-case.ts @@ -1,22 +1,23 @@ import { EntityNotFoundError, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { ICustomerInvoiceService } from "../../domain"; +import { CustomerInvoiceService } from "../../domain"; type DeleteCustomerInvoiceUseCaseInput = { - tenantId: string; - id: string; + companyId: UniqueID; + invoice_id: string; }; export class DeleteCustomerInvoiceUseCase { constructor( - private readonly service: ICustomerInvoiceService, + private readonly service: CustomerInvoiceService, private readonly transactionManager: ITransactionManager ) {} public execute(params: DeleteCustomerInvoiceUseCaseInput) { - const { id, tenantId } = params; - const idOrError = UniqueID.create(id); + const { invoice_id, companyId } = params; + + const idOrError = UniqueID.create(invoice_id); if (idOrError.isFailure) { return Result.fail(idOrError.error); @@ -26,17 +27,25 @@ export class DeleteCustomerInvoiceUseCase { return this.transactionManager.complete(async (transaction) => { try { - const existsCheck = await this.service.existsById(invoiceId, transaction); + const existsCheck = await this.service.existsByIdInCompany( + companyId, + invoiceId, + transaction + ); if (existsCheck.isFailure) { return Result.fail(existsCheck.error); } - if (!existsCheck.data) { - return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString())); + const invoiceExists = existsCheck.data; + + if (!invoiceExists) { + return Result.fail( + new EntityNotFoundError("Customer invoice", "id", invoiceId.toString()) + ); } - return await this.service.deleteById(invoiceId, transaction); + return await this.service.deleteInvoiceByIdInCompany(companyId, invoiceId, transaction); } catch (error: unknown) { return Result.fail(error as Error); } diff --git a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts index 86790761..4c588720 100644 --- a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts +++ b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-items-props.ts @@ -4,9 +4,9 @@ import { Result } from "@repo/rdx-utils"; import { CustomerInvoiceItem, CustomerInvoiceItemDescription, - CustomerInvoiceItemDiscount, CustomerInvoiceItemQuantity, - CustomerInvoiceItemUnitAmount, + ItemAmount, + ItemDiscount, } from "../../domain"; import { extractOrPushError } from "./extract-or-push-error"; import { hasNoUndefinedFields } from "./has-no-undefined-fields"; @@ -36,7 +36,7 @@ export function mapDTOToCustomerInvoiceItemsProps( ); const unitPrice = extractOrPushError( - CustomerInvoiceItemUnitAmount.create({ + ItemAmount.create({ value: item.unitPrice.amount, scale: item.unitPrice.scale, currency_code: item.unitPrice.currency, @@ -46,7 +46,7 @@ export function mapDTOToCustomerInvoiceItemsProps( ); const discount = extractOrPushError( - CustomerInvoiceItemDiscount.create({ + ItemDiscount.create({ value: item.discount.amount, scale: item.discount.scale, }), diff --git a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts index 18da09b7..0515b83c 100644 --- a/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts +++ b/modules/customer-invoices/src/api/domain/entities/customer-invoice-items/customer-invoice-item.ts @@ -9,26 +9,50 @@ import { import { Maybe, Result } from "@repo/rdx-utils"; import { CustomerInvoiceItemDescription, - CustomerInvoiceItemDiscount, - CustomerInvoiceItemQuantity, - CustomerInvoiceItemSubtotalAmount, - CustomerInvoiceItemTotalAmount, - CustomerInvoiceItemUnitAmount, + ItemAmount, + ItemDiscount, + ItemQuantity, } from "../../value-objects"; export interface CustomerInvoiceItemProps { description: Maybe; - quantity: Maybe; // Cantidad de unidades - unitAmount: Maybe; // Precio unitario en la moneda de la factura - discountPercentage: Maybe; // % descuento + quantity: Maybe; // Cantidad de unidades + unitAmount: Maybe; // Precio unitario en la moneda de la factura + discountPercentage: Maybe; // % descuento languageCode: LanguageCode; currencyCode: CurrencyCode; } -export class CustomerInvoiceItem extends DomainEntity { - private _subtotalAmount!: CustomerInvoiceItemSubtotalAmount; - private _totalAmount!: CustomerInvoiceItemTotalAmount; +export interface ICustomerInvoiceItem { + description: Maybe; + + quantity: Maybe; // Cantidad de unidades + unitAmount: Maybe; // Precio unitario en la moneda de la factura + subtotalAmount: ItemAmount; + + discountPercentage: Maybe; // % descuento + discountAmount: Maybe; + + taxableAmount: ItemAmount; + taxesAmount: ItemAmount; + + totalAmount: ItemAmount; + + languageCode: LanguageCode; + currencyCode: CurrencyCode; + + calculateSubtotal(): ItemAmount; + + calculateTotal(): ItemAmount; +} + +export class CustomerInvoiceItem + extends DomainEntity + implements ICustomerInvoiceItem +{ + private _subtotalAmount!: ItemAmount; + private _totalAmount!: ItemAmount; public static create( props: CustomerInvoiceItemProps, @@ -51,19 +75,16 @@ export class CustomerInvoiceItem extends DomainEntity return this.props.description; } - get quantity(): Maybe { + get quantity(): Maybe { return this.props.quantity; } - get unitAmount(): Maybe { + get unitAmount(): Maybe { return this.props.unitAmount; } - get subtotalAmount(): CustomerInvoiceItemSubtotalAmount { - if (!this._subtotalAmount) { - this._subtotalAmount = this.calculateSubtotal(); - } - return this._subtotalAmount; + get subtotalAmount(): ItemAmount { + throw new Error("Not implemented"); } get discountPercentage(): Maybe { @@ -74,11 +95,16 @@ export class CustomerInvoiceItem extends DomainEntity throw new Error("Not implemented"); } - get totalAmount(): CustomerInvoiceItemTotalAmount { - if (!this._totalAmount) { - this._totalAmount = this.calculateTotal(); - } - return this._totalAmount; + 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 { @@ -89,15 +115,15 @@ export class CustomerInvoiceItem extends DomainEntity return this.props.currencyCode; } - getValue(): CustomerInvoiceItemProps { + getProps(): CustomerInvoiceItemProps { return this.props; } toPrimitive() { - return this.getValue(); + return this.getProps(); } - calculateSubtotal(): CustomerInvoiceItemSubtotalAmount { + calculateSubtotal(): ItemAmount { throw new Error("Not implemented"); /*const unitPrice = this.unitPrice.isSome() @@ -106,7 +132,7 @@ export class CustomerInvoiceItem extends DomainEntity return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/ } - calculateTotal(): CustomerInvoiceItemTotalAmount { + calculateTotal(): ItemAmount { throw new Error("Not implemented"); //return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber())); } diff --git a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts b/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts index b9b7aeab..1fa6ade9 100644 --- a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts +++ b/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts @@ -128,7 +128,7 @@ export class CustomerInvoiceService { * @param transaction - Transacción activa para la operación. * @returns Result - Resultado de la operación. */ - async deleteById( + async deleteInvoiceByIdInCompany( companyId: UniqueID, invoiceId: UniqueID, transaction?: Transaction diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-discount.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-discount.ts deleted file mode 100644 index 82eea30e..00000000 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-discount.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Percentage } from "@repo/rdx-ddd"; - -export class CustomerInvoiceItemDiscount extends Percentage {} diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts deleted file mode 100644 index 1fca49d2..00000000 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-subtotal-amount.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; - -export class CustomerInvoiceItemSubtotalAmount extends MoneyValue { - public static DEFAULT_SCALE = 4; - - static create({ value, currency_code }: MoneyValueProps) { - const props = { - value: Number(value), - scale: CustomerInvoiceItemSubtotalAmount.DEFAULT_SCALE, - currency_code, - }; - return MoneyValue.create(props); - } -} diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts deleted file mode 100644 index 17e8b7b7..00000000 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-total-amount.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; - -export class ItemTotalAmount extends MoneyValue { - public static DEFAULT_SCALE = 4; - - static create({ value, currency_code, scale }: MoneyValueProps) { - const props = { - value: Number(value), - scale: scale ?? MoneyValue.DEFAULT_SCALE, - currency_code, - }; - return MoneyValue.create(props); - } -} diff --git a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts deleted file mode 100644 index e53736bd..00000000 --- a/modules/customer-invoices/src/api/domain/value-objects/customer-invoice-item-unit-amount.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; - -export class CustomerInvoiceItemUnitAmount extends MoneyValue { - public static DEFAULT_SCALE = 4; - - static create({ value: amount, currency_code, scale }: MoneyValueProps) { - const props = { - amount: Number(amount), - scale: scale ?? MoneyValue.DEFAULT_SCALE, - currency_code, - }; - return MoneyValue.create(props); - } - - static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitAmount.DEFAULT_SCALE) { - const props: MoneyValueProps = { - value: 0, - scale, - currency_code, - }; - return MoneyValue.create(props); - } -} diff --git a/modules/customer-invoices/src/api/domain/value-objects/index.ts b/modules/customer-invoices/src/api/domain/value-objects/index.ts index 9839be59..41658010 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/index.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/index.ts @@ -1,10 +1,8 @@ export * from "./customer-invoice-address-type"; export * from "./customer-invoice-item-description"; -export * from "./customer-invoice-item-discount"; -export * from "./customer-invoice-item-subtotal-amount"; -export * from "./customer-invoice-item-total-amount"; -export * from "./customer-invoice-item-unit-amount"; export * from "./customer-invoice-number"; export * from "./customer-invoice-serie"; export * from "./customer-invoice-status"; +export * from "./item-amount"; +export * from "./item-discount"; export * from "./item-quantity"; diff --git a/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts b/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts new file mode 100644 index 00000000..e07d4f03 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/value-objects/item-amount.ts @@ -0,0 +1,24 @@ +import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd"; + +type ItemAmountProps = Pick; + +export class ItemAmount extends MoneyValue { + public static DEFAULT_SCALE = 4; + + static create({ value, currency_code }: ItemAmountProps) { + const props = { + value: Number(value), + scale: ItemAmount.DEFAULT_SCALE, + currency_code, + }; + return MoneyValue.create(props); + } + + static zero(currency_code: string) { + const props = { + value: 0, + currency_code, + }; + return ItemAmount.create(props); + } +} diff --git a/modules/customer-invoices/src/api/domain/value-objects/item-discount.ts b/modules/customer-invoices/src/api/domain/value-objects/item-discount.ts new file mode 100644 index 00000000..268220df --- /dev/null +++ b/modules/customer-invoices/src/api/domain/value-objects/item-discount.ts @@ -0,0 +1,19 @@ +import { Percentage, PercentageProps } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +type ItemDiscountProps = Pick; + +export class ItemDiscount extends Percentage { + static DEFAULT_SCALE = 2; + + static create({ value }: ItemDiscountProps): Result { + return Percentage.create({ + value, + scale: ItemDiscount.DEFAULT_SCALE, + }); + } + + static zero() { + return ItemDiscount.create({ value: 0 }); + } +} diff --git a/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts b/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts index ffd62835..35ab91e5 100644 --- a/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts +++ b/modules/customer-invoices/src/api/domain/value-objects/item-quantity.ts @@ -1,13 +1,18 @@ import { Quantity, QuantityProps } from "@repo/rdx-ddd"; +type ItemQuantityProps = Pick; + export class ItemQuantity extends Quantity { public static DEFAULT_SCALE = 2; - static create({ value }: QuantityProps) { - const props = { - value: Number(value), + static create({ value }: ItemQuantityProps) { + return Quantity.create({ + value, scale: ItemQuantity.DEFAULT_SCALE, - }; - return Quantity.create(props); + }); + } + + static zero() { + return ItemQuantity.create({ value: 0 }); } } diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts index f9348e01..75a56456 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice-item.mapper.ts @@ -5,16 +5,22 @@ import { ValidationErrorDetail, extractOrPushError, } from "@erp/core/api"; -import { CurrencyCode, LanguageCode, Quantity, UniqueID, maybeFromNullableVO, toNullable } from "@repo/rdx-ddd"; +import { + CurrencyCode, + LanguageCode, + UniqueID, + maybeFromNullableVO, + toNullable, +} from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { InferCreationAttributes } from "sequelize"; import { CustomerInvoice, CustomerInvoiceItem, CustomerInvoiceItemDescription, - CustomerInvoiceItemDiscount, - CustomerInvoiceItemQuantity, - CustomerInvoiceItemUnitAmount, + ItemAmount, + ItemDiscount, + ItemQuantity, } from "../../domain"; import { CustomerInvoiceItemCreationAttributes, @@ -48,17 +54,17 @@ export class CustomerInvoiceItemMapper const itemId = extractOrPushError(UniqueID.create(source.item_id), "item_id", errors); - const languageCode = extractOrPushError( - LanguageCode.create(sourceParent.language_code), - "language_code", - errors - ); + const languageCode = extractOrPushError( + LanguageCode.create(sourceParent.language_code), + "language_code", + errors + ); - const currencyCode = extractOrPushError( - CurrencyCode.create(sourceParent.currency_code), - "currency_code", - errors - ); + const currencyCode = extractOrPushError( + CurrencyCode.create(sourceParent.currency_code), + "currency_code", + errors + ); const description = extractOrPushError( maybeFromNullableVO(source.description, (value) => @@ -69,36 +75,27 @@ export class CustomerInvoiceItemMapper ); const quantity = extractOrPushError( - maybeFromNullableVO(source.quantity_value, (value) => - Quantity.create({ value, }) - - CustomerInvoiceItemQuantity.create({ - value: source.quantity_amouwnt, - scale: source.quantity_scale, - }), + maybeFromNullableVO(source.quantity_value, (value) => ItemQuantity.create({ value })), "discount_percentage", errors ); const unitAmount = extractOrPushError( - CustomerInvoiceItemUnitAmount.create({ - value: source.unit_price_amount, - scale: source.unit_price_scale, - }), - "discount_percentage", + maybeFromNullableVO(source.unit_amount_value, (value) => + ItemAmount.create({ value, currency_code: currencyCode!.code }) + ), + "unit_amount", errors ); const discountPercentage = extractOrPushError( - CustomerInvoiceItemDiscount.create({ - value: source.discount_amount, - scale: source.discount_scale, - }), + maybeFromNullableVO(source.discount_percentage_value, (value) => + ItemDiscount.create({ value }) + ), "discount_percentage", errors ); - // Creación del objeto de dominio return CustomerInvoiceItem.create( { @@ -108,8 +105,8 @@ export class CustomerInvoiceItemMapper quantity: quantity!, unitAmount: unitAmount!, discountPercentage: discountPercentage!, - },º - id + }, + itemId ); } @@ -117,14 +114,12 @@ export class CustomerInvoiceItemMapper source: CustomerInvoiceItem, params?: MapperParamsType ): InferCreationAttributes { - 1 + 1; const { index, sourceParent } = params as { index: number; sourceParent: CustomerInvoice; }; - - return { item_id: source.id.toPrimitive(), invoice_id: sourceParent.id.toPrimitive(), @@ -135,23 +130,41 @@ export class CustomerInvoiceItemMapper quantity_value: toNullable(source.quantity, (value) => value.toPrimitive().value), quantity_scale: source.quantity.match( (value) => value.toPrimitive().scale, - () => CustomerInvoiceItemQuantity.DEFAULT_SCALE), + () => ItemQuantity.DEFAULT_SCALE + ), unit_amount_value: toNullable(source.unitAmount, (value) => value.toPrimitive().value), unit_amount_scale: source.unitAmount.match( (value) => value.toPrimitive().scale, - () => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE), + () => ItemAmount.DEFAULT_SCALE + ), subtotal_amount_value: source.subtotalAmount.toPrimitive().value, subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale, - discount_percentage_value: toNullable(source.discountPercentage, (value) => value.toPrimitive().value), + discount_percentage_value: toNullable( + source.discountPercentage, + (value) => value.toPrimitive().value + ), discount_percentage_scale: source.discountPercentage.match( (value) => value.toPrimitive().scale, - () => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE), + () => ItemAmount.DEFAULT_SCALE + ), - discount_amount_value: source.subtotalAmount.toPrimitive().value, - discount_amount_scale: source.subtotalAmount.toPrimitive().scale, + discount_amount_value: toNullable( + source.discountAmount, + (value) => value.toPrimitive().value + ), + discount_amount_scale: source.discountAmount.match( + (value) => value.toPrimitive().scale, + () => ItemDiscount.DEFAULT_SCALE + ), + + taxable_amount_value: source.taxableAmount.toPrimitive().value, + taxable_amount_scale: source.taxableAmount.toPrimitive().value, + + taxes_amount_value: source.taxesAmount.toPrimitive().value, + taxes_amount_scale: source.taxesAmount.toPrimitive().value, total_amount_value: source.totalAmount.toPrimitive().value, total_amount_scale: source.totalAmount.toPrimitive().scale, diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts index 3d44d69e..8b944777 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/customer-invoice.mapper.ts @@ -14,10 +14,12 @@ import { UniqueID, UtcDate, maybeFromNullableVO, + toNullable, } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { CustomerInvoice, + CustomerInvoiceItems, CustomerInvoiceNumber, CustomerInvoiceProps, CustomerInvoiceSerie, @@ -149,7 +151,10 @@ export class CustomerInvoiceMapper discountPercentage: discountPercentage!, - items: items!, + items: CustomerInvoiceItems.create({ + languageCode: languageCode!, + currencyCode: currencyCode!, + }), }; return CustomerInvoice.create(invoiceProps, invoiceId); @@ -162,26 +167,41 @@ export class CustomerInvoiceMapper source: CustomerInvoice, params?: MapperParamsType ): CustomerInvoiceCreationAttributes { - const subtotal = source.calculateSubtotal(); - const total = source.calculateTotal(); + //const subtotal = source.calculateSubtotal(); + //const total = source.calculateTotal(); const items = this._itemsMapper.mapCollectionToPersistence(source.items, params); return { - id: source.id.toString(), - invoice_status: source.status.toPrimitive(), - invoice_series: source.invoiceSeries.toPrimitive(), + id: source.id.toPrimitive(), + company_id: source.companyId.toPrimitive(), + + status: source.status.toPrimitive(), + series: toNullable(source.series, (series) => series.toPrimitive()), + invoice_number: source.invoiceNumber.toPrimitive(), invoice_date: source.invoiceDate.toPrimitive(), - operation_date: source.operationDate.toPrimitive(), - invoice_language: "es", - invoice_currency: source.currency || "EUR", - subtotal_amount: subtotal.amount, - subtotal_scale: subtotal.scale, + operation_date: toNullable(source.operationDate, (operationDate) => + operationDate.toPrimitive() + ), - total_amount: total.amount, - total_scale: total.scale, + notes: toNullable(source.notes, (notes) => notes.toPrimitive()), + + language_code: source.languageCode.code, + currency_code: source.currencyCode.code, + + subtotal_amount_value: 0, //subtotal.amount, + subtotal_amount_scale: 2, //subtotal.scale, + + /*discount_percentage_value: source.discountPercentage.value, + discount_percentage_scale: source.discountPercentage.scale, + + discount_amount_value: source.discountAmount.value, + discount_amount_scale: source.discountAmount.scale,*/ + + total_amount_value: 0, //total.amount, + total_amount_scale: 2, //total.scale, items, }; diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts index 1a3cf44d..132069e8 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item-taxes.model.ts @@ -17,7 +17,7 @@ export class CustomerInvoiceItemTaxesModel extends Model< InferAttributes, InferCreationAttributes > { - declare id: string; + declare tax_id: string; declare item_id: string; declare tax_code: string; //"iva_21" @@ -38,7 +38,7 @@ export class CustomerInvoiceItemTaxesModel extends Model< CustomerInvoiceItemTaxesModel.belongsTo(CustomerInvoiceItemModel, { as: "item", - targetKey: "id", + targetKey: "item_id", foreignKey: "item_id", onDelete: "CASCADE", }); @@ -50,7 +50,7 @@ export class CustomerInvoiceItemTaxesModel extends Model< export default (database: Sequelize) => { CustomerInvoiceItemTaxesModel.init( { - id: { + tax_id: { type: new DataTypes.UUID(), primaryKey: true, }, diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts index f0919373..f981bc88 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-item.model.ts @@ -195,10 +195,7 @@ export default (database: Sequelize) => { underscored: true, - indexes: [ - { name: "invoice_idx", fields: ["invoice_id"], unique: false }, - { name: "parent_idx", fields: ["parent_id"], unique: false }, - ], + indexes: [{ name: "invoice_idx", fields: ["invoice_id"], unique: false }], whereMergeStrategy: "and", // <- cómo tratar el merge de un scope diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts index 5fbb1021..2726d03e 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice-taxes.model.ts @@ -17,7 +17,7 @@ export class CustomerInvoiceTaxesModel extends Model< InferAttributes, InferCreationAttributes > { - declare id: string; + declare tax_id: string; declare invoice_id: string; declare tax_code: string; //"iva_21" @@ -50,7 +50,7 @@ export class CustomerInvoiceTaxesModel extends Model< export default (database: Sequelize) => { CustomerInvoiceTaxesModel.init( { - id: { + tax_id: { type: new DataTypes.UUID(), primaryKey: true, }, diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts index 7359e748..8cfc4f63 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/index.ts @@ -1,9 +1,19 @@ +import customerInvoiceItemTaxesModelInit from "./customer-invoice-item-taxes.model"; import customerInvoiceItemModelInit from "./customer-invoice-item.model"; +import customerInvoiceTaxesModelInit from "./customer-invoice-taxes.model"; import customerInvoiceModelInit from "./customer-invoice.model"; -export * from "./customer-invoice-item.model"; // exporta las clases, tipos +export * from "./customer-invoice-item-taxes.model"; +export * from "./customer-invoice-item.model"; +export * from "./customer-invoice-taxes.model"; export * from "./customer-invoice.model"; export * from "./customer-invoice.repository"; // Array de inicializadores para que registerModels() lo use -export const models = [customerInvoiceItemModelInit, customerInvoiceModelInit]; +export const models = [ + customerInvoiceModelInit, + customerInvoiceItemModelInit, + + customerInvoiceTaxesModelInit, + customerInvoiceItemTaxesModelInit, +]; diff --git a/packages/rdx-ddd/src/value-objects/currency-code.ts b/packages/rdx-ddd/src/value-objects/currency-code.ts index 51287f35..50cab41c 100644 --- a/packages/rdx-ddd/src/value-objects/currency-code.ts +++ b/packages/rdx-ddd/src/value-objects/currency-code.ts @@ -38,6 +38,10 @@ export class CurrencyCode extends ValueObject { return Result.ok(new CurrencyCode({ value: valueIsValid.data })); } + get code(): string { + return this.props.value; + } + getProps(): string { return this.props.value; } diff --git a/packages/rdx-ddd/src/value-objects/language-code.ts b/packages/rdx-ddd/src/value-objects/language-code.ts index 2338361d..50fbc8f9 100644 --- a/packages/rdx-ddd/src/value-objects/language-code.ts +++ b/packages/rdx-ddd/src/value-objects/language-code.ts @@ -38,6 +38,10 @@ export class LanguageCode extends ValueObject { return Result.ok(new LanguageCode({ value: valueIsValid.data })); } + get code(): string { + return this.props.value; + } + getProps(): string { return this.props.value; } diff --git a/packages/rdx-ddd/src/value-objects/text-value.ts b/packages/rdx-ddd/src/value-objects/text-value.ts index 2eb25433..43b1dc26 100644 --- a/packages/rdx-ddd/src/value-objects/text-value.ts +++ b/packages/rdx-ddd/src/value-objects/text-value.ts @@ -34,6 +34,6 @@ export class TextValue extends ValueObject { } toPrimitive(): string { - return this.getProps(); + return String(this.getProps()); } } diff --git a/packages/rdx-ddd/src/value-objects/unique-id.ts b/packages/rdx-ddd/src/value-objects/unique-id.ts index 5cdcaed7..642de3cf 100644 --- a/packages/rdx-ddd/src/value-objects/unique-id.ts +++ b/packages/rdx-ddd/src/value-objects/unique-id.ts @@ -37,10 +37,10 @@ export class UniqueID extends ValueObject { } toString(): string { - return this.getProps(); + return String(this.getProps()); } toPrimitive() { - return this.getProps(); + return this.toString(); } }