import { ArticleIdentifier } from "@/contexts/catalog/domain"; import { IUseCase, IUseCaseError, UseCaseError } from "@/contexts/common/application"; import { IRepositoryManager } from "@/contexts/common/domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; import { SequelizeBusinessTransactionType } from "@/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction"; import { Collection, CurrencyData, Description, DomainError, ICreateQuote_Request_DTO, IDomainError, Language, Percentage, Quantity, Result, TextValueObject, UTCDateValue, UniqueID, UnitPrice, ensureIdIsValid, } from "@shared/contexts"; import { Dealer, IQuoteRepository, Quote, QuoteCustomer, QuoteCustomerReference, QuoteItem, QuoteReference, QuoteStatus, } from "../../domain"; import { ISalesContext } from "../../infrastructure"; import { generateQuoteReferenceForDealer } from "../services"; export type CreateQuoteResponseOrError = | Result // Misc errors (value objects) | Result; // Success! export class CreateQuoteUseCase implements IUseCase> { private _adapter: ISequelizeAdapter; private _repositoryManager: IRepositoryManager; private _dealer: Dealer; private _transaction: SequelizeBusinessTransactionType; constructor(context: ISalesContext) { this._adapter = context.adapter; this._repositoryManager = context.repositoryManager; this._dealer = context.dealer!; } async execute(request: ICreateQuote_Request_DTO) { const { id } = request; // Validaciones de datos if (!this._dealer) { const message = "Error. Missing Dealer"; return Result.fail(UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message)); } const idOrError = ensureIdIsValid(id); if (idOrError.isFailure) { const message = idOrError.error.message; //`Quote ID ${quoteDTO.id} is not valid`; return Result.fail( UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message, [{ path: "id" }]) ); } this._transaction = this._adapter.startTransaction(); const quoteRepository = this._getQuoteRepository(); try { return await this._transaction.complete(async (t) => { const quoteRepo = quoteRepository({ transaction: t }); // Comprobar que no existe un quote previo con esos datos const idExists = await quoteRepo.exists(idOrError.object); if (idExists) { const message = `Another quote with same ID exists`; return Result.fail( UseCaseError.create(UseCaseError.RESOURCE_ALREADY_EXITS, message, { path: "id", }) ); } // Generate Reference const quoteReference = await generateQuoteReferenceForDealer(this._dealer, quoteRepo); // Crear quote const quoteOrError = this._tryCreateQuoteInstance( request, idOrError.object, quoteReference ); if (quoteOrError.isFailure) { const { error: domainError } = quoteOrError; let errorCode = ""; let message = ""; switch (domainError.code) { case DomainError.INVALID_INPUT_DATA: errorCode = UseCaseError.INVALID_INPUT_DATA; message = "La cotización tiene algún dato erróneo."; break; default: errorCode = UseCaseError.UNEXCEPTED_ERROR; message = domainError.message; break; } return Result.fail(UseCaseError.create(errorCode, message, domainError)); } await quoteRepo.create(quoteOrError.object); return Result.ok(quoteOrError.object); }); } catch (error: unknown) { const _error = error as IInfrastructureError; return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message)); } } private _tryCreateQuoteInstance( quoteDTO: ICreateQuote_Request_DTO, quoteId: UniqueID, quoteReference: QuoteReference ): Result { const dealerId: UniqueID = this._dealer.id; const statusOrError = QuoteStatus.create(quoteDTO.status); if (statusOrError.isFailure) { return Result.fail(statusOrError.error); } const dateOrError = UTCDateValue.create(quoteDTO.date); if (dateOrError.isFailure) { return Result.fail(dateOrError.error); } const languageOrError = Language.createFromCode( quoteDTO.lang_code ?? this._dealer?.language.code ); if (languageOrError.isFailure) { return Result.fail(languageOrError.error); } const customerReferenceOrError = QuoteCustomerReference.create(quoteDTO.customer_reference); if (customerReferenceOrError.isFailure) { return Result.fail(customerReferenceOrError.error); } const customerOrError = QuoteCustomer.create(quoteDTO.customer_information); if (customerOrError.isFailure) { return Result.fail(customerOrError.error); } const currencyOrError = CurrencyData.createFromCode( quoteDTO.currency_code ?? CurrencyData.DEFAULT_CURRENCY_CODE ); if (currencyOrError.isFailure) { return Result.fail(currencyOrError.error); } const paymentOrError = TextValueObject.create( quoteDTO.payment_method ?? this._dealer?.additionalInfo.get("default_payment_method")?.toString() ); if (paymentOrError.isFailure) { return Result.fail(paymentOrError.error); } const notesOrError = TextValueObject.create( quoteDTO.notes ?? this._dealer?.additionalInfo.get("default_notes")?.toString() ); if (notesOrError.isFailure) { return Result.fail(notesOrError.error); } const validityOrError = TextValueObject.create( quoteDTO.validity ?? this._dealer?.additionalInfo.get("default_quote_validity")?.toString() ); if (validityOrError.isFailure) { return Result.fail(validityOrError.error); } const discountOrError = Percentage.create(quoteDTO.discount); if (discountOrError.isFailure) { return Result.fail(discountOrError.error); } const taxOrError = Percentage.create( quoteDTO.tax ?? { amount: this._dealer?.additionalInfo.get("default_tax")?.toString(), scale: 2, } ); if (taxOrError.isFailure) { return Result.fail(taxOrError.error); } const dateSentOrError = UTCDateValue.create(null); if (dateSentOrError.isFailure) { return Result.fail(dateSentOrError.error); } // Items let items: Collection; try { items = new Collection( quoteDTO.items?.map((item) => { const idArticleOrError = ArticleIdentifier.create(item.id_article); if (idArticleOrError.isFailure) { throw idArticleOrError.error; } const descriptionOrError = Description.create(item.description); if (descriptionOrError.isFailure) { throw descriptionOrError.error; } const quantityOrError = Quantity.create({ amount: item.quantity.amount, scale: item.quantity.scale, }); if (quantityOrError.isFailure) { throw quantityOrError.error; } const unitPriceOrError = UnitPrice.create({ amount: item.unit_price?.amount, currencyCode: item.unit_price?.currency_code, scale: item.unit_price?.scale, }); if (unitPriceOrError.isFailure) { throw unitPriceOrError.error; } const percentageOrError = Percentage.create({ amount: item.discount?.amount, scale: item.discount?.scale, }); if (percentageOrError.isFailure) { throw percentageOrError.error; } const quoteItemOrError = QuoteItem.create({ idArticle: idArticleOrError.object, description: descriptionOrError.object, quantity: quantityOrError.object, unitPrice: unitPriceOrError.object, discount: percentageOrError.object, }); if (quoteItemOrError.isFailure) { throw quoteItemOrError.error; } return quoteItemOrError.object; }) ); } catch (e: unknown) { return Result.fail(e as IDomainError); } return Quote.create( { status: statusOrError.object, date: dateOrError.object, reference: quoteReference, language: languageOrError.object, customerReference: customerReferenceOrError.object, customer: customerOrError.object, currency: currencyOrError.object, paymentMethod: paymentOrError.object, notes: notesOrError.object, validity: validityOrError.object, discount: discountOrError.object, tax: taxOrError.object, items, dealerId, dateSent: dateSentOrError.object, }, quoteId ); } private _getQuoteRepository() { return this._repositoryManager.getRepository("Quote"); } }