This commit is contained in:
David Arranz 2024-07-19 17:02:19 +02:00
parent 5e4d58c112
commit c1b61a0b60
21 changed files with 474 additions and 219 deletions

View File

@ -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,
},
},
]);

View File

@ -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'>

View File

@ -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 (

View File

@ -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' />;

View File

@ -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) {

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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(),
}))
: [];

View File

@ -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),

View File

@ -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",
},
}))
: [];

View File

@ -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(),
}))
: [];

View File

@ -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,

View File

@ -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,

View File

@ -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[];

View File

@ -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;
}

View File

@ -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[];

View File

@ -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;
}

View File

@ -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(),

View File

@ -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;
}