diff --git a/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts b/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts index 4099a1a5..a33289d5 100644 --- a/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts +++ b/modules/core/src/api/domain/value-objects/discount-percentage.vo.ts @@ -1,16 +1,26 @@ -import { Percentage, type PercentageProps } from "@repo/rdx-ddd"; -import type { Result } from "@repo/rdx-utils"; +import { Percentage, type PercentageProps, ValidationErrorCollection } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; -type DiscountPercentageProps = Pick; +type DiscountPercentageProps = PercentageProps; export class DiscountPercentage extends Percentage { static DEFAULT_SCALE = 2; - static create({ value }: DiscountPercentageProps): Result { - return Percentage.create({ - value, - scale: DiscountPercentage.DEFAULT_SCALE, - }); + static create({ value, scale }: DiscountPercentageProps): Result { + if (scale && scale !== DiscountPercentage.DEFAULT_SCALE) { + return Result.fail( + new ValidationErrorCollection("InvalidScale", [ + { message: `DiscountPercentage scale must be ${DiscountPercentage.DEFAULT_SCALE}` }, + ]) + ); + } + + return Result.ok( + new DiscountPercentage({ + value, + scale: DiscountPercentage.DEFAULT_SCALE, + }) + ); } static zero() { diff --git a/modules/core/src/common/helpers/money-dto-helper.ts b/modules/core/src/common/helpers/money-dto-helper.ts index 72ebf1da..7371654c 100644 --- a/modules/core/src/common/helpers/money-dto-helper.ts +++ b/modules/core/src/common/helpers/money-dto-helper.ts @@ -92,6 +92,21 @@ const fromNumber = (amount: number, currency = "EUR", scale = 2): MoneyDTO => { }; }; +const fromNumberNulleable = ( + amount: number | null, + currency = "EUR", + scale = 2 +): MoneyDTO | null => { + if (amount === null) { + return null; + } + return { + value: String(Math.round(amount * 10 ** scale)), + scale: String(scale), + currency_code: currency, + }; +}; + /** * Convierte cadena numérica a MoneyDTO. */ @@ -126,6 +141,7 @@ export const MoneyDTOHelper = { toNumericString, toNumericNulleable, fromNumber, + fromNumberNulleable, fromNumericString, format, }; diff --git a/modules/core/src/common/helpers/percentage-dto-helper.ts b/modules/core/src/common/helpers/percentage-dto-helper.ts index 3f0fe8de..317d2853 100644 --- a/modules/core/src/common/helpers/percentage-dto-helper.ts +++ b/modules/core/src/common/helpers/percentage-dto-helper.ts @@ -84,6 +84,16 @@ const fromNumber = (amount: number, scale = 2): PercentageDTO => { }; }; +const fromNumberNulleable = (amount: number | null, scale = 2): PercentageDTO | null => { + if (amount === null) { + return null; + } + return { + value: String(Math.round(amount * 10 ** scale)), + scale: String(scale), + }; +}; + /** * Convierte cadena numérica a PercentageDTO. */ @@ -106,6 +116,7 @@ export const PercentageDTOHelper = { toNumericString, toNumericNulleable, fromNumber, + fromNumberNulleable, fromNumericString, format, }; diff --git a/modules/core/src/common/helpers/quantity-dto-helper.ts b/modules/core/src/common/helpers/quantity-dto-helper.ts index d6dcb378..b8bc87bf 100644 --- a/modules/core/src/common/helpers/quantity-dto-helper.ts +++ b/modules/core/src/common/helpers/quantity-dto-helper.ts @@ -78,6 +78,16 @@ const fromNumber = (amount: number, scale = 2): QuantityDTO => { }; }; +const fromNumberNulleable = (amount: number | null, scale = 2): QuantityDTO | null => { + if (amount === null) { + return null; + } + return { + value: String(Math.round(amount * 10 ** scale)), + scale: String(scale), + }; +}; + /** * Convierte cadena numérica a QuantityDTO. */ @@ -100,6 +110,7 @@ export const QuantityDTOHelper = { toNumericString, toNumericNulleable, fromNumber, + fromNumberNulleable, fromNumericString, format, }; diff --git a/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts b/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts index 539f6967..534520ae 100644 --- a/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts +++ b/modules/customer-invoices/src/api/application/proformas/di/proforma-input-mappers.di.ts @@ -11,12 +11,10 @@ export interface IProformaInputMappers { updateInputMapper: UpdateProformaInputMapper; } -export const buildProformaInputMappers = (_catalogs: ICatalogs): IProformaInputMappers => { - //const { taxCatalog } = catalogs; - +export const buildProformaInputMappers = (catalogs: ICatalogs): IProformaInputMappers => { // Mappers el DTO a las props validadas (ProformaProps) y luego construir agregado - const createInputMapper = new CreateProformaInputMapper(); - const updateInputMapper = new UpdateProformaInputMapper(); + const createInputMapper = new CreateProformaInputMapper(catalogs); + const updateInputMapper = new UpdateProformaInputMapper(catalogs); return { createInputMapper, diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-input.mapper.ts b/modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-input.mapper.ts index 1cfc2df7..948b525b 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-input.mapper.ts +++ b/modules/customer-invoices/src/api/application/proformas/mappers/create-proforma-input.mapper.ts @@ -1,4 +1,5 @@ -import { DiscountPercentage } from "@erp/core/api"; +import type { JsonTaxCatalogProvider } from "@erp/core"; +import { DiscountPercentage, type ICatalogs, Tax } from "@erp/core/api"; import { CurrencyCode, DomainError, @@ -43,6 +44,11 @@ export interface ICreateProformaInputMapper { */ export class CreateProformaInputMapper implements ICreateProformaInputMapper { + private readonly taxCatalog: JsonTaxCatalogProvider; + + constructor(catalogs: ICatalogs) { + this.taxCatalog = catalogs.taxCatalog; + } public map( dto: CreateProformaRequestDTO, params: { companyId: UniqueID } @@ -238,16 +244,35 @@ export class CreateProformaInputMapper implements ICreateProformaInputMapper { taxesDTO: CreateProformaRequestDTO["items"][number]["taxes"], params: { itemIndex: number; errors: ValidationErrorDetail[] } ): ProformaItemTaxesProps { - if (taxesDTO === "#;#;#") { + const parts = taxesDTO.split(";"); + if (parts.length !== 3) { + params.errors.push({ + path: `items[${params.itemIndex}].taxes`, + message: "Tax combination must contain exactly three elements", + }); return ProformaItemTaxes.empty().getProps(); } - params.errors.push({ - path: `items[${params.itemIndex}].taxes`, - message: "Tax combination mapping is not implemented yet", - }); + const [ivaCode, recCode, retentionCode] = parts; - return ProformaItemTaxes.empty().getProps(); + const iva = this.mapTaxCode(ivaCode, `items[${params.itemIndex}].taxes.iva`, params.errors); + const rec = this.mapTaxCode(recCode, `items[${params.itemIndex}].taxes.rec`, params.errors); + const retention = this.mapTaxCode( + retentionCode, + `items[${params.itemIndex}].taxes.retention`, + params.errors + ); + + return ProformaItemTaxes.create({ iva, rec, retention }).data.getProps(); + } + + private mapTaxCode(code: string, path: string, errors: ValidationErrorDetail[]): Maybe { + if (code === "#") { + return Maybe.none(); + } + + const tax = extractOrPushError(Tax.createFromCode(code, this.taxCatalog), path, errors); + return tax ? Maybe.some(tax) : Maybe.none(); } private throwIfValidationErrors(errors: ValidationErrorDetail[]): void { diff --git a/modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-input.mapper.ts b/modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-input.mapper.ts index d144d710..752b5d37 100644 --- a/modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-input.mapper.ts +++ b/modules/customer-invoices/src/api/application/proformas/mappers/update-proforma-input.mapper.ts @@ -1,4 +1,5 @@ -import { DiscountPercentage } from "@erp/core/api"; +import type { JsonTaxCatalogProvider } from "@erp/core"; +import { DiscountPercentage, type ICatalogs, Tax } from "@erp/core/api"; import { CurrencyCode, DomainError, @@ -11,7 +12,7 @@ import { extractOrPushError, maybeFromNullableResult, } from "@repo/rdx-ddd"; -import { NumberHelper, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; +import { Maybe, NumberHelper, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; import type { UpdateProformaByIdRequestDTO } from "../../../../common/dto"; import { @@ -47,6 +48,11 @@ export interface IUpdateProformaInputMapper { */ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper { + private readonly taxCatalog: JsonTaxCatalogProvider; + constructor(catalogs: ICatalogs) { + this.taxCatalog = catalogs.taxCatalog; + } + public map( dto: UpdateProformaByIdRequestDTO, _params: { companyId: UniqueID } @@ -201,26 +207,20 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper { ); const quantity = extractOrPushError( - maybeFromNullableResult(item.quantity, (value) => - ItemQuantity.create({ value: NumberHelper.toSafeNumber(value) }) - ), + maybeFromNullableResult(item.quantity, (dto) => ItemQuantity.fromObjectString(dto)), `items[${index}].quantity`, params.errors ); const unitAmount = extractOrPushError( - maybeFromNullableResult(item.unit_amount, (value) => - ItemAmount.create({ value: NumberHelper.toSafeNumber(value) }) - ), + maybeFromNullableResult(item.unit_amount, (dto) => ItemAmount.fromObjectString(dto)), `items[${index}].unit_amount`, params.errors ); const itemDiscountPercentage = extractOrPushError( - maybeFromNullableResult(item.item_discount_percentage, (value) => - DiscountPercentage.create({ - value: NumberHelper.toSafeNumber(value.value), - }) + maybeFromNullableResult(item.item_discount_percentage, (dto) => + DiscountPercentage.fromObjectString(dto) ), `items[${index}].item_discount_percentage`, params.errors @@ -246,24 +246,35 @@ export class UpdateProformaInputMapper implements IUpdateProformaInputMapper { taxesDTO: NonNullable[number]["taxes"], params: { itemIndex: number; errors: ValidationErrorDetail[] } ): ProformaItemTaxesProps { - if (taxesDTO === "#;#;#") { + const parts = taxesDTO.split(";"); + if (parts.length !== 3) { + params.errors.push({ + path: `items[${params.itemIndex}].taxes`, + message: "Tax combination must contain exactly three elements", + }); return ProformaItemTaxes.empty().getProps(); } - /** - * Pendiente: resolver códigos contra catálogo fiscal. - * - * taxesDTO llega como: - * - iva_21;#;retention_10 - * - iva_10;rec_5_2;# - * - #;#;# - */ - params.errors.push({ - path: `items[${params.itemIndex}].taxes`, - message: "Tax combination mapping is not implemented yet", - }); + const [ivaCode, recCode, retentionCode] = parts; - return ProformaItemTaxes.empty().getProps(); + const iva = this.mapTaxCode(ivaCode, `items[${params.itemIndex}].taxes.iva`, params.errors); + const rec = this.mapTaxCode(recCode, `items[${params.itemIndex}].taxes.rec`, params.errors); + const retention = this.mapTaxCode( + retentionCode, + `items[${params.itemIndex}].taxes.retention`, + params.errors + ); + + return ProformaItemTaxes.create({ iva, rec, retention }).data.getProps(); + } + + private mapTaxCode(code: string, path: string, errors: ValidationErrorDetail[]): Maybe { + if (code === "#") { + return Maybe.none(); + } + + const tax = extractOrPushError(Tax.createFromCode(code, this.taxCatalog), path, errors); + return tax ? Maybe.some(tax) : Maybe.none(); } private throwIfValidationErrors(errors: ValidationErrorDetail[]): void { diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/domain/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/domain/index.ts deleted file mode 100644 index ec1298bd..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/domain/index.ts +++ /dev/null @@ -1 +0,0 @@ -//export * from "./proformas"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/index.ts deleted file mode 100644 index 8a793490..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/index.ts +++ /dev/null @@ -1 +0,0 @@ -//export * from "./proforma.full.presenter"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma-items.full.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma-items.full.presenter.ts deleted file mode 100644 index c048d99f..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma-items.full.presenter.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { SnapshotBuilder } from "@erp/core/api"; -import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common"; -import { maybeToEmptyString } from "@repo/rdx-ddd"; -import type { ArrayElement } from "@repo/rdx-utils"; - -import type { CustomerInvoiceItems, IssuedInvoiceItem } from "../../../../domain"; - -type GetProformaItemByIdResponseDTO = ArrayElement; - -export class ProformaItemsFullPresenter extends SnapshotBuilder { - private _mapItem(proformaItem: IssuedInvoiceItem, index: number): GetProformaItemByIdResponseDTO { - const allAmounts = proformaItem.calculateAllAmounts(); - - return { - id: proformaItem.id.toPrimitive(), - is_valued: String(proformaItem.isValued), - position: String(index), - description: maybeToEmptyString(proformaItem.description, (value) => value.toPrimitive()), - - quantity: proformaItem.quantity.match( - (quantity) => quantity.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - unit_amount: proformaItem.unitAmount.match( - (unitAmount) => unitAmount.toObjectString(), - () => ({ value: "", scale: "", currency_code: "" }) - ), - - subtotal_amount: allAmounts.subtotalAmount.toObjectString(), - - discount_percentage: proformaItem.itemDiscountPercentage.match( - (discountPercentage) => discountPercentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - discount_amount: allAmounts.itemDiscountAmount.toObjectString(), - - global_discount_percentage: proformaItem.globalDiscountPercentage.match( - (discountPercentage) => discountPercentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - global_discount_amount: allAmounts.globalDiscountAmount.toObjectString(), - - taxable_amount: allAmounts.taxableAmount.toObjectString(), - - iva_code: proformaItem.taxes.iva.match( - (iva) => iva.code, - () => "" - ), - - iva_percentage: proformaItem.taxes.iva.match( - (iva) => iva.percentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - iva_amount: allAmounts.ivaAmount.toObjectString(), - - rec_code: proformaItem.taxes.rec.match( - (rec) => rec.code, - () => "" - ), - - rec_percentage: proformaItem.taxes.rec.match( - (rec) => rec.percentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - rec_amount: allAmounts.recAmount.toObjectString(), - - retention_code: proformaItem.taxes.retention.match( - (retention) => retention.code, - () => "" - ), - - retention_percentage: proformaItem.taxes.retention.match( - (retention) => retention.percentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - retention_amount: allAmounts.retentionAmount.toObjectString(), - - taxes_amount: allAmounts.taxesAmount.toObjectString(), - - total_amount: allAmounts.totalAmount.toObjectString(), - }; - } - - toOutput(proformaItems: CustomerInvoiceItems): GetProformaByIdResponseDTO["items"] { - return proformaItems.map(this._mapItem); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma-recipient.full.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma-recipient.full.presenter.ts deleted file mode 100644 index ecf03b4e..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma-recipient.full.presenter.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { SnapshotBuilder } from "@erp/core/api"; -import { DomainValidationError, maybeToEmptyString } from "@repo/rdx-ddd"; - -import type { GetIssuedInvoiceByIdResponseDTO as GetProformaByIdResponseDTO } from "../../../../../common/dto"; -import type { InvoiceRecipient, Proforma } from "../../../../domain"; - -type GetProformaRecipientByIdResponseDTO = GetProformaByIdResponseDTO["recipient"]; - -export class ProformaRecipientFullPresenter extends SnapshotBuilder { - toOutput(proforma: Proforma): GetProformaRecipientByIdResponseDTO { - if (!proforma.recipient) { - throw DomainValidationError.requiredValue("recipient", { - cause: proforma, - }); - } - - return proforma.recipient.match( - (recipient: InvoiceRecipient) => { - return { - id: proforma.customerId.toString(), - name: recipient.name.toString(), - tin: recipient.tin.toString(), - street: maybeToEmptyString(recipient.street, (value) => value.toString()), - street2: maybeToEmptyString(recipient.street2, (value) => value.toString()), - city: maybeToEmptyString(recipient.city, (value) => value.toString()), - province: maybeToEmptyString(recipient.province, (value) => value.toString()), - postal_code: maybeToEmptyString(recipient.postalCode, (value) => value.toString()), - country: maybeToEmptyString(recipient.country, (value) => value.toString()), - }; - }, - () => { - return { - id: "", - name: "", - tin: "", - street: "", - street2: "", - city: "", - province: "", - postal_code: "", - country: "", - }; - } - ); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma.full.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma.full.presenter.ts deleted file mode 100644 index 8d681493..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/domain/proformas/proforma.full.presenter.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Presenter } from "@erp/core/api"; -import { maybeToEmptyString } from "@repo/rdx-ddd"; - -import type { GetProformaByIdResponseDTO } from "../../../../../common/dto"; -import { InvoiceAmount, type Proforma } from "../../../../domain"; - -import type { ProformaItemsFullPresenter } from "./proforma-items.full.presenter"; -import type { ProformaRecipientFullPresenter } from "./proforma-recipient.full.presenter"; - -export class ProformaFullPresenter extends Presenter { - toOutput(proforma: Proforma): GetProformaByIdResponseDTO { - const itemsPresenter = this.presenterRegistry.getPresenter({ - resource: "proforma-items", - projection: "FULL", - }) as ProformaItemsFullPresenter; - - const recipientPresenter = this.presenterRegistry.getPresenter({ - resource: "proforma-recipient", - projection: "FULL", - }) as ProformaRecipientFullPresenter; - - const recipient = recipientPresenter.toOutput(proforma); - const items = itemsPresenter.toOutput(proforma.items); - const allAmounts = proforma.calculateAllAmounts(); - - const payment = proforma.paymentMethod.match( - (payment) => { - const { id, payment_description } = payment.toObjectString(); - return { - payment_id: id, - payment_description, - }; - }, - () => undefined - ); - - let totalIvaAmount = InvoiceAmount.zero(proforma.currencyCode.code); - let totalRecAmount = InvoiceAmount.zero(proforma.currencyCode.code); - let totalRetentionAmount = InvoiceAmount.zero(proforma.currencyCode.code); - - const invoiceTaxes = proforma.getTaxes().map((taxGroup) => { - const { ivaAmount, recAmount, retentionAmount, totalAmount } = taxGroup.calculateAmounts(); - - totalIvaAmount = totalIvaAmount.add(ivaAmount); - totalRecAmount = totalRecAmount.add(recAmount); - totalRetentionAmount = totalRetentionAmount.add(retentionAmount); - - return { - taxable_amount: taxGroup.taxableAmount.toObjectString(), - - iva_code: taxGroup.iva.code, - iva_percentage: taxGroup.iva.percentage.toObjectString(), - iva_amount: ivaAmount.toObjectString(), - - rec_code: taxGroup.rec.match( - (rec) => rec.code, - () => "" - ), - - rec_percentage: taxGroup.rec.match( - (rec) => rec.percentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - rec_amount: recAmount.toObjectString(), - - retention_code: taxGroup.retention.match( - (retention) => retention.code, - () => "" - ), - - retention_percentage: taxGroup.retention.match( - (retention) => retention.percentage.toObjectString(), - () => ({ value: "", scale: "" }) - ), - - retention_amount: retentionAmount.toObjectString(), - - taxes_amount: totalAmount.toObjectString(), - }; - }); - - return { - id: proforma.id.toString(), - company_id: proforma.companyId.toString(), - - invoice_number: proforma.invoiceNumber.toString(), - status: proforma.status.toPrimitive(), - series: maybeToEmptyString(proforma.series, (value) => value.toString()), - - invoice_date: proforma.invoiceDate.toDateString(), - operation_date: maybeToEmptyString(proforma.operationDate, (value) => value.toDateString()), - - reference: maybeToEmptyString(proforma.reference, (value) => value.toString()), - description: maybeToEmptyString(proforma.description, (value) => value.toString()), - notes: maybeToEmptyString(proforma.notes, (value) => value.toString()), - - language_code: proforma.languageCode.toString(), - currency_code: proforma.currencyCode.toString(), - - customer_id: proforma.customerId.toString(), - recipient, - - taxes: invoiceTaxes, - - payment_method: payment, - - subtotal_amount: allAmounts.subtotalAmount.toObjectString(), - items_discount_amount: allAmounts.itemDiscountAmount.toObjectString(), - - discount_percentage: proforma.globalDiscountPercentage.toObjectString(), - discount_amount: allAmounts.globalDiscountAmount.toObjectString(), - - taxable_amount: allAmounts.taxableAmount.toObjectString(), - - iva_amount: totalIvaAmount.toObjectString(), - rec_amount: totalRecAmount.toObjectString(), - retention_amount: totalRetentionAmount.toObjectString(), - - taxes_amount: allAmounts.taxesAmount.toObjectString(), - total_amount: allAmounts.totalAmount.toObjectString(), - - items, - - metadata: { - entity: "proforma", - }, - }; - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/index.ts deleted file mode 100644 index aaac488b..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -//export * from "./domain"; -//export * from "./queries"; -//export * from "./reports"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/queries/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/queries/index.ts deleted file mode 100644 index de2b09ca..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/queries/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./issued-invoices"; -export * from "./proformas"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/queries/issued-invoices/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/queries/issued-invoices/index.ts deleted file mode 100644 index 3ed1ac53..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/queries/issued-invoices/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./issued-invoice.list.presenter"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/queries/issued-invoices/issued-invoice.list.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/queries/issued-invoices/issued-invoice.list.presenter.ts deleted file mode 100644 index ddc83ff9..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/queries/issued-invoices/issued-invoice.list.presenter.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { Criteria } from "@repo/rdx-criteria/server"; -import { maybeToEmptyString } from "@repo/rdx-ddd"; -import type { ArrayElement, Collection } from "@repo/rdx-utils"; - -import type { ListIssuedInvoicesResponseDTO } from "../../../../../common/dto"; - -export class IssuedInvoiceListPresenter extends Presenter { - protected _mapInvoice(invoice: CustomerInvoiceListDTO) { - const recipientDTO = invoice.recipient.toObjectString(); - - const verifactuDTO = invoice.verifactu.match( - (verifactu) => verifactu.toObjectString(), - () => ({ - status: "", - url: "", - qr_code: "", - }) - ); - - const invoiceDTO: ArrayElement = { - id: invoice.id.toString(), - company_id: invoice.companyId.toString(), - customer_id: invoice.customerId.toString(), - - invoice_number: invoice.invoiceNumber.toString(), - status: invoice.status.toPrimitive(), - series: maybeToEmptyString(invoice.series, (value) => value.toString()), - - invoice_date: invoice.invoiceDate.toDateString(), - operation_date: maybeToEmptyString(invoice.operationDate, (value) => value.toDateString()), - reference: maybeToEmptyString(invoice.reference, (value) => value.toString()), - description: maybeToEmptyString(invoice.description, (value) => value.toString()), - - recipient: recipientDTO, - - language_code: invoice.languageCode.code, - currency_code: invoice.currencyCode.code, - - subtotal_amount: invoice.subtotalAmount.toObjectString(), - discount_percentage: invoice.discountPercentage.toObjectString(), - discount_amount: invoice.discountAmount.toObjectString(), - taxable_amount: invoice.taxableAmount.toObjectString(), - taxes_amount: invoice.taxesAmount.toObjectString(), - total_amount: invoice.totalAmount.toObjectString(), - - verifactu: verifactuDTO, - - metadata: { - entity: "issued-invoice", - }, - }; - - return invoiceDTO; - } - - toOutput(params: { - invoices: Collection; - criteria: Criteria; - }): ListIssuedInvoicesResponseDTO { - const { invoices, criteria } = params; - - const _invoices = invoices.map((invoice) => this._mapInvoice(invoice)); - const _totalItems = invoices.total(); - - return { - page: criteria.pageNumber, - per_page: criteria.pageSize, - total_pages: Math.ceil(_totalItems / criteria.pageSize), - total_items: _totalItems, - items: _invoices, - metadata: { - entity: "issued-invoices", - criteria: criteria.toJSON(), - //links: { - // self: `/api/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`, - // first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`, - // last: `/api/customer-invoices?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`, - //}, - }, - }; - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/queries/proformas/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/queries/proformas/index.ts deleted file mode 100644 index 3c1b3639..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/queries/proformas/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./proforma.list.presenter"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/queries/proformas/proforma.list.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/queries/proformas/proforma.list.presenter.ts deleted file mode 100644 index eca0249e..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/queries/proformas/proforma.list.presenter.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Presenter } from "@erp/core/api"; -import type { Criteria } from "@repo/rdx-criteria/server"; -import { maybeToEmptyString } from "@repo/rdx-ddd"; -import type { ArrayElement, Collection } from "@repo/rdx-utils"; - -import type { ListProformasResponseDTO } from "../../../../../common/dto"; -import type { CustomerInvoiceListDTO } from "../../../../infrastructure"; - -export class ProformaListPresenter extends Presenter { - protected _mapProforma(proforma: CustomerInvoiceListDTO) { - const recipientDTO = proforma.recipient.toObjectString(); - - const invoiceDTO: ArrayElement = { - id: proforma.id.toString(), - company_id: proforma.companyId.toString(), - is_proforma: proforma.isProforma, - customer_id: proforma.customerId.toString(), - - invoice_number: proforma.invoiceNumber.toString(), - status: proforma.status.toPrimitive(), - series: maybeToEmptyString(proforma.series, (value) => value.toString()), - - invoice_date: proforma.invoiceDate.toDateString(), - operation_date: maybeToEmptyString(proforma.operationDate, (value) => value.toDateString()), - reference: maybeToEmptyString(proforma.reference, (value) => value.toString()), - description: maybeToEmptyString(proforma.description, (value) => value.toString()), - - recipient: recipientDTO, - - language_code: proforma.languageCode.code, - currency_code: proforma.currencyCode.code, - - subtotal_amount: proforma.subtotalAmount.toObjectString(), - discount_percentage: proforma.discountPercentage.toObjectString(), - discount_amount: proforma.discountAmount.toObjectString(), - taxable_amount: proforma.taxableAmount.toObjectString(), - taxes_amount: proforma.taxesAmount.toObjectString(), - total_amount: proforma.totalAmount.toObjectString(), - - metadata: { - entity: "proforma", - }, - }; - - return invoiceDTO; - } - - toOutput(params: { - proformas: Collection; - criteria: Criteria; - }): ListProformasResponseDTO { - const { proformas, criteria } = params; - - const _proformas = proformas.map((proforma) => this._mapProforma(proforma)); - const _totalItems = proformas.total(); - - return { - page: criteria.pageNumber, - per_page: criteria.pageSize, - total_pages: Math.ceil(_totalItems / criteria.pageSize), - total_items: _totalItems, - items: _proformas, - metadata: { - entity: "proformas", - criteria: criteria.toJSON(), - //links: { - // self: `/api/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`, - // first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`, - // last: `/api/customer-invoices?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`, - //}, - }, - }; - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/index.ts deleted file mode 100644 index b52bd5d4..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./issued-invoices"; -//export * from "./proformas"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/index.ts deleted file mode 100644 index 0c5e6734..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -//export * from "./issued-invoice.report.presenter"; -//export * from "./issued-invoice-items.report.presenter"; -//export * from "./issued-invoice-taxes.report.presenter"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice-items.report.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice-items.report.presenter.ts deleted file mode 100644 index 256a2730..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice-items.report.presenter.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core"; -import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api"; -import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common"; -import type { ArrayElement } from "@repo/rdx-utils"; - -type IssuedInvoiceItemsDTO = GetIssuedInvoiceByIdResponseDTO["items"]; -type IssuedInvoiceItemDTO = ArrayElement; - -export class IssuedInvoiceItemsReportPresenter extends Presenter { - private _locale!: string; - - private _mapItem(invoiceItem: IssuedInvoiceItemDTO, _index: number) { - const moneyOptions = { - hideZeros: true, - minimumFractionDigits: 2, - }; - - return { - ...invoiceItem, - - quantity: QuantityDTOHelper.format(invoiceItem.quantity, this._locale, { - minimumFractionDigits: 0, - }), - unit_amount: MoneyDTOHelper.format(invoiceItem.unit_amount, this._locale, moneyOptions), - subtotal_amount: MoneyDTOHelper.format( - invoiceItem.subtotal_amount, - this._locale, - moneyOptions - ), - discount_percentage: PercentageDTOHelper.format( - invoiceItem.discount_percentage, - this._locale, - { - minimumFractionDigits: 0, - } - ), - discount_amount: MoneyDTOHelper.format( - invoiceItem.discount_amount, - this._locale, - moneyOptions - ), - taxable_amount: MoneyDTOHelper.format(invoiceItem.taxable_amount, this._locale, moneyOptions), - taxes_amount: MoneyDTOHelper.format(invoiceItem.taxes_amount, this._locale, moneyOptions), - total_amount: MoneyDTOHelper.format(invoiceItem.total_amount, this._locale, moneyOptions), - }; - } - - toOutput(issuedInvoiceItems: IssuedInvoiceItemsDTO, params: ISnapshotBuilderParams): unknown { - const { locale } = params as { - locale: string; - }; - - this._locale = locale; - - return issuedInvoiceItems.map((item, index) => { - return this._mapItem(item, index); - }); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice-taxes.report.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice-taxes.report.presenter.ts deleted file mode 100644 index 0ee787cf..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice-taxes.report.presenter.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - type JsonTaxCatalogProvider, - MoneyDTOHelper, - PercentageDTOHelper, - SpainTaxCatalogProvider, -} from "@erp/core"; -import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api"; -import type { GetIssuedInvoiceByIdResponseDTO } from "@erp/customer-invoices/common"; -import type { ArrayElement } from "@repo/rdx-utils"; - -type IssuedInvoiceTaxesDTO = GetIssuedInvoiceByIdResponseDTO["taxes"]; -type IssuedInvoiceTaxDTO = ArrayElement; - -export class IssuedInvoiceTaxesReportPresenter extends Presenter { - private _locale!: string; - private _taxCatalog!: JsonTaxCatalogProvider; - - private _mapTax(taxItem: IssuedInvoiceTaxDTO) { - const moneyOptions = { - hideZeros: true, - minimumFractionDigits: 2, - }; - - //const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code); - - return { - taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions), - - iva_code: taxItem.iva_code, - iva_percentage: PercentageDTOHelper.format(taxItem.iva_percentage, this._locale), - iva_amount: MoneyDTOHelper.format(taxItem.iva_amount, this._locale, moneyOptions), - - rec_code: taxItem.rec_code, - rec_percentage: PercentageDTOHelper.format(taxItem.rec_percentage, this._locale), - rec_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions), - - retention_code: taxItem.retention_code, - retention_percentage: PercentageDTOHelper.format(taxItem.retention_percentage, this._locale), - retention_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions), - - taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions), - }; - } - - toOutput(taxes: IssuedInvoiceTaxesDTO, params: ISnapshotBuilderParams): unknown { - const { locale } = params as { - locale: string; - }; - - this._locale = locale; - this._taxCatalog = SpainTaxCatalogProvider(); - - return taxes?.map((item, _index) => { - return this._mapTax(item); - }); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice.report.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice.report.presenter.ts deleted file mode 100644 index b62c2ad1..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/issued-invoices/issued-invoice.report.presenter.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { MoneyDTOHelper, PercentageDTOHelper } from "@erp/core"; -import { Presenter } from "@erp/core/api"; -import { DateHelper } from "@repo/rdx-utils"; - -import type { GetIssuedInvoiceByIdResponseDTO } from "../../../../../common/dto"; - -export class IssuedInvoiceReportPresenter extends Presenter< - GetIssuedInvoiceByIdResponseDTO, - unknown -> { - private _formatPaymentMethodDTO( - paymentMethod?: GetIssuedInvoiceByIdResponseDTO["payment_method"] - ) { - if (!paymentMethod) { - return ""; - } - - return paymentMethod.description ?? ""; - } - - toOutput(issuedInvoiceDTO: GetIssuedInvoiceByIdResponseDTO) { - const itemsPresenter = this.presenterRegistry.getPresenter({ - resource: "issued-invoice-items", - projection: "REPORT", - format: "DTO", - }); - - const taxesPresenter = this.presenterRegistry.getPresenter({ - resource: "issued-invoice-taxes", - projection: "REPORT", - format: "DTO", - }); - - const locale = issuedInvoiceDTO.language_code; - const itemsDTO = itemsPresenter.toOutput(issuedInvoiceDTO.items, { - locale, - }); - - const taxesDTO = taxesPresenter.toOutput(issuedInvoiceDTO.taxes, { - locale, - }); - - const moneyOptions = { - hideZeros: true, - minimumFractionDigits: 2, - }; - - return { - ...issuedInvoiceDTO, - taxes: taxesDTO, - items: itemsDTO, - - recipient: { - ...issuedInvoiceDTO.recipient, - format_address: this.formatAddress(issuedInvoiceDTO.recipient), - }, - - invoice_date: DateHelper.format(issuedInvoiceDTO.invoice_date, locale), - subtotal_amount: MoneyDTOHelper.format( - issuedInvoiceDTO.subtotal_amount, - locale, - moneyOptions - ), - discount_percentage: PercentageDTOHelper.format( - issuedInvoiceDTO.discount_percentage, - locale, - { hideZeros: true } - ), - discount_amount: MoneyDTOHelper.format( - issuedInvoiceDTO.discount_amount, - locale, - moneyOptions - ), - taxable_amount: MoneyDTOHelper.format(issuedInvoiceDTO.taxable_amount, locale, moneyOptions), - taxes_amount: MoneyDTOHelper.format(issuedInvoiceDTO.taxes_amount, locale, moneyOptions), - total_amount: MoneyDTOHelper.format(issuedInvoiceDTO.total_amount, locale, moneyOptions), - - payment_method: this._formatPaymentMethodDTO(issuedInvoiceDTO.payment_method), - - verifactu: { - ...issuedInvoiceDTO.verifactu, - qr_code: issuedInvoiceDTO.verifactu.qr_code.replace("data:image/png;base64,", ""), - }, - }; - } - - protected formatAddress(recipient: GetIssuedInvoiceByIdResponseDTO["recipient"]): string { - const lines: string[] = []; - - // Líneas de calle - if (recipient.street) { - lines.push(recipient.street); - } - - if (recipient.street2) { - lines.push(recipient.street2); - } - - // Ciudad + código postal - const cityLine = [recipient.postal_code, recipient.city].filter(Boolean).join(" "); - - if (cityLine) { - lines.push(cityLine); - } - - // Provincia - if (recipient.province && recipient.province !== recipient.city) { - lines.push(recipient.province); - } - - // País - if (recipient.country && recipient.country !== "es") { - lines.push(recipient.country); - } - - return lines.join("\n"); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/index.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/index.ts deleted file mode 100644 index 4e86d5bb..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./proforma.report.presenter"; -export * from "./proforma-items.report.presenter"; -export * from "./proforma-taxes.report.presenter"; diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma-items.report.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma-items.report.presenter.ts deleted file mode 100644 index ac0553bf..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma-items.report.presenter.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core"; -import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api"; -import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common"; -import type { ArrayElement } from "@repo/rdx-utils"; - -type ProformaItemsDTO = GetProformaByIdResponseDTO["items"]; -type ProformaItemDTO = ArrayElement; - -export class ProformaItemsReportPresenter extends Presenter { - private _locale!: string; - - private _mapItem(proformaItem: ProformaItemDTO, _index: number) { - const moneyOptions = { - hideZeros: true, - minimumFractionDigits: 0, - }; - - return { - ...proformaItem, - - quantity: QuantityDTOHelper.format(proformaItem.quantity, this._locale, { - minimumFractionDigits: 0, - }), - unit_amount: MoneyDTOHelper.format(proformaItem.unit_amount, this._locale, moneyOptions), - subtotal_amount: MoneyDTOHelper.format( - proformaItem.subtotal_amount, - this._locale, - moneyOptions - ), - discount_percentage: PercentageDTOHelper.format( - proformaItem.discount_percentage, - this._locale, - { - minimumFractionDigits: 0, - } - ), - discount_amount: MoneyDTOHelper.format( - proformaItem.discount_amount, - this._locale, - moneyOptions - ), - taxable_amount: MoneyDTOHelper.format( - proformaItem.taxable_amount, - this._locale, - moneyOptions - ), - taxes_amount: MoneyDTOHelper.format(proformaItem.taxes_amount, this._locale, moneyOptions), - total_amount: MoneyDTOHelper.format(proformaItem.total_amount, this._locale, moneyOptions), - }; - } - - toOutput(proformaItems: ProformaItemsDTO, params: ISnapshotBuilderParams): unknown { - const { locale } = params as { - locale: string; - }; - - this._locale = locale; - - return proformaItems.map((item, index) => { - return this._mapItem(item, index); - }); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma-taxes.report.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma-taxes.report.presenter.ts deleted file mode 100644 index 19878c39..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma-taxes.report.presenter.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - type JsonTaxCatalogProvider, - MoneyDTOHelper, - PercentageDTOHelper, - SpainTaxCatalogProvider, -} from "@erp/core"; -import { type ISnapshotBuilderParams, Presenter } from "@erp/core/api"; -import type { GetProformaByIdResponseDTO } from "@erp/customer-invoices/common"; -import type { ArrayElement } from "@repo/rdx-utils"; - -type ProformaTaxesDTO = GetProformaByIdResponseDTO["taxes"]; -type ProformaTaxDTO = ArrayElement; - -export class ProformaTaxesReportPresenter extends Presenter { - private _locale!: string; - private _taxCatalog!: JsonTaxCatalogProvider; - - private _mapTax(taxItem: ProformaTaxDTO) { - const moneyOptions = { - hideZeros: true, - minimumFractionDigits: 0, - }; - - //const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code); - - return { - taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions), - - iva_code: taxItem.iva_code, - iva_percentage: PercentageDTOHelper.format(taxItem.iva_percentage, this._locale), - iva_amount: MoneyDTOHelper.format(taxItem.iva_amount, this._locale, moneyOptions), - - rec_code: taxItem.rec_code, - rec_percentage: PercentageDTOHelper.format(taxItem.rec_percentage, this._locale), - rec_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions), - - retention_code: taxItem.retention_code, - retention_percentage: PercentageDTOHelper.format(taxItem.retention_percentage, this._locale), - retention_amount: MoneyDTOHelper.format(taxItem.rec_amount, this._locale, moneyOptions), - - taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions), - }; - } - - toOutput(taxes: ProformaTaxesDTO, params: ISnapshotBuilderParams): unknown { - const { locale } = params as { - locale: string; - }; - - this._locale = locale; - this._taxCatalog = SpainTaxCatalogProvider(); - - return taxes.map((item, _index) => { - return this._mapTax(item); - }); - } -} diff --git a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma.report.presenter.ts b/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma.report.presenter.ts deleted file mode 100644 index b876c0ed..00000000 --- a/modules/customer-invoices/src/api/application/snapshot-builders/reports/proformas/proforma.report.presenter.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core"; -import { Presenter } from "@erp/core/api"; - -import type { GetProformaByIdResponseDTO } from "../../../../../common/dto"; - -export class ProformaReportPresenter extends Presenter { - private _formatPaymentMethodDTO(paymentMethod?: GetProformaByIdResponseDTO["payment_method"]) { - if (!paymentMethod) { - return ""; - } - - return paymentMethod.description ?? ""; - } - - toOutput(proformaDTO: GetProformaByIdResponseDTO) { - const itemsPresenter = this.presenterRegistry.getPresenter({ - resource: "proforma-items", - projection: "REPORT", - format: "JSON", - }); - - const taxesPresenter = this.presenterRegistry.getPresenter({ - resource: "proforma-taxes", - projection: "REPORT", - format: "JSON", - }); - - const locale = proformaDTO.language_code; - const itemsDTO = itemsPresenter.toOutput(proformaDTO.items, { - locale, - }); - - const taxesDTO = taxesPresenter.toOutput(proformaDTO.taxes, { - locale, - }); - - const moneyOptions = { - hideZeros: true, - minimumFractionDigits: 0, - }; - - return { - ...proformaDTO, - taxes: taxesDTO, - items: itemsDTO, - - invoice_date: DateHelper.format(proformaDTO.invoice_date, locale), - subtotal_amount: MoneyDTOHelper.format(proformaDTO.subtotal_amount, locale, moneyOptions), - discount_percentage: PercentageDTOHelper.format(proformaDTO.discount_percentage, locale), - discount_amount: MoneyDTOHelper.format(proformaDTO.discount_amount, locale, moneyOptions), - taxable_amount: MoneyDTOHelper.format(proformaDTO.taxable_amount, locale, moneyOptions), - taxes_amount: MoneyDTOHelper.format(proformaDTO.taxes_amount, locale, moneyOptions), - total_amount: MoneyDTOHelper.format(proformaDTO.total_amount, locale, moneyOptions), - - payment_method: this._formatPaymentMethodDTO(proformaDTO.payment_method), - }; - } -} diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-amount.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/invoice-amount.vo.ts index fc8e8030..83eb23b2 100644 --- a/modules/customer-invoices/src/api/domain/common/value-objects/invoice-amount.vo.ts +++ b/modules/customer-invoices/src/api/domain/common/value-objects/invoice-amount.vo.ts @@ -1,12 +1,32 @@ -import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd"; -import { Result } from "@repo/rdx-utils"; +import { + MoneyValue, + type MoneyValueProps, + type Percentage, + type Quantity, + ValidationErrorCollection, +} from "@repo/rdx-ddd"; +import { NumberHelper, Result } from "@repo/rdx-utils"; -type InvoiceAmountProps = Pick; +type InvoiceAmountProps = MoneyValueProps; + +type InvoiceAmountObjectString = { + value: string; + scale: string; + currency_code: string; +}; export class InvoiceAmount extends MoneyValue { public static DEFAULT_SCALE = 2; - static create({ value, currency_code }: InvoiceAmountProps) { + static create({ value, currency_code, scale }: InvoiceAmountProps) { + if (scale && scale !== InvoiceAmount.DEFAULT_SCALE) { + return Result.fail( + new ValidationErrorCollection("InvalidScale", [ + { message: `InvoiceAmount scale must be ${InvoiceAmount.DEFAULT_SCALE}` }, + ]) + ); + } + const props = { value: Number(value), scale: InvoiceAmount.DEFAULT_SCALE, @@ -23,7 +43,26 @@ export class InvoiceAmount extends MoneyValue { return InvoiceAmount.create(props).data; } - toObjectString() { + static fromObjectString(dto: InvoiceAmountObjectString) { + const value = NumberHelper.toSafeNumber(dto.value); + const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : InvoiceAmount.DEFAULT_SCALE; + + if (!(Number.isFinite(value) && Number.isInteger(scale))) { + return Result.fail( + new ValidationErrorCollection("InvalidNumericValues", [ + { message: "InvoiceAmount payload contains invalid numeric values" }, + ]) + ); + } + + return InvoiceAmount.create({ + value, + scale, + currency_code: dto.currency_code, + }); + } + + toObjectString(): InvoiceAmountObjectString { return { value: String(this.value), scale: String(this.scale), diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/item-amount.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/item-amount.vo.ts index 7bebb40d..99f43526 100644 --- a/modules/customer-invoices/src/api/domain/common/value-objects/item-amount.vo.ts +++ b/modules/customer-invoices/src/api/domain/common/value-objects/item-amount.vo.ts @@ -1,14 +1,34 @@ -import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd"; -import { Result } from "@repo/rdx-utils"; +import { + MoneyValue, + type MoneyValueProps, + type Percentage, + type Quantity, + ValidationErrorCollection, +} from "@repo/rdx-ddd"; +import { NumberHelper, Result } from "@repo/rdx-utils"; -type ItemAmountProps = Pick; +type ItemAmountProps = MoneyValueProps; + +type ItemAmountObjectString = { + value: string; + scale: string; + currency_code: string; +}; export class ItemAmount extends MoneyValue { public static DEFAULT_SCALE = 4; - static create({ value, currency_code }: ItemAmountProps) { + static create({ value, currency_code, scale }: ItemAmountProps) { + if (scale && scale !== ItemAmount.DEFAULT_SCALE) { + return Result.fail( + new ValidationErrorCollection("InvalidScale", [ + { message: `ItemAmount scale must be ${ItemAmount.DEFAULT_SCALE}` }, + ]) + ); + } + const props = { - value: Number(value), + value, scale: ItemAmount.DEFAULT_SCALE, currency_code, }; @@ -23,7 +43,26 @@ export class ItemAmount extends MoneyValue { return ItemAmount.create(props).data; } - toObjectString() { + static fromObjectString(dto: ItemAmountObjectString) { + const value = NumberHelper.toSafeNumber(dto.value); + const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : ItemAmount.DEFAULT_SCALE; + + if (!(Number.isFinite(value) && Number.isInteger(scale))) { + return Result.fail( + new ValidationErrorCollection("InvalidNumericValues", [ + { message: "ItemAmount payload contains invalid numeric values" }, + ]) + ); + } + + return ItemAmount.create({ + value, + scale, + currency_code: dto.currency_code, + }); + } + + toObjectString(): ItemAmountObjectString { return { value: String(this.value), scale: String(this.scale), diff --git a/modules/customer-invoices/src/api/domain/common/value-objects/item-quantity.vo.ts b/modules/customer-invoices/src/api/domain/common/value-objects/item-quantity.vo.ts index 797bb751..efa394e0 100644 --- a/modules/customer-invoices/src/api/domain/common/value-objects/item-quantity.vo.ts +++ b/modules/customer-invoices/src/api/domain/common/value-objects/item-quantity.vo.ts @@ -1,15 +1,26 @@ -import { Quantity, type QuantityProps } from "@repo/rdx-ddd"; +import { Quantity, type QuantityProps, ValidationErrorCollection } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; -type ItemQuantityProps = Pick; +type ItemQuantityProps = QuantityProps; export class ItemQuantity extends Quantity { public static DEFAULT_SCALE = 2; - static create({ value }: ItemQuantityProps) { - return Quantity.create({ - value, - scale: ItemQuantity.DEFAULT_SCALE, - }); + static create({ value, scale }: ItemQuantityProps) { + if (scale && scale !== ItemQuantity.DEFAULT_SCALE) { + return Result.fail( + new ValidationErrorCollection("InvalidScale", [ + { message: `ItemQuantity scale must be ${ItemQuantity.DEFAULT_SCALE}` }, + ]) + ); + } + + return Result.ok( + new ItemQuantity({ + value, + scale: ItemQuantity.DEFAULT_SCALE, + }) + ); } static zero() { diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts index ea7e437d..befe225f 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proforma-persistence-mappers.di.ts @@ -23,7 +23,7 @@ export const buildProformaPersistenceMappers = ( const listMapper = new SequelizeProformaSummaryMapper(); // Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado - const createMapper = new CreateProformaInputMapper(); + const createMapper = new CreateProformaInputMapper(catalogs); return { domainMapper, diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts index a0afe5e6..474c6776 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/persistence/sequelize/mappers/domain/sequelize-proforma-item-domain.mapper.ts @@ -61,6 +61,8 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper< parent: Partial; }; + const { currencyCode } = parent; + const itemId = extractOrPushError( UniqueID.create(raw.item_id), `items[${index}].item_id`, @@ -74,14 +76,20 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper< ); const quantity = extractOrPushError( - maybeFromNullableResult(raw.quantity_value, (v) => ItemQuantity.create({ value: v })), + maybeFromNullableResult(raw.quantity_value, (v) => + ItemQuantity.create({ value: v, scale: raw.quantity_scale }) + ), `items[${index}].quantity_value`, errors ); const unitAmount = extractOrPushError( maybeFromNullableResult(raw.unit_amount_value, (value) => - ItemAmount.create({ value, currency_code: parent.currencyCode?.code }) + ItemAmount.create({ + value, + currency_code: currencyCode?.code, + scale: raw.unit_amount_scale, + }) ), `items[${index}].unit_amount_value`, errors @@ -89,7 +97,10 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper< const itemDiscountPercentage = extractOrPushError( maybeFromNullableResult(raw.item_discount_percentage_value, (v) => - DiscountPercentage.create({ value: v }) + DiscountPercentage.create({ + value: v, + scale: raw.item_discount_percentage_scale, + }) ), `items[${index}].item_discount_percentage`, errors diff --git a/modules/customer-invoices/src/common/dto/request/proformas/update-proforma-by-id.request.dto.ts b/modules/customer-invoices/src/common/dto/request/proformas/update-proforma-by-id.request.dto.ts index fbcd3520..b1629298 100644 --- a/modules/customer-invoices/src/common/dto/request/proformas/update-proforma-by-id.request.dto.ts +++ b/modules/customer-invoices/src/common/dto/request/proformas/update-proforma-by-id.request.dto.ts @@ -2,8 +2,9 @@ import { CurrencyCodeSchema, IsoDateSchema, LanguageCodeSchema, - NumericStringSchema, + MoneySchema, PercentageSchema, + QuantitySchema, } from "@erp/core"; import { z } from "zod/v4"; @@ -15,8 +16,8 @@ export const UpdateProformaItemRequestSchema = z.object({ description: z.string().nullable(), - quantity: NumericStringSchema.nullable(), - unit_amount: NumericStringSchema.nullable(), + quantity: QuantitySchema.nullable(), + unit_amount: MoneySchema.nullable(), item_discount_percentage: PercentageSchema.nullable(), diff --git a/modules/customer-invoices/src/common/dto/shared/proforma/proforma-item-detail.dto.ts b/modules/customer-invoices/src/common/dto/shared/proforma/proforma-item-detail.dto.ts index 01ad392b..52d6d524 100644 --- a/modules/customer-invoices/src/common/dto/shared/proforma/proforma-item-detail.dto.ts +++ b/modules/customer-invoices/src/common/dto/shared/proforma/proforma-item-detail.dto.ts @@ -6,8 +6,9 @@ import { TaxesBreakdownSchema } from "../taxes-breakdown.dto"; export const ProformaItemDetailSchema = z.object({ id: z.uuid(), - is_valued: z.boolean(), position: ItemPositionSchema, + is_valued: z.boolean(), + description: z.string().nullable(), quantity: QuantitySchema.nullable(), diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts index 8f2bb6ef..e5aa0317 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts @@ -11,7 +11,6 @@ import type { ProformaItemUpdateForm } from "../entities"; export const mapProformaItemsToProformaItemsUpdateForm = ( item: ProformaItem ): ProformaItemUpdateForm => { - console.log(item); return { id: item.id, position: item.position, diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts index c283bf8b..ae149e00 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts @@ -74,8 +74,6 @@ export const useUpdateProformaController = ( const initialValues = useMemo(() => { if (!proformaData) return buildProformaUpdateDefault(); - console.log("initialValues", proformaData); - return mapProformaToProformaUpdateForm(proformaData); }, [proformaData]); @@ -160,7 +158,7 @@ export const useUpdateProformaController = ( console.log("Parche de actualización construido:", patchData); - const params = buildUpdateProformaByIdParams(proformaId, patchData); + const params = buildUpdateProformaByIdParams(proformaId, patchData, formData); console.log("Enviando actualización con params:", params); diff --git a/modules/customer-invoices/src/web/proformas/update/utils/build-update-proforma-by-id-params.ts b/modules/customer-invoices/src/web/proformas/update/utils/build-update-proforma-by-id-params.ts index ed9535ff..b97c0f98 100644 --- a/modules/customer-invoices/src/web/proformas/update/utils/build-update-proforma-by-id-params.ts +++ b/modules/customer-invoices/src/web/proformas/update/utils/build-update-proforma-by-id-params.ts @@ -2,7 +2,7 @@ import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/cor import { ObjectHelper } from "@repo/rdx-utils"; import type { UpdateProformaByIdParams } from "../../shared/api"; -import type { ProformaItemUpdatePatch, ProformaUpdatePatch } from "../entities"; +import type { ProformaItemUpdatePatch, ProformaUpdateForm, ProformaUpdatePatch } from "../entities"; /** * Convierte el patch del formulario de actualización de proforma @@ -21,12 +21,18 @@ import type { ProformaItemUpdatePatch, ProformaUpdatePatch } from "../entities"; export const buildUpdateProformaByIdParams = ( id: string, - patch: ProformaUpdatePatch + patch: ProformaUpdatePatch, + formData: ProformaUpdateForm ): UpdateProformaByIdParams => { if (!id) { throw new Error("proformaId is required"); } + //const currencyCode = formData.currencyCode; + //const languageCode = formData.languageCode; + + console.log("PATCH => ", patch); + const data: UpdateProformaByIdParams["data"] = {}; if (ObjectHelper.hasOwn(patch, "series")) { @@ -73,9 +79,11 @@ export const buildUpdateProformaByIdParams = ( } if (ObjectHelper.hasOwn(patch, "items")) { - data.items = patch.items?.map(toProformaItemUpdateDTO); + data.items = patch.items?.map((item, index) => toProformaItemUpdateDTO(item, index, formData)); } + console.log("DATA => ", data); + return { id, data, @@ -83,13 +91,20 @@ export const buildUpdateProformaByIdParams = ( }; const toProformaItemUpdateDTO = ( - item: ProformaItemUpdatePatch + item: ProformaItemUpdatePatch, + _index: number, + formData: ProformaUpdateForm ): NonNullable[number] => { + const currencyCode = formData.currencyCode; + //const languageCode = formData.languageCode; + const quantity = - item.quantity === null ? null : QuantityDTOHelper.fromNumber(item.quantity, 4).value; + item.quantity === null ? null : QuantityDTOHelper.fromNumberNulleable(item.quantity, 2); const unit_amount = - item.unitAmount === null ? null : MoneyDTOHelper.fromNumber(item.unitAmount, "EUR", 2).value; + item.unitAmount === null + ? null + : MoneyDTOHelper.fromNumberNulleable(item.unitAmount, currencyCode, 4); const is_valued = item.isValued; @@ -105,8 +120,8 @@ const toProformaItemUpdateDTO = ( item_discount_percentage: item.itemDiscountPercentage === null ? null - : PercentageDTOHelper.fromNumber(item.itemDiscountPercentage, 2), + : PercentageDTOHelper.fromNumber(item.itemDiscountPercentage), - taxes: "#;#;#", + taxes: "#;#;#", // TODO: CAMBIAR!!!! }; }; diff --git a/packages/rdx-ddd/src/value-objects/money-value.ts b/packages/rdx-ddd/src/value-objects/money-value.ts index 595049dd..509868e4 100644 --- a/packages/rdx-ddd/src/value-objects/money-value.ts +++ b/packages/rdx-ddd/src/value-objects/money-value.ts @@ -1,6 +1,8 @@ import { Result } from "@repo/rdx-utils"; import DineroFactory, { type Currency, type Dinero } from "dinero.js"; +import type { DomainError } from "../errors"; + import type { Percentage } from "./percentage"; import type { Quantity } from "./quantity"; import { ValueObject } from "./value-object"; @@ -55,7 +57,7 @@ export class MoneyValue extends ValueObject implements IMoneyVa static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE; static EMPTY_MONEY_OBJECT = { value: "", scale: "", currency_code: "" }; - static create({ value, currency_code, scale }: MoneyValueProps) { + static create({ value, currency_code, scale }: MoneyValueProps): Result { const props = { value: Number(value), scale: scale ?? MoneyValue.DEFAULT_SCALE, diff --git a/packages/rdx-ddd/src/value-objects/percentage.ts b/packages/rdx-ddd/src/value-objects/percentage.ts index 23508754..7d083156 100644 --- a/packages/rdx-ddd/src/value-objects/percentage.ts +++ b/packages/rdx-ddd/src/value-objects/percentage.ts @@ -1,6 +1,7 @@ -import { Result } from "@repo/rdx-utils"; +import { NumberHelper, Result } from "@repo/rdx-utils"; import { z } from "zod/v4"; +import { ValidationErrorCollection } from "../errors"; import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; @@ -15,9 +16,14 @@ const DEFAULT_MAX_SCALE = 2; export interface PercentageProps { value: number; - scale: number; + scale?: number; } +type PercentageObjectString = { + value: string; + scale: string; +}; + export class Percentage extends ValueObject { static DEFAULT_SCALE = DEFAULT_SCALE; static MIN_VALUE = DEFAULT_MIN_VALUE; @@ -70,12 +76,30 @@ export class Percentage extends ValueObject { return Percentage.create({ value: 0, scale: Percentage.DEFAULT_SCALE }).data; } + static fromObjectString(dto: PercentageObjectString) { + const value = NumberHelper.toSafeNumber(dto.value); + const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : Percentage.DEFAULT_SCALE; + + if (!(Number.isFinite(value) && Number.isInteger(scale))) { + return Result.fail( + new ValidationErrorCollection("InvalidNumericValues", [ + { message: "Percentage payload contains invalid numeric values" }, + ]) + ); + } + + return Percentage.create({ + value, + scale, + }); + } + get value(): number { return this.props.value; } get scale(): number { - return this.props.scale; + return this.props.scale ?? Percentage.DEFAULT_SCALE; } getProps(): PercentageProps { diff --git a/packages/rdx-ddd/src/value-objects/quantity.ts b/packages/rdx-ddd/src/value-objects/quantity.ts index 957e6a31..d4d93b5e 100644 --- a/packages/rdx-ddd/src/value-objects/quantity.ts +++ b/packages/rdx-ddd/src/value-objects/quantity.ts @@ -1,6 +1,7 @@ -import { Result } from "@repo/rdx-utils"; +import { NumberHelper, Result } from "@repo/rdx-utils"; import { z } from "zod/v4"; +import { ValidationErrorCollection } from "../errors"; import { translateZodValidationError } from "../helpers"; import { ValueObject } from "./value-object"; @@ -11,9 +12,14 @@ const DEFAULT_MAX_SCALE = 2; export interface QuantityProps { value: number; - scale: number; + scale?: number; } +type QuantityObjectString = { + value: string; + scale: string; +}; + export class Quantity extends ValueObject { static EMPTY_QUANTITY_OBJECT = { value: "", scale: "" }; @@ -43,12 +49,30 @@ export class Quantity extends ValueObject { return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) })); } + static fromObjectString(dto: QuantityObjectString) { + const value = NumberHelper.toSafeNumber(dto.value); + const scale = dto.scale ? NumberHelper.toSafeNumber(dto.scale) : Quantity.DEFAULT_SCALE; + + if (!(Number.isFinite(value) && Number.isInteger(scale))) { + return Result.fail( + new ValidationErrorCollection("InvalidNumericValues", [ + { message: "Quantity payload contains invalid numeric values" }, + ]) + ); + } + + return Quantity.create({ + value, + scale, + }); + } + get value(): number { return this.props.value; } get scale(): number { - return this.props.scale; + return this.props.scale ?? Quantity.DEFAULT_SCALE; } getProps(): QuantityProps { @@ -67,7 +91,7 @@ export class Quantity extends ValueObject { return this.toNumber().toFixed(this.scale); } - toObjectString() { + toObjectString(): QuantityObjectString { return { value: String(this.value), scale: String(this.scale), diff --git a/uecko-erp.code-workspace b/uecko-erp.code-workspace index 8bdbf776..fb529bd6 100644 --- a/uecko-erp.code-workspace +++ b/uecko-erp.code-workspace @@ -5,6 +5,9 @@ } ], "settings": { - "chatgpt.openOnStartup": true + "chatgpt.openOnStartup": true, + "chat.tools.terminal.autoApprove": { + "pnpm": true + } } }