.
This commit is contained in:
parent
f9afa58b78
commit
fbb21c3fd3
@ -5,6 +5,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
||||||
<title>Uecko</title>
|
<title>Uecko</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
#root {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.react:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
a:nth-of-type(2) .logo {
|
|
||||||
animation: logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
import { Container } from "./components/Container";
|
|
||||||
|
|
||||||
export function TypographyDemo() {
|
|
||||||
return (
|
|
||||||
<Container className="mx-auto mt-8 prose prose-slate lg:prose-lg">
|
|
||||||
<h1>The Joke Tax Chronicles</h1>
|
|
||||||
<p className="leading-7">
|
|
||||||
Once upon a time, in a far-off land, there was a very lazy king who
|
|
||||||
spent all day lounging on his throne. One day, his advisors came to him
|
|
||||||
with a problem: the kingdom was running out of money.
|
|
||||||
</p>
|
|
||||||
<pre>
|
|
||||||
<code className="language-html">
|
|
||||||
<article class="prose"> <h1>Garlic bread with cheese: What
|
|
||||||
the science tells us</h1> <p> For years parents have
|
|
||||||
espoused the health benefits of eating garlic bread with cheese to
|
|
||||||
their children, with the food earning such an iconic status in our
|
|
||||||
culture that kids will often dress up as warm, cheesy loaf for
|
|
||||||
Halloween. </p> <p> But a recent study shows that the
|
|
||||||
celebrated appetizer may be linked to a series of rabies cases
|
|
||||||
springing up around the country. </p> <!-- ... -->
|
|
||||||
</article>
|
|
||||||
</code>
|
|
||||||
</pre>
|
|
||||||
<h2>The King's Plan</h2>
|
|
||||||
<p>
|
|
||||||
The king thought long and hard, and finally came up with{" "}
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="font-medium underline text-primary underline-offset-4"
|
|
||||||
>
|
|
||||||
a brilliant plan
|
|
||||||
</a>
|
|
||||||
: he would tax the jokes in the kingdom.
|
|
||||||
</p>
|
|
||||||
<blockquote className="pl-6 mt-6 italic border-l-2">
|
|
||||||
"After all," he said, "everyone enjoys a good joke, so it's only fair
|
|
||||||
that they should pay for the privilege."
|
|
||||||
</blockquote>
|
|
||||||
<h3 className="mt-8 text-2xl font-semibold tracking-tight scroll-m-20">
|
|
||||||
The Joke Tax
|
|
||||||
</h3>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
The king's subjects were not amused. They grumbled and complained, but
|
|
||||||
the king was firm:
|
|
||||||
</p>
|
|
||||||
<ul className="my-6 ml-6 list-disc [&>li]:mt-2">
|
|
||||||
<li>1st level of puns: 5 gold coins</li>
|
|
||||||
<li>2nd level of jokes: 10 gold coins</li>
|
|
||||||
<li>3rd level of one-liners : 20 gold coins</li>
|
|
||||||
</ul>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
As a result, people stopped telling jokes, and the kingdom fell into a
|
|
||||||
gloom. But there was one person who refused to let the king's
|
|
||||||
foolishness get him down: a court jester named Jokester.
|
|
||||||
</p>
|
|
||||||
<h3 className="mt-8 text-2xl font-semibold tracking-tight scroll-m-20">
|
|
||||||
Jokester's Revolt
|
|
||||||
</h3>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
Jokester began sneaking into the castle in the middle of the night and
|
|
||||||
leaving jokes all over the place: under the king's pillow, in his soup,
|
|
||||||
even in the royal toilet. The king was furious, but he couldn't seem to
|
|
||||||
stop Jokester.
|
|
||||||
</p>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
And then, one day, the people of the kingdom discovered that the jokes
|
|
||||||
left by Jokester were so funny that they couldn't help but laugh. And
|
|
||||||
once they started laughing, they couldn't stop.
|
|
||||||
</p>
|
|
||||||
<h3 className="mt-8 text-2xl font-semibold tracking-tight scroll-m-20">
|
|
||||||
The People's Rebellion
|
|
||||||
</h3>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
The people of the kingdom, feeling uplifted by the laughter, started to
|
|
||||||
tell jokes and puns again, and soon the entire kingdom was in on the
|
|
||||||
joke.
|
|
||||||
</p>
|
|
||||||
<div className="w-full my-6 overflow-y-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead>
|
|
||||||
<tr className="p-0 m-0 border-t even:bg-muted">
|
|
||||||
<th className="border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
King's Treasury
|
|
||||||
</th>
|
|
||||||
<th className="border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
People's happiness
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr className="p-0 m-0 border-t even:bg-muted">
|
|
||||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
Empty
|
|
||||||
</td>
|
|
||||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
Overflowing
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="p-0 m-0 border-t even:bg-muted">
|
|
||||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
Modest
|
|
||||||
</td>
|
|
||||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
Satisfied
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="p-0 m-0 border-t even:bg-muted">
|
|
||||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
Full
|
|
||||||
</td>
|
|
||||||
<td className="border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right">
|
|
||||||
Ecstatic
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
The king, seeing how much happier his subjects were, realized the error
|
|
||||||
of his ways and repealed the joke tax. Jokester was declared a hero, and
|
|
||||||
the kingdom lived happily ever after.
|
|
||||||
</p>
|
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">
|
|
||||||
The moral of the story is: never underestimate the power of a good laugh
|
|
||||||
and always be careful of bad ideas.
|
|
||||||
</p>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -20,12 +20,13 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import { useToast } from "@/ui/use-toast";
|
import { useToast } from "@/ui/use-toast";
|
||||||
|
import { formatDateToYYYYMMDD } from "@shared/utilities/helpers";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuotes } from "../hooks";
|
import { useQuotes } from "../hooks";
|
||||||
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
||||||
import { QuoteStatusEditor } from "./editors";
|
import { QuoteSentToEditor, QuoteStatusEditor } from "./editors";
|
||||||
import { QuotePDFPreview } from "./QuotePDFPreview";
|
import { QuotePDFPreview } from "./QuotePDFPreview";
|
||||||
|
|
||||||
type QuoteResumeProps = {
|
type QuoteResumeProps = {
|
||||||
@ -37,9 +38,10 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { useOne, useSetStatus, useDownloader, getQuotePDFFilename } = useQuotes();
|
const { useOne, useSetStatus, useSentTo, useDownloader, getQuotePDFFilename } = useQuotes();
|
||||||
const { data, status } = useOne(quoteId);
|
const { data, status } = useOne(quoteId);
|
||||||
const { mutate: setStatusMutation } = useSetStatus(quoteId);
|
const { mutate: setStatusMutation } = useSetStatus(quoteId);
|
||||||
|
const { mutate: sentToMutation } = useSentTo(quoteId);
|
||||||
const { download, ...downloadProps } = useDownloader();
|
const { download, ...downloadProps } = useDownloader();
|
||||||
|
|
||||||
const { formatCurrency, formatNumber } = useCustomLocalization({
|
const { formatCurrency, formatNumber } = useCustomLocalization({
|
||||||
@ -75,6 +77,9 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
};
|
};
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
const allowToSent = useMemo(() => data?.status === "accepted" && !data?.date_sent, [data]);
|
||||||
|
const isSent = useMemo(() => data?.status === "accepted" && data?.date_sent, [data]);
|
||||||
|
|
||||||
const handleOnChangeStatus = (_: string, newStatus: string) => {
|
const handleOnChangeStatus = (_: string, newStatus: string) => {
|
||||||
setStatusMutation(
|
setStatusMutation(
|
||||||
{ newStatus },
|
{ newStatus },
|
||||||
@ -89,6 +94,19 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnSentTo = (_: string) => {
|
||||||
|
sentToMutation(
|
||||||
|
{ sent_date: formatDateToYYYYMMDD(new Date()) },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
description: t("quotes.quote_sent_to_editor.toast_status_changed"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleFinishDownload = useCallback(() => {
|
const handleFinishDownload = useCallback(() => {
|
||||||
toast({
|
toast({
|
||||||
description: t("quotes.downloading_dialog.toast_success"),
|
description: t("quotes.downloading_dialog.toast_success"),
|
||||||
@ -129,21 +147,33 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className='flex mr-auto text-foreground'>
|
<div className='flex mr-auto text-foreground'>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
<Button
|
{allowToSent && !isSent && (
|
||||||
size='sm'
|
<>
|
||||||
variant='default'
|
<QuoteSentToEditor quote={data} onSentTo={handleOnSentTo} />
|
||||||
className='h-8 gap-1'
|
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
||||||
onClick={(e) => {
|
</>
|
||||||
e.preventDefault();
|
)}
|
||||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
|
||||||
}}
|
{!allowToSent && !isSent && (
|
||||||
>
|
<>
|
||||||
<FilePenLineIcon className='h-3.5 w-3.5' />
|
<Button
|
||||||
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
|
size='sm'
|
||||||
{t("quotes.list.columns.actions.edit")}
|
variant='default'
|
||||||
</span>
|
className='h-8 gap-1'
|
||||||
</Button>
|
onClick={(e) => {
|
||||||
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
e.preventDefault();
|
||||||
|
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FilePenLineIcon className='h-3.5 w-3.5' />
|
||||||
|
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
|
||||||
|
{t("quotes.list.columns.actions.edit")}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@ -154,7 +184,9 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
>
|
>
|
||||||
<DownloadIcon className='h-3.5 w-3.5 ' />
|
<DownloadIcon className='h-3.5 w-3.5 ' />
|
||||||
<span className='sr-only'>{t("quotes.list.resume.download_quote")}</span>
|
<span className={isSent ? "" : "sr-only"}>
|
||||||
|
{t("quotes.list.resume.download_quote")}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>{t("quotes.list.resume.download_quote")}</TooltipContent>
|
<TooltipContent>{t("quotes.list.resume.download_quote")}</TooltipContent>
|
||||||
|
|||||||
@ -162,6 +162,22 @@ export const QuotesDataTable = ({
|
|||||||
),
|
),
|
||||||
size: 600,
|
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,
|
id: "total_price" as const,
|
||||||
accessor: "total_price",
|
accessor: "total_price",
|
||||||
@ -179,27 +195,44 @@ export const QuotesDataTable = ({
|
|||||||
{
|
{
|
||||||
id: "row-actions",
|
id: "row-actions",
|
||||||
header: () => null,
|
header: () => null,
|
||||||
cell: ({ row }: { row: Row<IListQuotes_Response_DTO> }) => (
|
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => (
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
{original.status === "accepted" && !original.date_sent ? (
|
||||||
size='sm'
|
<Button
|
||||||
variant='outline'
|
size='sm'
|
||||||
className='h-8 gap-1'
|
variant='default'
|
||||||
onClick={(e) => {
|
className='h-8 gap-1'
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
handleEditQuote(row.original);
|
e.preventDefault();
|
||||||
}}
|
//handleSentToUecko(original);
|
||||||
>
|
}}
|
||||||
<FilePenLineIcon className='h-3.5 w-3.5' />
|
>
|
||||||
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
|
<FilePenLineIcon className='h-3.5 w-3.5' />
|
||||||
{t("quotes.list.columns.actions.edit")}
|
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
|
||||||
</span>
|
{t("quotes.list.columns.actions.sent_to")}
|
||||||
</Button>
|
</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>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("quotes.list.columns.actions.edit")}</p>
|
<p>{t("quotes.list.columns.actions.sent_to_uecko")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
@ -213,7 +246,7 @@ export const QuotesDataTable = ({
|
|||||||
<DropdownMenuContent align='end'>
|
<DropdownMenuContent align='end'>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
download(row.original.id, getQuotePDFFilename(row.original));
|
download(original.id, getQuotePDFFilename(original));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
Button,
|
||||||
|
} from "@/ui";
|
||||||
|
import { IGetQuote_Response_DTO } from "@shared/contexts";
|
||||||
|
import { t } from "i18next";
|
||||||
|
|
||||||
|
export const QuoteSentToEditor = ({
|
||||||
|
quote,
|
||||||
|
onSentTo,
|
||||||
|
}: {
|
||||||
|
quote: IGetQuote_Response_DTO;
|
||||||
|
onSentTo: (quoteId: string) => void;
|
||||||
|
}) => {
|
||||||
|
const handleSentTo = () => {
|
||||||
|
onSentTo(quote.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button size='sm' variant='default' className='h-8 gap-1'>
|
||||||
|
{t("quotes.quote_sent_to_editor.trigger_button")}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t("quotes.quote_sent_to_editor.title")}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t("quotes.quote_sent_to_editor.description")}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button onClick={handleSentTo}>{t("common.continue")}</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./QuoteDetailsCardEditor";
|
export * from "./QuoteDetailsCardEditor";
|
||||||
export * from "./QuoteDocumentsCardEditor";
|
export * from "./QuoteDocumentsCardEditor";
|
||||||
export * from "./QuoteGeneralCardEditor";
|
export * from "./QuoteGeneralCardEditor";
|
||||||
|
export * from "./QuoteSentToEditor";
|
||||||
export * from "./QuoteStatusEditor";
|
export * from "./QuoteStatusEditor";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useDownloader } from "@/lib/hooks";
|
import { useDownloader } from "@/lib/hooks";
|
||||||
import { UseListQueryResult, useList, useOne, useSave } from "@/lib/hooks/useDataSource";
|
import { useList, UseListQueryResult, useOne, useSave } from "@/lib/hooks/useDataSource";
|
||||||
import { IGetListDataProviderParams } from "@/lib/hooks/useDataSource/DataSource";
|
import { IGetListDataProviderParams } from "@/lib/hooks/useDataSource/DataSource";
|
||||||
import { TDataSourceError } from "@/lib/hooks/useDataSource/types";
|
import { TDataSourceError } from "@/lib/hooks/useDataSource/types";
|
||||||
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
|
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
IGetQuote_Response_DTO,
|
IGetQuote_Response_DTO,
|
||||||
IListQuotes_Response_DTO,
|
IListQuotes_Response_DTO,
|
||||||
IListResponse_DTO,
|
IListResponse_DTO,
|
||||||
|
ISendQuote_Request_DTO,
|
||||||
ISetStatusQuote_Request_DTO,
|
ISetStatusQuote_Request_DTO,
|
||||||
IUpdateQuote_Request_DTO,
|
IUpdateQuote_Request_DTO,
|
||||||
IUpdateQuote_Response_DTO,
|
IUpdateQuote_Response_DTO,
|
||||||
@ -173,17 +174,21 @@ export const useQuotes = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/*return useMutation<void, TDataSourceError, ISetStatusQuote_Request_DTO>({
|
useSentTo: (id?: string) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<void, TDataSourceError, ISendQuote_Request_DTO>({
|
||||||
mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(),
|
mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(),
|
||||||
mutationFn: (data) => {
|
mutationFn: (data) => {
|
||||||
const { newStatus } = data;
|
const { sent_date } = data;
|
||||||
|
|
||||||
return dataSource.updateOne({
|
return dataSource.custom({
|
||||||
resource: "quotes",
|
url: `${dataSource.getApiUrl()}/quotes/${id}/send`,
|
||||||
id,
|
method: "put",
|
||||||
data: {
|
data: {
|
||||||
newStatus,
|
sent_date,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -192,7 +197,7 @@ export const useQuotes = () => {
|
|||||||
queryKey: ["data", "default", "quotes"],
|
queryKey: ["data", "default", "quotes"],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});*/
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
useOne: (id?: string, params?: UseQuotesGetParamsType) =>
|
useOne: (id?: string, params?: UseQuotesGetParamsType) =>
|
||||||
|
|||||||
@ -82,6 +82,7 @@
|
|||||||
"dealers": "Dealers",
|
"dealers": "Dealers",
|
||||||
"catalog": "Catalog",
|
"catalog": "Catalog",
|
||||||
"quotes": "Quotes",
|
"quotes": "Quotes",
|
||||||
|
"orders": "Orders",
|
||||||
"search_placeholder": "Type here for search quotes and articles",
|
"search_placeholder": "Type here for search quotes and articles",
|
||||||
"user": {
|
"user": {
|
||||||
"user_menu": "User menu",
|
"user_menu": "User menu",
|
||||||
@ -138,13 +139,15 @@
|
|||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
|
"date_sent": "Sent to Uecko",
|
||||||
"reference": "Reference",
|
"reference": "Reference",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"customer_reference": "Customer Ref.",
|
"customer_reference": "Customer Ref.",
|
||||||
"customer_information": "Customer",
|
"customer_information": "Customer",
|
||||||
"total_price": "Imp. total",
|
"total_price": "Imp. total",
|
||||||
"actions": {
|
"actions": {
|
||||||
"edit": "Edit quote"
|
"edit": "Edit quote",
|
||||||
|
"sent_to": "Send to Uecko"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -82,6 +82,7 @@
|
|||||||
"dealers": "Distribuidores",
|
"dealers": "Distribuidores",
|
||||||
"catalog": "Catálogo",
|
"catalog": "Catálogo",
|
||||||
"quotes": "Cotizaciones",
|
"quotes": "Cotizaciones",
|
||||||
|
"orders": "Pedidos",
|
||||||
"search_placeholder": "Buscar productos, cotizaciones, etc...",
|
"search_placeholder": "Buscar productos, cotizaciones, etc...",
|
||||||
"user": {
|
"user": {
|
||||||
"user_menu": "Menú del usuario",
|
"user_menu": "Menú del usuario",
|
||||||
@ -138,13 +139,15 @@
|
|||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"date": "Fecha",
|
"date": "Fecha",
|
||||||
|
"date_sent": "Enviado a Uecko",
|
||||||
"reference": "Referencia",
|
"reference": "Referencia",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"customer_reference": "Ref. cliente",
|
"customer_reference": "Ref. cliente",
|
||||||
"customer_information": "Cliente",
|
"customer_information": "Cliente",
|
||||||
"total_price": "Imp. total",
|
"total_price": "Imp. total",
|
||||||
"actions": {
|
"actions": {
|
||||||
"edit": "Editar"
|
"edit": "Editar",
|
||||||
|
"sent_to": "Enviar a Uecko"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -217,6 +220,14 @@
|
|||||||
"description": "Para rellenar su cotización, puede añadir artículos del catálogo.",
|
"description": "Para rellenar su cotización, puede añadir artículos del catálogo.",
|
||||||
"toast_article_added": "Artículo del catálogo añadido:"
|
"toast_article_added": "Artículo del catálogo añadido:"
|
||||||
},
|
},
|
||||||
|
"quote_sent_to_editor": {
|
||||||
|
"trigger_button": "Enviar a Uecko",
|
||||||
|
"title": "Enviar la cotización a Uecko",
|
||||||
|
"description": "¿Desea enviar esta cotización a Uecko? Esta acción no se puede deshacer.",
|
||||||
|
"submit_button": "Enviar",
|
||||||
|
"toast_status_changed": "Cotización enviada a Uecko"
|
||||||
|
},
|
||||||
|
|
||||||
"quote_status_editor": {
|
"quote_status_editor": {
|
||||||
"trigger_button": "Cambiar el estado",
|
"trigger_button": "Cambiar el estado",
|
||||||
"title": "Cambiar el estado de la cotización",
|
"title": "Cambiar el estado de la cotización",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
|
||||||
|
import defaultTheme from "tailwindcss/defaultTheme";
|
||||||
import plugin from "tailwindcss/plugin";
|
import plugin from "tailwindcss/plugin";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -16,14 +17,14 @@ export default {
|
|||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
// https://tailwindcss.com/docs/font-family#font-families
|
// https://tailwindcss.com/docs/font-family#font-families
|
||||||
/*fontFamily: {
|
|
||||||
sans: ['"Source Sans Pro"', ...defaultTheme.fontFamily.sans],
|
|
||||||
},*/
|
|
||||||
|
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
|
sans: ['"Poppins"', ...defaultTheme.fontFamily.sans],
|
||||||
|
},
|
||||||
|
|
||||||
|
/*fontFamily: {
|
||||||
display: "Public Sans, ui-sans-serif",
|
display: "Public Sans, ui-sans-serif",
|
||||||
heading: "Noto Serif, ui-serif",
|
heading: "Noto Serif, ui-serif",
|
||||||
},
|
},*/
|
||||||
|
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(240, 5.9%, 90%)",
|
border: "hsl(240, 5.9%, 90%)",
|
||||||
|
|||||||
1
dist/client/assets/index-D1RJbR2g.css
vendored
Normal file
1
dist/client/assets/index-D1RJbR2g.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
dist/client/assets/index-DSV01hTS.css
vendored
1
dist/client/assets/index-DSV01hTS.css
vendored
File diff suppressed because one or more lines are too long
5
dist/client/index.html
vendored
5
dist/client/index.html
vendored
@ -5,9 +5,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
||||||
<title>Uecko</title>
|
<title>Uecko</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CPprBq9G.js"></script>
|
<script type="module" crossorigin src="/assets/index-DA9apz9T.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DSV01hTS.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D1RJbR2g.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -3,5 +3,5 @@ import { NextFunction, Request, Response } from "express";
|
|||||||
export const handleRequest =
|
export const handleRequest =
|
||||||
(controllerFactory: any) => (req: Request, res: Response, next: NextFunction) => {
|
(controllerFactory: any) => (req: Request, res: Response, next: NextFunction) => {
|
||||||
const context = res.locals["context"];
|
const context = res.locals["context"];
|
||||||
return controllerFactory(context).execute(req, res, next);
|
return controllerFactory(context, req, res, next).execute(req, res, next);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -205,6 +205,13 @@ export class CreateQuoteUseCase
|
|||||||
return Result.fail(taxOrError.error);
|
return Result.fail(taxOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dateSentOrError = UTCDateValue.create(null);
|
||||||
|
if (dateSentOrError.isFailure) {
|
||||||
|
return Result.fail(dateSentOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items
|
||||||
|
|
||||||
let items: Collection<QuoteItem>;
|
let items: Collection<QuoteItem>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -283,6 +290,7 @@ export class CreateQuoteUseCase
|
|||||||
items,
|
items,
|
||||||
|
|
||||||
dealerId,
|
dealerId,
|
||||||
|
dateSent: dateSentOrError.object,
|
||||||
},
|
},
|
||||||
quoteId
|
quoteId
|
||||||
);
|
);
|
||||||
|
|||||||
106
server/src/contexts/sales/application/Quote/SendQuote.useCase.ts
Normal file
106
server/src/contexts/sales/application/Quote/SendQuote.useCase.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
IUseCase,
|
||||||
|
IUseCaseError,
|
||||||
|
IUseCaseRequest,
|
||||||
|
UseCaseError,
|
||||||
|
} from "@/contexts/common/application/useCases";
|
||||||
|
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||||
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
ISendQuote_Request_DTO,
|
||||||
|
Result,
|
||||||
|
UniqueID,
|
||||||
|
UTCDateValue,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import { IQuoteRepository } from "../../domain";
|
||||||
|
|
||||||
|
export interface ISendQuoteUseCaseRequest extends IUseCaseRequest {
|
||||||
|
id: UniqueID;
|
||||||
|
sentQuoteDTO: ISendQuote_Request_DTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SendQuoteResponseOrError =
|
||||||
|
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||||
|
| Result<void, never>; // Success!
|
||||||
|
|
||||||
|
export class SendQuoteUseCase
|
||||||
|
implements IUseCase<ISendQuoteUseCaseRequest, Promise<SendQuoteResponseOrError>>
|
||||||
|
{
|
||||||
|
private _adapter: ISequelizeAdapter;
|
||||||
|
private _repositoryManager: IRepositoryManager;
|
||||||
|
|
||||||
|
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||||
|
this._adapter = props.adapter;
|
||||||
|
this._repositoryManager = props.repositoryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(request: ISendQuoteUseCaseRequest): Promise<SendQuoteResponseOrError> {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
sentQuoteDTO: { sent_date },
|
||||||
|
} = request;
|
||||||
|
|
||||||
|
const quoteRepository = this._getQuoteRepository();
|
||||||
|
|
||||||
|
// Comprobar que existe el Quote
|
||||||
|
const idExists = await quoteRepository().exists(id);
|
||||||
|
if (!idExists) {
|
||||||
|
const message = `Quote ID not found`;
|
||||||
|
return Result.fail(
|
||||||
|
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, {
|
||||||
|
path: "id",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// //
|
||||||
|
// regla de negocio -> no poder enviar un quote enviado
|
||||||
|
// regla de negocio -> no poder enviar un quote que no esté aceptado
|
||||||
|
|
||||||
|
// Comprobar el status
|
||||||
|
const sentDateOrError = UTCDateValue.create(sent_date);
|
||||||
|
if (sentDateOrError.isFailure) {
|
||||||
|
const { error: domainError } = sentDateOrError;
|
||||||
|
let errorCode = "";
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
switch (domainError.code) {
|
||||||
|
// Errores manuales
|
||||||
|
case DomainError.INVALID_INPUT_DATA:
|
||||||
|
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||||
|
message = "La fecha de envío no es correcta";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||||
|
message = domainError.message;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.fail(UseCaseError.create(errorCode, message, domainError));
|
||||||
|
}
|
||||||
|
|
||||||
|
const transaction = this._adapter.startTransaction();
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
const quoteRepo = quoteRepository({ transaction: t });
|
||||||
|
await quoteRepo.updateSentDateById(id, sentDateOrError.object);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.ok<void>();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
//const _error = error as IInfrastructureError;
|
||||||
|
return Result.fail(
|
||||||
|
UseCaseError.create(
|
||||||
|
UseCaseError.REPOSITORY_ERROR,
|
||||||
|
"Error al establecer el nuevo estado en la cotización"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getQuoteRepository() {
|
||||||
|
return this._repositoryManager.getRepository<IQuoteRepository>("Quote");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,6 +48,9 @@ export class SetStatusQuoteUseCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// //
|
||||||
|
// regla de negocio -> no poder modificar un quote enviado
|
||||||
|
|
||||||
// Comprobar el status
|
// Comprobar el status
|
||||||
const statusOrError = QuoteStatus.create(newStatus);
|
const statusOrError = QuoteStatus.create(newStatus);
|
||||||
if (statusOrError.isFailure) {
|
if (statusOrError.isFailure) {
|
||||||
|
|||||||
@ -83,6 +83,9 @@ export class UpdateQuoteUseCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// //
|
||||||
|
// regla de negocio -> no poder modificar un quote enviado
|
||||||
|
|
||||||
// Crear quote
|
// Crear quote
|
||||||
const quoteOrError = this._tryCreateQuoteInstance(quoteDTO, id, dealerId);
|
const quoteOrError = this._tryCreateQuoteInstance(quoteDTO, id, dealerId);
|
||||||
|
|
||||||
@ -194,6 +197,13 @@ export class UpdateQuoteUseCase
|
|||||||
return Result.fail(taxOrError.error);
|
return Result.fail(taxOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dateSentOrError = UTCDateValue.create(null);
|
||||||
|
if (dateSentOrError.isFailure) {
|
||||||
|
return Result.fail(dateSentOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items
|
||||||
|
|
||||||
let items: Collection<QuoteItem>;
|
let items: Collection<QuoteItem>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -272,6 +282,8 @@ export class UpdateQuoteUseCase
|
|||||||
items,
|
items,
|
||||||
|
|
||||||
dealerId,
|
dealerId,
|
||||||
|
|
||||||
|
dateSent: dateSentOrError.object,
|
||||||
},
|
},
|
||||||
quoteId
|
quoteId
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,5 +2,6 @@ export * from "./CreateQuote.useCase";
|
|||||||
export * from "./DeleteQuote.useCase";
|
export * from "./DeleteQuote.useCase";
|
||||||
export * from "./GetQuote.useCase";
|
export * from "./GetQuote.useCase";
|
||||||
export * from "./ListQuotes.useCase";
|
export * from "./ListQuotes.useCase";
|
||||||
|
export * from "./SendQuote.useCase";
|
||||||
export * from "./SetStatusQuote.useCase";
|
export * from "./SetStatusQuote.useCase";
|
||||||
export * from "./UpdateQuote.useCase";
|
export * from "./UpdateQuote.useCase";
|
||||||
|
|||||||
@ -36,6 +36,8 @@ export interface IQuoteProps {
|
|||||||
//totalPrice: MoneyValue;
|
//totalPrice: MoneyValue;
|
||||||
|
|
||||||
dealerId: UniqueID;
|
dealerId: UniqueID;
|
||||||
|
|
||||||
|
dateSent: UTCDateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuote {
|
export interface IQuote {
|
||||||
@ -67,6 +69,8 @@ export interface IQuote {
|
|||||||
items: ICollection<QuoteItem>;
|
items: ICollection<QuoteItem>;
|
||||||
|
|
||||||
dealerId: UniqueID;
|
dealerId: UniqueID;
|
||||||
|
|
||||||
|
dateSent: UTCDateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
||||||
@ -183,4 +187,8 @@ export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
|||||||
get totalPrice(): MoneyValue {
|
get totalPrice(): MoneyValue {
|
||||||
return this.beforeTaxPrice.add(this.taxPrice);
|
return this.beforeTaxPrice.add(this.taxPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dateSent() {
|
||||||
|
return this.props.dateSent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import { IRepository } from "@/contexts/common/domain/repositories";
|
import { IRepository } from "@/contexts/common/domain/repositories";
|
||||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
import { ICollection, IQueryCriteria, UniqueID, UTCDateValue } from "@shared/contexts";
|
||||||
import { Quote, QuoteReference, QuoteStatus } from "../entities";
|
import { Quote, QuoteReference, QuoteStatus } from "../entities";
|
||||||
|
|
||||||
export interface IQuoteRepository extends IRepository<Quote> {
|
export interface IQuoteRepository extends IRepository<Quote> {
|
||||||
@ -19,4 +19,5 @@ export interface IQuoteRepository extends IRepository<Quote> {
|
|||||||
findLastReferenceByDealerId(dealerId: UniqueID): Promise<QuoteReference | null>;
|
findLastReferenceByDealerId(dealerId: UniqueID): Promise<QuoteReference | null>;
|
||||||
|
|
||||||
updateStatusById(quoteId: UniqueID, newStatus: QuoteStatus): Promise<void>;
|
updateStatusById(quoteId: UniqueID, newStatus: QuoteStatus): Promise<void>;
|
||||||
|
updateSentDateById(quoteId: UniqueID, sentDate: UTCDateValue): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
|
import { ICollection, IQueryCriteria, UniqueID, UTCDateValue } from "@shared/contexts";
|
||||||
import { ModelDefined, Transaction } from "sequelize";
|
import { ModelDefined, Transaction } from "sequelize";
|
||||||
|
|
||||||
import { IQuoteRepository } from "../domain";
|
import { IQuoteRepository } from "../domain";
|
||||||
import { Quote, QuoteReference, QuoteStatus } from "../domain/entities";
|
import { Quote, QuoteReference, QuoteStatus } from "../domain/entities";
|
||||||
import { ISalesContext } from "./Sales.context";
|
import { ISalesContext } from "./Sales.context";
|
||||||
import { IQuoteMapper, createQuoteMapper } from "./mappers/quote.mapper";
|
import { createQuoteMapper, IQuoteMapper } from "./mappers/quote.mapper";
|
||||||
|
|
||||||
export type QueryParams = {
|
export type QueryParams = {
|
||||||
pagination: Record<string, any>;
|
pagination: Record<string, any>;
|
||||||
@ -158,6 +158,21 @@ export class QuoteRepository extends SequelizeRepository<Quote> implements IQuot
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateSentDateById(quoteId: UniqueID, sentDate: UTCDateValue): Promise<void> {
|
||||||
|
const quoteSentData = sentDate.toPrimitive(); //this.mapper.mapToPersistence(quote);
|
||||||
|
const _model = this._adapter.getModel("Quote_Model");
|
||||||
|
|
||||||
|
await _model.update(
|
||||||
|
{
|
||||||
|
date_sent: quoteSentData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
where: { id: quoteId.toPrimitive() },
|
||||||
|
transaction: this._transaction,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerQuoteRepository = (context: ISalesContext) => {
|
export const registerQuoteRepository = (context: ISalesContext) => {
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import { CreateQuoteUseCase } from "@/contexts/sales/application";
|
import { CreateQuoteUseCase } from "@/contexts/sales/application";
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import { registerQuoteRepository } from "../../../../Quote.repository";
|
import { registerQuoteRepository } from "../../../../Quote.repository";
|
||||||
import { ISalesContext } from "../../../../Sales.context";
|
import { ISalesContext } from "../../../../Sales.context";
|
||||||
import { CreateQuoteController } from "./CreateQuote.controller";
|
import { CreateQuoteController } from "./CreateQuote.controller";
|
||||||
import { CreateQuotePresenter } from "./presenter";
|
import { CreateQuotePresenter } from "./presenter";
|
||||||
|
|
||||||
export const createQuoteController = (req: Request, res: Response, next: NextFunction) => {
|
export const createQuoteController = (context: ISalesContext) => {
|
||||||
const context: ISalesContext = res.locals.context;
|
|
||||||
|
|
||||||
registerQuoteRepository(context);
|
registerQuoteRepository(context);
|
||||||
return new CreateQuoteController(
|
return new CreateQuoteController(
|
||||||
{
|
{
|
||||||
@ -15,5 +12,5 @@ export const createQuoteController = (req: Request, res: Response, next: NextFun
|
|||||||
presenter: CreateQuotePresenter,
|
presenter: CreateQuotePresenter,
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
).execute(req, res, next);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import { GetQuoteUseCase } from "@/contexts/sales/application";
|
import { GetQuoteUseCase } from "@/contexts/sales/application";
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import { registerQuoteRepository } from "../../../../Quote.repository";
|
import { registerQuoteRepository } from "../../../../Quote.repository";
|
||||||
import { ISalesContext } from "../../../../Sales.context";
|
import { ISalesContext } from "../../../../Sales.context";
|
||||||
import { GetQuoteController } from "./GetQuote.controller";
|
import { GetQuoteController } from "./GetQuote.controller";
|
||||||
import { GetQuotePresenter } from "./presenter";
|
import { GetQuotePresenter } from "./presenter";
|
||||||
|
|
||||||
export const getQuoteController = (req: Request, res: Response, next: NextFunction) => {
|
export const getQuoteController = (context: ISalesContext) => {
|
||||||
const context: ISalesContext = res.locals.context;
|
|
||||||
|
|
||||||
registerQuoteRepository(context);
|
registerQuoteRepository(context);
|
||||||
|
|
||||||
return new GetQuoteController(
|
return new GetQuoteController(
|
||||||
@ -16,5 +13,5 @@ export const getQuoteController = (req: Request, res: Response, next: NextFuncti
|
|||||||
presenter: GetQuotePresenter,
|
presenter: GetQuotePresenter,
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
).execute(req, res, next);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -41,6 +41,8 @@ export const GetQuotePresenter: IGetQuotePresenter = {
|
|||||||
|
|
||||||
items: quoteItemPresenter(quote.items, context),
|
items: quoteItemPresenter(quote.items, context),
|
||||||
dealer_id: quote.dealerId.toString(),
|
dealer_id: quote.dealerId.toString(),
|
||||||
|
|
||||||
|
date_sent: quote.dateSent.toISO8601(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import { ListQuotesUseCase } from "@/contexts/sales/application";
|
import { ListQuotesUseCase } from "@/contexts/sales/application";
|
||||||
import { registerQuoteRepository } from "@/contexts/sales/infrastructure/Quote.repository";
|
import { registerQuoteRepository } from "@/contexts/sales/infrastructure/Quote.repository";
|
||||||
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import { ListQuotesController } from "./ListQuotes.controller";
|
import { ListQuotesController } from "./ListQuotes.controller";
|
||||||
import { ListQuotesPresenter } from "./presenter";
|
import { ListQuotesPresenter } from "./presenter";
|
||||||
|
|
||||||
export const listQuotesController = (req: Request, res: Response, next: NextFunction) => {
|
export const listQuotesController = (context: ISalesContext) => {
|
||||||
const context: ISalesContext = res.locals.context;
|
|
||||||
|
|
||||||
registerQuoteRepository(context);
|
registerQuoteRepository(context);
|
||||||
|
|
||||||
return new ListQuotesController(
|
return new ListQuotesController(
|
||||||
@ -16,5 +13,5 @@ export const listQuotesController = (req: Request, res: Response, next: NextFunc
|
|||||||
presenter: ListQuotesPresenter,
|
presenter: ListQuotesPresenter,
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
).execute(req, res, next);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -39,6 +39,8 @@ export const ListQuotesPresenter: IListQuotesPresenter = {
|
|||||||
|
|
||||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||||
dealer_id: quote.dealerId.toString(),
|
dealer_id: quote.dealerId.toString(),
|
||||||
|
|
||||||
|
date_sent: quote.dateSent.toISO8601(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
import { GetQuoteUseCase } from "@/contexts/sales/application";
|
import { GetQuoteUseCase } from "@/contexts/sales/application";
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import { registerQuoteRepository } from "../../../../Quote.repository";
|
import { registerQuoteRepository } from "../../../../Quote.repository";
|
||||||
import { ISalesContext } from "../../../../Sales.context";
|
import { ISalesContext } from "../../../../Sales.context";
|
||||||
import { ReportQuotePresenter } from "./reporter/ReportQuote.reporter";
|
import { ReportQuotePresenter } from "./reporter/ReportQuote.reporter";
|
||||||
import { ReportQuoteController } from "./ReportQuote.controller";
|
import { ReportQuoteController } from "./ReportQuote.controller";
|
||||||
|
|
||||||
export const reportQuoteController = (req: Request, res: Response, next: NextFunction) => {
|
export const reportQuoteController = (context: ISalesContext) => {
|
||||||
const context: ISalesContext = res.locals.context;
|
|
||||||
|
|
||||||
registerQuoteRepository(context);
|
registerQuoteRepository(context);
|
||||||
|
|
||||||
return new ReportQuoteController(
|
return new ReportQuoteController(
|
||||||
@ -16,5 +13,5 @@ export const reportQuoteController = (req: Request, res: Response, next: NextFun
|
|||||||
reporter: ReportQuotePresenter,
|
reporter: ReportQuotePresenter,
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
).execute(req, res, next);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { SendQuoteUseCase } from "@/contexts/sales/application";
|
||||||
|
import { registerQuoteRepository } from "../../../../Quote.repository";
|
||||||
|
import { ISalesContext } from "../../../../Sales.context";
|
||||||
|
import { SendQuoteController } from "./sendQuote.controller";
|
||||||
|
|
||||||
|
export const sendQuoteController = (context: ISalesContext) => {
|
||||||
|
registerQuoteRepository(context);
|
||||||
|
return new SendQuoteController(
|
||||||
|
{
|
||||||
|
useCase: new SendQuoteUseCase(context),
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||||
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
|
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import { SendQuoteUseCase } from "@/contexts/sales/application";
|
||||||
|
import {
|
||||||
|
ensureIdIsValid,
|
||||||
|
ensureSendQuote_Request_DTOIsValid,
|
||||||
|
ISendQuote_Request_DTO,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import { ISalesContext } from "../../../../Sales.context";
|
||||||
|
|
||||||
|
export class SendQuoteController extends ExpressController {
|
||||||
|
private useCase: SendQuoteUseCase;
|
||||||
|
private context: ISalesContext;
|
||||||
|
|
||||||
|
constructor(props: { useCase: SendQuoteUseCase }, context: ISalesContext) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl(): Promise<any> {
|
||||||
|
try {
|
||||||
|
const { quoteId } = this.req.params;
|
||||||
|
const newStatusDTO: ISendQuote_Request_DTO = this.req.body;
|
||||||
|
|
||||||
|
// Validar ID
|
||||||
|
const quoteIdOrError = ensureIdIsValid(quoteId);
|
||||||
|
if (quoteIdOrError.isFailure) {
|
||||||
|
const errorMessage = "Quote ID is not valid";
|
||||||
|
const infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.INVALID_INPUT_DATA,
|
||||||
|
errorMessage,
|
||||||
|
quoteIdOrError.error
|
||||||
|
);
|
||||||
|
return this.invalidInputError(errorMessage, infraError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar DTO de datos
|
||||||
|
const sentQuoteDTOOrError = ensureSendQuote_Request_DTOIsValid(newStatusDTO);
|
||||||
|
|
||||||
|
if (sentQuoteDTOOrError.isFailure) {
|
||||||
|
const errorMessage = "New quote status is not valid";
|
||||||
|
const infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.INVALID_INPUT_DATA,
|
||||||
|
errorMessage,
|
||||||
|
sentQuoteDTOOrError.error
|
||||||
|
);
|
||||||
|
return this.invalidInputError(errorMessage, infraError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llamar al caso de uso
|
||||||
|
const result = await this.useCase.execute({
|
||||||
|
id: quoteIdOrError.object,
|
||||||
|
sentQuoteDTO: sentQuoteDTOOrError.object,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return this._handleExecuteError(result.error);
|
||||||
|
}
|
||||||
|
return this.noContent();
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return this.fail(e as IServerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleExecuteError(error: IUseCaseError) {
|
||||||
|
let errorMessage: string;
|
||||||
|
let infraError: IInfrastructureError;
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case UseCaseError.NOT_FOUND_ERROR:
|
||||||
|
errorMessage = "Quote not found";
|
||||||
|
|
||||||
|
infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||||
|
errorMessage,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.notFoundError(errorMessage, infraError);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UseCaseError.UNEXCEPTED_ERROR:
|
||||||
|
errorMessage = error.message;
|
||||||
|
|
||||||
|
infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.UNEXCEPTED_ERROR,
|
||||||
|
errorMessage,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return this.internalServerError(errorMessage, infraError);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorMessage = error.message;
|
||||||
|
return this.clientError(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,14 @@
|
|||||||
import { SetStatusQuoteUseCase } from "@/contexts/sales/application";
|
import { SetStatusQuoteUseCase } from "@/contexts/sales/application";
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import { registerQuoteRepository } from "../../../../Quote.repository";
|
import { registerQuoteRepository } from "../../../../Quote.repository";
|
||||||
import { ISalesContext } from "../../../../Sales.context";
|
import { ISalesContext } from "../../../../Sales.context";
|
||||||
import { SetStatusQuoteController } from "./SetStatusQuote.controller";
|
import { SetStatusQuoteController } from "./SetStatusQuote.controller";
|
||||||
|
|
||||||
export const setStatusQuoteController = (req: Request, res: Response, next: NextFunction) => {
|
export const setStatusQuoteController = (context: ISalesContext) => {
|
||||||
const context: ISalesContext = res.locals.context;
|
|
||||||
|
|
||||||
registerQuoteRepository(context);
|
registerQuoteRepository(context);
|
||||||
return new SetStatusQuoteController(
|
return new SetStatusQuoteController(
|
||||||
{
|
{
|
||||||
useCase: new SetStatusQuoteUseCase(context),
|
useCase: new SetStatusQuoteUseCase(context),
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
).execute(req, res, next);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -95,6 +95,8 @@ class QuoteMapper
|
|||||||
),*/
|
),*/
|
||||||
|
|
||||||
dealerId: this.mapsValue(source, "dealer_id", UniqueID.create),
|
dealerId: this.mapsValue(source, "dealer_id", UniqueID.create),
|
||||||
|
|
||||||
|
dateSent: this.mapsValue(source, "date_sent", UTCDateValue.create),
|
||||||
};
|
};
|
||||||
|
|
||||||
const quoteOrError = Quote.create(props, id);
|
const quoteOrError = Quote.create(props, id);
|
||||||
@ -139,6 +141,8 @@ class QuoteMapper
|
|||||||
|
|
||||||
items,
|
items,
|
||||||
dealer_id: source.dealerId.toPrimitive(),
|
dealer_id: source.dealerId.toPrimitive(),
|
||||||
|
|
||||||
|
date_sent: source.dateSent?.toPrimitive(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return quote;
|
return quote;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { QuoteItemCreationAttributes, QuoteItem_Model } from "./quoteItem.model"
|
|||||||
|
|
||||||
export type QuoteCreationAttributes = InferCreationAttributes<
|
export type QuoteCreationAttributes = InferCreationAttributes<
|
||||||
Quote_Model,
|
Quote_Model,
|
||||||
{ omit: "items" | "dealer" }
|
{ omit: "items" | "dealer" | "id_contract" }
|
||||||
> & {
|
> & {
|
||||||
items: QuoteItemCreationAttributes[];
|
items: QuoteItemCreationAttributes[];
|
||||||
dealer_id: string;
|
dealer_id: string;
|
||||||
@ -65,6 +65,9 @@ export class Quote_Model extends Model<
|
|||||||
|
|
||||||
declare items: NonAttribute<QuoteItem_Model[]>;
|
declare items: NonAttribute<QuoteItem_Model[]>;
|
||||||
declare dealer: NonAttribute<Dealer_Model>;
|
declare dealer: NonAttribute<Dealer_Model>;
|
||||||
|
|
||||||
|
declare id_contract: CreationOptional<string | null>;
|
||||||
|
declare date_sent: CreationOptional<string | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (sequelize: Sequelize) => {
|
export default (sequelize: Sequelize) => {
|
||||||
@ -155,6 +158,16 @@ export default (sequelize: Sequelize) => {
|
|||||||
type: new DataTypes.BIGINT(),
|
type: new DataTypes.BIGINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
id_contract: {
|
||||||
|
type: DataTypes.BIGINT().UNSIGNED,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
date_sent: {
|
||||||
|
type: new DataTypes.DATE(),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize,
|
sequelize,
|
||||||
@ -172,6 +185,8 @@ export default (sequelize: Sequelize) => {
|
|||||||
{ name: "status_idx", fields: ["status"] },
|
{ name: "status_idx", fields: ["status"] },
|
||||||
{ name: "reference_idx", fields: ["reference"] },
|
{ name: "reference_idx", fields: ["reference"] },
|
||||||
{ name: "deleted_at_idx", fields: ["deleted_at"] },
|
{ name: "deleted_at_idx", fields: ["deleted_at"] },
|
||||||
|
{ name: "date_sent_idx", fields: ["date_sent"] },
|
||||||
|
{ name: "id_contract_idx", fields: ["id_contract"] },
|
||||||
],
|
],
|
||||||
|
|
||||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { checkUser } from "@/contexts/auth";
|
import { checkUser } from "@/contexts/auth";
|
||||||
|
import { handleRequest } from "@/contexts/common/infrastructure/express";
|
||||||
import {
|
import {
|
||||||
createQuoteController,
|
createQuoteController,
|
||||||
getQuoteController,
|
getQuoteController,
|
||||||
@ -7,6 +8,7 @@ import {
|
|||||||
setStatusQuoteController,
|
setStatusQuoteController,
|
||||||
updateQuoteController,
|
updateQuoteController,
|
||||||
} from "@/contexts/sales/infrastructure/express/controllers";
|
} from "@/contexts/sales/infrastructure/express/controllers";
|
||||||
|
import { sendQuoteController } from "@/contexts/sales/infrastructure/express/controllers/quotes/sendQuote";
|
||||||
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/Dealer.middleware";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
|
||||||
@ -14,20 +16,30 @@ export const quoteRouter = (appRouter: Router): void => {
|
|||||||
const quoteRoutes: Router = Router({ mergeParams: true });
|
const quoteRoutes: Router = Router({ mergeParams: true });
|
||||||
|
|
||||||
// Users CRUD
|
// Users CRUD
|
||||||
quoteRoutes.get("/", checkUser, getDealerMiddleware, listQuotesController);
|
quoteRoutes.get("/", checkUser, getDealerMiddleware, handleRequest(listQuotesController));
|
||||||
quoteRoutes.get("/:quoteId", checkUser, getDealerMiddleware, getQuoteController);
|
quoteRoutes.get("/:quoteId", checkUser, getDealerMiddleware, handleRequest(getQuoteController));
|
||||||
quoteRoutes.post("/", checkUser, getDealerMiddleware, createQuoteController);
|
quoteRoutes.post("/", checkUser, getDealerMiddleware, handleRequest(createQuoteController));
|
||||||
quoteRoutes.put("/:quoteId", checkUser, getDealerMiddleware, updateQuoteController);
|
quoteRoutes.put(
|
||||||
|
"/:quoteId",
|
||||||
|
checkUser,
|
||||||
|
getDealerMiddleware,
|
||||||
|
handleRequest(updateQuoteController)
|
||||||
|
);
|
||||||
|
|
||||||
// Reports
|
// Reports
|
||||||
quoteRoutes.get("/:quoteId/report", checkUser, getDealerMiddleware, reportQuoteController);
|
quoteRoutes.get(
|
||||||
|
"/:quoteId/report",
|
||||||
|
checkUser,
|
||||||
|
getDealerMiddleware,
|
||||||
|
handleRequest(reportQuoteController)
|
||||||
|
);
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
quoteRoutes.put(
|
quoteRoutes.put("/:quoteId/setStatus", checkUser, handleRequest(setStatusQuoteController));
|
||||||
"/:quoteId/setStatus",
|
|
||||||
checkUser,
|
// Send to Uecko
|
||||||
/*getDealerMiddleware, */ setStatusQuoteController
|
quoteRoutes.put("/:quoteId/send", checkUser, handleRequest(sendQuoteController));
|
||||||
);
|
|
||||||
/*
|
/*
|
||||||
quoteRoutes.post("/", isAdmin, createQuoteController);
|
quoteRoutes.post("/", isAdmin, createQuoteController);
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export interface IGetQuote_Response_DTO {
|
|||||||
items: IGetQuote_QuoteItem_Response_DTO[];
|
items: IGetQuote_QuoteItem_Response_DTO[];
|
||||||
|
|
||||||
dealer_id: string;
|
dealer_id: string;
|
||||||
|
|
||||||
|
date_sent: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGetQuote_QuoteItem_Response_DTO {
|
export interface IGetQuote_QuoteItem_Response_DTO {
|
||||||
|
|||||||
@ -19,4 +19,6 @@ export interface IListQuotes_Response_DTO {
|
|||||||
total_price: IMoney_DTO;
|
total_price: IMoney_DTO;
|
||||||
|
|
||||||
dealer_id: string;
|
dealer_id: string;
|
||||||
|
|
||||||
|
date_sent: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { Result, RuleValidator } from "../../../../../common";
|
||||||
|
|
||||||
|
export interface ISendQuote_Request_DTO {
|
||||||
|
sent_date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureSendQuote_Request_DTOIsValid(quoteDTO: ISendQuote_Request_DTO) {
|
||||||
|
const schema = Joi.object({
|
||||||
|
sent_date: Joi.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = RuleValidator.validate<ISendQuote_Request_DTO>(schema, quoteDTO);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(result.object);
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./ISendQuote_Request.dto";
|
||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./CreateQuote.dto";
|
export * from "./CreateQuote.dto";
|
||||||
export * from "./GetQuote.dto";
|
export * from "./GetQuote.dto";
|
||||||
export * from "./ListQuotes.dto";
|
export * from "./ListQuotes.dto";
|
||||||
|
export * from "./SendQuote.dto";
|
||||||
export * from "./SetStatusQuote.dto";
|
export * from "./SetStatusQuote.dto";
|
||||||
export * from "./UpdateQuote.dto";
|
export * from "./UpdateQuote.dto";
|
||||||
|
|||||||
@ -2,3 +2,11 @@ export const adjustPrecision = ({ amount, scale }: { amount: number; scale: numb
|
|||||||
const factor = 10 ** scale;
|
const factor = 10 ** scale;
|
||||||
return Number(amount) / factor;
|
return Number(amount) / factor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatDateToYYYYMMDD = (date: Date): string => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0"); // Los meses van de 0 a 11, por lo que sumamos 1
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user