Uecko_ERP/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts

484 lines
14 KiB
TypeScript

import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
CurrencyCode,
LanguageCode,
Percentage,
TextValue,
UniqueID,
UtcDate,
ValidationErrorCollection,
ValidationErrorDetail,
extractOrPushError,
maybeFromNullableVO,
toNullable,
} from "@repo/rdx-ddd";
import { Collection, Maybe, Result } from "@repo/rdx-utils";
import {
CustomerInvoice,
CustomerInvoiceItems,
CustomerInvoiceNumber,
CustomerInvoiceProps,
CustomerInvoiceSerie,
CustomerInvoiceStatus,
InvoicePaymentMethod,
} from "../../../domain";
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
import { CustomerInvoiceItemDomainMapper as CustomerInvoiceItemFullMapper } from "./customer-invoice-item.mapper";
import { InvoiceRecipientDomainMapper as InvoiceRecipientFullMapper } from "./invoice-recipient.mapper";
import { TaxesDomainMapper as TaxesFullMapper } from "./invoice-taxes.mapper";
export interface ICustomerInvoiceDomainMapper
extends ISequelizeDomainMapper<
CustomerInvoiceModel,
CustomerInvoiceCreationAttributes,
CustomerInvoice
> {}
export class CustomerInvoiceDomainMapper
extends SequelizeDomainMapper<
CustomerInvoiceModel,
CustomerInvoiceCreationAttributes,
CustomerInvoice
>
implements ICustomerInvoiceDomainMapper
{
private _itemsMapper: CustomerInvoiceItemFullMapper;
private _recipientMapper: InvoiceRecipientFullMapper;
private _taxesMapper: TaxesFullMapper;
constructor(params: MapperParamsType) {
super();
this._itemsMapper = new CustomerInvoiceItemFullMapper(params); // Instanciar el mapper de items
this._recipientMapper = new InvoiceRecipientFullMapper();
this._taxesMapper = new TaxesFullMapper(params);
}
private _mapPaymentMethodToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
const { errors } = params as {
errors: ValidationErrorDetail[];
};
const paymentId = extractOrPushError(
maybeFromNullableVO(source.payment_method_id, (value) => UniqueID.create(value)),
"payment_method_id",
errors
);
const paymentDescription = extractOrPushError(
maybeFromNullableVO(source.payment_method_description, (value) => Result.ok(String(value))),
"payment_method_description",
errors
);
if (errors.length > 0) {
return Result.fail(new ValidationErrorCollection("Invoice payment mapping failed", errors));
}
if (paymentDescription!.isNone() || paymentId!.isNone()) {
return Result.ok(Maybe.none<InvoicePaymentMethod>());
}
const paymentResult = InvoicePaymentMethod.create(
{
paymentDescription: paymentDescription?.getOrUndefined()!,
},
paymentId?.getOrUndefined()!
);
if (paymentResult.isFailure) {
return Result.fail(
new ValidationErrorCollection("Invoice payment method creation failed", [
{ path: "paymentMethod", message: paymentResult.error.message },
])
);
}
return Result.ok(Maybe.some<InvoicePaymentMethod>(paymentResult.data));
}
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(
maybeFromNullableVO(source.invoice_number, (value) => CustomerInvoiceNumber.create(value)),
"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 reference = extractOrPushError(
maybeFromNullableVO(source.reference, (value) => Result.ok(String(value))),
"reference",
errors
);
const description = extractOrPushError(
maybeFromNullableVO(source.description, (value) => Result.ok(String(value))),
"description",
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(
Percentage.create({
value: source.discount_percentage_value,
scale: source.discount_percentage_scale,
}),
"discount_percentage_value",
errors
);
return {
invoiceId,
companyId,
customerId,
isProforma,
status,
series,
invoiceNumber,
invoiceDate,
operationDate,
reference,
description,
notes,
languageCode,
currencyCode,
discountPercentage,
};
}
public mapToDomain(
source: CustomerInvoiceModel,
params?: MapperParamsType
): Result<CustomerInvoice, Error> {
try {
const errors: ValidationErrorDetail[] = [];
// 1) Valores escalares (atributos generales)
const attributes = this._mapAttributesToDomain(source, { errors, ...params });
// 2) 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,
});
}
// 3) Items (colección)
const itemsResults = this._itemsMapper.mapToDomainCollection(
source.items,
source.items.length,
{
errors,
attributes,
...params,
}
);
if (itemsResults.isFailure) {
errors.push({
path: "items",
message: recipientResult.error.message,
});
}
// 4) Taxes (colección a nivel factura)
const taxesResults = this._taxesMapper.mapToDomainCollection(
source.taxes,
source.taxes.length,
{
errors,
attributes,
...params,
}
);
if (taxesResults.isFailure) {
errors.push({
path: "taxes",
message: taxesResults.error.message,
});
}
// Payment method
const paymentMethodResult = this._mapPaymentMethodToDomain(source, { errors, ...params });
if (paymentMethodResult.isFailure) {
errors.push({
path: "paymentMethod",
message: paymentMethodResult.error.message,
});
}
// 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 [mapToDomain]", errors)
);
}
// 6) Construcción del agregado (Dominio)
const recipient = recipientResult.data;
const paymentMethod = paymentMethodResult.data;
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,
reference: attributes.reference!,
description: attributes.description!,
notes: attributes.notes!,
languageCode: attributes.languageCode!,
currencyCode: attributes.currencyCode!,
discountPercentage: attributes.discountPercentage!,
paymentMethod: paymentMethod!,
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
): Result<CustomerInvoiceCreationAttributes, Error> {
const errors: ValidationErrorDetail[] = [];
// 1) Items
const itemsResult = this._itemsMapper.mapToPersistenceArray(source.items, {
errors,
parent: source,
...params,
});
if (itemsResult.isFailure) {
errors.push({
path: "items",
message: itemsResult.error.message,
});
}
const items = itemsResult.data;
// 1) Taxes
const taxesResult = this._taxesMapper.mapToPersistenceArray(new Collection(source.taxes), {
errors,
parent: source,
...params,
});
if (taxesResult.isFailure) {
errors.push({
path: "taxes",
message: taxesResult.error.message,
});
}
const taxes = taxesResult.data;
// 3) Calcular totales
const allAmounts = source.getAllAmounts();
// 4) Cliente
const recipient = this._mapRecipientToPersistence(source, {
errors,
parent: source,
...params,
});
// 7) Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
);
}
const invoiceValues: CustomerInvoiceCreationAttributes = {
id: source.id.toPrimitive(),
company_id: source.companyId.toPrimitive(),
is_proforma: source.isProforma,
status: source.status.toPrimitive(),
series: toNullable(source.series, (series) => series.toPrimitive()),
invoice_number: toNullable(source.invoiceNumber, (invoiceNumber) =>
invoiceNumber.toPrimitive()
),
invoice_date: source.invoiceDate.toPrimitive(),
operation_date: toNullable(source.operationDate, (operationDate) =>
operationDate.toPrimitive()
),
language_code: source.languageCode.toPrimitive(),
currency_code: source.currencyCode.toPrimitive(),
reference: toNullable(source.reference, (reference) => reference),
description: toNullable(source.description, (description) => description),
notes: toNullable(source.notes, (notes) => notes.toPrimitive()),
subtotal_amount_value: allAmounts.subtotalAmount.value,
subtotal_amount_scale: allAmounts.subtotalAmount.scale,
discount_percentage_value: source.discountPercentage.toPrimitive().value,
discount_percentage_scale: source.discountPercentage.toPrimitive().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,
payment_method_id: toNullable(source.paymentMethod, (payment) => payment.toObjectString().id),
payment_method_description: toNullable(
source.paymentMethod,
(payment) => payment.toObjectString().payment_description
),
customer_id: source.customerId.toPrimitive(),
...recipient,
taxes,
items,
};
return Result.ok<CustomerInvoiceCreationAttributes>(invoiceValues);
}
protected _mapRecipientToPersistence(source: CustomerInvoice, params?: MapperParamsType) {
const { errors } = params as {
errors: ValidationErrorDetail[];
};
const recipient = source.recipient.getOrUndefined();
if (!source.isProforma && !recipient) {
errors.push({
path: "recipient",
message: "[CustomerInvoiceDomainMapper] Issued customer invoice w/o recipient data",
});
}
const recipientValues = {
customer_tin: !source.isProforma ? recipient!.tin.toPrimitive() : null,
customer_name: !source.isProforma ? recipient!.name.toPrimitive() : null,
customer_street: !source.isProforma
? toNullable(recipient!.street, (v) => v.toPrimitive())
: null,
customer_street2: !source.isProforma
? toNullable(recipient!.street2, (v) => v.toPrimitive())
: null,
customer_city: !source.isProforma
? toNullable(recipient!.city, (v) => v.toPrimitive())
: null,
customer_province: !source.isProforma
? toNullable(recipient!.province, (v) => v.toPrimitive())
: null,
customer_postal_code: !source.isProforma
? toNullable(recipient!.postalCode, (v) => v.toPrimitive())
: null,
customer_country: !source.isProforma
? toNullable(recipient!.country, (v) => v.toPrimitive())
: null,
};
return recipientValues;
}
}