414 lines
12 KiB
TypeScript
414 lines
12 KiB
TypeScript
import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
|
import {
|
|
CurrencyCode,
|
|
extractOrPushError,
|
|
LanguageCode,
|
|
maybeFromNullableVO,
|
|
Percentage,
|
|
TextValue,
|
|
toNullable,
|
|
UniqueID,
|
|
UtcDate,
|
|
ValidationErrorCollection,
|
|
ValidationErrorDetail,
|
|
} from "@repo/rdx-ddd";
|
|
import { Collection, isNullishOrEmpty, Maybe, Result } from "@repo/rdx-utils";
|
|
import {
|
|
CustomerInvoice,
|
|
CustomerInvoiceItems,
|
|
CustomerInvoiceNumber,
|
|
CustomerInvoiceProps,
|
|
CustomerInvoiceSerie,
|
|
CustomerInvoiceStatus,
|
|
InvoicePaymentMethod,
|
|
} from "../../../domain";
|
|
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
|
import { CustomerInvoiceItemDomainMapper } from "./customer-invoice-item.mapper";
|
|
import { InvoiceRecipientDomainMapper } from "./invoice-recipient.mapper";
|
|
import { TaxesDomainMapper } from "./invoice-taxes.mapper";
|
|
|
|
export interface ICustomerInvoiceDomainMapper
|
|
extends ISequelizeDomainMapper<
|
|
CustomerInvoiceModel,
|
|
CustomerInvoiceCreationAttributes,
|
|
CustomerInvoice
|
|
> {}
|
|
|
|
export class CustomerInvoiceDomainMapper
|
|
extends SequelizeDomainMapper<
|
|
CustomerInvoiceModel,
|
|
CustomerInvoiceCreationAttributes,
|
|
CustomerInvoice
|
|
>
|
|
implements ICustomerInvoiceDomainMapper
|
|
{
|
|
private _itemsMapper: CustomerInvoiceItemDomainMapper;
|
|
private _recipientMapper: InvoiceRecipientDomainMapper;
|
|
private _taxesMapper: TaxesDomainMapper;
|
|
|
|
constructor(params: MapperParamsType) {
|
|
super();
|
|
|
|
this._itemsMapper = new CustomerInvoiceItemDomainMapper(params); // Instanciar el mapper de items
|
|
this._recipientMapper = new InvoiceRecipientDomainMapper();
|
|
this._taxesMapper = new TaxesDomainMapper(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 proformaId = extractOrPushError(
|
|
maybeFromNullableVO(source.proforma_id, (v) => UniqueID.create(v)),
|
|
"proforma_id",
|
|
errors
|
|
);
|
|
|
|
const status = extractOrPushError(
|
|
CustomerInvoiceStatus.create(source.status),
|
|
"status",
|
|
errors
|
|
);
|
|
|
|
const series = extractOrPushError(
|
|
maybeFromNullableVO(source.series, (v) => CustomerInvoiceSerie.create(v)),
|
|
"series",
|
|
errors
|
|
);
|
|
|
|
const invoiceNumber = extractOrPushError(
|
|
CustomerInvoiceNumber.create(source.invoice_number),
|
|
"invoice_number",
|
|
errors
|
|
);
|
|
|
|
// Fechas
|
|
const invoiceDate = extractOrPushError(
|
|
UtcDate.createFromISO(source.invoice_date),
|
|
"invoice_date",
|
|
errors
|
|
);
|
|
|
|
const operationDate = extractOrPushError(
|
|
maybeFromNullableVO(source.operation_date, (v) => UtcDate.createFromISO(v)),
|
|
"operation_date",
|
|
errors
|
|
);
|
|
|
|
// Idioma / divisa
|
|
const languageCode = extractOrPushError(
|
|
LanguageCode.create(source.language_code),
|
|
"language_code",
|
|
errors
|
|
);
|
|
|
|
const currencyCode = extractOrPushError(
|
|
CurrencyCode.create(source.currency_code),
|
|
"currency_code",
|
|
errors
|
|
);
|
|
|
|
// Textos opcionales
|
|
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
|
|
);
|
|
|
|
// Método de pago (VO opcional con id + descripción)
|
|
let paymentMethod = Maybe.none<InvoicePaymentMethod>();
|
|
|
|
if (!isNullishOrEmpty(source.payment_method_id)) {
|
|
const paymentId = extractOrPushError(
|
|
UniqueID.create(String(source.payment_method_id)),
|
|
"paymentMethod.id",
|
|
errors
|
|
);
|
|
|
|
const paymentVO = extractOrPushError(
|
|
InvoicePaymentMethod.create(
|
|
{ paymentDescription: String(source.payment_method_description ?? "") },
|
|
paymentId ?? undefined
|
|
),
|
|
"payment_method_description",
|
|
errors
|
|
);
|
|
|
|
if (paymentVO) {
|
|
paymentMethod = Maybe.some(paymentVO);
|
|
}
|
|
}
|
|
|
|
// % descuento (VO)
|
|
const discountPercentage = extractOrPushError(
|
|
Percentage.create({
|
|
value: Number(source.discount_percentage_value ?? 0),
|
|
scale: Number(source.discount_percentage_scale ?? 2),
|
|
}),
|
|
"discount_percentage_value",
|
|
errors
|
|
);
|
|
|
|
return {
|
|
invoiceId,
|
|
companyId,
|
|
customerId,
|
|
isProforma,
|
|
proformaId,
|
|
status,
|
|
series,
|
|
invoiceNumber,
|
|
invoiceDate,
|
|
operationDate,
|
|
reference,
|
|
description,
|
|
notes,
|
|
languageCode,
|
|
currencyCode,
|
|
discountPercentage,
|
|
paymentMethod,
|
|
};
|
|
}
|
|
|
|
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: itemsResults.error.message,
|
|
});
|
|
}*/
|
|
|
|
// Nota: los impuestos a nivel factura (tabla customer_invoice_taxes) se derivan de los items.
|
|
// El agregado expone un getter `taxes` (derivado). No se incluye en las props.
|
|
|
|
// 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 items = CustomerInvoiceItems.create({
|
|
languageCode: attributes.languageCode!,
|
|
currencyCode: attributes.currencyCode!,
|
|
items: itemsResults.data.getAll(),
|
|
});
|
|
|
|
const invoiceProps: CustomerInvoiceProps = {
|
|
companyId: attributes.companyId!,
|
|
|
|
isProforma: attributes.isProforma,
|
|
proformaId: attributes.proformaId!,
|
|
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: attributes.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;
|
|
|
|
// 2) Taxes
|
|
const taxesResult = this._taxesMapper.mapToPersistenceArray(new Collection(source.getTaxes()), {
|
|
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._recipientMapper.mapToPersistence(source.recipient, {
|
|
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: Partial<CustomerInvoiceCreationAttributes> = {
|
|
// Identificación
|
|
id: source.id.toPrimitive(),
|
|
company_id: source.companyId.toPrimitive(),
|
|
|
|
// Flags / estado / serie / número
|
|
is_proforma: source.isProforma,
|
|
proforma_id: toNullable(source.proformaId, (v) => v.toPrimitive()),
|
|
status: source.status.toPrimitive(),
|
|
series: toNullable(source.series, (v) => v.toPrimitive()),
|
|
invoice_number: source.invoiceNumber.toPrimitive(),
|
|
|
|
invoice_date: source.invoiceDate.toPrimitive(),
|
|
operation_date: toNullable(source.operationDate, (v) => v.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, (v) => v.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 as CustomerInvoiceCreationAttributes
|
|
);
|
|
}
|
|
}
|