This commit is contained in:
David Arranz 2026-03-23 18:51:49 +01:00
parent 969f47357d
commit 8a0e776ce3
16 changed files with 161 additions and 214 deletions

View File

@ -18,8 +18,6 @@ import { InfrastructureUnavailableError } from "../../errors/infrastructure-unav
* 👉 Este traductor pertenece a la infraestructura (persistencia) * 👉 Este traductor pertenece a la infraestructura (persistencia)
*/ */
export function translateSequelizeError(err: unknown): Error { export function translateSequelizeError(err: unknown): Error {
console.error(err);
// 1) Duplicados (índices únicos) // 1) Duplicados (índices únicos)
if (err instanceof UniqueConstraintError) { if (err instanceof UniqueConstraintError) {
// Tomamos el primer detalle (puede haber varios) // Tomamos el primer detalle (puede haber varios)

View File

@ -1,4 +1,3 @@
import { ProformaFactory } from "../factories";
import type { IProformaRepository } from "../repositories"; import type { IProformaRepository } from "../repositories";
import { type IProformaCreator, type IProformaNumberGenerator, ProformaCreator } from "../services"; import { type IProformaCreator, type IProformaNumberGenerator, ProformaCreator } from "../services";
@ -7,11 +6,9 @@ export const buildProformaCreator = (params: {
repository: IProformaRepository; repository: IProformaRepository;
}): IProformaCreator => { }): IProformaCreator => {
const { numberService, repository } = params; const { numberService, repository } = params;
const factory = new ProformaFactory();
return new ProformaCreator({ return new ProformaCreator({
numberService, numberService,
factory,
repository, repository,
}); });
}; };

View File

@ -1,2 +0,0 @@
export * from "./proforma-factory";
export * from "./proforma-factory.interface";

View File

@ -1,17 +0,0 @@
import type { UniqueID } from "@repo/rdx-ddd";
import type { Result } from "@repo/rdx-utils";
import type { IProformaCreateProps, Proforma } from "../../../domain";
export interface IProformaFactory {
/**
* Crea una proforma válida para una empresa a partir de props ya validadas.
*
* No persiste el agregado.
*/
createProforma(
companyId: UniqueID,
props: Omit<IProformaCreateProps, "companyId">,
proformaId?: UniqueID
): Result<Proforma, Error>;
}

View File

@ -1,16 +0,0 @@
import type { UniqueID } from "@repo/rdx-ddd";
import type { Result } from "@repo/rdx-utils";
import { type IProformaCreateProps, Proforma } from "../../../domain";
import type { IProformaFactory } from "./proforma-factory.interface";
export class ProformaFactory implements IProformaFactory {
createProforma(
companyId: UniqueID,
props: Omit<IProformaCreateProps, "companyId">,
proformaId?: UniqueID
): Result<Proforma, Error> {
return Proforma.create({ ...props, companyId }, proformaId);
}
}

View File

@ -18,7 +18,7 @@ import { Maybe, Result } from "@repo/rdx-utils";
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../common"; import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../common";
import { import {
type IProformaCreateProps, type IProformaCreateProps,
type IProformaItemProps, type IProformaItemCreateProps,
InvoiceNumber, InvoiceNumber,
InvoicePaymentMethod, InvoicePaymentMethod,
type InvoiceRecipient, type InvoiceRecipient,
@ -210,8 +210,8 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
globalDiscountPercentage: DiscountPercentage; globalDiscountPercentage: DiscountPercentage;
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
} }
): IProformaItemProps[] { ): IProformaItemCreateProps[] {
const itemsProps: IProformaItemProps[] = []; const itemsProps: IProformaItemCreateProps[] = [];
dto.items.forEach((item, index) => { dto.items.forEach((item, index) => {
const description = extractOrPushError( const description = extractOrPushError(

View File

@ -2,8 +2,7 @@ import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import type { Transaction } from "sequelize"; import type { Transaction } from "sequelize";
import type { IProformaCreateProps, Proforma } from "../../../domain"; import { type IProformaCreateProps, Proforma } from "../../../domain";
import type { IProformaFactory } from "../factories";
import type { IProformaRepository } from "../repositories"; import type { IProformaRepository } from "../repositories";
import type { IProformaNumberGenerator } from "./proforma-number-generator.interface"; import type { IProformaNumberGenerator } from "./proforma-number-generator.interface";
@ -21,18 +20,15 @@ export interface IProformaCreator {
type ProformaCreatorDeps = { type ProformaCreatorDeps = {
numberService: IProformaNumberGenerator; numberService: IProformaNumberGenerator;
factory: IProformaFactory;
repository: IProformaRepository; repository: IProformaRepository;
}; };
export class ProformaCreator implements IProformaCreator { export class ProformaCreator implements IProformaCreator {
private readonly numberService: IProformaNumberGenerator; private readonly numberService: IProformaNumberGenerator;
private readonly factory: IProformaFactory;
private readonly repository: IProformaRepository; private readonly repository: IProformaRepository;
constructor(deps: ProformaCreatorDeps) { constructor(deps: ProformaCreatorDeps) {
this.numberService = deps.numberService; this.numberService = deps.numberService;
this.factory = deps.factory;
this.repository = deps.repository; this.repository = deps.repository;
} }
@ -55,20 +51,13 @@ export class ProformaCreator implements IProformaCreator {
const invoiceNumber = numberResult.data; const invoiceNumber = numberResult.data;
// 2. Crear agregado // 2. Crear agregado
const buildResult = this.factory.createProforma( const proformaResult = Proforma.create({ ...props, invoiceNumber, companyId }, id);
companyId,
{
...props,
invoiceNumber,
},
id
);
if (buildResult.isFailure) { if (proformaResult.isFailure) {
return Result.fail(buildResult.error); return Result.fail(proformaResult.error);
} }
const proforma = buildResult.data; const proforma = proformaResult.data;
// 3. Persistir // 3. Persistir
const saveResult = await this.repository.create(proforma, transaction); const saveResult = await this.repository.create(proforma, transaction);

View File

@ -18,12 +18,12 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
private readonly taxesBuilder: IProformaTaxesFullSnapshotBuilder private readonly taxesBuilder: IProformaTaxesFullSnapshotBuilder
) {} ) {}
toOutput(invoice: Proforma): IProformaFullSnapshot { toOutput(proforma: Proforma): IProformaFullSnapshot {
const items = this.itemsBuilder.toOutput(invoice.items); const items = this.itemsBuilder.toOutput(proforma.items);
const recipient = this.recipientBuilder.toOutput(invoice); const recipient = this.recipientBuilder.toOutput(proforma);
const taxes = this.taxesBuilder.toOutput(invoice.taxes()); const taxes = this.taxesBuilder.toOutput(proforma.taxes());
const payment = invoice.paymentMethod.match( const payment = proforma.paymentMethod.match(
(payment) => { (payment) => {
const { id, payment_description } = payment.toObjectString(); const { id, payment_description } = payment.toObjectString();
return { return {
@ -34,27 +34,27 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
() => undefined () => undefined
); );
const allTotals = invoice.totals(); const allTotals = proforma.totals();
return { return {
id: invoice.id.toString(), id: proforma.id.toString(),
company_id: invoice.companyId.toString(), company_id: proforma.companyId.toString(),
invoice_number: invoice.invoiceNumber.toString(), invoice_number: proforma.invoiceNumber.toString(),
status: invoice.status.toPrimitive(), status: proforma.status.toPrimitive(),
series: maybeToEmptyString(invoice.series, (value) => value.toString()), series: maybeToEmptyString(proforma.series, (value) => value.toString()),
invoice_date: invoice.invoiceDate.toDateString(), invoice_date: proforma.invoiceDate.toDateString(),
operation_date: maybeToEmptyString(invoice.operationDate, (value) => value.toDateString()), operation_date: maybeToEmptyString(proforma.operationDate, (value) => value.toDateString()),
reference: maybeToEmptyString(invoice.reference, (value) => value.toString()), reference: maybeToEmptyString(proforma.reference, (value) => value.toString()),
description: maybeToEmptyString(invoice.description, (value) => value.toString()), description: maybeToEmptyString(proforma.description, (value) => value.toString()),
notes: maybeToEmptyString(invoice.notes, (value) => value.toString()), notes: maybeToEmptyString(proforma.notes, (value) => value.toString()),
language_code: invoice.languageCode.toString(), language_code: proforma.languageCode.toString(),
currency_code: invoice.currencyCode.toString(), currency_code: proforma.currencyCode.toString(),
customer_id: invoice.customerId.toString(), customer_id: proforma.customerId.toString(),
recipient, recipient,
payment_method: payment, payment_method: payment,
@ -62,7 +62,7 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
subtotal_amount: allTotals.subtotalAmount.toObjectString(), subtotal_amount: allTotals.subtotalAmount.toObjectString(),
items_discount_amount: allTotals.itemDiscountAmount.toObjectString(), items_discount_amount: allTotals.itemDiscountAmount.toObjectString(),
global_discount_percentage: invoice.globalDiscountPercentage.toObjectString(), global_discount_percentage: proforma.globalDiscountPercentage.toObjectString(),
global_discount_amount: allTotals.globalDiscountAmount.toObjectString(), global_discount_amount: allTotals.globalDiscountAmount.toObjectString(),
total_discount_amount: allTotals.totalDiscountAmount.toObjectString(), total_discount_amount: allTotals.totalDiscountAmount.toObjectString(),

View File

@ -21,7 +21,7 @@ import {
type ItemAmount, type ItemAmount,
} from "../../common/value-objects"; } from "../../common/value-objects";
import { import {
type IProformaItemProps, type IProformaItemCreateProps,
type IProformaItems, type IProformaItems,
ProformaItem, ProformaItem,
ProformaItems, ProformaItems,
@ -52,7 +52,7 @@ export interface IProformaCreateProps {
paymentMethod: Maybe<InvoicePaymentMethod>; paymentMethod: Maybe<InvoicePaymentMethod>;
items: IProformaItemProps[]; items: IProformaItemCreateProps[];
globalDiscountPercentage: DiscountPercentage; globalDiscountPercentage: DiscountPercentage;
} }
@ -104,18 +104,32 @@ export type ProformaPatchProps = Partial<Omit<IProformaCreateProps, "companyId"
//items?: ProformaItems; //items?: ProformaItems;
}; };
type InternalProformaProps = Omit<IProformaCreateProps, "items">; export type InternalProformaProps = Omit<IProformaCreateProps, "items">;
export class Proforma extends AggregateRoot<InternalProformaProps> implements IProforma { export class Proforma extends AggregateRoot<InternalProformaProps> implements IProforma {
private readonly _items: ProformaItems;
protected constructor(props: InternalProformaProps, items: ProformaItems, id?: UniqueID) {
super(props, id);
this._items = items;
}
// Creación funcional // Creación funcional
static create(props: IProformaCreateProps, id?: UniqueID): Result<Proforma, Error> { static create(props: IProformaCreateProps, id?: UniqueID): Result<Proforma, Error> {
const internalItems = ProformaItems.create({
items: [],
languageCode: props.languageCode,
currencyCode: props.currencyCode,
globalDiscountPercentage: props.globalDiscountPercentage,
});
const { items, ...internalProps } = props; const { items, ...internalProps } = props;
const proforma = new Proforma(internalProps, id); const proforma = new Proforma(internalProps, internalItems, id);
const addItemsResult = proforma.initializeItems(items); const initializeResult = proforma.initializeItems(items);
if (addItemsResult.isFailure) { if (initializeResult.isFailure) {
return Result.fail(addItemsResult.error); return Result.fail(initializeResult.error);
} }
// Reglas de negocio / validaciones // Reglas de negocio / validaciones
@ -128,21 +142,25 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
} }
// Rehidratación desde persistencia // Rehidratación desde persistencia
static rehydrate(props: InternalProformaProps, id: UniqueID): Proforma { static rehydrate(props: InternalProformaProps, items: ProformaItems, id: UniqueID): Proforma {
return new Proforma(props, id); return new Proforma(props, items, id);
} }
private readonly _items: ProformaItems; private initializeItems(itemsProps: IProformaItemCreateProps[]): Result<void, Error> {
for (const [index, itemProps] of itemsProps.entries()) {
const itemResult = ProformaItem.create(itemProps);
protected constructor(props: InternalProformaProps, id?: UniqueID) { if (itemResult.isFailure) {
super(props, id); return Result.fail(itemResult.error);
}
this._items = ProformaItems.create({ const added = this._items.add(itemResult.data);
languageCode: props.languageCode,
currencyCode: props.currencyCode, if (!added) {
globalDiscountPercentage: props.globalDiscountPercentage, return Result.fail(new ProformaItemMismatch(index));
items: [], }
}); }
return Result.ok();
} }
// Getters // Getters
@ -206,7 +224,7 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
return this.props.globalDiscountPercentage; return this.props.globalDiscountPercentage;
} }
public get items(): IProformaItems { public get items(): ProformaItems {
return this._items; return this._items;
} }
@ -277,7 +295,7 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
return new ProformaTaxesCalculator(this.items).calculate(); return new ProformaTaxesCalculator(this.items).calculate();
} }
public addItem(props: IProformaItemProps): Result<void, Error> { public addItem(props: IProformaItemCreateProps): Result<void, Error> {
const taxesResult = ProformaItemTaxes.create(props.taxes); const taxesResult = ProformaItemTaxes.create(props.taxes);
if (taxesResult.isFailure) return Result.fail(taxesResult.error); if (taxesResult.isFailure) return Result.fail(taxesResult.error);
@ -318,23 +336,6 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
// Helpers // Helpers
private initializeItems(itemsProps: IProformaItemProps[]): Result<void, Error> {
for (const [index, itemProps] of itemsProps.entries()) {
const itemResult = ProformaItem.create(itemProps);
if (itemResult.isFailure) {
return Result.fail(itemResult.error);
}
const added = this._items.add(itemResult.data);
if (!added) {
return Result.fail(new ProformaItemMismatch(index));
}
}
return Result.ok();
}
/** /**
* @summary Convierte un ItemAmount a InvoiceAmount (mantiene moneda y escala homogénea). * @summary Convierte un ItemAmount a InvoiceAmount (mantiene moneda y escala homogénea).
*/ */

View File

@ -25,7 +25,7 @@ import {
* *
*/ */
export interface IProformaItemProps { export interface IProformaItemCreateProps {
description: Maybe<ItemDescription>; description: Maybe<ItemDescription>;
quantity: Maybe<ItemQuantity>; // Cantidad de unidades quantity: Maybe<ItemQuantity>; // Cantidad de unidades
@ -87,22 +87,25 @@ export interface IProformaItem {
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado" isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
} }
type CreateProformaItemProps = IProformaItemProps; type InternalProformaItemProps = Omit<IProformaItemCreateProps, "taxes"> & {
type InternalProformaItemProps = Omit<IProformaItemProps, "taxes"> & {
taxes: ProformaItemTaxes; taxes: ProformaItemTaxes;
}; };
export class ProformaItem extends DomainEntity<InternalProformaItemProps> implements IProformaItem { export class ProformaItem extends DomainEntity<InternalProformaItemProps> implements IProformaItem {
public static create(props: CreateProformaItemProps, id?: UniqueID): Result<ProformaItem, Error> { public static create(
const taxesResult = ProformaItemTaxes.create(props.taxes); props: IProformaItemCreateProps,
id?: UniqueID
): Result<ProformaItem, Error> {
const { taxes, ...internalProps } = props;
const taxesResult = ProformaItemTaxes.create(taxes);
if (taxesResult.isFailure) { if (taxesResult.isFailure) {
return Result.fail(taxesResult.error); return Result.fail(taxesResult.error);
} }
const item = new ProformaItem( const item = new ProformaItem(
{ {
...props, ...internalProps,
taxes: taxesResult.data, taxes: taxesResult.data,
}, },
id id
@ -155,7 +158,7 @@ export class ProformaItem extends DomainEntity<InternalProformaItemProps> implem
return this.props.taxes; return this.props.taxes;
} }
getProps(): IProformaItemProps { getProps(): IProformaItemCreateProps {
return this.props; return this.props;
} }

View File

@ -5,15 +5,10 @@ import { Collection, Result } from "@repo/rdx-utils";
import { ProformaItemMismatch } from "../../errors"; import { ProformaItemMismatch } from "../../errors";
import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator"; import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator";
import type { import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity";
IProformaItem,
IProformaItemProps,
IProformaItemTotals,
ProformaItem,
} from "./proforma-item.entity";
export interface IProformaItemsProps { export interface IProformaItemsProps {
items: IProformaItemProps[]; items?: ProformaItem[];
// Estos campos vienen de la cabecera, // Estos campos vienen de la cabecera,
// pero se necesitan para cálculos y representaciones de la línea. // pero se necesitan para cálculos y representaciones de la línea.
@ -22,8 +17,19 @@ export interface IProformaItemsProps {
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
} }
type CreateProformaProps = IProformaItemsProps; /*type ProformaItemCreateProps = {
type InternalProformaProps = Omit<IProformaItemsProps, "items">; items: IProformaItemCreateProps[];
// Estos campos vienen de la cabecera,
// pero se necesitan para cálculos y representaciones de la línea.
globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
languageCode: LanguageCode; // Para formateos específicos de idioma
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
};
type InternalProformaProps = Omit<ProformaItemCreateProps, "items"> & {
items: ProformaItem[];
};*/
export interface IProformaItems { export interface IProformaItems {
// OJO, no extendemos de Collection<IProformaItem> para no exponer // OJO, no extendemos de Collection<IProformaItem> para no exponer
@ -38,21 +44,23 @@ export interface IProformaItems {
} }
export class ProformaItems extends Collection<ProformaItem> implements IProformaItems { export class ProformaItems extends Collection<ProformaItem> implements IProformaItems {
static create(props: CreateProformaProps): ProformaItems {
return new ProformaItems(props);
}
public readonly languageCode!: LanguageCode; public readonly languageCode!: LanguageCode;
public readonly currencyCode!: CurrencyCode; public readonly currencyCode!: CurrencyCode;
public readonly globalDiscountPercentage!: DiscountPercentage; public readonly globalDiscountPercentage!: DiscountPercentage;
protected constructor(props: InternalProformaProps) { private constructor(props: IProformaItemsProps) {
super([]); super(props.items ?? []);
this.languageCode = props.languageCode; this.languageCode = props.languageCode;
this.currencyCode = props.currencyCode; this.currencyCode = props.currencyCode;
this.globalDiscountPercentage = props.globalDiscountPercentage; this.globalDiscountPercentage = props.globalDiscountPercentage;
this.ensureSameCurrencyAndLanguage(this.items); if (this.items.length > 0) {
this.ensureSameContext(this.items);
}
}
static create(props: IProformaItemsProps): ProformaItems {
return new ProformaItems(props);
} }
public add(item: ProformaItem): boolean { public add(item: ProformaItem): boolean {
@ -105,10 +113,15 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
return new ProformaItemsTotalsCalculator(this).calculate(); return new ProformaItemsTotalsCalculator(this).calculate();
} }
private ensureSameCurrencyAndLanguage(items: IProformaItem[]): void { private ensureSameContext(items: IProformaItem[]): void {
for (const item of items) { for (const item of items) {
if (!item.currencyCode.equals(this.currencyCode)) { const same =
throw new Error("[ProformaItems] All items must share the same currency."); item.languageCode.equals(this.languageCode) &&
item.currencyCode.equals(this.currencyCode) &&
item.globalDiscountPercentage.equals(this.globalDiscountPercentage);
if (!same) {
throw new Error("[ProformaItems] All items must share the same context.");
} }
} }
} }

View File

@ -17,7 +17,7 @@ import { Maybe, Result } from "@repo/rdx-utils";
import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common"; import type { CreateProformaItemRequestDTO, CreateProformaRequestDTO } from "../../../../../common";
import { import {
type IProformaCreateProps, type IProformaCreateProps,
type IProformaItemProps, type IProformaItemCreateProps,
InvoiceNumber, InvoiceNumber,
InvoicePaymentMethod, InvoicePaymentMethod,
type InvoiceRecipient, type InvoiceRecipient,
@ -148,7 +148,7 @@ export class CreateProformaRequestMapper {
); );
} }
const proformaProps: Omit<IProformaCreateProps, "items"> & { items: IProformaItemProps[] } = { const proformaProps: Omit<IProformaCreateProps, "items"> & { items: IProformaItemCreateProps[] } = {
companyId, companyId,
status: defaultStatus!, status: defaultStatus!,
@ -181,7 +181,7 @@ export class CreateProformaRequestMapper {
} }
} }
private mapItems(items: CreateProformaItemRequestDTO[]): IProformaItemProps[] { private mapItems(items: CreateProformaItemRequestDTO[]): IProformaItemCreateProps[] {
const proformaItems = CustomerInvoiceItems.create({ const proformaItems = CustomerInvoiceItems.create({
currencyCode: this.currencyCode!, currencyCode: this.currencyCode!,
languageCode: this.languageCode!, languageCode: this.languageCode!,

View File

@ -14,7 +14,7 @@ import {
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils"; import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
import { import {
type IProformaCreateProps, type InternalProformaProps,
InvoiceNumber, InvoiceNumber,
InvoicePaymentMethod, InvoicePaymentMethod,
InvoiceSerie, InvoiceSerie,
@ -67,7 +67,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors); const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
const series = extractOrPushError( const series = extractOrPushError(
maybeFromNullableResult(raw.series, (v) => InvoiceSerie.create(v)), maybeFromNullableResult(raw.series, (value) => InvoiceSerie.create(value)),
"series", "series",
errors errors
); );
@ -86,7 +86,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
); );
const operationDate = extractOrPushError( const operationDate = extractOrPushError(
maybeFromNullableResult(raw.operation_date, (v) => UtcDate.createFromISO(v)), maybeFromNullableResult(raw.operation_date, (value) => UtcDate.createFromISO(value)),
"operation_date", "operation_date",
errors errors
); );
@ -195,11 +195,15 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
}); });
// 3) Items (colección) // 3) Items (colección)
const itemsResults = this._itemsMapper.mapToDomainCollection(raw.items, raw.items.length, { const itemCollectionResults = this._itemsMapper.mapToDomainCollection(
errors, raw.items,
parent: attributes, raw.items.length,
...params, {
}); errors,
parent: attributes,
...params,
}
);
// 4) Si hubo errores de mapeo, devolvemos colección de validación // 4) Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) { if (errors.length > 0) {
@ -209,15 +213,14 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
} }
// 6) Construcción del agregado (Dominio) // 6) Construcción del agregado (Dominio)
const items = ProformaItems.create({ const items = ProformaItems.create({
languageCode: attributes.languageCode!, languageCode: attributes.languageCode!,
currencyCode: attributes.currencyCode!, currencyCode: attributes.currencyCode!,
globalDiscountPercentage: attributes.globalDiscountPercentage!, globalDiscountPercentage: attributes.globalDiscountPercentage!,
items: itemsResults.data.getAll(), items: itemCollectionResults.data.getAll(),
}); });
const invoiceProps: IProformaCreateProps = { const invoiceProps: InternalProformaProps = {
companyId: attributes.companyId!, companyId: attributes.companyId!,
status: attributes.status!, status: attributes.status!,
@ -239,21 +242,12 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
globalDiscountPercentage: attributes.globalDiscountPercentage!, globalDiscountPercentage: attributes.globalDiscountPercentage!,
paymentMethod: attributes.paymentMethod!, paymentMethod: attributes.paymentMethod!,
items,
}; };
const createResult = Proforma.create(invoiceProps, attributes.invoiceId); const invoiceId = attributes.invoiceId!;
const proforma = Proforma.rehydrate(invoiceProps, items, invoiceId);
if (createResult.isFailure) { return Result.ok(proforma);
return Result.fail(
new ValidationErrorCollection("Customer invoice entity creation failed", [
{ path: "invoice", message: createResult.error.message },
])
);
}
return Result.ok(createResult.data);
} catch (err: unknown) { } catch (err: unknown) {
return Result.fail(err as Error); return Result.fail(err as Error);
} }
@ -312,7 +306,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
const allAmounts = source.totals(); // Da los totales ya calculados const allAmounts = source.totals(); // Da los totales ya calculados
const invoiceValues: Partial<CustomerInvoiceCreationAttributes> = { const proformaValues: Partial<CustomerInvoiceCreationAttributes> = {
// Identificación // Identificación
id: source.id.toPrimitive(), id: source.id.toPrimitive(),
company_id: source.companyId.toPrimitive(), company_id: source.companyId.toPrimitive(),
@ -360,6 +354,15 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
taxable_amount_value: allAmounts.taxableAmount.value, taxable_amount_value: allAmounts.taxableAmount.value,
taxable_amount_scale: allAmounts.taxableAmount.scale, taxable_amount_scale: allAmounts.taxableAmount.scale,
iva_amount_value: allAmounts.ivaAmount.value,
iva_amount_scale: allAmounts.ivaAmount.scale,
rec_amount_value: allAmounts.recAmount.value,
rec_amount_scale: allAmounts.recAmount.scale,
retention_amount_value: allAmounts.retentionAmount.value,
retention_amount_scale: allAmounts.retentionAmount.scale,
taxes_amount_value: allAmounts.taxesAmount.value, taxes_amount_value: allAmounts.taxesAmount.value,
taxes_amount_scale: allAmounts.taxesAmount.scale, taxes_amount_scale: allAmounts.taxesAmount.scale,
@ -374,7 +377,7 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
}; };
return Result.ok<CustomerInvoiceCreationAttributes>( return Result.ok<CustomerInvoiceCreationAttributes>(
invoiceValues as CustomerInvoiceCreationAttributes proformaValues as CustomerInvoiceCreationAttributes
); );
} }
} }

View File

@ -17,7 +17,7 @@ import { Result } from "@repo/rdx-utils";
import { import {
type IProformaCreateProps, type IProformaCreateProps,
type IProformaItemProps, type IProformaItemCreateProps,
ItemAmount, ItemAmount,
ItemDescription, ItemDescription,
ItemQuantity, ItemQuantity,
@ -54,7 +54,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
private mapAttributesToDomain( private mapAttributesToDomain(
raw: CustomerInvoiceItemModel, raw: CustomerInvoiceItemModel,
params?: MapperParamsType params?: MapperParamsType
): Partial<IProformaItemProps & ProformaItemTaxesProps> & { itemId?: UniqueID } { ): Partial<IProformaItemCreateProps & ProformaItemTaxesProps> & { itemId?: UniqueID } {
const { errors, index, parent } = params as { const { errors, index, parent } = params as {
index: number; index: number;
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
@ -160,7 +160,8 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
}); });
// 2) Construcción del elemento de dominio // 2) Construcción del elemento de dominio
const createResult = ProformaItem.create( const itemId = attributes.itemId!;
const newItem = ProformaItem.rehydrate(
{ {
languageCode: attributes.languageCode!, languageCode: attributes.languageCode!,
currencyCode: attributes.currencyCode!, currencyCode: attributes.currencyCode!,
@ -171,18 +172,18 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
globalDiscountPercentage: attributes.globalDiscountPercentage!, globalDiscountPercentage: attributes.globalDiscountPercentage!,
taxes: taxesResult.data, taxes: taxesResult.data,
}, },
attributes.itemId itemId
); );
if (createResult.isFailure) { /*if (createResult.isFailure) {
return Result.fail( return Result.fail(
new ValidationErrorCollection("Invoice item entity creation failed", [ new ValidationErrorCollection("Invoice item entity creation failed", [
{ path: `items[${index}]`, message: createResult.error.message }, { path: `items[${index}]`, message: createResult.error.message },
]) ])
); );
} }*/
return createResult; return Result.ok(newItem);
} }
public mapToPersistence( public mapToPersistence(

View File

@ -365,8 +365,6 @@ export class ProformaRepository
? [options.include] ? [options.include]
: []; : [];
console.log(query.where);
query.where = { query.where = {
...query.where, ...query.where,
...(options.where ?? {}), ...(options.where ?? {}),

View File

@ -1,7 +1,7 @@
import type { JsonTaxCatalogProvider } from "@erp/core"; import type { JsonTaxCatalogProvider } from "@erp/core";
import { DiscountPercentage, Tax } from "@erp/core/api"; import { DiscountPercentage, Tax } from "@erp/core/api";
import { import {
type IProformaItemProps, type IProformaItemCreateProps,
InvoicePaymentMethod, InvoicePaymentMethod,
InvoiceSerie, InvoiceSerie,
ItemAmount, ItemAmount,
@ -9,7 +9,6 @@ import {
ItemQuantity, ItemQuantity,
type ProformaItemTaxesProps, type ProformaItemTaxesProps,
} from "@erp/customer-invoices/api/domain"; } from "@erp/customer-invoices/api/domain";
import type { CustomerTaxesProps } from "@erp/customers/api/domain";
import { import {
City, City,
Country, Country,
@ -91,7 +90,7 @@ export interface IProformaFromFactuGESProps {
paymentMethod: Maybe<InvoicePaymentMethod>; paymentMethod: Maybe<InvoicePaymentMethod>;
items: IProformaItemProps[]; items: IProformaItemCreateProps[];
globalDiscountPercentage: DiscountPercentage; globalDiscountPercentage: DiscountPercentage;
}; };
} }
@ -409,29 +408,6 @@ export class CreateProformaFromFactugesInputMapper
return customerProps; return customerProps;
} }
private mapCustomerTaxesProps(
dto: CreateProformaFromFactugesRequestDTO["customer"],
params: {
errors: ValidationErrorDetail[];
}
): CustomerTaxesProps {
const { errors } = params;
const iva = extractOrPushError(
maybeFromNullableResult("iva_21", (value) => Tax.createFromCode(value, this.taxCatalog)),
"iva_21",
errors
);
this.throwIfValidationErrors(errors);
return {
iva: iva!,
rec: Maybe.none(),
retention: Maybe.none(),
};
}
private mapItemsProps( private mapItemsProps(
dto: CreateProformaFromFactugesRequestDTO, dto: CreateProformaFromFactugesRequestDTO,
params: { params: {
@ -440,8 +416,8 @@ export class CreateProformaFromFactugesInputMapper
globalDiscountPercentage: DiscountPercentage; globalDiscountPercentage: DiscountPercentage;
errors: ValidationErrorDetail[]; errors: ValidationErrorDetail[];
} }
): IProformaItemProps[] { ): IProformaItemCreateProps[] {
const itemsProps: IProformaItemProps[] = []; const itemsProps: IProformaItemCreateProps[] = [];
dto.items.forEach((item, index) => { dto.items.forEach((item, index) => {
const description = extractOrPushError( const description = extractOrPushError(
@ -482,15 +458,18 @@ export class CreateProformaFromFactugesInputMapper
this.throwIfValidationErrors(params.errors); this.throwIfValidationErrors(params.errors);
itemsProps.push({ itemsProps.push({
description: description!,
quantity: quantity!,
unitAmount: unitAmount!,
itemDiscountPercentage: discountPercentage!,
taxes,
globalDiscountPercentage: params.globalDiscountPercentage, globalDiscountPercentage: params.globalDiscountPercentage,
languageCode: params.languageCode, languageCode: params.languageCode,
currencyCode: params.currencyCode, currencyCode: params.currencyCode,
description: description!,
quantity: quantity!,
unitAmount: unitAmount!,
itemDiscountPercentage: discountPercentage!,
taxes,
}); });
}); });