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 "./CreateQuote.useCase";
|
||||||
export * from "./DeleteQuote.useCase";
|
export * from "./DeleteQuote.useCase";
|
||||||
|
export * from "./DuplicateQuote.useCase";
|
||||||
export * from "./GetQuote.useCase";
|
export * from "./GetQuote.useCase";
|
||||||
export * from "./ListQuotes.useCase";
|
export * from "./ListQuotes.useCase";
|
||||||
export * from "./SendQuote.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 (
|
const generateQuoteReferenceForDealer = async (
|
||||||
dealer: IDealer,
|
dealer: IDealer,
|
||||||
@ -38,4 +39,11 @@ const generateQuoteReferenceForDealer = async (
|
|||||||
return newQuoteReference;
|
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 "./createQuote";
|
||||||
export * from "./deleteQuote";
|
export * from "./deleteQuote";
|
||||||
|
export * from "./duplicateQuote";
|
||||||
export * from "./getQuote";
|
export * from "./getQuote";
|
||||||
export * from "./listQuotes";
|
export * from "./listQuotes";
|
||||||
export * from "./reportQuote";
|
export * from "./reportQuote";
|
||||||
|
export * from "./sendQuote";
|
||||||
export * from "./setStatusQuote";
|
export * from "./setStatusQuote";
|
||||||
export * from "./updateQuote";
|
export * from "./updateQuote";
|
||||||
|
|||||||
@ -2,13 +2,14 @@ import { checkUser } from "@/contexts/auth";
|
|||||||
import { handleRequest } from "@/contexts/common/infrastructure/express";
|
import { handleRequest } from "@/contexts/common/infrastructure/express";
|
||||||
import {
|
import {
|
||||||
createQuoteController,
|
createQuoteController,
|
||||||
|
duplicateQuoteController,
|
||||||
getQuoteController,
|
getQuoteController,
|
||||||
listQuotesController,
|
listQuotesController,
|
||||||
reportQuoteController,
|
reportQuoteController,
|
||||||
|
sendQuoteController,
|
||||||
setStatusQuoteController,
|
setStatusQuoteController,
|
||||||
updateQuoteController,
|
updateQuoteController,
|
||||||
} from "@/contexts/sales/infrastructure/express/controllers";
|
} 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 { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
|
||||||
@ -26,6 +27,14 @@ export const quoteRouter = (appRouter: Router): void => {
|
|||||||
handleRequest(updateQuoteController)
|
handleRequest(updateQuoteController)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Duplicate
|
||||||
|
quoteRoutes.post(
|
||||||
|
"/:quoteId/duplicate",
|
||||||
|
checkUser,
|
||||||
|
getDealerMiddleware,
|
||||||
|
handleRequest(duplicateQuoteController)
|
||||||
|
);
|
||||||
|
|
||||||
// Reports
|
// Reports
|
||||||
quoteRoutes.get(
|
quoteRoutes.get(
|
||||||
"/:quoteId/report",
|
"/: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 "./CreateQuote.dto";
|
||||||
|
export * from "./DuplicateQuote.dto";
|
||||||
export * from "./GetQuote.dto";
|
export * from "./GetQuote.dto";
|
||||||
export * from "./ListQuotes.dto";
|
export * from "./ListQuotes.dto";
|
||||||
export * from "./SendQuote.dto";
|
export * from "./SendQuote.dto";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user