From ac11eaffdf33c879c30e11d3a8b8f53be39fdf82 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 18 Sep 2025 10:49:32 +0200 Subject: [PATCH] Facturas de cliente --- .../application/helpers/format-money-dto.ts | 12 +++++++ .../src/api/application/helpers/index.ts | 1 + ...map-dto-to-customer-invoice-items-props.ts | 16 +++++---- .../map-dto-to-customer-invoice-props.ts | 33 ++++++++++++------- .../customer-invoice.report.presenter.ts | 17 ++++++++++ .../application/presenters/queries/index.ts | 1 + .../reporter/customer-invoice.report.html.ts | 9 ++++- .../templates/customer-invoice/template.hbs | 2 +- .../src/api/infrastructure/dependencies.ts | 9 +++++ .../mappers/domain/customer-invoice.mapper.ts | 6 ++-- .../rdx-ddd/src/value-objects/money-value.ts | 4 +-- 11 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 modules/customer-invoices/src/api/application/helpers/format-money-dto.ts create mode 100644 modules/customer-invoices/src/api/application/presenters/queries/customer-invoice.report.presenter.ts diff --git a/modules/customer-invoices/src/api/application/helpers/format-money-dto.ts b/modules/customer-invoices/src/api/application/helpers/format-money-dto.ts new file mode 100644 index 00000000..ce5d39a8 --- /dev/null +++ b/modules/customer-invoices/src/api/application/helpers/format-money-dto.ts @@ -0,0 +1,12 @@ +import { MoneyDTO } from "@erp/core"; +import { MoneyValue } from "@repo/rdx-ddd"; + +export function formatMoneyDTO(amount: MoneyDTO, locale: string) { + const money = MoneyValue.create({ + value: Number(amount.value), + currency_code: amount.currency_code, + scale: Number(amount.scale), + }).data; + + return money.format(locale); +} diff --git a/modules/customer-invoices/src/api/application/helpers/index.ts b/modules/customer-invoices/src/api/application/helpers/index.ts index 02b751c0..907223bb 100644 --- a/modules/customer-invoices/src/api/application/helpers/index.ts +++ b/modules/customer-invoices/src/api/application/helpers/index.ts @@ -1 +1,2 @@ +export * from "./format-money-dto"; export * from "./map-dto-to-customer-invoice-props"; 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 e0369f3b..e37fbef2 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 @@ -1,16 +1,21 @@ -import { ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd"; +import { + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, +} from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; +import { CreateCustomerInvoiceRequestDTO } from "../../../common"; import { CustomerInvoiceItem, CustomerInvoiceItemDescription, ItemAmount, ItemDiscount, + ItemQuantity, } from "../../domain"; -import { extractOrPushError } from "./extract-or-push-error"; import { hasNoUndefinedFields } from "./has-no-undefined-fields"; export function mapDTOToCustomerInvoiceItemsProps( - dtoItems: Pick["items"] + dtoItems: Pick["items"] ): Result { const errors: ValidationErrorDetail[] = []; const items: CustomerInvoiceItem[] = []; @@ -25,9 +30,8 @@ export function mapDTOToCustomerInvoiceItemsProps( ); const quantity = extractOrPushError( - CustomerInvoiceItemQuantity.create({ - amount: item.quantity.amount, - scale: item.quantity.scale, + ItemQuantity.create({ + value: Number(item.quantity), }), path("quantity"), errors diff --git a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts index 9a180234..8d1947a2 100644 --- a/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts +++ b/modules/customer-invoices/src/api/application/helpers/map-dto-to-customer-invoice-props.ts @@ -1,14 +1,20 @@ -import { ValidationErrorCollection, ValidationErrorDetail } from "@erp/core/api"; -import { UniqueID, UtcDate } from "@repo/rdx-ddd"; +import { + CurrencyCode, + UniqueID, + UtcDate, + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, + maybeFromNullableVO, +} from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CreateCustomerInvoiceCommandDTO } from "../../../common/dto"; +import { CreateCustomerInvoiceRequestDTO } from "../../../common"; import { CustomerInvoiceNumber, CustomerInvoiceProps, CustomerInvoiceSerie, CustomerInvoiceStatus, } from "../../domain"; -import { extractOrPushError } from "./extract-or-push-error"; import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice-items-props"; /** @@ -21,7 +27,7 @@ import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice * */ -export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDTO) { +export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) { const errors: ValidationErrorDetail[] = []; const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors); @@ -32,7 +38,7 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT errors ); const invoiceSeries = extractOrPushError( - CustomerInvoiceSerie.create(dto.invoice_series), + maybeFromNullableVO(dto.invoice_series, (value) => CustomerInvoiceSerie.create(value)), "invoice_series", errors ); @@ -42,13 +48,16 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT errors ); const operationDate = extractOrPushError( - UtcDate.createFromISO(dto.operation_date), + maybeFromNullableVO(dto.operation_date, (value) => UtcDate.createFromISO(value)), "operation_date", errors ); - //const currency = extractOrPushError(Currency.(dto.currency), "currency", errors); - const currency = dto.currency; + const currencyCode = extractOrPushError( + CurrencyCode.create(dto.currency_code), + "currency", + errors + ); // 🔄 Validar y construir los items de factura con helper especializado const itemsResult = mapDTOToCustomerInvoiceItemsProps(dto.items); @@ -57,16 +66,16 @@ export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDT } if (errors.length > 0) { - return Result.fail(new ValidationErrorCollection(errors)); + return Result.fail(new ValidationErrorCollection("Customer dto mapping failed", errors)); } const invoiceProps: CustomerInvoiceProps = { invoiceNumber: invoiceNumber!, - invoiceSeries: invoiceSeries!, + series: invoiceSeries!, invoiceDate: invoiceDate!, operationDate: operationDate!, status: CustomerInvoiceStatus.createDraft(), - currency, + currencyCode: currencyCode!, }; return Result.ok({ id: invoiceId!, props: invoiceProps }); diff --git a/modules/customer-invoices/src/api/application/presenters/queries/customer-invoice.report.presenter.ts b/modules/customer-invoices/src/api/application/presenters/queries/customer-invoice.report.presenter.ts new file mode 100644 index 00000000..1000c31a --- /dev/null +++ b/modules/customer-invoices/src/api/application/presenters/queries/customer-invoice.report.presenter.ts @@ -0,0 +1,17 @@ +import { Presenter } from "@erp/core/api"; +import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto"; +import { formatMoneyDTO } from "../../helpers"; + +export class CustomerInvoiceReportPresenter extends Presenter< + GetCustomerInvoiceByIdResponseDTO, + unknown +> { + toOutput(invoiceDTO: GetCustomerInvoiceByIdResponseDTO) { + const locale = invoiceDTO.language_code; + + return { + ...invoiceDTO, + total_amount: formatMoneyDTO(invoiceDTO.total_amount, locale), + }; + } +} diff --git a/modules/customer-invoices/src/api/application/presenters/queries/index.ts b/modules/customer-invoices/src/api/application/presenters/queries/index.ts index 3db39470..8813c089 100644 --- a/modules/customer-invoices/src/api/application/presenters/queries/index.ts +++ b/modules/customer-invoices/src/api/application/presenters/queries/index.ts @@ -1 +1,2 @@ +export * from "./customer-invoice.report.presenter"; export * from "./list-customer-invoices.presenter"; diff --git a/modules/customer-invoices/src/api/application/use-cases/report/reporter/customer-invoice.report.html.ts b/modules/customer-invoices/src/api/application/use-cases/report/reporter/customer-invoice.report.html.ts index fc932571..e4331168 100644 --- a/modules/customer-invoices/src/api/application/use-cases/report/reporter/customer-invoice.report.html.ts +++ b/modules/customer-invoices/src/api/application/use-cases/report/reporter/customer-invoice.report.html.ts @@ -11,13 +11,20 @@ export class CustomerInvoiceReportHTMLPresenter extends Presenter { projection: "FULL", }); + const prePresenter = this.presenterRegistry.getPresenter({ + resource: "customer-invoice", + projection: "REPORT", + format: "JSON", + }); + const invoiceDTO = dtoPresenter.toOutput(customerInvoice); + const prettyDTO = prePresenter.toOutput(invoiceDTO); // Obtener y compilar la plantilla HTML const templateHtml = readFileSync( path.join(__dirname, "./templates/customer-invoice/template.hbs") ).toString(); const template = handlebars.compile(templateHtml, {}); - return template(invoiceDTO); + return template(prettyDTO); } } diff --git a/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs b/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs index bbaba5ce..624ca84e 100644 --- a/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs +++ b/modules/customer-invoices/src/api/application/use-cases/report/reporter/templates/customer-invoice/template.hbs @@ -139,7 +139,7 @@
- TOTAL: {{total_amount.value}} € + TOTAL: {{total_amount}} €
diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index 62a4716e..60408dee 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -14,6 +14,7 @@ import { CustomerInvoiceItemsFullPresenter, CustomerInvoiceReportHTMLPresenter, CustomerInvoiceReportPDFPresenter, + CustomerInvoiceReportPresenter, GetCustomerInvoiceUseCase, ListCustomerInvoicesPresenter, ListCustomerInvoicesUseCase, @@ -99,6 +100,14 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer }, presenter: new ListCustomerInvoicesPresenter(presenterRegistry), }, + { + key: { + resource: "customer-invoice", + projection: "REPORT", + format: "JSON", + }, + presenter: new CustomerInvoiceReportPresenter(presenterRegistry), + }, { key: { resource: "customer-invoice", diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts index a72f6ef7..5e7b619a 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts @@ -119,7 +119,7 @@ export class CustomerInvoiceDomainMapper const discountPercentage = extractOrPushError( Percentage.create({ - value: source.discount_amount_scale, + value: source.discount_percentage_value, scale: source.discount_percentage_scale, }), "discount_percentage_value", @@ -206,9 +206,7 @@ export class CustomerInvoiceDomainMapper // 5) Si hubo errores de mapeo, devolvemos colección de validación if (errors.length > 0) { - return Result.fail( - new ValidationErrorCollection("Customer invoice mapping failed", errors) - ); + return Result.fail(new ValidationErrorCollection("Customer mapping failed", errors)); } // 6) Construcción del agregado (Dominio) diff --git a/packages/rdx-ddd/src/value-objects/money-value.ts b/packages/rdx-ddd/src/value-objects/money-value.ts index 5d291742..fd1500b9 100644 --- a/packages/rdx-ddd/src/value-objects/money-value.ts +++ b/packages/rdx-ddd/src/value-objects/money-value.ts @@ -207,7 +207,7 @@ export class MoneyValue extends ValueObject implements IMoneyVa /** * Devuelve una cadena con el importe formateado. - * Ejemplo: 123456 -> €1,234.56 + * Ejemplo: 123456 -> 1.234,56 € * @param locale Código de idioma y país (ej. "es-ES") * @returns Importe formateado */ @@ -222,6 +222,6 @@ export class MoneyValue extends ValueObject implements IMoneyVa minimumFractionDigits: scale, maximumFractionDigits: scale, useGrouping: true, - }).format(value); + }).format(this.formattedValue); } }