.
This commit is contained in:
parent
5e4d58c112
commit
c1b61a0b60
@ -241,42 +241,62 @@ export function QuoteItemsSortableDataTable({
|
||||
const hadleNewItem = useCallback(() => {
|
||||
actions.append([
|
||||
{
|
||||
quantity: { amount: "123" },
|
||||
description: "aaaa",
|
||||
article_id: 2000004503,
|
||||
description: "Lacquered Norma with 1 spline for lacquered panel up to 700 mm (27.56 in)",
|
||||
quantity: { amount: "15", scale: 0 },
|
||||
unit_price: {
|
||||
amount: "10000",
|
||||
amount: "150000",
|
||||
scale: 4,
|
||||
currency: "EUR",
|
||||
currency_code: "EUR",
|
||||
},
|
||||
discount: {
|
||||
amount: 35,
|
||||
amount: 3500,
|
||||
scale: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
quantity: {
|
||||
amount: "2",
|
||||
article_id: 2000005891,
|
||||
description:
|
||||
"Split walnut HPL 3 elephant gray faux-leather central earring tray compartment 150x410x50 mm (5.91 in x 16.14 in x 1.97 in)",
|
||||
quantity: { amount: "8", scale: 0 },
|
||||
unit_price: {
|
||||
amount: "384560",
|
||||
scale: 4,
|
||||
currency_code: "EUR",
|
||||
},
|
||||
description: "bbbb",
|
||||
discount: {
|
||||
amount: 55,
|
||||
amount: null,
|
||||
scale: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
quantity: {
|
||||
amount: "3",
|
||||
article_id: 2000007412,
|
||||
description:
|
||||
"Nara H=3000 mm (118.11 in) fabric-covered glass panel up to 600 mm (23.62 in) wide",
|
||||
quantity: { amount: "4", scale: 0 },
|
||||
unit_price: {
|
||||
amount: "8450000",
|
||||
scale: 4,
|
||||
currency_code: "EUR",
|
||||
},
|
||||
description: "cccc",
|
||||
discount: {
|
||||
amount: 75,
|
||||
amount: 500,
|
||||
scale: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
quantity: {
|
||||
amount: "4",
|
||||
article_id: 2000002589,
|
||||
description:
|
||||
"Panoramic anodized sliding H=2600 mm (102.36 in) GR3 glass panel up to 1200 mm (47.24 in) wide",
|
||||
quantity: { amount: "25", scale: 0 },
|
||||
unit_price: {
|
||||
amount: "67481",
|
||||
scale: 4,
|
||||
currency_code: "EUR",
|
||||
},
|
||||
description: "dddd",
|
||||
discount: {
|
||||
amount: 10,
|
||||
amount: 100,
|
||||
scale: 2,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@ -5,14 +5,12 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle, Separator }
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
export const QuotePricesResume = () => {
|
||||
const { getValues } = useFormContext();
|
||||
const { watch } = useFormContext();
|
||||
const { formatNumber, formatCurrency, formatPercentage } = useLocalization();
|
||||
|
||||
console.log(getValues());
|
||||
|
||||
const subtotal_price = formatNumber(getValues("subtotal_price"));
|
||||
const discount = formatPercentage(getValues("discount"));
|
||||
const total_price = formatCurrency(getValues("total_price"));
|
||||
const subtotal_price = formatNumber(watch("subtotal_price"));
|
||||
const discount = formatPercentage(watch("discount"));
|
||||
const total_price = formatNumber(watch("total_price"));
|
||||
|
||||
return (
|
||||
<Card className='w-full'>
|
||||
|
||||
@ -2,7 +2,7 @@ import {
|
||||
FormCurrencyField,
|
||||
FormPercentageField,
|
||||
FormQuantityField,
|
||||
FormTextField,
|
||||
FormTextAreaField,
|
||||
} from "@/components";
|
||||
import { DataTableProvider } from "@/lib/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -68,7 +68,11 @@ export const QuoteDetailsCardEditor = ({
|
||||
size: 24,
|
||||
cell: ({ row: { index } }) => {
|
||||
return (
|
||||
<FormTextField variant='outline' autoSize {...register(`items.${index}.description`)} />
|
||||
<FormTextAreaField
|
||||
variant='outline'
|
||||
autoSize
|
||||
{...register(`items.${index}.description`)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -207,12 +211,22 @@ export const QuoteDetailsCardEditor = ({
|
||||
const navCollapsedSize = 4;
|
||||
|
||||
return (
|
||||
<QuoteItemsSortableDataTable
|
||||
actions={fieldActions}
|
||||
columns={columns}
|
||||
data={fields}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
<>
|
||||
<QuoteItemsSortableDataTable
|
||||
actions={fieldActions}
|
||||
columns={columns}
|
||||
data={fields}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
<FormCurrencyField
|
||||
variant='outline'
|
||||
currency={currency}
|
||||
language={language}
|
||||
scale={4}
|
||||
className='text-right'
|
||||
{...register("subtotal_price")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -5,10 +5,10 @@ import {
|
||||
LoadingOverlay,
|
||||
SubmitButton,
|
||||
} from "@/components";
|
||||
import { calculateItemTotals } from "@/lib/calc";
|
||||
import { calculateQuoteItemTotals, calculateQuoteTotals } from "@/lib/calc";
|
||||
import { useUrlId } from "@/lib/hooks/useUrlId";
|
||||
import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
|
||||
import { CurrencyData, IGetQuote_Response_DTO, Language, MoneyValue } from "@shared/contexts";
|
||||
import { CurrencyData, IGetQuote_Response_DTO, Language } from "@shared/contexts";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
@ -24,34 +24,6 @@ import { useQuotes } from "./hooks";
|
||||
|
||||
type QuoteDataForm = IGetQuote_Response_DTO;
|
||||
|
||||
const recalculateItemTotals = (items, setValue) => {
|
||||
let quoteSubtotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
scale: 4,
|
||||
}).object;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const itemTotals = calculateItemTotals(item);
|
||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
});
|
||||
|
||||
setValue("subtotal_price", quoteSubtotal.convertScale(2).toObject());
|
||||
};
|
||||
|
||||
const updateCurrency = (value, setQuoteCurrency) => {
|
||||
setQuoteCurrency(
|
||||
CurrencyData.createFromCode(value.currency_code ?? CurrencyData.DEFAULT_CURRENCY_CODE).object
|
||||
);
|
||||
};
|
||||
|
||||
const updateLanguage = (value, setQuoteLanguage) => {
|
||||
setQuoteLanguage(
|
||||
Language.createFromCode(value.lang_code ?? Language.DEFAULT_LANGUAGE_CODE).object
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const QuoteEdit = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -175,11 +147,13 @@ export const QuoteEdit = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/*useEffect(() => {
|
||||
/* useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { unsubscribe } = watch((_, { name, type }) => {
|
||||
const value = getValues();
|
||||
|
||||
console.log(name);
|
||||
|
||||
if (name) {
|
||||
if (name === "currency_code") {
|
||||
setQuoteCurrency(
|
||||
@ -195,6 +169,7 @@ export const QuoteEdit = () => {
|
||||
}
|
||||
|
||||
if (name === "items") {
|
||||
console.log(">> Recalculo todas las líneas");
|
||||
const { items } = value;
|
||||
|
||||
let quoteSubtotal = MoneyValue.create({
|
||||
@ -205,7 +180,7 @@ export const QuoteEdit = () => {
|
||||
// Recálculo líneas
|
||||
items &&
|
||||
items.map((item, index) => {
|
||||
const itemTotals = calculateItemTotals(item);
|
||||
const itemTotals = calculateQuoteItemTotals(item);
|
||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
@ -216,14 +191,22 @@ export const QuoteEdit = () => {
|
||||
}
|
||||
|
||||
if (name.endsWith("quantity") || name.endsWith("unit_price") || name.endsWith("discount")) {
|
||||
console.group(">>>>>>>>>>>>>");
|
||||
|
||||
const { items } = value;
|
||||
|
||||
const [, indexString, fieldName] = String(name).split(".");
|
||||
const index = parseInt(indexString);
|
||||
|
||||
const itemTotals = calculateItemTotals(items[index]);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
console.group(">> Voy a recalcular la línea ", index, items[index]);
|
||||
|
||||
const itemTotals_1 = calculateQuoteItemTotals(items[index]);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals_1.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals_1.totalPrice.toObject());
|
||||
|
||||
console.log(">> Total de la línea -> ", index, itemTotals_1.subtotalPrice.toObject());
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
// Recálculo completo
|
||||
let quoteSubtotal = MoneyValue.create({
|
||||
@ -231,16 +214,33 @@ export const QuoteEdit = () => {
|
||||
scale: 4,
|
||||
}).object;
|
||||
|
||||
console.group(">> Recalculo todas las líneas");
|
||||
|
||||
items &&
|
||||
items.map((item) => {
|
||||
const itemTotals = calculateItemTotals(item);
|
||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
items.map((item, itemIndex) => {
|
||||
const itemTotals_2 = calculateQuoteItemTotals(item);
|
||||
|
||||
console.log(
|
||||
">> Recalculo la linea ",
|
||||
itemIndex,
|
||||
itemTotals_2.subtotalPrice.toObject()
|
||||
);
|
||||
|
||||
quoteSubtotal = quoteSubtotal.add(itemTotals_2.totalPrice);
|
||||
setValue(`items.${itemIndex}.subtotal_price`, itemTotals_2.subtotalPrice.toObject());
|
||||
setValue(`items.${itemIndex}.total_price`, itemTotals_2.totalPrice.toObject());
|
||||
});
|
||||
|
||||
console.log(
|
||||
">> Gruardo el total en la cabecera ",
|
||||
quoteSubtotal.convertScale(2).toObject()
|
||||
);
|
||||
|
||||
// Recálculo completo
|
||||
setValue("subtotal_price", quoteSubtotal.convertScale(2).toObject());
|
||||
console.groupEnd();
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -248,22 +248,40 @@ export const QuoteEdit = () => {
|
||||
}, [watch, getValues, setValue]);*/
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = watch((_, { name }) => {
|
||||
const value = getValues();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { unsubscribe } = watch((_, { name, type }) => {
|
||||
const quote = getValues();
|
||||
|
||||
if (name) {
|
||||
switch (true) {
|
||||
case name === "currency_code":
|
||||
updateCurrency(value, setQuoteCurrency);
|
||||
setQuoteCurrency(
|
||||
CurrencyData.createFromCode(quote.currency_code ?? CurrencyData.DEFAULT_CURRENCY_CODE)
|
||||
.object
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case name === "lang_code":
|
||||
updateLanguage(value, setQuoteLanguage);
|
||||
setQuoteLanguage(
|
||||
Language.createFromCode(quote.lang_code ?? Language.DEFAULT_LANGUAGE_CODE).object
|
||||
);
|
||||
break;
|
||||
|
||||
case name === "items":
|
||||
recalculateItemTotals(value.items, setValue);
|
||||
case name === "items": {
|
||||
quote.items &&
|
||||
quote.items.map((item, index) => {
|
||||
const quoteItemTotals = calculateQuoteItemTotals(item);
|
||||
setValue(`items.${index}.subtotal_price`, quoteItemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, quoteItemTotals.totalPrice.toObject());
|
||||
});
|
||||
|
||||
const quoteTotals = calculateQuoteTotals(quote);
|
||||
setValue("subtotal_price", quoteTotals.subtotalPrice.toObject());
|
||||
setValue("total_price", quoteTotals.totalPrice.toObject());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case name.endsWith("quantity") ||
|
||||
name.endsWith("unit_price") ||
|
||||
@ -271,11 +289,15 @@ export const QuoteEdit = () => {
|
||||
const [, indexString] = String(name).split(".");
|
||||
const index = parseInt(indexString);
|
||||
|
||||
const itemTotals = calculateItemTotals(value.items[index]);
|
||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||
const quoteItemTotals = calculateQuoteItemTotals(quote.items[index]);
|
||||
setValue(`items.${index}.subtotal_price`, quoteItemTotals.subtotalPrice.toObject());
|
||||
setValue(`items.${index}.total_price`, quoteItemTotals.totalPrice.toObject());
|
||||
|
||||
// Cabecera
|
||||
const quoteTotals = calculateQuoteTotals(quote);
|
||||
setValue("subtotal_price", quoteTotals.subtotalPrice.toObject());
|
||||
setValue("total_price", quoteTotals.totalPrice.toObject());
|
||||
|
||||
recalculateItemTotals(value.items, setValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -285,7 +307,7 @@ export const QuoteEdit = () => {
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, [watch, getValues, setValue, setQuoteCurrency, setQuoteLanguage]);
|
||||
}, [watch, getValues, setValue]);
|
||||
|
||||
if (isSubmitting) {
|
||||
return <LoadingOverlay title='Guardando cotización' />;
|
||||
|
||||
@ -1,6 +1,61 @@
|
||||
import { MoneyValue, Percentage, Quantity } from "@shared/contexts";
|
||||
|
||||
export const calculateItemTotals = (item: any) => {
|
||||
export const calculateQuoteTotals = (quote: any) => {
|
||||
const { discount: discount_dto /* tax: tax_dto */ } = quote;
|
||||
|
||||
const discountOrError = Percentage.create(discount_dto);
|
||||
if (discountOrError.isFailure) {
|
||||
throw discountOrError.error;
|
||||
}
|
||||
const discount = discountOrError.object;
|
||||
|
||||
/*const taxOrError = Percentage.create(tax_dto);
|
||||
if (taxOrError.isFailure) {
|
||||
throw taxOrError.error;
|
||||
}
|
||||
const tax = taxOrError.object;*/
|
||||
const tax = Percentage.create({
|
||||
amount: 2100,
|
||||
scale: 2,
|
||||
}).object;
|
||||
|
||||
const subtotalPrice = calculateQuoteItemsTotals(quote.items).convertScale(2);
|
||||
|
||||
const discountPrice = subtotalPrice.percentage(discount.toNumber()).convertScale(2);
|
||||
|
||||
const priceBeforeTaxes = subtotalPrice.subtract(discountPrice).convertScale(2);
|
||||
|
||||
const taxesPrice = priceBeforeTaxes.percentage(tax.toNumber()).convertScale(2);
|
||||
|
||||
const totalPrice = priceBeforeTaxes.add(taxesPrice).convertScale(2);
|
||||
|
||||
return {
|
||||
subtotalPrice,
|
||||
discount: quote.discount,
|
||||
discountPrice,
|
||||
priceBeforeTaxes,
|
||||
tax,
|
||||
taxesPrice,
|
||||
totalPrice,
|
||||
};
|
||||
};
|
||||
|
||||
export const calculateQuoteItemsTotals = (items: any[]) => {
|
||||
let totalPrice = MoneyValue.create({
|
||||
amount: 0,
|
||||
scale: 4,
|
||||
}).object;
|
||||
|
||||
items &&
|
||||
items.map((item: any) => {
|
||||
const quoteItemTotals = calculateQuoteItemTotals(item);
|
||||
totalPrice = totalPrice.add(quoteItemTotals.totalPrice);
|
||||
});
|
||||
|
||||
return totalPrice;
|
||||
};
|
||||
|
||||
export const calculateQuoteItemTotals = (item: any) => {
|
||||
const { quantity: quantity_dto, unit_price: unit_price_dto, discount: discount_dto } = item;
|
||||
|
||||
if (quantity_dto.amount === null || unit_price_dto.amount === null) {
|
||||
|
||||
@ -50,11 +50,11 @@ export const useCustomLocalization = (props: UseLocalizationProps) => {
|
||||
|
||||
const { amount, scale } = value;
|
||||
|
||||
const result = new Intl.NumberFormat(locale, {
|
||||
minimumSignificantDigits: scale,
|
||||
const result = new Intl.NumberFormat("es", {
|
||||
/*minimumSignificantDigits: scale,
|
||||
maximumSignificantDigits: scale,
|
||||
minimumFractionDigits: scale,
|
||||
useGrouping: true,
|
||||
useGrouping: true,*/
|
||||
}).format(amount === null ? 0 : adjustPrecision({ amount, scale }));
|
||||
|
||||
console.log(value, result);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
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";
|
||||
@ -184,28 +185,69 @@ export class CreateQuoteUseCase
|
||||
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.amount,
|
||||
scale: item.quantity.scale,
|
||||
}).object,
|
||||
unitPrice: UnitPrice.create({
|
||||
amount: item.unit_price?.amount,
|
||||
currencyCode: item.unit_price?.currency_code,
|
||||
scale: item.unit_price?.scale,
|
||||
}).object,
|
||||
discount: Percentage.create({
|
||||
amount: item.discount?.amount,
|
||||
scale: item.discount?.scale,
|
||||
}).object,
|
||||
}).object
|
||||
)
|
||||
);
|
||||
const taxOrError = Percentage.create(quoteDTO.tax);
|
||||
if (taxOrError.isFailure) {
|
||||
return Result.fail(taxOrError.error);
|
||||
}
|
||||
|
||||
let items: Collection<QuoteItem>;
|
||||
|
||||
try {
|
||||
items = new Collection<QuoteItem>(
|
||||
quoteDTO.items?.map((item) => {
|
||||
const articleIdOrError = ArticleIdentifier.create(item.article_id);
|
||||
if (articleIdOrError.isFailure) {
|
||||
throw articleIdOrError.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({
|
||||
articleId: articleIdOrError.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(
|
||||
{
|
||||
@ -220,6 +262,7 @@ export class CreateQuoteUseCase
|
||||
validity: validityOrError.object,
|
||||
|
||||
discount: discountOrError.object,
|
||||
tax: taxOrError.object,
|
||||
|
||||
items,
|
||||
|
||||
|
||||
@ -183,6 +183,11 @@ export class UpdateQuoteUseCase
|
||||
return Result.fail(discountOrError.error);
|
||||
}
|
||||
|
||||
const taxOrError = Percentage.create(quoteDTO.tax);
|
||||
if (taxOrError.isFailure) {
|
||||
return Result.fail(taxOrError.error);
|
||||
}
|
||||
|
||||
let items: Collection<QuoteItem>;
|
||||
|
||||
try {
|
||||
@ -239,15 +244,6 @@ export class UpdateQuoteUseCase
|
||||
})
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
//let error = e as Error;
|
||||
/*if (error.name === "ValidationError") {
|
||||
error = e as ValidationError;
|
||||
}
|
||||
|
||||
if (error.name === "DomainError") {
|
||||
error = e as DomainError;
|
||||
}*/
|
||||
|
||||
return Result.fail(e as IDomainError);
|
||||
}
|
||||
|
||||
@ -264,6 +260,7 @@ export class UpdateQuoteUseCase
|
||||
validity: validityOrError.object,
|
||||
|
||||
discount: discountOrError.object,
|
||||
tax: taxOrError.object,
|
||||
|
||||
items,
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ export interface IQuoteProps {
|
||||
|
||||
//subtotalPrice: MoneyValue;
|
||||
discount: Percentage;
|
||||
tax: Percentage;
|
||||
//totalPrice: MoneyValue;
|
||||
|
||||
dealerId: UniqueID;
|
||||
@ -50,7 +51,15 @@ export interface IQuote {
|
||||
validity: Note;
|
||||
|
||||
subtotalPrice: MoneyValue;
|
||||
|
||||
discount: Percentage;
|
||||
discountPrice: MoneyValue;
|
||||
|
||||
beforeTaxPrice: MoneyValue;
|
||||
|
||||
tax: Percentage;
|
||||
taxPrice: MoneyValue;
|
||||
|
||||
totalPrice: MoneyValue;
|
||||
|
||||
items: ICollection<QuoteItem>;
|
||||
@ -70,6 +79,7 @@ export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
||||
}
|
||||
|
||||
protected _items: ICollection<QuoteItem>;
|
||||
protected _subtotal: MoneyValue;
|
||||
|
||||
protected _calculateTotalPriceItems = (): MoneyValue => {
|
||||
const result = this.props.items
|
||||
@ -86,7 +96,7 @@ export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
||||
protected constructor(props: IQuoteProps, id?: UniqueID) {
|
||||
super(props, id);
|
||||
|
||||
this._items = props.items;
|
||||
this._items = Object.freeze(props.items);
|
||||
}
|
||||
|
||||
get id(): UniqueID {
|
||||
@ -138,14 +148,33 @@ export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
||||
}
|
||||
|
||||
get subtotalPrice(): MoneyValue {
|
||||
return this._calculateTotalPriceItems();
|
||||
if (!this._subtotal) {
|
||||
this._subtotal = this._calculateTotalPriceItems();
|
||||
}
|
||||
return this._subtotal;
|
||||
}
|
||||
|
||||
get discount(): Percentage {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get discountPrice(): MoneyValue {
|
||||
return this.subtotalPrice.percentage(this.discount.toNumber());
|
||||
}
|
||||
|
||||
get beforeTaxPrice(): MoneyValue {
|
||||
return this.subtotalPrice.subtract(this.discountPrice);
|
||||
}
|
||||
|
||||
get tax(): Percentage {
|
||||
return this.props.tax;
|
||||
}
|
||||
|
||||
get taxPrice(): MoneyValue {
|
||||
return this.beforeTaxPrice.percentage(this.tax.toNumber());
|
||||
}
|
||||
|
||||
get totalPrice(): MoneyValue {
|
||||
return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||
return this.beforeTaxPrice.add(this.taxPrice);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { ICollection, ICreateQuote_Response_DTO } from "@shared/contexts";
|
||||
import {
|
||||
ICollection,
|
||||
ICreateQuote_QuoteItem_Response_DTO,
|
||||
ICreateQuote_Response_DTO,
|
||||
} from "@shared/contexts";
|
||||
import { Quote, QuoteItem } from "../../../../../../domain";
|
||||
import { ISalesContext } from "../../../../../Sales.context";
|
||||
|
||||
@ -11,48 +15,48 @@ export const CreateQuotePresenter: ICreateQuotePresenter = {
|
||||
map: (quote: Quote, context: ISalesContext): ICreateQuote_Response_DTO => {
|
||||
return {
|
||||
id: quote.id.toString(),
|
||||
reference: quote.reference.toString(),
|
||||
status: quote.status.toString(),
|
||||
date: quote.date.toISO8601(),
|
||||
reference: quote.reference.toString(),
|
||||
customer_information: quote.customer.toString(),
|
||||
lang_code: quote.language.toString(),
|
||||
currency_code: quote.currency.toString(),
|
||||
customer_information: quote.customer.toString(),
|
||||
subtotal: {
|
||||
amount: 0,
|
||||
precision: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
total: {
|
||||
amount: 0,
|
||||
precision: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
|
||||
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) =>
|
||||
const quoteItemPresenter = (
|
||||
items: ICollection<QuoteItem>,
|
||||
context: ISalesContext
|
||||
): ICreateQuote_QuoteItem_Response_DTO[] =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: QuoteItem) => ({
|
||||
article_id: item.articleId.toString(),
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toObject(),
|
||||
unit_measure: "",
|
||||
unit_price: {
|
||||
amount: 0,
|
||||
scale: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
subtotal: {
|
||||
amount: 0,
|
||||
scale: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
total: {
|
||||
amount: 0,
|
||||
scale: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
quantity: item.quantity.convertScale(2).toObject(),
|
||||
unit_price: item.unitPrice.convertScale(4).toObject(),
|
||||
subtotal_price: item.subtotalPrice.convertScale(4).toObject(),
|
||||
discount: item.discount.convertScale(2).toObject(),
|
||||
total_price: item.totalPrice.convertScale(4).toObject(),
|
||||
}))
|
||||
: [];
|
||||
|
||||
@ -27,7 +27,15 @@ export const GetQuotePresenter: IGetQuotePresenter = {
|
||||
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),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ICollection, IListQuotes_Response_DTO, IListResponse_DTO } from "@shared/contexts";
|
||||
import { Quote, QuoteItem } from "../../../../../../domain";
|
||||
import { Quote } from "../../../../../../domain";
|
||||
import { ISalesContext } from "../../../../../Sales.context";
|
||||
|
||||
export interface IListQuotesPresenter {
|
||||
@ -26,10 +26,18 @@ export const ListQuotesPresenter: IListQuotesPresenter = {
|
||||
lang_code: quote.language.toString(),
|
||||
currency_code: quote.currency.toString(),
|
||||
|
||||
subtotal_price: quote.subtotalPrice.toObject(),
|
||||
discount: quote.discount.toObject(),
|
||||
total_price: quote.totalPrice.toObject(),
|
||||
//items: quoteItemPresenter(quote.items, context),
|
||||
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(),
|
||||
dealer_id: quote.dealerId.toString(),
|
||||
};
|
||||
},
|
||||
|
||||
@ -57,28 +65,3 @@ export const ListQuotesPresenter: IListQuotesPresenter = {
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const quoteItemPresenter = (items: ICollection<QuoteItem>, context: ISalesContext) =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: QuoteItem) => ({
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toString(),
|
||||
unit_measure: "",
|
||||
unit_price: {
|
||||
amount: 0,
|
||||
precision: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
subtotal: {
|
||||
amount: 0,
|
||||
precision: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
total: {
|
||||
amount: 0,
|
||||
precision: 2,
|
||||
currency: "EUR",
|
||||
},
|
||||
}))
|
||||
: [];
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { Quote, QuoteItem } from "@/contexts/sales/domain";
|
||||
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||
import { ICollection, IUpdateQuote_Response_DTO } from "@shared/contexts";
|
||||
import {
|
||||
ICollection,
|
||||
IUpdateQuote_QuoteItem_Response_DTO,
|
||||
IUpdateQuote_Response_DTO,
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IUpdateQuotePresenter {
|
||||
map: (quote: Quote, context: ISalesContext) => IUpdateQuote_Response_DTO;
|
||||
@ -22,9 +26,17 @@ export const UpdateQuotePresenter: IUpdateQuotePresenter = {
|
||||
validity: quote.validity.toString(),
|
||||
notes: quote.notes.toString(),
|
||||
|
||||
subtotal_price: quote.subtotalPrice.toObject(),
|
||||
discount: quote.discount.toObject(),
|
||||
total_price: quote.totalPrice.toObject(),
|
||||
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(),
|
||||
@ -33,15 +45,18 @@ export const UpdateQuotePresenter: IUpdateQuotePresenter = {
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const quoteItemPresenter = (items: ICollection<QuoteItem>, context: ISalesContext) =>
|
||||
const quoteItemPresenter = (
|
||||
items: ICollection<QuoteItem>,
|
||||
context: ISalesContext
|
||||
): IUpdateQuote_QuoteItem_Response_DTO[] =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: QuoteItem) => ({
|
||||
article_id: item.articleId.toString(),
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toObject(),
|
||||
unit_price: item.unitPrice.toObject(),
|
||||
subtotal_price: item.subtotalPrice.toObject(),
|
||||
discount: item.discount.toObject(),
|
||||
total_price: item.totalPrice.toObject(),
|
||||
quantity: item.quantity.convertScale(2).toObject(),
|
||||
unit_price: item.unitPrice.convertScale(4).toObject(),
|
||||
subtotal_price: item.subtotalPrice.convertScale(4).toObject(),
|
||||
discount: item.discount.convertScale(2).toObject(),
|
||||
total_price: item.totalPrice.convertScale(4).toObject(),
|
||||
}))
|
||||
: [];
|
||||
|
||||
@ -68,6 +68,13 @@ class QuoteMapper
|
||||
})
|
||||
),
|
||||
|
||||
tax: this.mapsValue(source, "tax", (tax) =>
|
||||
Percentage.create({
|
||||
amount: tax,
|
||||
scale: 2,
|
||||
})
|
||||
),
|
||||
|
||||
/*totalPrice: this.mapsValue(source, "total_price", (total_price) =>
|
||||
MoneyValue.create({
|
||||
amount: total_price,
|
||||
@ -107,7 +114,15 @@ class QuoteMapper
|
||||
notes: source.notes.toPrimitive(),
|
||||
|
||||
subtotal_price: source.subtotalPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
discount: source.discount.convertScale(2).toPrimitive(),
|
||||
discount_price: source.discountPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
before_tax_price: source.beforeTaxPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
tax: source.tax.convertScale(2).toPrimitive(),
|
||||
tax_price: source.taxPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
total_price: source.totalPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
items,
|
||||
|
||||
@ -51,7 +51,15 @@ export class Quote_Model extends Model<
|
||||
declare validity: CreationOptional<string>;
|
||||
|
||||
declare subtotal_price: CreationOptional<number | null>;
|
||||
|
||||
declare discount: CreationOptional<number | null>;
|
||||
declare discount_price: CreationOptional<number | null>;
|
||||
|
||||
declare before_tax_price: CreationOptional<number | null>;
|
||||
|
||||
declare tax: CreationOptional<number | null>;
|
||||
declare tax_price: CreationOptional<number | null>;
|
||||
|
||||
declare total_price: CreationOptional<number | null>;
|
||||
|
||||
declare items: NonAttribute<QuoteItem_Model[]>;
|
||||
@ -118,6 +126,26 @@ export default (sequelize: Sequelize) => {
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
discount_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
before_tax_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
tax: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
tax_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
total_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
|
||||
@ -21,6 +21,7 @@ export interface ICreateQuote_Request_DTO {
|
||||
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
tax: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
|
||||
items: ICreateQuoteItem_Request_DTO[];
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
IMoney_Response_DTO,
|
||||
IPercentage_Response_DTO,
|
||||
IQuantity_Response_DTO,
|
||||
} from "../../../../../common";
|
||||
import { IMoney_DTO, IPercentage_DTO, IQuantity_DTO } from "../../../../../common";
|
||||
|
||||
export interface ICreateQuote_Response_DTO {
|
||||
id: string;
|
||||
@ -12,20 +8,30 @@ export interface ICreateQuote_Response_DTO {
|
||||
customer_information: string;
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
|
||||
payment_method: string;
|
||||
notes: string;
|
||||
validity: string;
|
||||
|
||||
discount: IPercentage_Response_DTO;
|
||||
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: ICreateQuote_QuoteItem_Response_DTO[];
|
||||
|
||||
dealer_id: string;
|
||||
}
|
||||
|
||||
export interface ICreateQuote_QuoteItem_Response_DTO {
|
||||
article_id: string;
|
||||
quantity: IQuantity_Response_DTO;
|
||||
quantity: IQuantity_DTO;
|
||||
description: string;
|
||||
unit_price: IMoney_Response_DTO;
|
||||
price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
unit_price: IMoney_DTO;
|
||||
subtotal_price: IMoney_DTO;
|
||||
discount: IPercentage_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
}
|
||||
|
||||
@ -15,6 +15,10 @@ export interface IGetQuote_Response_DTO {
|
||||
|
||||
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: IGetQuote_QuoteItem_Response_DTO[];
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IMoney_Response_DTO, IPercentage_Response_DTO } from "../../../../../common";
|
||||
import { IMoney_DTO, IPercentage_DTO } from "../../../../../common";
|
||||
|
||||
export interface IListQuotes_Response_DTO {
|
||||
id: string;
|
||||
@ -9,7 +9,13 @@ export interface IListQuotes_Response_DTO {
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
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;
|
||||
|
||||
dealer_id: string;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ export interface IUpdateQuote_Request_DTO {
|
||||
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
tax: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
|
||||
items: IUpdateQuoteItem_Request_DTO[];
|
||||
@ -60,6 +61,11 @@ export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Requ
|
||||
scale: Joi.number(),
|
||||
}).optional(),
|
||||
|
||||
tax: Joi.object({
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
}).optional(),
|
||||
|
||||
total_price: Joi.object({
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
IMoney_Response_DTO,
|
||||
IPercentage_Response_DTO,
|
||||
IQuantity_Response_DTO,
|
||||
} from "../../../../../common";
|
||||
import { IMoney_DTO, IPercentage_DTO, IQuantity_DTO } from "../../../../../common";
|
||||
|
||||
export interface IUpdateQuote_Response_DTO {
|
||||
id: string;
|
||||
@ -12,13 +8,18 @@ export interface IUpdateQuote_Response_DTO {
|
||||
customer_information: string;
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
|
||||
payment_method: string;
|
||||
notes: string;
|
||||
validity: string;
|
||||
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
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: IUpdateQuote_QuoteItem_Response_DTO[];
|
||||
|
||||
@ -27,10 +28,10 @@ export interface IUpdateQuote_Response_DTO {
|
||||
|
||||
export interface IUpdateQuote_QuoteItem_Response_DTO {
|
||||
article_id: string;
|
||||
quantity: IQuantity_Response_DTO;
|
||||
quantity: IQuantity_DTO;
|
||||
description: string;
|
||||
unit_price: IMoney_Response_DTO;
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
unit_price: IMoney_DTO;
|
||||
subtotal_price: IMoney_DTO;
|
||||
discount: IPercentage_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user