This commit is contained in:
David Arranz 2024-07-09 18:21:21 +02:00
parent 981d70cffe
commit 54dc76534a
15 changed files with 156 additions and 128 deletions

View File

@ -54,21 +54,21 @@ export const QuoteDetailsCardEditor = () => {
},
{
id: "retail_price" as const,
accessorKey: "retail_price",
header: "retail_price",
id: "unit_price" as const,
accessorKey: "unit_price",
header: "unit_price",
size: 10,
cell: ({ row: { index }, column: { id } }) => {
return <FormMoneyField {...register(`items.${index}.retail_price`)} />;
return <FormMoneyField {...register(`items.${index}.unit_price`)} />;
},
},
{
id: "price" as const,
accessorKey: "price",
header: "price",
id: "subtotal_price" as const,
accessorKey: "subtotal_price",
header: "subtotal_price",
size: 10,
cell: ({ row: { index }, column: { id } }) => {
return <FormMoneyField {...register(`items.${index}.price`)} />;
return <FormMoneyField {...register(`items.${index}.subtotal_price`)} />;
},
},
{
@ -81,12 +81,12 @@ export const QuoteDetailsCardEditor = () => {
},
},
{
id: "total" as const,
accessorKey: "total",
header: "total",
id: "total_price" as const,
accessorKey: "total_price",
header: "total_price",
size: 10,
cell: ({ row: { index }, column: { id } }) => {
return <FormMoneyField {...register(`items.${index}.total`)} />;
return <FormMoneyField {...register(`items.${index}.total_price`)} />;
},
},
],

View File

@ -33,7 +33,7 @@ interface QuoteDataForm extends IUpdateQuote_Request_DTO {
items: {
quantity: IQuantity;
description: string;
retail_price: IMoney;
unit_price: IMoney;
price: IMoney;
discount: IPercentage;
total: IMoney;
@ -105,10 +105,10 @@ export const QuoteEdit = () => {
// Recálculo líneas
items.map((item, index) => {
const itemTotals = calculateItemTotals(item);
quoteSubtotal = quoteSubtotal.add(itemTotals.total);
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
setValue(`items.${index}.price`, itemTotals.price.toObject());
setValue(`items.${index}.total`, itemTotals.total.toObject());
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
});
console.log(quoteSubtotal.toFormat());
@ -128,8 +128,8 @@ export const QuoteEdit = () => {
const itemTotals = calculateItemTotals(items[index]);
setValue(`items.${index}.price`, itemTotals.price.toObject());
setValue(`items.${index}.total`, itemTotals.total.toObject());
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
// Recálculo completo
}
@ -168,9 +168,9 @@ export const QuoteEdit = () => {
</div>
<FormMoneyField
label={"subtotal"}
label={"subtotal_price"}
disabled={form.formState.disabled}
{...form.register("subtotal")}
{...form.register("subtotal_price")}
/>
<Tabs defaultValue='items' className='space-y-4'>

View File

@ -3,57 +3,43 @@ import { IMoney, IPercentage, IQuantity } from "./types";
export const calculateItemTotals = (item: {
quantity?: IQuantity;
retail_price?: IMoney;
unit_price?: IMoney;
discount?: IPercentage;
}): {
quantity: Quantity;
retailPrice: MoneyValue;
price: MoneyValue;
unitPrice: MoneyValue;
subtotalPrice: MoneyValue;
discount: Percentage;
total: MoneyValue;
totalPrice: MoneyValue;
} => {
const { quantity: quantityValue, retail_price: retailPriceValue, discount: discountValue } = item;
const { quantity: quantity_value, unit_price: unit_price_value, discount: discount_value } = item;
console.log({
quantityValue,
retailPriceValue,
discountValue,
});
const quantityOrError = Quantity.create(quantityValue);
const quantityOrError = Quantity.create(quantity_value);
if (quantityOrError.isFailure) {
throw quantityOrError.error;
}
const quantity = quantityOrError.object;
const retailPriceOrError = MoneyValue.create(retailPriceValue);
if (retailPriceOrError.isFailure) {
throw retailPriceOrError.error;
const unitPriceOrError = MoneyValue.create(unit_price_value);
if (unitPriceOrError.isFailure) {
throw unitPriceOrError.error;
}
const retailPrice = retailPriceOrError.object;
const unitPrice = unitPriceOrError.object;
const discountOrError = Percentage.create(discountValue);
const discountOrError = Percentage.create(discount_value);
if (discountOrError.isFailure) {
throw discountOrError.error;
}
const discount = discountOrError.object;
const price = retailPrice.multiply(quantity.toNumber());
const total = price.subtract(price.percentage(discount.toNumber()));
const subtotalPrice = unitPrice.multiply(quantity.toNumber());
const totalPrice = subtotalPrice.subtract(subtotalPrice.percentage(discount.toNumber()));
return {
quantity,
retailPrice,
price,
unitPrice,
subtotalPrice,
discount,
total,
totalPrice,
};
/*return {
quantity: quantity.toObject(),
retail_price: retailPrice.toObject(),
price: price.toObject(),
discount: discount.toObject(),
total: total.toObject(),
};*/
};

View File

@ -158,7 +158,6 @@ export abstract class SequelizeRepository<T> implements IRepository<T> {
},
transaction: this._transaction,
force,
logging: console.log,
});
}

View File

@ -144,7 +144,9 @@ export class CreateQuoteUseCase
return Result.fail(referenceOrError.error);
}
const languageOrError = Language.createFromCode(quoteDTO.lang_code);
const languageOrError = Language.createFromCode(
quoteDTO.lang_code ?? this._dealer?.language.code
);
if (languageOrError.isFailure) {
return Result.fail(languageOrError.error);
}
@ -154,7 +156,9 @@ export class CreateQuoteUseCase
return Result.fail(customerOrError.error);
}
const currencyOrError = CurrencyData.createFromCode(quoteDTO.currency_code);
const currencyOrError = CurrencyData.createFromCode(
quoteDTO.currency_code ?? CurrencyData.DEFAULT_CURRENCY_CODE
);
if (currencyOrError.isFailure) {
return Result.fail(currencyOrError.error);
}
@ -174,6 +178,11 @@ export class CreateQuoteUseCase
return Result.fail(validityOrError.error);
}
const discountOrError = Percentage.create(quoteDTO.discount);
if (discountOrError.isFailure) {
return Result.fail(discountOrError.error);
}
const items = new Collection<QuoteItem>(
quoteDTO.items?.map(
(item) =>
@ -182,11 +191,14 @@ export class CreateQuoteUseCase
description: Description.create(item.description).object,
quantity: Quantity.create(item.quantity).object,
unitPrice: UnitPrice.create({
amount: item.unit_price.amount,
currencyCode: item.unit_price.currency_code,
precision: item.unit_price.precision,
amount: item.unit_price?.amount,
currencyCode: item.unit_price?.currency_code,
precision: item.unit_price?.precision,
}).object,
discount: Percentage.create({
amount: item.discount?.amount,
precision: item.discount?.precision,
}).object,
discount: Percentage.create(item.discount.amount).object,
}).object
)
);
@ -203,6 +215,8 @@ export class CreateQuoteUseCase
notes: notesOrError.object,
validity: validityOrError.object,
discount: discountOrError.object,
items,
dealerId,

