import { ISequelizeMapper, MapperParamsType, SequelizeMapper, ValidationErrorCollection, ValidationErrorDetail, extractOrPushError, } from "@erp/core/api"; import { CurrencyCode, LanguageCode, Percentage, TextValue, UniqueID, UtcDate, maybeFromNullableVO, } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { CustomerInvoice, CustomerInvoiceItems, CustomerInvoiceNumber, CustomerInvoiceProps, CustomerInvoiceSerie, CustomerInvoiceStatus, } from "../../domain"; import { InvoiceTaxes } from "../../domain/entities/invoice-taxes"; import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../sequelize"; import { CustomerInvoiceItemMapper } from "./customer-invoice-item.mapper"; import { InvoiceRecipientMapper } from "./invoice-recipient.mapper"; import { TaxesMapper } from "./taxes.mapper"; export interface ICustomerInvoiceMapper extends ISequelizeMapper< CustomerInvoiceModel, CustomerInvoiceCreationAttributes, CustomerInvoice > {} export class CustomerInvoiceMapper extends SequelizeMapper implements ICustomerInvoiceMapper { private _itemsMapper: CustomerInvoiceItemMapper; private _recipientMapper: InvoiceRecipientMapper; private _taxesMapper: TaxesMapper; constructor(params: MapperParamsType) { super(); this._itemsMapper = new CustomerInvoiceItemMapper(params); // Instanciar el mapper de items this._recipientMapper = new InvoiceRecipientMapper(); this._taxesMapper = new TaxesMapper(params); } private mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) { const { errors } = params as { errors: ValidationErrorDetail[]; }; const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors); const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors); const customerId = extractOrPushError( UniqueID.create(source.customer_id), "customer_id", errors ); const isProforma = Boolean(source.is_proforma); const status = extractOrPushError( CustomerInvoiceStatus.create(source.status), "status", errors ); const series = extractOrPushError( maybeFromNullableVO(source.series, (value) => CustomerInvoiceSerie.create(value)), "serie", errors ); const invoiceNumber = extractOrPushError( CustomerInvoiceNumber.create(source.invoice_number), "invoice_number", errors ); const invoiceDate = extractOrPushError( UtcDate.createFromISO(source.invoice_date), "invoice_date", errors ); const operationDate = extractOrPushError( maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)), "operation_date", errors ); const notes = extractOrPushError( maybeFromNullableVO(source.notes, (value) => TextValue.create(value)), "notes", errors ); const languageCode = extractOrPushError( LanguageCode.create(source.language_code), "language_code", errors ); const currencyCode = extractOrPushError( CurrencyCode.create(source.currency_code), "currency_code", errors ); const discountPercentage = extractOrPushError( maybeFromNullableVO(source.discount_percentage_value, (value) => Percentage.create({ value: value, scale: source.discount_percentage_scale, }) ), "discount_percentage", errors ); return { invoiceId, companyId, customerId, isProforma, status, series, invoiceNumber, invoiceDate, operationDate, notes, languageCode, currencyCode, discountPercentage, }; } public mapToDomain( source: CustomerInvoiceModel, params?: MapperParamsType ): Result { try { const errors: ValidationErrorDetail[] = []; // 1) Valores escalares (atributos generales) const attributes = this.mapAttributesToDomain(source, { errors, ...params }); // 2) Comprobar relaciones const requireIncludes = Boolean(params?.requireIncludes); if (requireIncludes) { if (!source.items) { errors.push({ path: "items", message: "Items not included in query (requireIncludes=true)", }); } if (!source.taxes) { errors.push({ path: "taxes", message: "Taxes not included in query (requireIncludes=true)", }); } if (attributes.isProforma && !source.current_customer) { errors.push({ path: "current_customer", message: "Current customer not included in query (requireIncludes=true)", }); } } // 3) Recipient (snapshot en la factura o include) const recipientResult = this._recipientMapper.mapToDomain(source, { errors, attributes, ...params, }); if (recipientResult.isFailure) { errors.push({ path: "recipient", message: recipientResult.error.message, }); } // 4) Items (colección) const itemsResults = this._itemsMapper.mapArrayToDomain(source.items, { errors, attributes, ...params, }); if (itemsResults.isFailure) { errors.push({ path: "items", message: recipientResult.error.message, }); } // 5) Taxes (colección a nivel factura) const taxesResults = this._taxesMapper.mapArrayToDomain(source.taxes, { errors, attributes, ...params, }); if (taxesResults.isFailure) { errors.push({ path: "taxes", message: taxesResults.error.message, }); } // 6) 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) ); } // 7) Construcción del agregado (Dominio) const recipient = recipientResult.data; const taxes = InvoiceTaxes.create({ items: taxesResults.data.getAll(), }); const items = CustomerInvoiceItems.create({ languageCode: attributes.languageCode!, currencyCode: attributes.currencyCode!, items: itemsResults.data.getAll(), }); const invoiceProps: CustomerInvoiceProps = { companyId: attributes.companyId!, isProforma: attributes.isProforma, status: attributes.status!, series: attributes.series!, invoiceNumber: attributes.invoiceNumber!, invoiceDate: attributes.invoiceDate!, operationDate: attributes.operationDate!, customerId: attributes.customerId!, recipient: recipient, notes: attributes.notes!, languageCode: attributes.languageCode!, currencyCode: attributes.currencyCode!, //discountPercentage: attributes.discountPercentage!, taxes, items, }; const createResult = CustomerInvoice.create(invoiceProps, attributes.invoiceId); if (createResult.isFailure) { return Result.fail( new ValidationErrorCollection("Customer invoice entity creation failed", [ { path: "invoice", message: createResult.error.message }, ]) ); } return Result.ok(createResult.data); } catch (err: unknown) { return Result.fail(err as Error); } } public mapToPersistence( source: CustomerInvoice, params?: MapperParamsType ): CustomerInvoiceCreationAttributes { throw new Error("not implemented"); /*const items = this._itemsMapper.mapCollectionToPersistence(source.items, params); const customer = source.recipient.match( (recipient) => ({ customer_id: recipient.id.toPrimitive(), customer_tin: recipient.tin.toPrimitive(), customer_name: recipient.name.toPrimitive(), customer_street: toNullable(recipient.address.street, (street) => street.toPrimitive()), customer_street2: toNullable(recipient.address.street2, (street2) => street2.toPrimitive() ), customer_city: toNullable(recipient.address.city, (city) => city.toPrimitive()), customer_province: toNullable(recipient.address.province, (province) => province.toPrimitive() ), customer_postal_code: toNullable(recipient.address.postalCode, (postalCode) => postalCode.toPrimitive() ), customer_country: toNullable(recipient.address.country, (country) => country.toPrimitive() ), }) as any, () => ({ customer_id: source.customerId.toPrimitive(), customer_tin: null, customer_name: null, customer_street: null, customer_street2: null, customer_city: null, customer_province: null, customer_postal_code: null, customer_country: null, }) ) as any; return { 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: toNullable(source.operationDate, (operationDate) => operationDate.toPrimitive() ), 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, taxable_amount_value: source.taxableAmount.value, taxable_amount_scale: source.taxableAmount.scale, taxes_amount_value: source.taxAmount.value, taxes_amount_scale: source.taxAmount.scale, total_amount_value: 0, //total.amount, total_amount_scale: 2, //total.scale, items, ...customer, };*/ } }