diff --git a/client/src/app/quotes/components/QuoteItemsSortableDataTable.tsx b/client/src/app/quotes/components/QuoteItemsSortableDataTable.tsx index 25bead1..a25aa9c 100644 --- a/client/src/app/quotes/components/QuoteItemsSortableDataTable.tsx +++ b/client/src/app/quotes/components/QuoteItemsSortableDataTable.tsx @@ -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, }, }, ]); diff --git a/client/src/app/quotes/components/QuotePricesResume.tsx b/client/src/app/quotes/components/QuotePricesResume.tsx index b8d00cc..3013e62 100644 --- a/client/src/app/quotes/components/QuotePricesResume.tsx +++ b/client/src/app/quotes/components/QuotePricesResume.tsx @@ -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 ( diff --git a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx index ee74994..1620458 100644 --- a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx +++ b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx @@ -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 ( - + ); }, }, @@ -207,12 +211,22 @@ export const QuoteDetailsCardEditor = ({ const navCollapsedSize = 4; return ( - + <> + + + ); return ( diff --git a/client/src/app/quotes/edit.tsx b/client/src/app/quotes/edit.tsx index cdaf214..55b3d11 100644 --- a/client/src/app/quotes/edit.tsx +++ b/client/src/app/quotes/edit.tsx @@ -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 ; diff --git a/client/src/lib/calc.ts b/client/src/lib/calc.ts index f70f844..a6ab57d 100644 --- a/client/src/lib/calc.ts +++ b/client/src/lib/calc.ts @@ -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) { diff --git a/client/src/lib/hooks/useLocalization/useLocatlization.tsx b/client/src/lib/hooks/useLocalization/useLocatlization.tsx index 0f135b2..c815c0e 100644 --- a/client/src/lib/hooks/useLocalization/useLocatlization.tsx +++ b/client/src/lib/hooks/useLocalization/useLocatlization.tsx @@ -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); diff --git a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts index a070749..8480962 100644 --- a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts +++ b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts @@ -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( - 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; + + try { + items = new Collection( + 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, diff --git a/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts b/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts index f66daa4..5775a66 100644 --- a/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts +++ b/server/src/contexts/sales/application/Quote/UpdateQuote.useCase.ts @@ -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; 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, diff --git a/server/src/contexts/sales/domain/entities/Quotes/Quote.ts b/server/src/contexts/sales/domain/entities/Quotes/Quote.ts index bad023b..3114a1b 100644 --- a/server/src/contexts/sales/domain/entities/Quotes/Quote.ts +++ b/server/src/contexts/sales/domain/entities/Quotes/Quote.ts @@ -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; @@ -70,6 +79,7 @@ export class Quote extends AggregateRoot implements IQuote { } protected _items: ICollection; + protected _subtotal: MoneyValue; protected _calculateTotalPriceItems = (): MoneyValue => { const result = this.props.items @@ -86,7 +96,7 @@ export class Quote extends AggregateRoot 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 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); } } diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts index 5c8684b..843e7da 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts @@ -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, context: ISalesContext) => +const quoteItemPresenter = ( + items: ICollection, + 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(), })) : []; diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts index ed2a944..d61956e 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts @@ -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), diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts index 605c28e..a0c58ae 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/presenter/ListQuotes.presenter.ts @@ -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, 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", - }, - })) - : []; diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/updateQuote/presenter/UpdateQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/updateQuote/presenter/UpdateQuote.presenter.ts index 5c97ff5..8bbcbd3 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/updateQuote/presenter/UpdateQuote.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/updateQuote/presenter/UpdateQuote.presenter.ts @@ -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, context: ISalesContext) => +const quoteItemPresenter = ( + items: ICollection, + 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(), })) : []; diff --git a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts index 67910d0..4cabc08 100644 --- a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts +++ b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts @@ -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, diff --git a/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts b/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts index 7c1a16e..82af486 100644 --- a/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts +++ b/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts @@ -51,7 +51,15 @@ export class Quote_Model extends Model< declare validity: CreationOptional; declare subtotal_price: CreationOptional; + declare discount: CreationOptional; + declare discount_price: CreationOptional; + + declare before_tax_price: CreationOptional; + + declare tax: CreationOptional; + declare tax_price: CreationOptional; + declare total_price: CreationOptional; declare items: NonAttribute; @@ -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, diff --git a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts index 58aa800..faf1280 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts @@ -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[]; diff --git a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts index 50c90a8..de9cadb 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts @@ -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; } diff --git a/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts index 963821e..d4cdd1a 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts @@ -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[]; diff --git a/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts index 718a80b..e9a8e17 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts @@ -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; } diff --git a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts index c8928ac..fa04197 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts @@ -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(), diff --git a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts index c12e0e7..c867baa 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts @@ -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; }