Uecko_ERP/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice-item.mapper.ts
2025-11-07 18:42:18 +01:00

233 lines
6.6 KiB
TypeScript

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<CustomerInvoiceItemProps> & { itemId?: UniqueID } {
const { errors, index, attributes } = params as {
index: number;
errors: ValidationErrorDetail[];
attributes: Partial<CustomerInvoiceProps>;
};
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<CustomerInvoiceItem, Error> {
const { errors, index } = params as {
index: number;
errors: ValidationErrorDetail[];
attributes: Partial<CustomerInvoiceProps>;
};
// 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<CustomerInvoiceItemCreationAttributes, Error> {
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,
});
}
}