v1.0.3 - 2 decimales en importes

This commit is contained in:
David Arranz 2024-10-25 11:49:10 +02:00
parent 6e236bba91
commit ee26a8bfa4
21 changed files with 165 additions and 139 deletions

View File

@ -1,7 +1,7 @@
{
"name": "@uecko-presupuestador/client",
"private": true,
"version": "1.0.2",
"version": "1.0.3",
"author": "Rodax Software <dev@rodax-software.com>",
"type": "module",
"scripts": {

View File

@ -10,7 +10,6 @@ export const CatalogList = () => {
<h2 className='text-2xl font-bold tracking-tight'>
<Trans i18nKey='catalog.list.title' />
</h2>
<p className='text-muted-foreground'>descripción</p>
</div>
</div>

View File

@ -25,7 +25,7 @@ import { useToast } from "@/ui/use-toast";
import { IListQuotes_Response_DTO, UTCDateValue } from "@shared/contexts";
import { ColumnDef, Row } from "@tanstack/react-table";
import { t } from "i18next";
import { FilePenLineIcon, MoreVerticalIcon } from "lucide-react";
import { FilePenLineIcon, MoreVerticalIcon, SendIcon } from "lucide-react";
import { useCallback, useEffect, useId, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuotes } from "../hooks";
@ -74,7 +74,6 @@ export const QuotesDataTable = ({
const handleEditQuote = useCallback(
(quote: IListQuotes_Response_DTO) => {
toast({ title: "Guardo => " + quote.id });
navigate(`/quotes/edit/${quote.id}`, { relative: "path" });
},
[navigate, toast]
@ -92,19 +91,25 @@ export const QuotesDataTable = ({
}: {
row: { original: IListQuotes_Response_DTO };
renderValue: () => any;
}) => (
<Button
size='sm'
variant='link'
className='h-8 gap-1 px-0 text-left text-ellipsis'
onClick={(e) => {
e.preventDefault();
handleEditQuote(original);
}}
>
<div className=''>{renderValue()}</div>
</Button>
),
}) => {
const allowToSent = original?.status === "accepted" && !original?.date_sent;
const isSent = Boolean(original?.status === "accepted" && original?.date_sent);
return (
<Button
size='sm'
variant='link'
disabled={allowToSent || isSent}
className='h-8 gap-1 px-0 text-left text-ellipsis'
onClick={(e) => {
e.preventDefault();
handleEditQuote(original);
}}
>
<div className=''>{renderValue()}</div>
</Button>
);
},
},
{
@ -116,16 +121,35 @@ export const QuotesDataTable = ({
<ColorBadge label={t(`quotes.status.${original.status}`)} />
),
},
{
id: "date_sent" as const,
accessor: "date_sent",
header: () => (
<div className='text-left text-ellipsis'>{t("quotes.list.columns.date_sent")}</div>
),
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const quoteDate = UTCDateValue.create(original.date_sent);
return (
<div className='text-left text-ellipsis'>
{quoteDate.isSuccess ? (
<ColorBadge label={quoteDate.object.toLocaleDateString("es-ES")} />
) : (
<>-</>
)}
</div>
);
},
},
{
id: "date" as const,
accessor: "date",
header: () => (
<div className='text-right text-ellipsis'>{t("quotes.list.columns.date")}</div>
<div className='text-left text-ellipsis'>{t("quotes.list.columns.date")}</div>
),
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const quoteDate = UTCDateValue.create(original.date);
return (
<div className='text-right text-ellipsis'>
<div className='text-left text-ellipsis'>
{quoteDate.isSuccess ? quoteDate.object.toLocaleDateString("es-ES") : "-"}
</div>
);
@ -162,22 +186,7 @@ export const QuotesDataTable = ({
),
size: 600,
},
{
id: "date_sent" as const,
accessor: "date_sent",
header: () => (
<div className='text-right text-ellipsis'>{t("quotes.list.columns.date_sent")}</div>
),
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const quoteDate = UTCDateValue.create(original.date_sent);
return (
<div className='text-right text-ellipsis'>
{quoteDate.isSuccess}
{quoteDate.isSuccess ? quoteDate.object.toLocaleDateString("es-ES") : "-"}
</div>
);
},
},
/*{
id: "total_price" as const,
accessor: "total_price",
@ -195,68 +204,77 @@ export const QuotesDataTable = ({
{
id: "row-actions",
header: () => null,
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => (
<ButtonGroup>
<Tooltip>
<TooltipTrigger asChild>
{original.status === "accepted" && !original.date_sent ? (
<Button
size='sm'
variant='default'
className='h-8 gap-1'
onClick={(e) => {
e.preventDefault();
//handleSentToUecko(original);
}}
>
<FilePenLineIcon className='h-3.5 w-3.5' />
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
{t("quotes.list.columns.actions.sent_to")}
</span>
</Button>
) : (
<Button
size='sm'
variant='outline'
className='h-8 gap-1'
onClick={(e) => {
e.preventDefault();
handleEditQuote(original);
}}
>
<FilePenLineIcon className='h-3.5 w-3.5' />
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
{t("quotes.list.columns.actions.edit")}
</span>
</Button>
)}
</TooltipTrigger>
<TooltipContent>
<p>{t("quotes.list.columns.actions.sent_to_uecko")}</p>
</TooltipContent>
</Tooltip>
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
const allowToSent = original?.status === "accepted" && !original?.date_sent;
const isSent = original?.status === "accepted" && original?.date_sent;
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size='icon' variant='outline' className='w-8 h-8'>
<MoreVerticalIcon className='h-3.5 w-3.5' />
<span className='sr-only'>{t("common.more")}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem
onClick={() => {
download(original.id, getQuotePDFFilename(original));
}}
>
Download
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>{t("common.archive")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
),
return (
<ButtonGroup>
<Tooltip>
<TooltipTrigger asChild>
<>
{allowToSent && !isSent && (
<Button
size='sm'
variant='default'
className='h-8 gap-1'
onClick={(e) => {
e.preventDefault();
//handleSentToUecko(original);
}}
>
<SendIcon className='h-3.5 w-3.5' />
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
{t("quotes.list.columns.actions.sent_to")}
</span>
</Button>
)}
{!allowToSent && !isSent && (
<Button
size='sm'
variant='outline'
className='h-8 gap-1'
onClick={(e) => {
e.preventDefault();
handleEditQuote(original);
}}
>
<FilePenLineIcon className='h-3.5 w-3.5' />
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
{t("quotes.list.columns.actions.edit")}
</span>
</Button>
)}
</>
</TooltipTrigger>
<TooltipContent>
<p>{t("quotes.list.columns.actions.sent_to_uecko")}</p>
</TooltipContent>
</Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size='icon' variant='outline' className='w-8 h-8'>
<MoreVerticalIcon className='h-3.5 w-3.5' />
<span className='sr-only'>{t("common.more")}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem
onClick={() => {
download(original.id, getQuotePDFFilename(original));
}}
>
Download
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>{t("common.archive")}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
);
},
},
];

View File

@ -111,7 +111,7 @@ export const QuoteDetailsCardEditor = ({
<FormCurrencyField
currency={currency}
language={language}
scale={4}
scale={2}
className='text-right'
{...register(`items.${index}.unit_price`)}
/>
@ -129,7 +129,7 @@ export const QuoteDetailsCardEditor = ({
<FormCurrencyField
currency={currency}
language={language}
scale={4}
scale={2}
readOnly
className='text-right'
{...register(`items.${index}.subtotal_price`)}
@ -165,7 +165,7 @@ export const QuoteDetailsCardEditor = ({
variant='ghost'
currency={currency}
language={language}
scale={4}
scale={2}
readOnly
className='font-semibold text-right'
{...register(`items.${index}.total_price`)}

View File

@ -12,6 +12,7 @@ import {
} from "@/ui";
import { IGetQuote_Response_DTO } from "@shared/contexts";
import { t } from "i18next";
import { SendIcon } from "lucide-react";
export const QuoteSentToEditor = ({
quote,
@ -28,6 +29,7 @@ export const QuoteSentToEditor = ({
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size='sm' variant='default' className='h-8 gap-1'>
<SendIcon className='h-3.5 w-3.5' />
{t("quotes.quote_sent_to_editor.trigger_button")}
</Button>
</AlertDialogTrigger>

View File

@ -99,12 +99,12 @@ export const QuoteEdit = () => {
},
unit_price: {
amount: null,
scale: 4,
scale: 2,
currency_code: data?.currency_code ?? quoteCurrency.code,
},
subtotal_price: {
amount: null,
scale: 4,
scale: 2,
currency_code: data?.currency_code ?? quoteCurrency.code,
},
discount: {
@ -113,7 +113,7 @@ export const QuoteEdit = () => {
},
total_price: {
amount: null,
scale: 4,
scale: 2,
currency_code: data?.currency_code ?? quoteCurrency.code,
},
},
@ -266,7 +266,7 @@ export const QuoteEdit = () => {
<h1 className='flex-1 text-xl font-semibold tracking-tight shrink-0 whitespace-nowrap sm:grow-0'>
{t("quotes.edit.title")} {data.reference}
</h1>
<ColorBadge label={data.status} className='ml-auto sm:ml-0' />
<ColorBadge label={t(`quotes.status.${data.status}`)} className='ml-auto sm:ml-0' />
<div className='items-center hidden gap-2 md:ml-auto md:flex'>
<CancelButton

View File

@ -40,7 +40,13 @@ function stringToColorPair(str: string) {
return [textColor, bgColor];
}
export const ColorBadge = ({ label, className }: { label: string; className?: string }) => {
export type ColorBadgeProps = {
label: string;
className?: string;
bgcolor?: string;
};
export const ColorBadge = ({ label, className }: ColorBadgeProps) => {
const [color, backgroundColor] = stringToColorPair(label);
return (

View File

@ -14,7 +14,7 @@ export const LoadingOverlay = ({
return (
<div
className={
"fixed top-0 bottom-0 left-0 right-0 z-50 w-full h-screen overflow-hidden flex justify-center bg-secondary-foreground/85"
"fixed top-0 bottom-0 left-0 right-0 z-50 w-full h-screen overflow-hidden flex justify-center"
}
{...props}
>

View File

@ -8,9 +8,9 @@ type ProctectRouteProps = {
export const ProtectedRoute = ({ children }: ProctectRouteProps) => {
const { isSuccess, data: { authenticated, redirectTo } = {} } = useIsLoggedIn();
//const { data: profile, ...profileStatus } = useGetProfile();
/*const { data: profile, ...profileStatus } = useGetProfile();
/*const { i18n } = useTranslation();
const { i18n } = useTranslation();
const [langCode, setLangCode] = useState(i18n.language);
if (i18n.language !== langCode) {

View File

@ -63,7 +63,7 @@ export const calculateQuoteTotals = (quote: any, force: boolean = false) => {
export const calculateQuoteItemsTotals = (items: any[]) => {
let totalPrice = MoneyValue.create({
amount: 0,
scale: 4,
scale: 2,
}).object;
items &&
@ -89,11 +89,11 @@ export const calculateQuoteItemTotals = (item: any) => {
}).object,
unitPrice: MoneyValue.create({
amount: unit_price_dto.amount,
scale: 4,
scale: 2,
}).object,
subtotalPrice: MoneyValue.create({
amount: null,
scale: 4,
scale: 2,
}).object,
discount: Percentage.create({
amount: discount_dto.amount,
@ -101,7 +101,7 @@ export const calculateQuoteItemTotals = (item: any) => {
}).object,
totalPrice: MoneyValue.create({
amount: null,
scale: 4,
scale: 2,
}).object,
};
}

View File

@ -50,8 +50,8 @@ export const useCustomLocalization = (props: UseLocalizationProps) => {
const result = new Intl.NumberFormat("es", {
/*minimumSignificantDigits: scale,
maximumSignificantDigits: scale,
minimumFractionDigits: scale,*/
maximumSignificantDigits: scale,*/
minimumFractionDigits: scale,
useGrouping: true,
}).format(amount === null ? 0 : adjustPrecision({ amount, scale }));

View File

@ -1,6 +1,6 @@
{
"name": "uecko-presupuestador",
"version": "1.0.2",
"version": "1.0.3",
"author": "Rodax Software <dev@rodax-software.com>",
"license": "ISC",
"private": true,

View File

@ -1,7 +1,7 @@
{
"name": "@uecko-presupuestador/server",
"private": true,
"version": "1.0.0",
"version": "1.0.3",
"author": "Rodax Software <dev@rodax-software.com>",
"main": "./src/index.ts",
"scripts": {

View File

@ -20,10 +20,10 @@ class ArticleMapper
const id_article = this.mapsValue(source, "id_article", ArticleIdentifier.create);
const reference = this.mapsValue(source, "reference", Slug.create);
const points = this.mapsValue(source, "points", (value: any) =>
Quantity.create({ amount: value, scale: 4 })
Quantity.create({ amount: value, scale: 2 })
);
const retail_price = this.mapsValue(source, "retail_price", (value: any) =>
UnitPrice.create({ amount: value, scale: 4 })
UnitPrice.create({ amount: value, scale: 2 })
);
const language = this.mapsValue(source, "lang_code", Language.createFromCode);

View File

@ -92,9 +92,8 @@ export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
.toArray()
.reduce<MoneyValue>(
(accumulator, currentItem) => accumulator.add(currentItem.totalPrice),
MoneyValue.create({ amount: 0, scale: 4, currencyCode: this.currency.code }).object
)
.convertScale(2);
MoneyValue.create({ amount: 0, scale: 2, currencyCode: this.currency.code }).object
);
return result;
};

View File

@ -54,7 +54,7 @@ export class QuoteItem extends Entity<IQuoteItemProps> implements IQuoteItem {
get subtotalPrice(): MoneyValue {
return this.quantity.isNull() || this.unitPrice.isNull()
? MoneyValue.create({ amount: null, scale: 4 }).object
? MoneyValue.create({ amount: null, scale: 2 }).object
: this.unitPrice.multiply(this.quantity.toNumber());
}
@ -64,7 +64,7 @@ export class QuoteItem extends Entity<IQuoteItemProps> implements IQuoteItem {
get totalPrice(): MoneyValue {
return this.subtotalPrice.isNull()
? MoneyValue.create({ amount: null, scale: 4 }).object
? MoneyValue.create({ amount: null, scale: 2 }).object
: this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
}
}

View File

@ -55,9 +55,9 @@ const quoteItemPresenter = (
id_article: item.idArticle.toString(),
description: item.description.toString(),
quantity: item.quantity.convertScale(2).toObject(),
unit_price: item.unitPrice.convertScale(4).toObject(),
subtotal_price: item.subtotalPrice.convertScale(4).toObject(),
unit_price: item.unitPrice.convertScale(2).toObject(),
subtotal_price: item.subtotalPrice.convertScale(2).toObject(),
discount: item.discount.convertScale(2).toObject(),
total_price: item.totalPrice.convertScale(4).toObject(),
total_price: item.totalPrice.convertScale(2).toObject(),
}))
: [];

View File

@ -57,9 +57,9 @@ const quoteItemPresenter = (
id_article: item.idArticle.toString(),
description: item.description.toString(),
quantity: item.quantity.convertScale(2).toObject(),
unit_price: item.unitPrice.convertScale(4).toObject(),
subtotal_price: item.subtotalPrice.convertScale(4).toObject(),
unit_price: item.unitPrice.convertScale(2).toObject(),
subtotal_price: item.subtotalPrice.convertScale(2).toObject(),
discount: item.discount.convertScale(2).toObject(),
total_price: item.totalPrice.convertScale(4).toObject(),
total_price: item.totalPrice.convertScale(2).toObject(),
}))
: [];

