import type { JsonTaxCatalogProvider } from "@erp/core"; import { type ITransactionManager, Tax, isEntityNotFoundError } from "@erp/core/api"; import type { ProformaPublicServices } from "@erp/customer-invoices/api"; import { InvoicePaymentMethod, type InvoiceRecipient, InvoiceStatus, } from "@erp/customer-invoices/api/domain"; 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"; import paymentsCatalog from "./payments.json"; type FakePaymentMethod = { id: UniqueID; description: string; factuges_id: string; }; type CreateProformaFromFactugesUseCaseInput = { companyId: UniqueID; dto: CreateProformaFromFactugesRequestDTO; }; type CreateProformaFromFactugesUseCaseDeps = { customerServices: CustomerPublicServices; proformaServices: ProformaPublicServices; dtoMapper: ICreateProformaFromFactugesInputMapper; taxCatalog: JsonTaxCatalogProvider; transactionManager: ITransactionManager; }; type CreateProformaProps = Parameters["1"]; 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); } const { customerLookup, paymentLookup, customerDraft, proformaDraft, paymentDraft } = mappedPropsResult.data; 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; const paymentResult = await this.resolvePayment(paymentLookup, paymentDraft, { companyId, transaction, }); 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, }, }); if (createPropsResult.isFailure) { return Result.fail(createPropsResult.error); } const newId = UniqueID.generateNewID(); const createResult = await this.proformaServices.createProforma( newId, createPropsResult.data, { companyId, transaction, } ); if (createResult.isFailure) { return Result.fail(createResult.error); } const readResult = await this.proformaServices.getProformaSnapshotById( createResult.data.id, { companyId, transaction, } ); if (readResult.isFailure) { return Result.fail(readResult.error); } const snapshot = readResult.data; const result = { customer_id: customer.id.toString(), proforma_id: snapshot.id.toString(), }; return Result.ok(result); } catch (error: unknown) { return Result.fail(error as Error); } }); } private buildProformaCreateProps(deps: { proformaDraft: IProformaFromFactuGESProps["proformaDraft"]; customerId: UniqueID; payment: FakePaymentMethod; context: { companyId: UniqueID; transaction: Transaction; }; }): Result { const { proformaDraft, payment, customerId, context } = deps; const { companyId } = context; const defaultStatus = InvoiceStatus.fromApproved(); const recipient = Maybe.none(); const paymentMethod = Maybe.some( InvoicePaymentMethod.create({ paymentDescription: payment.description }, payment.id).data ); return Result.ok({ ...proformaDraft, companyId, customerId, status: defaultStatus, paymentMethod, 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> { 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, }); } private async resolvePayment( paymentLookup: IProformaFromFactuGESProps["paymentLookup"], paymentDraft: IProformaFromFactuGESProps["paymentDraft"], context: { companyId: UniqueID; transaction: Transaction; } ): Promise> { 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!!!")); } private buildCustomerCreateProps( customerDraft: IProformaFromFactuGESProps["customerDraft"], context: { companyId: UniqueID; transaction: Transaction; } ): Result { 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(); const reference = Maybe.none(); const fax = Maybe.none(); const legalRecord = Maybe.none(); return Result.ok({ ...customerDraft, companyId, status, tin, tradeName, reference, fax, legalRecord, defaultTaxes, }); } }