.
This commit is contained in:
parent
969f47357d
commit
8a0e776ce3
@ -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)
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
export * from "./proforma-factory";
|
|
||||||
export * from "./proforma-factory.interface";
|
|
||||||
@ -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>;
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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(
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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!,
|
||||||
|
|||||||
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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 ?? {}),
|
||||||
|
|||||||
@ -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,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user