Añadir el IVA como un campo más en ajustes

This commit is contained in:
David Arranz 2024-08-16 16:57:44 +02:00
parent 990e0850f2
commit 0ff5c39023
20 changed files with 263 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": {

View File

@ -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": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,6 +41,8 @@ export class ListQuotesUseCase implements IUseCase<IListQuotesParams, Promise<Li
); );
} }
console.log(queryCriteria?.toJSON());
return this.findQuotes(queryCriteria); return this.findQuotes(queryCriteria);
} }

View File

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

View File

@ -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() ?? "",
}; };
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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