View File

@ -55,9 +55,9 @@ const quoteItemPresenter = (
id_article: item.idArticle.toString(),
description: item.description.toString(),
quantity: item.quantity.convertScale(2).toObject(),
unit_price: item.unitPrice.convertScale(4).toObject(),
subtotal_price: item.subtotalPrice.convertScale(4).toObject(),
unit_price: item.unitPrice.convertScale(2).toObject(),
subtotal_price: item.subtotalPrice.convertScale(2).toObject(),
discount: item.discount.convertScale(2).toObject(),
total_price: item.totalPrice.convertScale(4).toObject(),
total_price: item.totalPrice.convertScale(2).toObject(),
}))
: [];

View File

@ -38,7 +38,7 @@ class QuoteItemMapper
MoneyValue.create({
amount: unit_price,
currencyCode: sourceParent.currency_code,
scale: 4,
scale: 2,
})
),
@ -46,7 +46,7 @@ class QuoteItemMapper
MoneyValue.create({
amount: subtotal_price,
currencyCode: sourceParent.currency_code,
scale: 4,
scale: 2,
})
),
*/
@ -62,7 +62,7 @@ class QuoteItemMapper
MoneyValue.create({
amount: total_price,
currencyCode: sourceParent.currency_code,
scale: 4,
scale: 2,
})
),*/
};
@ -89,10 +89,10 @@ class QuoteItemMapper
id_article: source.idArticle.toPrimitive(),
description: source.description.toPrimitive(),
quantity: source.quantity.convertScale(2).toPrimitive(),
unit_price: source.unitPrice.convertScale(4).toPrimitive(),
subtotal_price: source.subtotalPrice.convertScale(4).toPrimitive(),
unit_price: source.unitPrice.convertScale(2).toPrimitive(),
subtotal_price: source.subtotalPrice.convertScale(2).toPrimitive(),
discount: source.discount.convertScale(2).toPrimitive(),
total_price: source.totalPrice.convertScale(4).toPrimitive(),
total_price: source.totalPrice.convertScale(2).toPrimitive(),
};
}
}

View File

@ -8,9 +8,11 @@ export interface IUnitPriceProps {
scale: number;
}
export const DEFAULT_UNIT_PRICE_SCALE = 2;
export class UnitPrice extends MoneyValue {
public static create(props: IUnitPriceProps) {
const { amount, currencyCode, scale = 4 } = props;
const { amount, currencyCode, scale = DEFAULT_UNIT_PRICE_SCALE } = props;
const _unitPriceOrError = MoneyValue.create({
amount,
currencyCode,
@ -20,7 +22,7 @@ export class UnitPrice extends MoneyValue {
return _unitPriceOrError;
}
const _unitPrice = _unitPriceOrError.object.convertScale(4);
const _unitPrice = _unitPriceOrError.object.convertScale(DEFAULT_UNIT_PRICE_SCALE);
return Result.ok<UnitPrice>(_unitPrice);
}