import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { extractOrPushError, maybeFromNullableVO, toNullable, UniqueID, ValidationErrorCollection, ValidationErrorDetail, } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { CustomerInvoice, CustomerInvoiceItem, CustomerInvoiceItemDescription, CustomerInvoiceItemProps, CustomerInvoiceProps, ItemAmount, ItemDiscount, ItemQuantity, ItemTaxes, } from "../../../domain"; import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../../sequelize"; import { ItemTaxesDomainMapper } from "./item-taxes.mapper"; export interface ICustomerInvoiceItemDomainMapper extends ISequelizeDomainMapper< CustomerInvoiceItemModel, CustomerInvoiceItemCreationAttributes, CustomerInvoiceItem > {} export class CustomerInvoiceItemDomainMapper extends SequelizeDomainMapper< CustomerInvoiceItemModel, CustomerInvoiceItemCreationAttributes, CustomerInvoiceItem > implements ICustomerInvoiceItemDomainMapper { private _taxesMapper: ItemTaxesDomainMapper; constructor(params: MapperParamsType) { super(); this._taxesMapper = new ItemTaxesDomainMapper(params); } private mapAttributesToDomain( source: CustomerInvoiceItemModel, params?: MapperParamsType ): Partial & { itemId?: UniqueID } { const { errors, index, attributes } = params as { index: number; errors: ValidationErrorDetail[]; attributes: Partial; }; const itemId = extractOrPushError( UniqueID.create(source.item_id), `items[${index}].item_id`, errors ); const description = extractOrPushError( maybeFromNullableVO(source.description, (v) => CustomerInvoiceItemDescription.create(v)), `items[${index}].description`, errors ); const quantity = extractOrPushError( maybeFromNullableVO(source.quantity_value, (v) => ItemQuantity.create({ value: v })), `items[${index}].quantity`, errors ); const unitAmount = extractOrPushError( maybeFromNullableVO(source.unit_amount_value, (value) => ItemAmount.create({ value, currency_code: attributes.currencyCode?.code }) ), `items[${index}].unit_amount`, errors ); const discountPercentage = extractOrPushError( maybeFromNullableVO(source.discount_percentage_value, (v) => ItemDiscount.create({ value: v }) ), `items[${index}].discount_percentage`, errors ); return { itemId, languageCode: attributes.languageCode, currencyCode: attributes.currencyCode, description, quantity, unitAmount, discountPercentage, }; } public mapToDomain( source: CustomerInvoiceItemModel, params?: MapperParamsType ): Result { const { errors, index } = params as { index: number; errors: ValidationErrorDetail[]; attributes: Partial; }; // 1) Valores escalares (atributos generales) const attributes = this.mapAttributesToDomain(source, params); // 2) Taxes (colección a nivel de item/línea) const taxesResults = this._taxesMapper.mapToDomainCollection( source.taxes, source.taxes.length, { attributes, ...params, } ); if (taxesResults.isFailure) { errors.push({ path: "taxes", message: taxesResults.error.message, }); } // Si hubo errores de mapeo, devolvemos colección de validación if (errors.length > 0) { return Result.fail( new ValidationErrorCollection("Customer invoice item mapping failed [mapToDomain]", errors) ); } const taxes = ItemTaxes.create(taxesResults.data.getAll()); // 3) Construcción del elemento de dominio const createResult = CustomerInvoiceItem.create( { languageCode: attributes.languageCode!, currencyCode: attributes.currencyCode!, description: attributes.description!, quantity: attributes.quantity!, unitAmount: attributes.unitAmount!, discountPercentage: attributes.discountPercentage!, taxes, }, attributes.itemId ); if (createResult.isFailure) { return Result.fail( new ValidationErrorCollection("Invoice item entity creation failed", [ { path: `items[${index}]`, message: createResult.error.message }, ]) ); } return createResult; } public mapToPersistence( source: CustomerInvoiceItem, params?: MapperParamsType ): Result { const { errors, index, parent } = params as { index: number; parent: CustomerInvoice; errors: ValidationErrorDetail[]; }; const taxesResults = this._taxesMapper.mapToPersistenceArray(source.taxes, { ...params, parent: source, }); if (taxesResults.isFailure) { errors.push({ path: "taxes", message: taxesResults.error.message, }); } const allAmounts = source.getAllAmounts(); return Result.ok({ item_id: source.id.toPrimitive(), invoice_id: parent.id.toPrimitive(), position: index, description: toNullable(source.description, (v) => v.toPrimitive()), quantity_value: toNullable(source.quantity, (v) => v.toPrimitive().value), quantity_scale: toNullable(source.quantity, (v) => v.toPrimitive().scale) ?? ItemQuantity.DEFAULT_SCALE, unit_amount_value: toNullable(source.unitAmount, (v) => v.toPrimitive().value), unit_amount_scale: toNullable(source.unitAmount, (v) => v.toPrimitive().scale) ?? ItemAmount.DEFAULT_SCALE, subtotal_amount_value: allAmounts.subtotalAmount.value, subtotal_amount_scale: allAmounts.subtotalAmount.scale, discount_percentage_value: toNullable( source.discountPercentage, (v) => v.toPrimitive().value ), discount_percentage_scale: toNullable(source.discountPercentage, (v) => v.toPrimitive().scale) ?? ItemDiscount.DEFAULT_SCALE, discount_amount_value: allAmounts.discountAmount.value, discount_amount_scale: allAmounts.discountAmount.scale, taxable_amount_value: allAmounts.taxableAmount.value, taxable_amount_scale: allAmounts.taxableAmount.scale, taxes_amount_value: allAmounts.taxesAmount.value, taxes_amount_scale: allAmounts.taxesAmount.scale, total_amount_value: allAmounts.totalAmount.value, total_amount_scale: allAmounts.totalAmount.scale, taxes: taxesResults.data, }); } }