View File

@ -9,12 +9,13 @@ import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import {
Collection,
Currency,
CurrencyData,
Description,
DomainError,
IDomainError,
Language,
Note,
Percentage,
Quantity,
Result,
UTCDateValue,
@ -23,11 +24,20 @@ import {
} from "@shared/contexts";
import { IUpdateQuote_Request_DTO } from "@shared/contexts";
import { IQuoteRepository, Quote, QuoteCustomer, QuoteItem, QuoteStatus } from "../../domain";
import {
Dealer,
IQuoteRepository,
Quote,
QuoteCustomer,
QuoteItem,
QuoteReference,
QuoteStatus,
} from "../../domain";
import { ISalesContext } from "../../infrastructure";
export interface IUpdateQuoteUseCaseRequest extends IUseCaseRequest {
id: UniqueID;
QuoteDTO: IUpdateQuote_Request_DTO;
quoteDTO: IUpdateQuote_Request_DTO;
}
export type UpdateQuoteResponseOrError =
@ -39,16 +49,26 @@ export class UpdateQuoteUseCase
{
private _adapter: ISequelizeAdapter;
private _repositoryManager: IRepositoryManager;
private _dealer?: Dealer;
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
this._adapter = props.adapter;
this._repositoryManager = props.repositoryManager;
constructor(context: ISalesContext) {
this._adapter = context.adapter;
this._repositoryManager = context.repositoryManager;
this._dealer = context.dealer;
}
async execute(request: IUpdateQuoteUseCaseRequest): Promise<UpdateQuoteResponseOrError> {
const { id, QuoteDTO } = request;
const { id, quoteDTO } = request;
const QuoteRepository = this._getQuoteRepository();
// Validaciones de datos
if (!this._dealer) {
const message = "Error. Missing Dealer";
return Result.fail(UseCaseError.create(UseCaseError.INVALID_INPUT_DATA, message));
}
const dealerId = this._dealer.id;
// Comprobar que existe el Quote
const idExists = await QuoteRepository().exists(id);
if (!idExists) {
@ -61,10 +81,10 @@ export class UpdateQuoteUseCase
}
// Crear usuario
const QuoteOrError = this._tryCreateQuoteInstance(QuoteDTO, id);
const quoteOrError = this._tryCreateQuoteInstance(quoteDTO, id, dealerId);
if (QuoteOrError.isFailure) {
const { error: domainError } = QuoteOrError;
if (quoteOrError.isFailure) {
const { error: domainError } = quoteOrError;
let errorCode = "";
let message = "";
@ -84,22 +104,23 @@ export class UpdateQuoteUseCase
return Result.fail(UseCaseError.create(errorCode, message, domainError));
}
return this._updateQuote(QuoteOrError.object);
return this._updateQuote(quoteOrError.object);
}
private async _updateQuote(Quote: Quote) {
private async _updateQuote(quote: Quote) {
// Guardar el contacto
const transaction = this._adapter.startTransaction();
const QuoteRepository = this._getQuoteRepository();
let QuoteRepo: IQuoteRepository;
const quoteRepository = this._getQuoteRepository();
let quoteRepo: IQuoteRepository;
try {
await transaction.complete(async (t) => {
QuoteRepo = QuoteRepository({ transaction: t });
await QuoteRepo.update(Quote);
console.log(t);
quoteRepo = quoteRepository({ transaction: t });
await quoteRepo.update(quote);
});
return Result.ok<Quote>(Quote);
return Result.ok<Quote>(quote);
} catch (error: unknown) {
const _error = error as IInfrastructureError;
return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
@ -108,7 +129,8 @@ export class UpdateQuoteUseCase
private _tryCreateQuoteInstance(
quoteDTO: IUpdateQuote_Request_DTO,
quoteId: UniqueID
quoteId: UniqueID,
dealerId: UniqueID
): Result<Quote, IDomainError> {
const statusOrError = QuoteStatus.create(quoteDTO.status);
if (statusOrError.isFailure) {
@ -120,7 +142,7 @@ export class UpdateQuoteUseCase
return Result.fail(dateOrError.error);
}
const referenceOrError = QuoteStatus.create(quoteDTO.reference);
const referenceOrError = QuoteReference.create(quoteDTO.reference);
if (referenceOrError.isFailure) {
return Result.fail(referenceOrError.error);
}
@ -135,7 +157,7 @@ export class UpdateQuoteUseCase
return Result.fail(customerOrError.error);
}
const currencyOrError = Currency.createFromCode(quoteDTO.currency_code);
const currencyOrError = CurrencyData.createFromCode(quoteDTO.currency_code);
if (currencyOrError.isFailure) {
return Result.fail(currencyOrError.error);
}
@ -155,16 +177,26 @@ export class UpdateQuoteUseCase
return Result.fail(validityOrError.error);
}
const discountOrError = Percentage.create(quoteDTO.discount);
if (discountOrError.isFailure) {
return Result.fail(discountOrError.error);
}
const items = new Collection<QuoteItem>(
quoteDTO.items?.map(
(item) =>
QuoteItem.create({
articleId: item.article_id,
description: Description.create(item.description).object,
quantity: Quantity.create({ amount: item.quantity, precision: 4 }).object,
quantity: Quantity.create(item.quantity).object,
unitPrice: UnitPrice.create({
amount: item.unit_price.amount,
currencyCode: item.unit_price.currency,
precision: item.unit_price.precision,
amount: item.unit_price?.amount,
currencyCode: item.unit_price?.currency_code,
precision: item.unit_price?.precision,
}).object,
discount: Percentage.create({
amount: item.discount?.amount,
precision: item.discount?.precision,
}).object,
}).object
)
@ -182,7 +214,11 @@ export class UpdateQuoteUseCase
notes: notesOrError.object,
validity: validityOrError.object,
discount: discountOrError.object,
items,
dealerId,
},
quoteId
);

View File

@ -1,6 +1,6 @@
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
import { Transaction } from "sequelize";
import { ModelDefined, Transaction } from "sequelize";
import { IQuoteRepository } from "../domain";
import { Quote } from "../domain/entities";
@ -35,11 +35,27 @@ export class QuoteRepository extends SequelizeRepository<Quote> implements IQuot
}
public async update(user: Quote): Promise<void> {
console.time("update");
const userData = this.mapper.mapToPersistence(user);
// borrando y luego creando
// await this.removeById(user.id, true);
await this._save("Quote_Model", user.id, userData, {});
const QuoteItem_Model: ModelDefined<any, any> = this._adapter.getModel("QuoteItem_Model");
await Promise.all([
this._save("Quote_Model", user.id, userData, {}),
QuoteItem_Model.destroy({
where: {
quote_id: userData.id,
},
transaction: this._transaction,
force: true,
}),
]);
await QuoteItem_Model.bulkCreate(userData.items, {
transaction: this._transaction,
});
console.timeEnd("update");
}
public async getById(id: UniqueID): Promise<Quote | null> {

View File

@ -55,7 +55,7 @@ export default (sequelize: Sequelize) => {
},
id_article: {
type: DataTypes.BIGINT().UNSIGNED,
allowNull: false,
allowNull: true,
},
position: {
type: new DataTypes.MEDIUMINT(),

View File

@ -14,12 +14,12 @@ export const QuoteRouter = (appRouter: Express.Router) => {
quoteRoutes.get("/", checkUser, getDealerMiddleware, listQuotesController);
quoteRoutes.get("/:quoteId", checkUser, getDealerMiddleware, getQuoteController);
quoteRoutes.post("/", checkUser, getDealerMiddleware, createQuoteController);
quoteRoutes.put("/:quoteId", checkUser, updateQuoteController);
quoteRoutes.put("/:quoteId", checkUser, getDealerMiddleware, updateQuoteController);
/*
quoteRoutes.post("/", isAdmin, createQuoteController);
quoteRoutes.delete("/:quoteId", isAdmin, deleteQuoteController);*/
quoteRoutes.delete("/:quoteId", isAdmin, getDealerMiddleware, deleteQuoteController);*/
appRouter.use("/quotes", quoteRoutes);
};

View File

@ -1,6 +1,7 @@
import Joi from "joi";
import { isNull } from "lodash";
import { NullOr } from "../../../../utilities";
import { DomainError, handleDomainError } from "../errors";
import { RuleValidator } from "../RuleValidator";
import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject";
import { Result } from "./Result";
@ -70,7 +71,9 @@ export class Percentage extends NullableValueObject<IPercentage> {
const validationResult = Percentage.validate(amount, _options);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
return Result.fail(
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
);
}
let _amount: NullOr<number> = Percentage.sanitize(validationResult.object);

View File

@ -74,7 +74,6 @@ export class Quantity extends NullableValueObject<IQuantity> {
}
let _amount: NullOr<number> = Quantity.sanitize(validationResult.object);
const _props = {
amount: isNull(_amount) ? 0 : _amount,
precision,

View File

@ -1,6 +1,6 @@
import { NullOr } from '../../../../utilities';
import { MoneyValue } from './MoneyValue';
import { Result } from './Result';
import { NullOr } from "../../../../utilities";
import { MoneyValue } from "./MoneyValue";
import { Result } from "./Result";
export interface IUnitPriceProps {
amount: NullOr<number | string>;
@ -10,8 +10,7 @@ export interface IUnitPriceProps {
export class UnitPrice extends MoneyValue {
public static create(props: IUnitPriceProps) {
const {amount, currencyCode, precision = 4} = props;
const { amount, currencyCode, precision = 4 } = props;
const _unitPriceOrError = MoneyValue.create({
amount,
currencyCode,

View File

@ -22,7 +22,7 @@ export * from "./StringValueObject";
export * from "./TextValueObject";
export * from "./TINNumber";
export * from "./UniqueID";
//export * from "./UnitPrice";
export * from "./UnitPrice";
export * from "./UTCDateValue";
export * from "./ValueObject";

View File

@ -41,34 +41,10 @@ export interface ICreateQuoteItem_Request_DTO {
export function ensureCreateQuote_Request_DTOIsValid(quoteDTO: ICreateQuote_Request_DTO) {
const schema = Joi.object({
id: Joi.string(),
status: Joi.string(),
date: Joi.string(),
reference: Joi.string(),
lang_code: Joi.string(),
customer_information: Joi.string(),
currency_code: Joi.string(),
payment_method: Joi.string(),
notes: Joi.string(),
validity: Joi.string(),
items: Joi.array().items(
Joi.object({
article_id: Joi.string(),
description: Joi.string(),
quantity: {
amount: Joi.number(),
precision: Joi.number(),
},
unit_price: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
}),
discount: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
}),
}).unknown(true)
),
}).unknown(true);
const result = RuleValidator.validate<ICreateQuote_Request_DTO>(schema, quoteDTO);

View File

@ -43,14 +43,14 @@ export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Requ
customer_information: Joi.string(),
lang_code: Joi.string(),
currency_code: Joi.string(),
payment_method: Joi.string(),
notes: Joi.string(),
validity: Joi.string(),
payment_method: Joi.string().optional().allow(null).allow("").default(""),
notes: Joi.string().optional().allow(null).allow("").default(""),
validity: Joi.string().optional().allow(null).allow("").default(""),
subtotal: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
currency_code: Joi.string(),
}),
discount: Joi.object({
@ -69,12 +69,12 @@ export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Requ
unit_price: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
currency_code: Joi.string(),
}),
subtotal_price: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
currency_code: Joi.string(),
}),
discount: Joi.object({
amount: Joi.number(),
@ -83,7 +83,7 @@ export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Requ
total_price: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
currency_code: Joi.string(),
}),
}).unknown(true)
),