303 lines
9.1 KiB
TypeScript
303 lines
9.1 KiB
TypeScript
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<never, IUseCaseError> // Misc errors (value objects)
|
|
| Result<Quote, never>; // Success!
|
|
|
|
export class CreateQuoteUseCase
|
|
implements IUseCase<ICreateQuote_Request_DTO, Promise<CreateQuoteResponseOrError>>
|
|
{
|
|
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<Quote>(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<Quote, IDomainError> {
|
|
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<QuoteItem>;
|
|
|
|
try {
|
|
items = new Collection<QuoteItem>(
|
|
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<IQuoteRepository>("Quote");
|
|
}
|
|
}
|