Duplicar una propuesta (servidor)
This commit is contained in:
parent
9329f5abbe
commit
adcc6e3028
@ -0,0 +1,130 @@
|
||||
import {
|
||||
IUseCase,
|
||||
IUseCaseError,
|
||||
IUseCaseRequest,
|
||||
UseCaseError,
|
||||
} from "@/contexts/common/application/useCases";
|
||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||
import { Collection, IDomainError, Result, UniqueID, UTCDateValue } from "@shared/contexts";
|
||||
import {
|
||||
Dealer,
|
||||
IQuoteRepository,
|
||||
QuoteCustomerReference,
|
||||
QuoteItem,
|
||||
QuoteReference,
|
||||
QuoteStatus,
|
||||
} from "../../domain";
|
||||
|
||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { SequelizeBusinessTransactionType } from "@/contexts/common/infrastructure/sequelize/SequelizeBusinessTransaction";
|
||||
import { Quote } from "../../domain/entities/Quotes/Quote";
|
||||
import { ISalesContext } from "../../infrastructure";
|
||||
import { findQuoteById, generateQuoteReferenceForDealer } from "../services";
|
||||
|
||||
export interface IDuplicateQuoteUseCaseRequest extends IUseCaseRequest {
|
||||
sourceId: UniqueID;
|
||||
}
|
||||
|
||||
export type DuplicateQuoteResponseOrError =
|
||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<Quote, never>; // Success!
|
||||
|
||||
export class DuplicateQuoteUseCase
|
||||
implements IUseCase<IDuplicateQuoteUseCaseRequest, Promise<DuplicateQuoteResponseOrError>>
|
||||
{
|
||||
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: IDuplicateQuoteUseCaseRequest): Promise<DuplicateQuoteResponseOrError> {
|
||||
const { sourceId } = request;
|
||||
const targetId = UniqueID.generateNewID().object;
|
||||
|
||||
const transaction = this._adapter.startTransaction();
|
||||
const QuoteRepoBuilder = this._repositoryManager.getRepository<IQuoteRepository>("Quote");
|
||||
|
||||
let sourceQuote: Quote | null = null;
|
||||
let quoteReference: QuoteReference;
|
||||
let targetQuoteOrError: Result<Quote, IDomainError>;
|
||||
|
||||
// Buscar el quote
|
||||
try {
|
||||
return await transaction.complete(async (t) => {
|
||||
const quoteRepo = QuoteRepoBuilder({ transaction: t });
|
||||
|
||||
sourceQuote = await findQuoteById(sourceId, quoteRepo);
|
||||
|
||||
if (!sourceQuote) {
|
||||
return Result.fail(UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "Quote not found"));
|
||||
}
|
||||
|
||||
// Generate Reference
|
||||
quoteReference = await generateQuoteReferenceForDealer(this._dealer, quoteRepo);
|
||||
|
||||
targetQuoteOrError = this._tryDuplicateQuoteInstance(sourceQuote, targetId, quoteReference);
|
||||
|
||||
await quoteRepo.create(targetQuoteOrError.object);
|
||||
return Result.ok<Quote>(targetQuoteOrError.object);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const _error = error as IInfrastructureError;
|
||||
return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
|
||||
}
|
||||
}
|
||||
|
||||
private _tryDuplicateQuoteInstance(
|
||||
sourceQuote: Quote,
|
||||
targetId: UniqueID,
|
||||
targetReference: QuoteReference
|
||||
): Result<Quote, IDomainError> {
|
||||
const dealerId: UniqueID = this._dealer.id;
|
||||
|
||||
const status = QuoteStatus.createDraft();
|
||||
const date = UTCDateValue.createCurrentDate().object;
|
||||
const customerReference = QuoteCustomerReference.create("").object;
|
||||
const dateSent = UTCDateValue.create(null).object;
|
||||
|
||||
// items
|
||||
let items: Collection<QuoteItem>;
|
||||
|
||||
try {
|
||||
items = new Collection<QuoteItem>(
|
||||
sourceQuote.items.toArray().map((item) => QuoteItem.create(item).object)
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
return Result.fail(e as IDomainError);
|
||||
}
|
||||
|
||||
return Quote.create(
|
||||
{
|
||||
status,
|
||||
date,
|
||||
reference: targetReference,
|
||||
language: sourceQuote.language,
|
||||
customerReference,
|
||||
customer: sourceQuote.customer,
|
||||
currency: sourceQuote.currency,
|
||||
paymentMethod: sourceQuote.paymentMethod,
|
||||
notes: sourceQuote.notes,
|
||||
validity: sourceQuote.validity,
|
||||
|
||||
discount: sourceQuote.discount,
|
||||
tax: sourceQuote.tax,
|
||||
|
||||
items,
|
||||
|
||||
dealerId,
|
||||
dateSent,
|
||||
},
|
||||
targetId
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
export * from "./CreateQuote.useCase";
|
||||
export * from "./DeleteQuote.useCase";
|
||||
export * from "./DuplicateQuote.useCase";
|
||||
export * from "./GetQuote.useCase";
|
||||
export * from "./ListQuotes.useCase";
|
||||
export * from "./SendQuote.useCase";
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { IDealer, IQuoteRepository, QuoteReference } from "../../domain";
|
||||
import { UniqueID } from "@shared/contexts";
|
||||
import { IDealer, IQuoteRepository, Quote, QuoteReference } from "../../domain";
|
||||
|
||||
const generateQuoteReferenceForDealer = async (
|
||||
dealer: IDealer,
|
||||
@ -38,4 +39,11 @@ const generateQuoteReferenceForDealer = async (
|
||||
return newQuoteReference;
|
||||
};
|
||||
|
||||
export { generateQuoteReferenceForDealer };
|
||||
const findQuoteById = async (
|
||||
id: UniqueID,
|
||||
quoteRepository: IQuoteRepository
|
||||
): Promise<Quote | null> => {
|
||||
return quoteRepository.getById(id);
|
||||
};
|
||||
|
||||
export { findQuoteById, generateQuoteReferenceForDealer };
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||
import { IServerError } from "@/contexts/common/domain/errors";
|
||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||
import { DuplicateQuoteUseCase } from "@/contexts/sales/application";
|
||||
import { Quote } from "@/contexts/sales/domain/entities";
|
||||
import { ensureIdIsValid, IDuplicateQuote_Response_DTO } from "@shared/contexts";
|
||||
import { ISalesContext } from "../../../../Sales.context";
|
||||
import { IDuplicateQuotePresenter } from "./presenter";
|
||||
|
||||
export class DuplicateQuoteController extends ExpressController {
|
||||
private useCase: DuplicateQuoteUseCase;
|
||||
private presenter: IDuplicateQuotePresenter;
|
||||
private context: ISalesContext;
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
useCase: DuplicateQuoteUseCase;
|
||||
presenter: IDuplicateQuotePresenter;
|
||||
},
|
||||
context: ISalesContext
|
||||
) {
|
||||
super();
|
||||
|
||||
const { useCase, presenter } = props;
|
||||
this.useCase = useCase;
|
||||
this.presenter = presenter;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
async executeImpl() {
|
||||
try {
|
||||
const { quoteId } = this.req.params;
|
||||
|
||||
// Validar ID
|
||||
const quoteIdOrError = ensureIdIsValid(quoteId);
|
||||
if (quoteIdOrError.isFailure) {
|
||||
const errorMessage = "Quote ID is not valid";
|
||||
const infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
quoteIdOrError.error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
}
|
||||
|
||||
// Llamar al caso de uso
|
||||
const result = await this.useCase.execute({
|
||||
sourceId: quoteIdOrError.object,
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
return this._handleExecuteError(result.error);
|
||||
}
|
||||
|
||||
const quote = <Quote>result.object;
|
||||
|
||||
return this.created<IDuplicateQuote_Response_DTO>(this.presenter.map(quote, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleExecuteError(error: IUseCaseError) {
|
||||
let errorMessage: string;
|
||||
let infraError: IInfrastructureError;
|
||||
|
||||
switch (error.code) {
|
||||
case UseCaseError.INVALID_INPUT_DATA:
|
||||
errorMessage = "Quote data not valid";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.INVALID_INPUT_DATA,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.invalidInputError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.RESOURCE_ALREADY_EXITS:
|
||||
errorMessage = "Quote already exists";
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.RESOURCE_ALREADY_REGISTERED,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.REPOSITORY_ERROR:
|
||||
errorMessage = "Error saving quote";
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.conflictError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
case UseCaseError.UNEXCEPTED_ERROR:
|
||||
errorMessage = error.message;
|
||||
|
||||
infraError = InfrastructureError.create(
|
||||
InfrastructureError.UNEXCEPTED_ERROR,
|
||||
errorMessage,
|
||||
error
|
||||
);
|
||||
return this.internalServerError(errorMessage, infraError);
|
||||
break;
|
||||
|
||||
default:
|
||||
errorMessage = error.message;
|
||||
return this.clientError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { DuplicateQuoteUseCase } from "@/contexts/sales/application";
|
||||
import { registerQuoteRepository } from "../../../../Quote.repository";
|
||||
import { ISalesContext } from "../../../../Sales.context";
|
||||
import { DuplicateQuoteController } from "./DuplicateQuote.controller";
|
||||
import { DuplicateQuotePresenter } from "./presenter";
|
||||
|
||||
export const duplicateQuoteController = (context: ISalesContext) => {
|
||||
registerQuoteRepository(context);
|
||||
return new DuplicateQuoteController(
|
||||
{
|
||||
useCase: new DuplicateQuoteUseCase(context),
|
||||
presenter: DuplicateQuotePresenter,
|
||||
},
|
||||
context
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
import {
|
||||
ICollection,
|
||||
IDuplicateQuote_QuoteItem_Response_DTO,
|
||||
IDuplicateQuote_Response_DTO,
|
||||
} from "@shared/contexts";
|
||||
import { Quote, QuoteItem } from "../../../../../../domain";
|
||||
import { ISalesContext } from "../../../../../Sales.context";
|
||||
|
||||
export interface IDuplicateQuotePresenter {
|
||||
map: (quote: Quote, context: ISalesContext) => IDuplicateQuote_Response_DTO;
|
||||
}
|
||||
|
||||
export const DuplicateQuotePresenter: IDuplicateQuotePresenter = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
map: (quote: Quote, context: ISalesContext): IDuplicateQuote_Response_DTO => {
|
||||
return {
|
||||
id: quote.id.toString(),
|
||||
status: quote.status.toString(),
|
||||
date: quote.date.toISO8601(),
|
||||
reference: quote.reference.toString(),
|
||||
customer_reference: quote.customerReference.toString(),
|
||||
customer_information: quote.customer.toString(),
|
||||
lang_code: quote.language.toString(),
|
||||
currency_code: quote.currency.toString(),
|
||||
|
||||
payment_method: quote.paymentMethod.toString(),
|
||||
validity: quote.validity.toString(),
|
||||
notes: quote.notes.toString(),
|
||||
|
||||
subtotal_price: quote.subtotalPrice.convertScale(2).toObject(),
|
||||
|
||||
discount: quote.discount.convertScale(2).toObject(),
|
||||
discount_price: quote.discountPrice.convertScale(2).toObject(),
|
||||
|
||||
before_tax_price: quote.beforeTaxPrice.convertScale(2).toObject(),
|
||||
|
||||
tax: quote.tax.convertScale(2).toObject(),
|
||||
tax_price: quote.taxPrice.convertScale(2).toObject(),
|
||||
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
items: quoteItemPresenter(quote.items, context),
|
||||
dealer_id: quote.dealerId.toString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const quoteItemPresenter = (
|
||||
items: ICollection<QuoteItem>,
|
||||
context: ISalesContext
|
||||
): IDuplicateQuote_QuoteItem_Response_DTO[] =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: QuoteItem) => ({
|
||||
id_article: item.idArticle.toString(),
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.convertScale(2).toObject(),
|
||||
unit_price: item.unitPrice.convertScale(2).toObject(),
|
||||
subtotal_price: item.subtotalPrice.convertScale(2).toObject(),
|
||||
discount: item.discount.convertScale(2).toObject(),
|
||||
total_price: item.totalPrice.convertScale(2).toObject(),
|
||||
}))
|
||||
: [];
|
||||
@ -0,0 +1 @@
|
||||
export * from "./DuplicateQuote.presenter";
|
||||
@ -1,7 +1,9 @@
|
||||
export * from "./createQuote";
|
||||
export * from "./deleteQuote";
|
||||
export * from "./duplicateQuote";
|
||||
export * from "./getQuote";
|
||||
export * from "./listQuotes";
|
||||
export * from "./reportQuote";
|
||||
export * from "./sendQuote";
|
||||
export * from "./setStatusQuote";
|
||||
export * from "./updateQuote";
|
||||
|
||||
@ -2,13 +2,14 @@ import { checkUser } from "@/contexts/auth";
|
||||
import { handleRequest } from "@/contexts/common/infrastructure/express";
|
||||
import {
|
||||
createQuoteController,
|
||||
duplicateQuoteController,
|
||||
getQuoteController,
|
||||
listQuotesController,
|
||||
reportQuoteController,
|
||||
sendQuoteController,
|
||||
setStatusQuoteController,
|
||||
updateQuoteController,
|
||||
} from "@/contexts/sales/infrastructure/express/controllers";
|
||||
import { sendQuoteController } from "@/contexts/sales/infrastructure/express/controllers/quotes/sendQuote";
|
||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
||||
import { Router } from "express";
|
||||
|
||||
@ -26,6 +27,14 @@ export const quoteRouter = (appRouter: Router): void => {
|
||||
handleRequest(updateQuoteController)
|
||||
);
|
||||
|
||||
// Duplicate
|
||||
quoteRoutes.post(
|
||||
"/:quoteId/duplicate",
|
||||
checkUser,
|
||||
getDealerMiddleware,
|
||||
handleRequest(duplicateQuoteController)
|
||||
);
|
||||
|
||||
// Reports
|
||||
quoteRoutes.get(
|
||||
"/:quoteId/report",
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import { IMoney_DTO, IPercentage_DTO, IQuantity_DTO } from "../../../../../common";
|
||||
|
||||
export interface IDuplicateQuote_Response_DTO {
|
||||
id: string;
|
||||
status: string;
|
||||
date: string;
|
||||
reference: string;
|
||||
customer_reference: string;
|
||||
customer_information: string;
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
|
||||
payment_method: string;
|
||||
notes: string;
|
||||
validity: string;
|
||||
|
||||
subtotal_price: IMoney_DTO;
|
||||
discount: IPercentage_DTO;
|
||||
discount_price: IMoney_DTO;
|
||||
before_tax_price: IMoney_DTO;
|
||||
tax: IPercentage_DTO;
|
||||
tax_price: IMoney_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
items: IDuplicateQuote_QuoteItem_Response_DTO[];
|
||||
|
||||
dealer_id: string;
|
||||
}
|
||||
|
||||
export interface IDuplicateQuote_QuoteItem_Response_DTO {
|
||||
id_article: string;
|
||||
quantity: IQuantity_DTO;
|
||||
description: string;
|
||||
unit_price: IMoney_DTO;
|
||||
subtotal_price: IMoney_DTO;
|
||||
discount: IPercentage_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from "./IDuplicateQuote_Response.dto";
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./CreateQuote.dto";
|
||||
export * from "./DuplicateQuote.dto";
|
||||
export * from "./GetQuote.dto";
|
||||
export * from "./ListQuotes.dto";
|
||||
export * from "./SendQuote.dto";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user