2026-03-16 17:45:45 +00:00
|
|
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
|
|
|
|
import { type ITransactionManager, Tax, isEntityNotFoundError } from "@erp/core/api";
|
|
|
|
|
import type { ProformaPublicServices } from "@erp/customer-invoices/api";
|
2026-03-25 09:34:17 +00:00
|
|
|
import {
|
|
|
|
|
InvoicePaymentMethod,
|
|
|
|
|
type InvoiceRecipient,
|
|
|
|
|
InvoiceStatus,
|
|
|
|
|
} from "@erp/customer-invoices/api/domain";
|
2026-03-16 17:45:45 +00:00
|
|
|
import type { CustomerPublicServices } from "@erp/customers/api";
|
|
|
|
|
import {
|
|
|
|
|
type Customer,
|
|
|
|
|
CustomerStatus,
|
|
|
|
|
type CustomerTaxesProps,
|
|
|
|
|
type ICustomerCreateProps,
|
|
|
|
|
} from "@erp/customers/api/domain";
|
|
|
|
|
import { type Name, type PhoneNumber, type TextValue, UniqueID } from "@repo/rdx-ddd";
|
|
|
|
|
import { Maybe, Result } from "@repo/rdx-utils";
|
|
|
|
|
import type { Transaction } from "sequelize";
|
|
|
|
|
|
|
|
|
|
import type { CreateProformaFromFactugesRequestDTO } from "../../../common";
|
|
|
|
|
import type {
|
|
|
|
|
ICreateProformaFromFactugesInputMapper,
|
|
|
|
|
IProformaFromFactuGESProps,
|
|
|
|
|
} from "../mappers";
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
import paymentsCatalog from "./payments.json";
|
|
|
|
|
|
|
|
|
|
type FakePaymentMethod = {
|
|
|
|
|
id: UniqueID;
|
|
|
|
|
description: string;
|
|
|
|
|
factuges_id: string;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-16 17:45:45 +00:00
|
|
|
type CreateProformaFromFactugesUseCaseInput = {
|
|
|
|
|
companyId: UniqueID;
|
|
|
|
|
dto: CreateProformaFromFactugesRequestDTO;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type CreateProformaFromFactugesUseCaseDeps = {
|
|
|
|
|
customerServices: CustomerPublicServices;
|
|
|
|
|
proformaServices: ProformaPublicServices;
|
|
|
|
|
dtoMapper: ICreateProformaFromFactugesInputMapper;
|
|
|
|
|
taxCatalog: JsonTaxCatalogProvider;
|
|
|
|
|
transactionManager: ITransactionManager;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
type CreateProformaProps = Parameters<ProformaPublicServices["createProforma"]>["1"];
|
|
|
|
|
|
2026-03-16 17:45:45 +00:00
|
|
|
export class CreateProformaFromFactugesUseCase {
|
|
|
|
|
private readonly dtoMapper: ICreateProformaFromFactugesInputMapper;
|
|
|
|
|
private readonly customerServices: CustomerPublicServices;
|
|
|
|
|
private readonly proformaServices: ProformaPublicServices;
|
|
|
|
|
private readonly taxCatalog: JsonTaxCatalogProvider;
|
|
|
|
|
private readonly transactionManager: ITransactionManager;
|
|
|
|
|
|
|
|
|
|
constructor(deps: CreateProformaFromFactugesUseCaseDeps) {
|
|
|
|
|
this.customerServices = deps.customerServices;
|
|
|
|
|
this.proformaServices = deps.proformaServices;
|
|
|
|
|
this.dtoMapper = deps.dtoMapper;
|
|
|
|
|
this.taxCatalog = deps.taxCatalog;
|
|
|
|
|
this.transactionManager = deps.transactionManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async execute(params: CreateProformaFromFactugesUseCaseInput) {
|
|
|
|
|
const { dto, companyId } = params;
|
|
|
|
|
|
|
|
|
|
// 1) Mapear DTO → props
|
|
|
|
|
const mappedPropsResult = this.dtoMapper.map(dto, { companyId });
|
|
|
|
|
if (mappedPropsResult.isFailure) {
|
|
|
|
|
return Result.fail(mappedPropsResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
const { customerLookup, paymentLookup, customerDraft, proformaDraft, paymentDraft } =
|
|
|
|
|
mappedPropsResult.data;
|
2026-03-16 17:45:45 +00:00
|
|
|
|
|
|
|
|
return this.transactionManager.complete(async (transaction: Transaction) => {
|
|
|
|
|
try {
|
|
|
|
|
const customerResult = await this.resolveCustomer(customerLookup, customerDraft, {
|
|
|
|
|
companyId,
|
|
|
|
|
transaction,
|
|
|
|
|
});
|
|
|
|
|
if (customerResult.isFailure) {
|
|
|
|
|
return Result.fail(customerResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const customer = customerResult.data;
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
const paymentResult = await this.resolvePayment(paymentLookup, paymentDraft, {
|
2026-03-16 17:45:45 +00:00
|
|
|
companyId,
|
|
|
|
|
transaction,
|
|
|
|
|
});
|
2026-03-25 09:34:17 +00:00
|
|
|
if (customerResult.isFailure) {
|
|
|
|
|
return Result.fail(customerResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payment = paymentResult.data;
|
|
|
|
|
|
|
|
|
|
// Crear la proforma para ese cliente
|
|
|
|
|
const createPropsResult = this.buildProformaCreateProps({
|
|
|
|
|
proformaDraft,
|
|
|
|
|
payment,
|
|
|
|
|
customerId: customer.id,
|
|
|
|
|
context: {
|
|
|
|
|
companyId,
|
|
|
|
|
transaction,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-16 17:45:45 +00:00
|
|
|
if (createPropsResult.isFailure) {
|
|
|
|
|
return Result.fail(createPropsResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
const newId = UniqueID.generateNewID();
|
|
|
|
|
|
2026-03-16 17:45:45 +00:00
|
|
|
const createResult = await this.proformaServices.createProforma(
|
2026-03-25 09:34:17 +00:00
|
|
|
newId,
|
2026-03-16 17:45:45 +00:00
|
|
|
createPropsResult.data,
|
|
|
|
|
{
|
|
|
|
|
companyId,
|
|
|
|
|
transaction,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (createResult.isFailure) {
|
|
|
|
|
return Result.fail(createResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
const readResult = await this.proformaServices.getProformaSnapshotById(
|
|
|
|
|
createResult.data.id,
|
|
|
|
|
{
|
|
|
|
|
companyId,
|
|
|
|
|
transaction,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (readResult.isFailure) {
|
|
|
|
|
return Result.fail(readResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const snapshot = readResult.data;
|
2026-03-16 17:45:45 +00:00
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
const result = {
|
2026-03-16 17:45:45 +00:00
|
|
|
customer_id: customer.id.toString(),
|
2026-03-25 09:34:17 +00:00
|
|
|
proforma_id: snapshot.id.toString(),
|
2026-03-16 17:45:45 +00:00
|
|
|
};
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
return Result.ok(result);
|
2026-03-16 17:45:45 +00:00
|
|
|
} catch (error: unknown) {
|
|
|
|
|
return Result.fail(error as Error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
private buildProformaCreateProps(deps: {
|
|
|
|
|
proformaDraft: IProformaFromFactuGESProps["proformaDraft"];
|
|
|
|
|
customerId: UniqueID;
|
|
|
|
|
payment: FakePaymentMethod;
|
2026-03-16 17:45:45 +00:00
|
|
|
context: {
|
|
|
|
|
companyId: UniqueID;
|
|
|
|
|
transaction: Transaction;
|
2026-03-25 09:34:17 +00:00
|
|
|
};
|
|
|
|
|
}): Result<CreateProformaProps, Error> {
|
|
|
|
|
const { proformaDraft, payment, customerId, context } = deps;
|
2026-03-16 17:45:45 +00:00
|
|
|
const { companyId } = context;
|
|
|
|
|
|
|
|
|
|
const defaultStatus = InvoiceStatus.fromApproved();
|
|
|
|
|
const recipient = Maybe.none<InvoiceRecipient>();
|
2026-03-25 09:34:17 +00:00
|
|
|
const paymentMethod = Maybe.some(
|
|
|
|
|
InvoicePaymentMethod.create({ paymentDescription: payment.description }, payment.id).data
|
|
|
|
|
);
|
2026-03-16 17:45:45 +00:00
|
|
|
|
|
|
|
|
return Result.ok({
|
|
|
|
|
...proformaDraft,
|
|
|
|
|
companyId,
|
|
|
|
|
customerId,
|
|
|
|
|
status: defaultStatus,
|
2026-03-25 09:34:17 +00:00
|
|
|
paymentMethod,
|
2026-03-16 17:45:45 +00:00
|
|
|
recipient,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resuelve un cliente existente o lo crea si todavía no existe.
|
|
|
|
|
*
|
|
|
|
|
* Motivo:
|
|
|
|
|
* - Centraliza la política "find or create" del caso de uso.
|
|
|
|
|
* - Evita duplicar lógica de control y branching en `execute`.
|
|
|
|
|
* - Separa los datos de búsqueda de los datos necesarios para alta.
|
|
|
|
|
*
|
|
|
|
|
* @param customerLookup - Datos mínimos para localizar un cliente existente.
|
|
|
|
|
* @param customerDraft - Datos necesarios para crear el cliente si no existe.
|
|
|
|
|
* @param context - Contexto transaccional y de compañía.
|
|
|
|
|
* @returns `Result` con el cliente resuelto o el error producido.
|
|
|
|
|
*/
|
|
|
|
|
private async resolveCustomer(
|
|
|
|
|
customerLookup: IProformaFromFactuGESProps["customerLookup"],
|
|
|
|
|
customerDraft: IProformaFromFactuGESProps["customerDraft"],
|
|
|
|
|
context: {
|
|
|
|
|
companyId: UniqueID;
|
|
|
|
|
transaction: Transaction;
|
|
|
|
|
}
|
|
|
|
|
): Promise<Result<Customer, Error>> {
|
|
|
|
|
const { companyId, transaction } = context;
|
|
|
|
|
|
|
|
|
|
const existingCustomerResult = await this.customerServices.findCustomerByTIN(
|
|
|
|
|
customerLookup.tin,
|
|
|
|
|
{ companyId, transaction }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingCustomerResult.isSuccess) {
|
|
|
|
|
return Result.ok(existingCustomerResult.data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isEntityNotFoundError(existingCustomerResult.error)) {
|
|
|
|
|
return Result.fail(existingCustomerResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createPropsResult = this.buildCustomerCreateProps(customerDraft, context);
|
|
|
|
|
if (createPropsResult.isFailure) {
|
|
|
|
|
return Result.fail(createPropsResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.customerServices.createCustomer(UniqueID.generateNewID(), createPropsResult.data, {
|
|
|
|
|
companyId,
|
|
|
|
|
transaction,
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-03-25 09:34:17 +00:00
|
|
|
|
|
|
|
|
private async resolvePayment(
|
|
|
|
|
paymentLookup: IProformaFromFactuGESProps["paymentLookup"],
|
|
|
|
|
paymentDraft: IProformaFromFactuGESProps["paymentDraft"],
|
|
|
|
|
context: {
|
|
|
|
|
companyId: UniqueID;
|
|
|
|
|
transaction: Transaction;
|
|
|
|
|
}
|
|
|
|
|
): Promise<Result<FakePaymentMethod, Error>> {
|
|
|
|
|
const { companyId, transaction } = context;
|
|
|
|
|
|
|
|
|
|
const existingPaymentResult = paymentsCatalog.find(
|
|
|
|
|
(payment) =>
|
|
|
|
|
payment.factuges_id === paymentLookup.factuges_id &&
|
|
|
|
|
payment.company_id === companyId.toString()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingPaymentResult) {
|
|
|
|
|
return Result.ok({
|
|
|
|
|
id: UniqueID.create(existingPaymentResult.id).data,
|
|
|
|
|
description: existingPaymentResult.description,
|
|
|
|
|
factuges_id: existingPaymentResult.factuges_id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Result.fail(new Error("Forma de pago no existe!!!"));
|
|
|
|
|
}
|
2026-03-16 17:45:45 +00:00
|
|
|
|
|
|
|
|
private buildCustomerCreateProps(
|
|
|
|
|
customerDraft: IProformaFromFactuGESProps["customerDraft"],
|
|
|
|
|
context: {
|
|
|
|
|
companyId: UniqueID;
|
|
|
|
|
transaction: Transaction;
|
|
|
|
|
}
|
|
|
|
|
): Result<ICustomerCreateProps, Error> {
|
|
|
|
|
const { companyId } = context;
|
|
|
|
|
|
|
|
|
|
const status = CustomerStatus.createActive();
|
|
|
|
|
|
|
|
|
|
const ivaResult = Tax.createFromCode("iva_21", this.taxCatalog);
|
|
|
|
|
if (ivaResult.isFailure) {
|
|
|
|
|
return Result.fail(ivaResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const defaultTaxes: CustomerTaxesProps = {
|
|
|
|
|
iva: Maybe.some(ivaResult.data),
|
|
|
|
|
rec: Maybe.none(),
|
|
|
|
|
retention: Maybe.none(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const tin = Maybe.some(customerDraft.tin);
|
|
|
|
|
const tradeName = Maybe.none<Name>();
|
|
|
|
|
const reference = Maybe.none<Name>();
|
|
|
|
|
const fax = Maybe.none<PhoneNumber>();
|
|
|
|
|
const legalRecord = Maybe.none<TextValue>();
|
|
|
|
|
|
|
|
|
|
return Result.ok({
|
|
|
|
|
...customerDraft,
|
|
|
|
|
companyId,
|
|
|
|
|
status,
|
|
|
|
|
tin,
|
|
|
|
|
tradeName,
|
|
|
|
|
reference,
|
|
|
|
|
fax,
|
|
|
|
|
legalRecord,
|
|
|
|
|
defaultTaxes,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|