Añadir el IVA como un campo más en ajustes
This commit is contained in:
parent
990e0850f2
commit
0ff5c39023
@ -1,27 +1,37 @@
|
|||||||
import { FormPercentageField } from "@/components";
|
import { FormPercentageField } from "@/components";
|
||||||
import { useLocalization } from "@/lib/hooks";
|
import { useLocalization } from "@/lib/hooks";
|
||||||
import { Card, CardContent, CardDescription, CardTitle, Separator } from "@/ui";
|
import { Card, CardContent, CardDescription, CardTitle, Separator } from "@/ui";
|
||||||
|
import { CurrencyData } from "@shared/contexts";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
export const QuotePricesResume = () => {
|
export const QuotePricesResume = () => {
|
||||||
const { watch, register, formState } = useFormContext();
|
const { watch, register, formState } = useFormContext();
|
||||||
const { formatNumber } = useLocalization();
|
const { formatNumber } = useLocalization();
|
||||||
|
|
||||||
|
const currency_code = watch("currency_code");
|
||||||
const subtotal_price = formatNumber(watch("subtotal_price"));
|
const subtotal_price = formatNumber(watch("subtotal_price"));
|
||||||
const discount_price = formatNumber(watch("discount_price"));
|
const discount_price = formatNumber(watch("discount_price"));
|
||||||
const tax_price = formatNumber(watch("tax_price"));
|
const tax_price = formatNumber(watch("tax_price"));
|
||||||
const total_price = formatNumber(watch("total_price"));
|
const total_price = formatNumber(watch("total_price"));
|
||||||
|
|
||||||
|
const currency_symbol = useMemo(() => {
|
||||||
|
const currencyOrError = CurrencyData.createFromCode(currency_code);
|
||||||
|
return currencyOrError.isSuccess ? currencyOrError.object.symbol : "";
|
||||||
|
}, [currency_code]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='w-full'>
|
<Card className='w-full'>
|
||||||
<CardContent className='flex flex-row items-end gap-2 p-4 border-t'>
|
<CardContent className='flex flex-row items-end gap-2 p-4 border-t'>
|
||||||
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
||||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||||
<CardDescription className='text-sm'>Importe neto</CardDescription>
|
<CardDescription className='text-sm'>
|
||||||
|
{t("quotes.form_fields.subtotal_price.label")}
|
||||||
|
</CardDescription>
|
||||||
<CardTitle className='flex items-baseline text-2xl tabular-nums'>
|
<CardTitle className='flex items-baseline text-2xl tabular-nums'>
|
||||||
{subtotal_price}
|
{subtotal_price}
|
||||||
<span className='ml-1 text-lg tracking-normal'>€</span>
|
<span className='ml-1 text-lg tracking-normal'>{currency_symbol}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -41,10 +51,12 @@ export const QuotePricesResume = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||||
<CardDescription className='text-sm'>Imp. descuento</CardDescription>
|
<CardDescription className='text-sm'>
|
||||||
|
{t("quotes.form_fields.discount_price.label")}
|
||||||
|
</CardDescription>
|
||||||
<CardTitle className='flex items-baseline text-2xl tabular-nums'>
|
<CardTitle className='flex items-baseline text-2xl tabular-nums'>
|
||||||
{discount_price}
|
{discount_price}
|
||||||
<span className='ml-1 text-lg tracking-normal'>€</span>
|
<span className='ml-1 text-lg tracking-normal'>{currency_symbol}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,20 +77,24 @@ export const QuotePricesResume = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||||
<CardDescription className='text-sm'>Importe IVA</CardDescription>
|
<CardDescription className='text-sm'>
|
||||||
|
{t("quotes.form_fields.tax_price.label")}
|
||||||
|
</CardDescription>
|
||||||
<CardTitle className='flex items-baseline gap-1 text-2xl tabular-nums'>
|
<CardTitle className='flex items-baseline gap-1 text-2xl tabular-nums'>
|
||||||
{tax_price}
|
{tax_price}
|
||||||
<span className='text-base font-medium tracking-normal'>€</span>
|
<span className='text-base font-medium tracking-normal'>{currency_symbol}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
<Separator orientation='vertical' className='w-px h-16 mx-2' />
|
<Separator orientation='vertical' className='w-px h-16 mx-2' />
|
||||||
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
<div className='grid flex-1 h-16 grid-cols-1 auto-rows-max'>
|
||||||
<div className='grid gap-0'>
|
<div className='grid gap-0'>
|
||||||
<CardDescription className='text-sm font-semibold'>Importe total</CardDescription>
|
<CardDescription className='text-sm font-semibold'>
|
||||||
|
{t("quotes.form_fields.total_price.label")}
|
||||||
|
</CardDescription>
|
||||||
<CardTitle className='flex items-baseline gap-1 text-3xl tabular-nums'>
|
<CardTitle className='flex items-baseline gap-1 text-3xl tabular-nums'>
|
||||||
{total_price}
|
{total_price}
|
||||||
<span className='ml-1 text-lg tracking-normal'>€</span>
|
<span className='ml-1 text-lg tracking-normal'>{currency_symbol}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { useQuotes } from "../hooks";
|
|||||||
export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
const { pagination, globalFilter, isFiltered } = useDataTableContext();
|
||||||
|
|
||||||
const { useList } = useQuotes();
|
const { useList } = useQuotes();
|
||||||
|
|
||||||
const { data, isPending, isError, error } = useList({
|
const { data, isPending, isError, error } = useList({
|
||||||
@ -48,7 +47,12 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
|||||||
<div className='text-ellipsis'>
|
<div className='text-ellipsis'>
|
||||||
{original.customer_information.split("\n").map((item, index) => {
|
{original.customer_information.split("\n").map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<span key={index} className={index === 0 ? "font-semibold" : "font-medium"}>
|
<span
|
||||||
|
key={index}
|
||||||
|
className={
|
||||||
|
index === 0 ? "font-medium" : "hidden text-sm text-muted-foreground md:inline"
|
||||||
|
}
|
||||||
|
>
|
||||||
{item}
|
{item}
|
||||||
<br />
|
<br />
|
||||||
</span>
|
</span>
|
||||||
@ -78,7 +82,7 @@ export const QuotesDataTable = ({ status = "all" }: { status?: string }) => {
|
|||||||
id: "total_price" as const,
|
id: "total_price" as const,
|
||||||
accessor: "total_price",
|
accessor: "total_price",
|
||||||
header: () => <div className='text-right'>{t("quotes.list.columns.total_price")}</div>,
|
header: () => <div className='text-right'>{t("quotes.list.columns.total_price")}</div>,
|
||||||
cell: ({ table, row: { index, original }, column, getValue }) => {
|
cell: ({ row: { original } }) => {
|
||||||
const price = MoneyValue.create(original.total_price);
|
const price = MoneyValue.create(original.total_price);
|
||||||
return (
|
return (
|
||||||
<div className='text-right'>{price.isSuccess ? price.object.toFormat() : "-"}</div>
|
<div className='text-right'>{price.isSuccess ? price.object.toFormat() : "-"}</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ErrorOverlay, FormTextAreaField, LoadingOverlay } from "@/components";
|
import { ErrorOverlay, FormPercentageField, FormTextAreaField, LoadingOverlay } from "@/components";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
AlertDescription,
|
AlertDescription,
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
Form,
|
Form,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
|
import { joiResolver } from "@hookform/resolvers/joi";
|
||||||
|
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { AlertCircleIcon } from "lucide-react";
|
import { AlertCircleIcon } from "lucide-react";
|
||||||
@ -22,13 +23,14 @@ import { Trans } from "react-i18next";
|
|||||||
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { IUpdateProfile_Request_DTO } from "@shared/contexts";
|
import { IUpdateProfile_Request_DTO } from "@shared/contexts";
|
||||||
|
import Joi from "joi";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useSettings } from "./hooks";
|
import { useSettings } from "./hooks";
|
||||||
|
|
||||||
type SettingsDataForm = IUpdateProfile_Request_DTO;
|
type SettingsDataForm = IUpdateProfile_Request_DTO;
|
||||||
|
|
||||||
export const SettingsEditor = () => {
|
export const SettingsEditor = () => {
|
||||||
const [activeSection, setActiveSection] = useState("profile");
|
const [activeSection, setActiveSection] = useState("quotes");
|
||||||
const { useOne, useUpdate } = useSettings();
|
const { useOne, useUpdate } = useSettings();
|
||||||
|
|
||||||
const { data, status, error: queryError } = useOne();
|
const { data, status, error: queryError } = useOne();
|
||||||
@ -40,6 +42,10 @@ export const SettingsEditor = () => {
|
|||||||
default_notes: "",
|
default_notes: "",
|
||||||
default_legal_terms: "",
|
default_legal_terms: "",
|
||||||
default_quote_validity: "",
|
default_quote_validity: "",
|
||||||
|
default_tax: {
|
||||||
|
amount: undefined,
|
||||||
|
scale: 2,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@ -50,18 +56,19 @@ export const SettingsEditor = () => {
|
|||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
values: data?.dealer,
|
values: data?.dealer,
|
||||||
defaultValues,
|
defaultValues,
|
||||||
//defaultValues: _defaultValues,
|
resolver: joiResolver(
|
||||||
/*resolver: joiResolver(
|
|
||||||
Joi.object({
|
Joi.object({
|
||||||
email: Joi.string()
|
contact_information: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
.email({ tlds: { allow: false } })
|
default_payment_method: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
.required(),
|
default_notes: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
password: Joi.string().min(4).alphanum().required(),
|
default_legal_terms: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
}),
|
default_quote_validity: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
{
|
default_tax: Joi.object({
|
||||||
messages: SpanishJoiMessages,
|
amount: Joi.number().allow(null),
|
||||||
}
|
scale: Joi.number(),
|
||||||
),*/
|
}).required(),
|
||||||
|
})
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { formState, reset, getValues, handleSubmit } = form;
|
const { formState, reset, getValues, handleSubmit } = form;
|
||||||
@ -159,10 +166,8 @@ export const SettingsEditor = () => {
|
|||||||
//autoSize
|
//autoSize
|
||||||
rows={8}
|
rows={8}
|
||||||
placeholder={t("settings.form_fields.contact_information.placeholder")}
|
placeholder={t("settings.form_fields.contact_information.placeholder")}
|
||||||
{...form.register("contact_information", {
|
name='contact_information'
|
||||||
required: true,
|
required
|
||||||
})}
|
|
||||||
errors={form.formState.errors}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className='px-6 py-4 border-t'>
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
@ -173,6 +178,31 @@ export const SettingsEditor = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn("grid gap-6", activeSection === "quotes" ? "visible" : "hidden")}>
|
<div className={cn("grid gap-6", activeSection === "quotes" ? "visible" : "hidden")}>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
<Trans i18nKey='settings.form_fields.default_tax.label' />
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<Trans i18nKey='settings.form_fields.default_tax.desc' />
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<FormPercentageField
|
||||||
|
scale={2}
|
||||||
|
disabled={formState.disabled}
|
||||||
|
placeholder={t("settings.form_fields.default_tax.desc")}
|
||||||
|
name='default_tax'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
|
<Button>
|
||||||
|
<Trans i18nKey='common.save' />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
@ -186,9 +216,8 @@ export const SettingsEditor = () => {
|
|||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
autoSize
|
autoSize
|
||||||
placeholder={t("settings.form_fields.default_payment_method.placeholder")}
|
placeholder={t("settings.form_fields.default_payment_method.placeholder")}
|
||||||
{...form.register("default_payment_method", {
|
name='default_payment_method'
|
||||||
required: true,
|
required
|
||||||
})}
|
|
||||||
errors={form.formState.errors}
|
errors={form.formState.errors}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -211,10 +240,8 @@ export const SettingsEditor = () => {
|
|||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
autoSize
|
autoSize
|
||||||
placeholder={t("settings.form_fields.default_quote_validity.placeholder")}
|
placeholder={t("settings.form_fields.default_quote_validity.placeholder")}
|
||||||
{...form.register("default_quote_validity", {
|
name='default_quote_validity'
|
||||||
required: true,
|
required
|
||||||
})}
|
|
||||||
errors={form.formState.errors}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className='px-6 py-4 border-t'>
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
@ -237,10 +264,8 @@ export const SettingsEditor = () => {
|
|||||||
<FormTextAreaField
|
<FormTextAreaField
|
||||||
autoSize
|
autoSize
|
||||||
placeholder={t("settings.form_fields.default_notes.placeholder")}
|
placeholder={t("settings.form_fields.default_notes.placeholder")}
|
||||||
{...form.register("default_notes", {
|
name='default_notes'
|
||||||
required: true,
|
required
|
||||||
})}
|
|
||||||
errors={form.formState.errors}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className='px-6 py-4 border-t'>
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
@ -265,10 +290,8 @@ export const SettingsEditor = () => {
|
|||||||
//autoSize
|
//autoSize
|
||||||
rows={25}
|
rows={25}
|
||||||
placeholder={t("settings.form_fields.default_legal_terms.placeholder")}
|
placeholder={t("settings.form_fields.default_legal_terms.placeholder")}
|
||||||
{...form.register("default_legal_terms", {
|
name='default_legal_terms'
|
||||||
required: true,
|
required
|
||||||
})}
|
|
||||||
errors={form.formState.errors}
|
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className='px-6 py-4 border-t'>
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
|
|||||||
@ -106,7 +106,7 @@ export function DataTable<TData>({
|
|||||||
<TableRow
|
<TableRow
|
||||||
key={row.id}
|
key={row.id}
|
||||||
data-state={row.getIsSelected() && "selected"}
|
data-state={row.getIsSelected() && "selected"}
|
||||||
className={rowClassName}
|
className={cn(row.getIsSelected() ? "bg-accent" : "", rowClassName)}
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell key={cell.id} className={cellClassName}>
|
<TableCell key={cell.id} className={cellClassName}>
|
||||||
|
|||||||
@ -26,12 +26,7 @@ export function DataTableColumnHeader<TData, TValue>({
|
|||||||
if (!header.column.getCanSort()) {
|
if (!header.column.getCanSort()) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div className={cn("data-[state=open]:bg-accent tracking-wide text-ellipsis", className)}>
|
||||||
className={cn(
|
|
||||||
"data-[state=open]:bg-accent font-bold text-muted-foreground uppercase text-xs tracking-wide text-ellipsis",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
|||||||
@ -194,21 +194,41 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Quote's validity time"
|
"desc": "Quote's validity time"
|
||||||
},
|
},
|
||||||
"discount": {
|
|
||||||
"label": "Discount",
|
|
||||||
"placeholder": "%",
|
|
||||||
"desc": "Percentage discount"
|
|
||||||
},
|
|
||||||
"tax": {
|
|
||||||
"label": "Tax",
|
|
||||||
"placeholder": "%",
|
|
||||||
"desc": "Percentage Tax"
|
|
||||||
},
|
|
||||||
"subtotal_price": {
|
"subtotal_price": {
|
||||||
"label": "Subtotal",
|
"label": "Subtotal",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "Quote subtotal"
|
"desc": "Quote subtotal"
|
||||||
},
|
},
|
||||||
|
"discount": {
|
||||||
|
"label": "Discount (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Percentage discount"
|
||||||
|
},
|
||||||
|
"discount_price": {
|
||||||
|
"label": "Discount price",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Percentage discount price"
|
||||||
|
},
|
||||||
|
"before_tax_price": {
|
||||||
|
"label": "Before tax price",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Before tax price"
|
||||||
|
},
|
||||||
|
"tax": {
|
||||||
|
"label": "Tax (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Percentage Tax"
|
||||||
|
},
|
||||||
|
"tax_price": {
|
||||||
|
"label": "Tax price",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Percentage tax price"
|
||||||
|
},
|
||||||
|
"total_price": {
|
||||||
|
"label": "Total price",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Quote total price"
|
||||||
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"quantity": {
|
"quantity": {
|
||||||
"label": "Quantity",
|
"label": "Quantity",
|
||||||
@ -256,14 +276,19 @@
|
|||||||
"form_fields": {
|
"form_fields": {
|
||||||
"image": {
|
"image": {
|
||||||
"label": "Logotype",
|
"label": "Logotype",
|
||||||
"placeholder": "placeholder",
|
"placeholder": "",
|
||||||
"desc": "Información de contacto"
|
"desc": "Información de contacto"
|
||||||
},
|
},
|
||||||
"contact_information": {
|
"contact_information": {
|
||||||
"label": "Your contact information",
|
"label": "Your contact information",
|
||||||
"placeholder": "placeholder",
|
"placeholder": "",
|
||||||
"desc": "Your contact information as a dealer that will appear on the quotes given to your customers."
|
"desc": "Your contact information as a dealer that will appear on the quotes given to your customers."
|
||||||
},
|
},
|
||||||
|
"default_tax": {
|
||||||
|
"label": "Default tax (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Default tax rate for your quotes"
|
||||||
|
},
|
||||||
"default_legal_terms": {
|
"default_legal_terms": {
|
||||||
"label": "Legal terms",
|
"label": "Legal terms",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
@ -271,7 +296,7 @@
|
|||||||
},
|
},
|
||||||
"default_payment_method": {
|
"default_payment_method": {
|
||||||
"label": "Payment method",
|
"label": "Payment method",
|
||||||
"placeholder": "placeholder",
|
"placeholder": "",
|
||||||
"desc": "Default payment method to be used for new quotes"
|
"desc": "Default payment method to be used for new quotes"
|
||||||
},
|
},
|
||||||
"default_notes": {
|
"default_notes": {
|
||||||
|
|||||||
@ -194,21 +194,41 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": "desc"
|
"desc": "desc"
|
||||||
},
|
},
|
||||||
"discount": {
|
|
||||||
"label": "Descuento",
|
|
||||||
"placeholder": "%",
|
|
||||||
"desc": "Porcentaje de descuento"
|
|
||||||
},
|
|
||||||
"tax": {
|
|
||||||
"label": "IVA",
|
|
||||||
"placeholder": "%",
|
|
||||||
"desc": "Porcentaje de IVA"
|
|
||||||
},
|
|
||||||
"subtotal_price": {
|
"subtotal_price": {
|
||||||
"label": "Importe neto",
|
"label": "Importe neto",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"desc": ""
|
"desc": ""
|
||||||
},
|
},
|
||||||
|
"discount": {
|
||||||
|
"label": "Descuento (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Porcentaje de descuento"
|
||||||
|
},
|
||||||
|
"discount_price": {
|
||||||
|
"label": "Imp. descuento",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Importe del descuento"
|
||||||
|
},
|
||||||
|
"before_tax_price": {
|
||||||
|
"label": "Base imponible",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"tax": {
|
||||||
|
"label": "IVA (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Porcentaje de IVA"
|
||||||
|
},
|
||||||
|
"tax_price": {
|
||||||
|
"label": "Imp. descuento",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Importe del descuento"
|
||||||
|
},
|
||||||
|
"total_price": {
|
||||||
|
"label": "Total price",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Quote total price"
|
||||||
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"quantity": {
|
"quantity": {
|
||||||
"label": "Cantidad",
|
"label": "Cantidad",
|
||||||
@ -244,37 +264,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Ajustes",
|
"edit": {
|
||||||
"profile": {
|
"title": "Ajustes",
|
||||||
"title": "Ajustes de perfil",
|
"subtitle": "",
|
||||||
"items": {
|
"tabs": {
|
||||||
"image": {
|
"profile": "Ajustes de perfil",
|
||||||
"label": "Información de contacto",
|
"quotes": "Ajustes legales",
|
||||||
"placeholder": "placeholder",
|
"legal": "Ajustes para cotizaciones"
|
||||||
"desc": "Información de contacto"
|
|
||||||
},
|
|
||||||
"contact_information": {
|
|
||||||
"label": "Información de contacto",
|
|
||||||
"placeholder": "placeholder",
|
|
||||||
"desc": "Información de contacto"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"legal": {
|
"form_fields": {
|
||||||
"title": "Ajustes legales",
|
"image": {
|
||||||
"items": {
|
"label": "Información de contacto",
|
||||||
"default_legal_terms": {
|
"placeholder": "",
|
||||||
"label": "Cláusulas legales",
|
"desc": "Información de contacto"
|
||||||
"placeholder": "",
|
},
|
||||||
"desc": "desc"
|
"contact_information": {
|
||||||
}
|
"label": "Información de contacto",
|
||||||
}
|
"placeholder": "",
|
||||||
},
|
"desc": "Información de contacto"
|
||||||
"quotes": {
|
},
|
||||||
"title": "Ajustes para cotizaciones",
|
"default_tax": {
|
||||||
|
"label": "IVA por defecto (%)",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "Porcentaje de IVA por defecto para tus cotizaciones"
|
||||||
|
},
|
||||||
|
"default_legal_terms": {
|
||||||
|
"label": "Cláusulas legales",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
"default_payment_method": {
|
"default_payment_method": {
|
||||||
"label": "Forma de pago",
|
"label": "Forma de pago",
|
||||||
"placeholder": "placeholder",
|
"placeholder": "",
|
||||||
"desc": "desc"
|
"desc": "desc"
|
||||||
},
|
},
|
||||||
"default_notes": {
|
"default_notes": {
|
||||||
|
|||||||
@ -7,7 +7,13 @@ import {
|
|||||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { IUpdateProfile_Request_DTO, Result, TextValueObject, UniqueID } from "@shared/contexts";
|
import {
|
||||||
|
IUpdateProfile_Request_DTO,
|
||||||
|
Percentage,
|
||||||
|
Result,
|
||||||
|
TextValueObject,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
import { IProfileRepository, Profile } from "../domain";
|
import { IProfileRepository, Profile } from "../domain";
|
||||||
|
|
||||||
export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest {
|
export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest {
|
||||||
@ -33,8 +39,6 @@ export class UpdateProfileUseCase
|
|||||||
async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> {
|
async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> {
|
||||||
const { userId, profileDTO } = request;
|
const { userId, profileDTO } = request;
|
||||||
|
|
||||||
console.log(request);
|
|
||||||
|
|
||||||
// Comprobar que existe el profile
|
// Comprobar que existe el profile
|
||||||
const exitsOrError = await this._getProfileDealer(userId);
|
const exitsOrError = await this._getProfileDealer(userId);
|
||||||
if (exitsOrError.isFailure) {
|
if (exitsOrError.isFailure) {
|
||||||
@ -55,6 +59,13 @@ export class UpdateProfileUseCase
|
|||||||
profile.defaultNotes = TextValueObject.create(profileDTO.default_notes).object;
|
profile.defaultNotes = TextValueObject.create(profileDTO.default_notes).object;
|
||||||
profile.defaultQuoteValidity = TextValueObject.create(profileDTO.default_quote_validity).object;
|
profile.defaultQuoteValidity = TextValueObject.create(profileDTO.default_quote_validity).object;
|
||||||
|
|
||||||
|
const taxOrError = Percentage.create(profileDTO.default_tax);
|
||||||
|
if (taxOrError.isFailure) {
|
||||||
|
return Result.fail(taxOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.defaultTax = taxOrError.object;
|
||||||
|
|
||||||
// Guardar los cambios
|
// Guardar los cambios
|
||||||
return this._saveProfile(profile);
|
return this._saveProfile(profile);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
Language,
|
Language,
|
||||||
Name,
|
Name,
|
||||||
Note,
|
Note,
|
||||||
|
Percentage,
|
||||||
Result,
|
Result,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
} from "@shared/contexts";
|
} from "@shared/contexts";
|
||||||
@ -21,6 +22,7 @@ export interface IProfileProps {
|
|||||||
defaultNotes: Note;
|
defaultNotes: Note;
|
||||||
defaultLegalTerms: Note;
|
defaultLegalTerms: Note;
|
||||||
defaultQuoteValidity: Note;
|
defaultQuoteValidity: Note;
|
||||||
|
defaultTax: Percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProfile {
|
export interface IProfile {
|
||||||
@ -34,7 +36,9 @@ export interface IProfile {
|
|||||||
defaultPaymentMethod: Note;
|
defaultPaymentMethod: Note;
|
||||||
defaultNotes: Note;
|
defaultNotes: Note;
|
||||||
defaultLegalTerms: Note;
|
defaultLegalTerms: Note;
|
||||||
|
|
||||||
defaultQuoteValidity: Note;
|
defaultQuoteValidity: Note;
|
||||||
|
defaultTax: Percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
||||||
@ -98,4 +102,12 @@ export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
|||||||
set defaultQuoteValidity(newDefaultQuoteValidity: Note) {
|
set defaultQuoteValidity(newDefaultQuoteValidity: Note) {
|
||||||
this.props.defaultQuoteValidity = newDefaultQuoteValidity;
|
this.props.defaultQuoteValidity = newDefaultQuoteValidity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get defaultTax(): Percentage {
|
||||||
|
return this.props.defaultTax;
|
||||||
|
}
|
||||||
|
|
||||||
|
set defaultTax(newDefaultTax: Percentage) {
|
||||||
|
this.props.defaultTax = newDefaultTax;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export const GetProfilePresenter: IGetProfilePresenter = {
|
|||||||
default_notes: profile.defaultNotes.toString(),
|
default_notes: profile.defaultNotes.toString(),
|
||||||
default_legal_terms: profile.defaultLegalTerms.toString(),
|
default_legal_terms: profile.defaultLegalTerms.toString(),
|
||||||
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
||||||
|
default_tax: profile.defaultTax.convertScale(2).toObject(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export const UpdateProfilePresenter: IUpdateProfilePresenter = {
|
|||||||
default_notes: profile.defaultNotes.toString(),
|
default_notes: profile.defaultNotes.toString(),
|
||||||
default_legal_terms: profile.defaultLegalTerms.toString(),
|
default_legal_terms: profile.defaultLegalTerms.toString(),
|
||||||
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
||||||
|
default_tax: profile.defaultTax.convertScale(2).toObject(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,14 @@ import {
|
|||||||
} from "@/contexts/common/infrastructure";
|
} from "@/contexts/common/infrastructure";
|
||||||
import { DealerStatus } from "@/contexts/sales/domain";
|
import { DealerStatus } from "@/contexts/sales/domain";
|
||||||
import { Dealer_Model, DealerCreationAttributes } from "@/contexts/sales/infrastructure/sequelize";
|
import { Dealer_Model, DealerCreationAttributes } from "@/contexts/sales/infrastructure/sequelize";
|
||||||
import { CurrencyData, Language, Name, TextValueObject, UniqueID } from "@shared/contexts";
|
import {
|
||||||
|
CurrencyData,
|
||||||
|
Language,
|
||||||
|
Name,
|
||||||
|
Percentage,
|
||||||
|
TextValueObject,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
import { IProfileProps, Profile } from "../../domain";
|
import { IProfileProps, Profile } from "../../domain";
|
||||||
import { IProfileContext } from "../Profile.context";
|
import { IProfileContext } from "../Profile.context";
|
||||||
|
|
||||||
@ -44,6 +51,13 @@ class ProfileMapper
|
|||||||
TextValueObject.create
|
TextValueObject.create
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const defaultTax = this.mapsValue(source, "default_tax", (tax) =>
|
||||||
|
Percentage.create({
|
||||||
|
amount: tax,
|
||||||
|
scale: 2,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const props: IProfileProps = {
|
const props: IProfileProps = {
|
||||||
name,
|
name,
|
||||||
status,
|
status,
|
||||||
@ -55,6 +69,7 @@ class ProfileMapper
|
|||||||
defaultNotes,
|
defaultNotes,
|
||||||
defaultLegalTerms,
|
defaultLegalTerms,
|
||||||
defaultQuoteValidity,
|
defaultQuoteValidity,
|
||||||
|
defaultTax,
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
@ -76,6 +91,7 @@ class ProfileMapper
|
|||||||
default_notes: source.defaultNotes.toPrimitive(),
|
default_notes: source.defaultNotes.toPrimitive(),
|
||||||
default_legal_terms: source.defaultLegalTerms.toPrimitive(),
|
default_legal_terms: source.defaultLegalTerms.toPrimitive(),
|
||||||
default_quote_validity: source.defaultQuoteValidity.toPrimitive(),
|
default_quote_validity: source.defaultQuoteValidity.toPrimitive(),
|
||||||
|
default_tax: source.defaultTax.convertScale(2).toPrimitive(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,8 @@ export class ListQuotesUseCase implements IUseCase<IListQuotesParams, Promise<Li
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(queryCriteria?.toJSON());
|
||||||
|
|
||||||
return this.findQuotes(queryCriteria);
|
return this.findQuotes(queryCriteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -87,9 +87,17 @@ export class QuoteRepository extends SequelizeRepository<Quote> implements IQuot
|
|||||||
return this.mapper.mapToDomain(rawQuote);
|
return this.mapper.mapToDomain(rawQuote);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<any>> {
|
public async findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<Quote>> {
|
||||||
|
const QuoteItem_Model: ModelDefined<any, any> = this._adapter.getModel("QuoteItem_Model");
|
||||||
|
|
||||||
const { rows, count } = await this._findAll("Quote_Model", queryCriteria, {
|
const { rows, count } = await this._findAll("Quote_Model", queryCriteria, {
|
||||||
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
include: [], // esto es para quitar las asociaciones al hacer la consulta
|
||||||
|
/*include: [
|
||||||
|
{
|
||||||
|
model: QuoteItem_Model,
|
||||||
|
as: "items",
|
||||||
|
},
|
||||||
|
],*/
|
||||||
order: [
|
order: [
|
||||||
["date", "DESC"],
|
["date", "DESC"],
|
||||||
["customer_information", "ASC"],
|
["customer_information", "ASC"],
|
||||||
|
|||||||
@ -32,6 +32,7 @@ class DealerMapper
|
|||||||
["default_notes", source.default_notes],
|
["default_notes", source.default_notes],
|
||||||
["default_legal_terms", source.default_legal_terms],
|
["default_legal_terms", source.default_legal_terms],
|
||||||
["default_quote_validity", source.default_quote_validity],
|
["default_quote_validity", source.default_quote_validity],
|
||||||
|
["default_tax", source.default_tax],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const props: IDealerProps = {
|
const props: IDealerProps = {
|
||||||
@ -69,6 +70,7 @@ class DealerMapper
|
|||||||
default_notes: source.additionalInfo.get("default_notes")?.toString() ?? "",
|
default_notes: source.additionalInfo.get("default_notes")?.toString() ?? "",
|
||||||
default_legal_terms: source.additionalInfo.get("default_legal_terms")?.toString() ?? "",
|
default_legal_terms: source.additionalInfo.get("default_legal_terms")?.toString() ?? "",
|
||||||
default_quote_validity: source.additionalInfo.get("default_quote_validity")?.toString() ?? "",
|
default_quote_validity: source.additionalInfo.get("default_quote_validity")?.toString() ?? "",
|
||||||
|
default_tax: source.additionalInfo.get("default_tax")?.toString() ?? "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export class Dealer_Model extends Model<
|
|||||||
declare default_notes: CreationOptional<string>;
|
declare default_notes: CreationOptional<string>;
|
||||||
declare default_legal_terms: CreationOptional<string>;
|
declare default_legal_terms: CreationOptional<string>;
|
||||||
declare default_quote_validity: CreationOptional<string>;
|
declare default_quote_validity: CreationOptional<string>;
|
||||||
|
declare default_tax: CreationOptional<number | null>;
|
||||||
declare status: CreationOptional<string>;
|
declare status: CreationOptional<string>;
|
||||||
declare lang_code: CreationOptional<string>;
|
declare lang_code: CreationOptional<string>;
|
||||||
declare currency_code: CreationOptional<string>;
|
declare currency_code: CreationOptional<string>;
|
||||||
@ -85,6 +86,12 @@ export default (sequelize: Sequelize) => {
|
|||||||
default_legal_terms: DataTypes.TEXT,
|
default_legal_terms: DataTypes.TEXT,
|
||||||
default_quote_validity: DataTypes.TEXT,
|
default_quote_validity: DataTypes.TEXT,
|
||||||
|
|
||||||
|
default_tax: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: 2100,
|
||||||
|
},
|
||||||
|
|
||||||
lang_code: {
|
lang_code: {
|
||||||
type: DataTypes.STRING(2),
|
type: DataTypes.STRING(2),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export const INITIAL_PAGE_INDEX = 0;
|
export const INITIAL_PAGE_INDEX = 0;
|
||||||
export const INITIAL_PAGE_SIZE = 5;
|
export const INITIAL_PAGE_SIZE = 10;
|
||||||
|
|
||||||
export const MIN_PAGE_INDEX = 0;
|
export const MIN_PAGE_INDEX = 0;
|
||||||
export const MIN_PAGE_SIZE = 1;
|
export const MIN_PAGE_SIZE = 1;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { IPercentage_DTO } from "../../../../common";
|
||||||
|
|
||||||
export interface IGetProfileResponse_DTO {
|
export interface IGetProfileResponse_DTO {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -12,6 +14,7 @@ export interface IGetProfileResponse_DTO {
|
|||||||
default_notes: string;
|
default_notes: string;
|
||||||
default_legal_terms: string;
|
default_legal_terms: string;
|
||||||
default_quote_validity: string;
|
default_quote_validity: string;
|
||||||
|
default_tax: IPercentage_DTO;
|
||||||
status: string;
|
status: string;
|
||||||
lang_code: string;
|
lang_code: string;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import { Result, RuleValidator } from "../../../../common";
|
import { IPercentage_DTO, Result, RuleValidator } from "../../../../common";
|
||||||
|
|
||||||
export interface IUpdateProfile_Request_DTO {
|
export interface IUpdateProfile_Request_DTO {
|
||||||
contact_information: string;
|
contact_information: string;
|
||||||
@ -7,6 +7,7 @@ export interface IUpdateProfile_Request_DTO {
|
|||||||
default_notes: string;
|
default_notes: string;
|
||||||
default_legal_terms: string;
|
default_legal_terms: string;
|
||||||
default_quote_validity: string;
|
default_quote_validity: string;
|
||||||
|
default_tax: IPercentage_DTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_Request_DTO) {
|
export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_Request_DTO) {
|
||||||
@ -16,6 +17,10 @@ export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_R
|
|||||||
default_notes: Joi.string().optional().allow(null).allow("").default(""),
|
default_notes: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
default_legal_terms: Joi.string().optional().allow(null).allow("").default(""),
|
default_legal_terms: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
default_quote_validity: Joi.string().optional().allow(null).allow("").default(""),
|
default_quote_validity: Joi.string().optional().allow(null).allow("").default(""),
|
||||||
|
default_tax: Joi.object({
|
||||||
|
amount: Joi.number().allow(null),
|
||||||
|
scale: Joi.number(),
|
||||||
|
}).optional(),
|
||||||
}).unknown(true);
|
}).unknown(true);
|
||||||
|
|
||||||
const result = RuleValidator.validate<IUpdateProfile_Request_DTO>(schema, userDTO);
|
const result = RuleValidator.validate<IUpdateProfile_Request_DTO>(schema, userDTO);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { IPercentage_DTO } from "../../../../common";
|
||||||
|
|
||||||
export interface IUpdateProfileResponse_DTO {
|
export interface IUpdateProfileResponse_DTO {
|
||||||
dealer_id: string;
|
dealer_id: string;
|
||||||
contact_information: string;
|
contact_information: string;
|
||||||
@ -5,4 +7,5 @@ export interface IUpdateProfileResponse_DTO {
|
|||||||
default_notes: string;
|
default_notes: string;
|
||||||
default_legal_terms: string;
|
default_legal_terms: string;
|
||||||
default_quote_validity: string;
|
default_quote_validity: string;
|
||||||
|
default_tax: IPercentage_DTO;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user