.
This commit is contained in:
parent
f77b6adb99
commit
d405ce40d6
@ -15,18 +15,18 @@ import {
|
|||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import { joiResolver } from "@hookform/resolvers/joi";
|
import { joiResolver } from "@hookform/resolvers/joi";
|
||||||
import { ILogin_DTO } from "@shared/contexts";
|
import { ILogin_DTO } from "@shared/contexts";
|
||||||
|
import { t } from "i18next";
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import { AlertCircleIcon } from "lucide-react";
|
import { AlertCircleIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SubmitHandler, useForm } from "react-hook-form";
|
import { SubmitHandler, useForm } from "react-hook-form";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import SpanishJoiMessages from "../../spanish-joi-messages.json";
|
import SpanishJoiMessages from "../../spanish-joi-messages.json";
|
||||||
|
|
||||||
type LoginDataForm = ILogin_DTO;
|
type LoginDataForm = ILogin_DTO;
|
||||||
|
|
||||||
export const LoginPage = () => {
|
export const LoginPage = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { mutate: login } = useLogin({
|
const { mutate: login } = useLogin({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@ -121,7 +121,7 @@ export const LoginPage = () => {
|
|||||||
<Alert variant='destructive'>
|
<Alert variant='destructive'>
|
||||||
<AlertCircleIcon className='w-4 h-4' />
|
<AlertCircleIcon className='w-4 h-4' />
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
<Trans i18nKey='login_page.error' />
|
<Trans i18nKey='common.error' />
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
|
<AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
51
client/src/app/settings/SettingsActions.ts
Normal file
51
client/src/app/settings/SettingsActions.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useOne } from '@/lib/hooks/useDataSource';
|
||||||
|
import { IDataSource } from '@/lib/hooks/useDataSource/DataSource';
|
||||||
|
|
||||||
|
export type SuccessNotificationResponse = {
|
||||||
|
message: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PermissionResponse = unknown;
|
||||||
|
|
||||||
|
export type IdentityResponse = unknown;
|
||||||
|
|
||||||
|
export type CatalogActionCheckResponse = {
|
||||||
|
authenticated: boolean;
|
||||||
|
redirectTo?: string;
|
||||||
|
logout?: boolean;
|
||||||
|
error?: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CatalogActionOnErrorResponse = {
|
||||||
|
redirectTo?: string;
|
||||||
|
logout?: boolean;
|
||||||
|
error?: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CatalogActionResponse = {
|
||||||
|
success: boolean;
|
||||||
|
redirectTo?: string;
|
||||||
|
error?: Error;
|
||||||
|
[key: string]: unknown;
|
||||||
|
successNotification?: SuccessNotificationResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ISettingsActions {
|
||||||
|
getSettings: (
|
||||||
|
dataSource: IDataSource,
|
||||||
|
): Promise<IGetProfileResponse_DTO> => {
|
||||||
|
|
||||||
|
return useOne(
|
||||||
|
|
||||||
|
)
|
||||||
|
return dataProvider.getList({
|
||||||
|
resource: "invoices",
|
||||||
|
quickSearchTerm,
|
||||||
|
pagination,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
9
client/src/app/settings/SettingsContext.tsx
Normal file
9
client/src/app/settings/SettingsContext.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { PropsWithChildren, createContext } from "react";
|
||||||
|
|
||||||
|
export interface ISettingsContextState {}
|
||||||
|
|
||||||
|
export const SettingsContext = createContext<ISettingsContextState | null>(null);
|
||||||
|
|
||||||
|
export const SettingsProvider = ({ children }: PropsWithChildren) => {
|
||||||
|
return <SettingsContext.Provider value={{}}>{children}</SettingsContext.Provider>;
|
||||||
|
};
|
||||||
@ -1,4 +1,8 @@
|
|||||||
|
import { FormTextAreaField } from "@/components";
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertDescription,
|
||||||
|
AlertTitle,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@ -6,67 +10,227 @@ import {
|
|||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
Input,
|
Form,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
|
|
||||||
import { Checkbox } from "@radix-ui/react-checkbox";
|
import { t } from "i18next";
|
||||||
|
import { AlertCircleIcon } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { SubmitHandler, useForm } from "react-hook-form";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useSettings } from "./hooks";
|
||||||
|
|
||||||
|
type SettingsDataForm = {
|
||||||
|
contact_information: string;
|
||||||
|
default_payment_method: string;
|
||||||
|
default_notes: string;
|
||||||
|
default_legal_terms: string;
|
||||||
|
default_quote_validity: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const SettingsEditor = () => {
|
export const SettingsEditor = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
query: { data },
|
||||||
|
mutation: { mutate },
|
||||||
|
} = useSettings();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
const form = useForm<SettingsDataForm>({
|
||||||
|
mode: "onBlur",
|
||||||
|
defaultValues: {
|
||||||
|
contact_information: data?.contact_information ?? "",
|
||||||
|
default_payment_method: data?.default_payment_method ?? "",
|
||||||
|
default_notes: data?.default_notes ?? "",
|
||||||
|
default_legal_terms: data?.default_legal_terms ?? "",
|
||||||
|
default_quote_validity: data?.default_quote_validity ?? "",
|
||||||
|
},
|
||||||
|
/*resolver: joiResolver(
|
||||||
|
Joi.object({
|
||||||
|
email: Joi.string()
|
||||||
|
.email({ tlds: { allow: false } })
|
||||||
|
.required(),
|
||||||
|
password: Joi.string().min(4).alphanum().required(),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
messages: SpanishJoiMessages,
|
||||||
|
}
|
||||||
|
),*/
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<SettingsDataForm> = async (data) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
console.log(data);
|
||||||
|
mutate(data);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'>
|
<Form {...form}>
|
||||||
<nav className='grid gap-4 text-sm text-muted-foreground' x-chunk='dashboard-04-chunk-0'>
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
<Link to='#' className='font-semibold text-primary'>
|
<div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'>
|
||||||
General
|
{form.formState.errors.root?.message && (
|
||||||
</Link>
|
<Alert variant='destructive'>
|
||||||
<Link to='#'>Security</Link>
|
<AlertCircleIcon className='w-4 h-4' />
|
||||||
<Link to='#'>Integrations</Link>
|
<AlertTitle>
|
||||||
<Link to='#'>Support</Link>
|
<Trans i18nKey='common.error' />
|
||||||
<Link to='#'>Organizations</Link>
|
</AlertTitle>
|
||||||
<Link to='#'>Advanced</Link>
|
<AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
|
||||||
</nav>
|
</Alert>
|
||||||
<div className='grid gap-6'>
|
)}
|
||||||
<Card x-chunk='dashboard-04-chunk-1'>
|
|
||||||
<CardHeader>
|
<nav className='grid gap-4 text-sm text-muted-foreground'>
|
||||||
<CardTitle>Store Name</CardTitle>
|
<Link to='#' className='font-semibold text-primary'>
|
||||||
<CardDescription>Used to identify your store in the marketplace.</CardDescription>
|
<Trans i18nKey='settings.quotes.title' />
|
||||||
</CardHeader>
|
</Link>
|
||||||
<CardContent>
|
<Link to='#'>
|
||||||
<form>
|
<Trans i18nKey='settings.quotes.general' />
|
||||||
<Input placeholder='Store Name' />
|
</Link>
|
||||||
</form>
|
<Link to='#'>Integrations</Link>
|
||||||
</CardContent>
|
<Link to='#'>Support</Link>
|
||||||
<CardFooter className='px-6 py-4 border-t'>
|
<Link to='#'>Organizations</Link>
|
||||||
<Button>Save</Button>
|
<Link to='#'>Advanced</Link>
|
||||||
</CardFooter>
|
</nav>
|
||||||
</Card>
|
<div className='grid gap-6'>
|
||||||
<Card x-chunk='dashboard-04-chunk-2'>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Plugins Directory</CardTitle>
|
<CardTitle>
|
||||||
<CardDescription>
|
<Trans i18nKey='settings.quotes.contact_information.label' />
|
||||||
The directory within your project, in which your plugins are located.
|
</CardTitle>
|
||||||
</CardDescription>
|
<CardDescription>
|
||||||
</CardHeader>
|
<Trans i18nKey='settings.quotes.contact_information.desc' />
|
||||||
<CardContent>
|
</CardDescription>
|
||||||
<form className='flex flex-col gap-4'>
|
</CardHeader>
|
||||||
<Input placeholder='Project Name' defaultValue='/content/plugins' />
|
<CardContent>
|
||||||
<div className='flex items-center space-x-2'>
|
<FormTextAreaField
|
||||||
<Checkbox id='include' defaultChecked />
|
disabled={loading}
|
||||||
<label
|
placeholder={t("settings.quotes.contact_information.placeholder")}
|
||||||
htmlFor='include'
|
{...form.register("contact_information", {
|
||||||
className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
required: true,
|
||||||
>
|
})}
|
||||||
Allow administrators to change the directory.
|
errors={form.formState.errors}
|
||||||
</label>
|
/>
|
||||||
</div>
|
</CardContent>
|
||||||
</form>
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
</CardContent>
|
<Button>
|
||||||
<CardFooter className='px-6 py-4 border-t'>
|
<Trans i18nKey='common.save' />
|
||||||
<Button>Save</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
|
||||||
</div>
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
<Trans i18nKey='settings.quotes.default_payment_method.label' />
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<Trans i18nKey='settings.quotes.default_payment_method.desc' />
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<FormTextAreaField
|
||||||
|
disabled={loading}
|
||||||
|
placeholder={t("settings.quotes.default_payment_method.placeholder")}
|
||||||
|
{...form.register("default_payment_method", {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
errors={form.formState.errors}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
|
<Button>
|
||||||
|
<Trans i18nKey='common.save' />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
<Trans i18nKey='settings.quotes.default_quote_validity.label' />
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<Trans i18nKey='settings.quotes.default_quote_validity.desc' />
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<FormTextAreaField
|
||||||
|
disabled={loading}
|
||||||
|
placeholder={t("settings.quotes.default_quote_validity.placeholder")}
|
||||||
|
{...form.register("default_quote_validity", {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
errors={form.formState.errors}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
|
<Button>
|
||||||
|
<Trans i18nKey='common.save' />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
<Trans i18nKey='settings.quotes.default_notes.label' />
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<Trans i18nKey='settings.quotes.default_notes.desc' />
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<FormTextAreaField
|
||||||
|
disabled={loading}
|
||||||
|
placeholder={t("settings.quotes.default_notes.placeholder")}
|
||||||
|
{...form.register("default_notes", {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
errors={form.formState.errors}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
|
<Button>
|
||||||
|
<Trans i18nKey='common.save' />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
<Trans i18nKey='settings.quotes.default_legal_terms.label' />
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<Trans i18nKey='settings.quotes.default_legal_terms.desc' />
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<FormTextAreaField
|
||||||
|
disabled={loading}
|
||||||
|
placeholder={t("settings.quotes.default_legal_terms.placeholder")}
|
||||||
|
{...form.register("default_legal_terms", {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
errors={form.formState.errors}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className='px-6 py-4 border-t'>
|
||||||
|
<Button>
|
||||||
|
<Trans i18nKey='common.save' />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
1
client/src/app/settings/hooks/index.ts
Normal file
1
client/src/app/settings/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./useSettings";
|
||||||
35
client/src/app/settings/hooks/useSettings.tsx
Normal file
35
client/src/app/settings/hooks/useSettings.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { useOne, useSave } from "@/lib/hooks/useDataSource";
|
||||||
|
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
|
||||||
|
import { useQueryKey } from "@/lib/hooks/useQueryKey";
|
||||||
|
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export type UseSettingsGetParamsType = {
|
||||||
|
enabled?: boolean;
|
||||||
|
queryOptions?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSettings = (params?: UseSettingsGetParamsType) => {
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const keys = useQueryKey();
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: useOne<IGetProfileResponse_DTO>({
|
||||||
|
queryKey: keys().data().resource("settings").action("one").id("").params().get(),
|
||||||
|
queryFn: () =>
|
||||||
|
dataSource.getOne({
|
||||||
|
resource: "profile",
|
||||||
|
id: "",
|
||||||
|
}),
|
||||||
|
...params,
|
||||||
|
}),
|
||||||
|
mutation: useSave({
|
||||||
|
mutationKey: keys().data().resource("settings").action("one").id("").params().get(),
|
||||||
|
mutationFn: (data) =>
|
||||||
|
dataSource.updateOne({
|
||||||
|
resource: "profile",
|
||||||
|
data,
|
||||||
|
id: "",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
9
client/src/app/settings/useSettingsContext.tsx
Normal file
9
client/src/app/settings/useSettingsContext.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { SettingsContext } from "./SettingsContext";
|
||||||
|
|
||||||
|
export const useSettingsContext = () => {
|
||||||
|
const context = useContext(SettingsContext);
|
||||||
|
if (context === null)
|
||||||
|
throw new Error("useSettingsContext must be used within a SettingsProvider");
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@ -21,12 +21,14 @@ export type UseOneQueryOptions<TUseOneQueryData, TUseOneQueryError> = {
|
|||||||
queryOptions?: Record<string, unknown>;
|
queryOptions?: Record<string, unknown>;
|
||||||
} & UseLoadingOvertimeOptionsProps;
|
} & UseLoadingOvertimeOptionsProps;
|
||||||
|
|
||||||
export type UseOneQueryResult<TUseOneQueryData, TUseOneQueryError> =
|
export type UseOneQueryResult<TUseOneQueryData, TUseOneQueryError> = UseQueryResult<
|
||||||
UseQueryResult<TUseOneQueryData, TUseOneQueryError> & {
|
TUseOneQueryData,
|
||||||
isEmpty: boolean;
|
TUseOneQueryError
|
||||||
} & UseLoadingOvertimeReturnType;
|
> & {
|
||||||
|
isEmpty: boolean;
|
||||||
|
} & UseLoadingOvertimeReturnType;
|
||||||
|
|
||||||
export function useOne<TUseOneQueryData, TUseOneQueryError>({
|
export function useOne<TUseOneQueryData, TUseOneQueryError = Error>({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn,
|
queryFn,
|
||||||
enabled,
|
enabled,
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"yes": "Sí",
|
"yes": "Sí",
|
||||||
|
"save": "Guardar",
|
||||||
"accept": "Aceptar",
|
"accept": "Aceptar",
|
||||||
"hide": "Ocultar",
|
"hide": "Ocultar",
|
||||||
"sort_asc": "Asc",
|
"sort_asc": "Asc",
|
||||||
@ -18,7 +19,8 @@
|
|||||||
"go_to_prev_page": "Ir a la página anterior",
|
"go_to_prev_page": "Ir a la página anterior",
|
||||||
"go_to_next_page": "Ir a la página siguiente",
|
"go_to_next_page": "Ir a la página siguiente",
|
||||||
"go_to_last_page": "Ir a la última página",
|
"go_to_last_page": "Ir a la última página",
|
||||||
"reset_filter": "Quitar el filtro"
|
"reset_filter": "Quitar el filtro",
|
||||||
|
"error": "Error"
|
||||||
},
|
},
|
||||||
"main_menu": {
|
"main_menu": {
|
||||||
"home": "Inicio",
|
"home": "Inicio",
|
||||||
@ -46,8 +48,7 @@
|
|||||||
"forgotten_password": "¿Has olvidado tu contraseña?",
|
"forgotten_password": "¿Has olvidado tu contraseña?",
|
||||||
"become_dealer": "¿Quieres ser distribuidor de Uecko?",
|
"become_dealer": "¿Quieres ser distribuidor de Uecko?",
|
||||||
"contact_us": "Contacta con nosotros",
|
"contact_us": "Contacta con nosotros",
|
||||||
"login": "Entrar",
|
"login": "Entrar"
|
||||||
"error": "Error"
|
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"welcome": "Bienvenido"
|
"welcome": "Bienvenido"
|
||||||
@ -64,7 +65,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Ajustes"
|
"title": "Ajustes",
|
||||||
|
"quotes": {
|
||||||
|
"title": "Cotizaciones",
|
||||||
|
"contact_information": {
|
||||||
|
"label": "Información de contacto",
|
||||||
|
"placeholder": "placeholder",
|
||||||
|
"desc": "Información de contacto"
|
||||||
|
},
|
||||||
|
"default_payment_method": {
|
||||||
|
"label": "Forma de pago",
|
||||||
|
"placeholder": "placeholder",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
|
"default_notes": {
|
||||||
|
"label": "Notas",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
|
"default_legal_terms": {
|
||||||
|
"label": "Cláusulas legales",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "desc"
|
||||||
|
},
|
||||||
|
"default_quote_validity": {
|
||||||
|
"label": "Validez por defecto",
|
||||||
|
"placeholder": "",
|
||||||
|
"desc": "desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,6 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"],
|
||||||
"src",
|
|
||||||
"../shared/lib/contexts/common/domain/entities/QueryCriteria/Pagination/defaults.ts"
|
|
||||||
],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
IUseCase,
|
||||||
|
IUseCaseError,
|
||||||
|
IUseCaseRequest,
|
||||||
|
UseCaseError,
|
||||||
|
} from "@/contexts/common/application/useCases";
|
||||||
|
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||||
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
|
|
||||||
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
|
import { IProfileRepository, Profile } from "../domain";
|
||||||
|
|
||||||
|
export interface IGetProfileUseCaseRequest extends IUseCaseRequest {
|
||||||
|
userId: UniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetProfileResponseOrError =
|
||||||
|
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||||
|
| Result<Profile, never>; // Success!
|
||||||
|
|
||||||
|
export class GetProfileUseCase
|
||||||
|
implements IUseCase<IGetProfileUseCaseRequest, Promise<GetProfileResponseOrError>>
|
||||||
|
{
|
||||||
|
private _adapter: ISequelizeAdapter;
|
||||||
|
private _repositoryManager: IRepositoryManager;
|
||||||
|
|
||||||
|
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||||
|
this._adapter = props.adapter;
|
||||||
|
this._repositoryManager = props.repositoryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRepositoryByName<T>(name: string) {
|
||||||
|
return this._repositoryManager.getRepository<T>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(request: IGetProfileUseCaseRequest): Promise<GetProfileResponseOrError> {
|
||||||
|
const { userId } = request;
|
||||||
|
|
||||||
|
// Validación de datos
|
||||||
|
// No hay en este caso
|
||||||
|
|
||||||
|
return await this._getProfileDealer(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getProfileDealer(userId: UniqueID) {
|
||||||
|
const transaction = this._adapter.startTransaction();
|
||||||
|
const dealerRepoBuilder = this.getRepositoryByName<IProfileRepository>("Profile");
|
||||||
|
|
||||||
|
let profile: Profile | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
const dealerRepo = dealerRepoBuilder({ transaction: t });
|
||||||
|
profile = await dealerRepo.getById(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!profile) {
|
||||||
|
return Result.fail(UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, "Profile not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<Profile>(profile!);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const _error = error as IInfrastructureError;
|
||||||
|
return Result.fail(
|
||||||
|
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Error al consultar el usuario", _error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getProfileRepository() {
|
||||||
|
return this._repositoryManager.getRepository<IProfileRepository>("Profile");
|
||||||
|
}
|
||||||
|
}
|
||||||
106
server/src/contexts/profile/application/UpdateProfile.useCase.ts
Normal file
106
server/src/contexts/profile/application/UpdateProfile.useCase.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
IUseCase,
|
||||||
|
IUseCaseError,
|
||||||
|
IUseCaseRequest,
|
||||||
|
UseCaseError,
|
||||||
|
} from "@/contexts/common/application";
|
||||||
|
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||||
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { DomainError, IUpdateProfile_Request_DTO, Result, UniqueID } from "@shared/contexts";
|
||||||
|
import { IProfileRepository, Profile } from "../domain";
|
||||||
|
|
||||||
|
export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest {
|
||||||
|
id: UniqueID;
|
||||||
|
profileDTO: IUpdateProfile_Request_DTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateProfileResponseOrError =
|
||||||
|
| Result<never, IUseCaseError> // Misc errors (value objects)
|
||||||
|
| Result<Profile, never>; // Success!
|
||||||
|
|
||||||
|
export class UpdateProfileUseCase
|
||||||
|
implements IUseCase<IUpdateProfileUseCaseRequest, Promise<UpdateProfileResponseOrError>>
|
||||||
|
{
|
||||||
|
private _adapter: ISequelizeAdapter;
|
||||||
|
private _repositoryManager: IRepositoryManager;
|
||||||
|
|
||||||
|
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
||||||
|
this._adapter = props.adapter;
|
||||||
|
this._repositoryManager = props.repositoryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> {
|
||||||
|
const { id, profileDTO } = request;
|
||||||
|
const profileRepository = this._getProfileRepository();
|
||||||
|
|
||||||
|
// Comprobar que existe el profile
|
||||||
|
const idExists = await profileRepository().exists(id);
|
||||||
|
if (!idExists) {
|
||||||
|
const message = `Profile not found`;
|
||||||
|
return Result.fail(
|
||||||
|
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, message, {
|
||||||
|
path: "id",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear perfil con datos actualizados
|
||||||
|
const profileOrError = Profile.create(
|
||||||
|
{
|
||||||
|
contactInformation: profileDTO.contact_information,
|
||||||
|
defaultPaymentMethod: profileDTO.default_payment_method,
|
||||||
|
defaultLegalTerms: profileDTO.default_legal_terms,
|
||||||
|
defaultNotes: profileDTO.default_notes,
|
||||||
|
defaultQuoteValidity: profileDTO.default_quote_validity,
|
||||||
|
},
|
||||||
|
id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (profileOrError.isFailure) {
|
||||||
|
const { error: domainError } = profileOrError;
|
||||||
|
let errorCode = "";
|
||||||
|
let message = "";
|
||||||
|
|
||||||
|
switch (domainError.code) {
|
||||||
|
// Errores manuales
|
||||||
|
case DomainError.INVALID_INPUT_DATA:
|
||||||
|
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
||||||
|
message = "The profile has some incorrect data";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
||||||
|
message = domainError.message;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.fail(UseCaseError.create(errorCode, message, domainError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar los cambios
|
||||||
|
return this._saveProfile(profileOrError.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _saveProfile(updatedProfile: Profile) {
|
||||||
|
// Guardar el contacto
|
||||||
|
const transaction = this._adapter.startTransaction();
|
||||||
|
const profileRepository = this._getProfileRepository();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transaction.complete(async (t) => {
|
||||||
|
const profileRepo = profileRepository({ transaction: t });
|
||||||
|
await profileRepo.update(updatedProfile);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.ok<Profile>(updatedProfile);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const _error = error as IInfrastructureError;
|
||||||
|
return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, _error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getProfileRepository() {
|
||||||
|
return this._repositoryManager.getRepository<IProfileRepository>("Profile");
|
||||||
|
}
|
||||||
|
}
|
||||||
1
server/src/contexts/profile/application/index.ts
Normal file
1
server/src/contexts/profile/application/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./UpdateProfile.useCase";
|
||||||
13
server/src/contexts/profile/application/profileServices.ts
Normal file
13
server/src/contexts/profile/application/profileServices.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { IProfileRepository, Profile } from "../domain";
|
||||||
|
|
||||||
|
export const findProfileByID = async (
|
||||||
|
id: UniqueID,
|
||||||
|
adapter: IAdapter,
|
||||||
|
repository: RepositoryBuilder<IProfileRepository>
|
||||||
|
): Promise<Profile | null> => {
|
||||||
|
return await adapter
|
||||||
|
.startTransaction()
|
||||||
|
.complete(async (t) => repository({ transaction: t }).getById(id));
|
||||||
|
};
|
||||||
45
server/src/contexts/profile/domain/entities/Profile.ts
Normal file
45
server/src/contexts/profile/domain/entities/Profile.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { AggregateRoot, IDomainError, Result, UniqueID } from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IProfileProps {
|
||||||
|
contactInformation: string;
|
||||||
|
defaultPaymentMethod: string;
|
||||||
|
defaultNotes: string;
|
||||||
|
defaultLegalTerms: string;
|
||||||
|
defaultQuoteValidity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProfile {
|
||||||
|
id: UniqueID;
|
||||||
|
contactInformation: string;
|
||||||
|
defaultPaymentMethod: string;
|
||||||
|
defaultNotes: string;
|
||||||
|
defaultLegalTerms: string;
|
||||||
|
defaultQuoteValidity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
||||||
|
public static create(props: IProfileProps, id?: UniqueID): Result<Profile, IDomainError> {
|
||||||
|
const profile = new Profile(props, id);
|
||||||
|
return Result.ok<Profile>(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
get contactInformation(): string {
|
||||||
|
return this.props.contactInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultPaymentMethod(): string {
|
||||||
|
return this.props.defaultPaymentMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultNotes(): string {
|
||||||
|
return this.props.defaultNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultLegalTerms(): string {
|
||||||
|
return this.props.defaultLegalTerms;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultQuoteValidity(): string {
|
||||||
|
return this.props.defaultQuoteValidity;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
server/src/contexts/profile/domain/entities/index.ts
Normal file
1
server/src/contexts/profile/domain/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./Profile";
|
||||||
2
server/src/contexts/profile/domain/index.ts
Normal file
2
server/src/contexts/profile/domain/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./entities";
|
||||||
|
export * from "./repository";
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { IRepository } from "@/contexts/common/domain";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { Profile } from "../entities";
|
||||||
|
|
||||||
|
export interface IProfileRepository extends IRepository<any> {
|
||||||
|
exists(id: UniqueID): Promise<boolean>;
|
||||||
|
getById(id: UniqueID): Promise<Profile | null>;
|
||||||
|
update(profile: Profile): Promise<void>;
|
||||||
|
}
|
||||||
1
server/src/contexts/profile/domain/repository/index.ts
Normal file
1
server/src/contexts/profile/domain/repository/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./ProfileRepository.interface";
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import { ISequelizeAdapter, SequelizeRepository } from "@/contexts/common/infrastructure/sequelize";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { Transaction } from "sequelize";
|
||||||
|
import { IProfileContext } from ".";
|
||||||
|
import { IProfileRepository, Profile } from "../domain";
|
||||||
|
import { IProfileMapper, createProfileMapper } from "./mappers";
|
||||||
|
|
||||||
|
export class ProfileRepository extends SequelizeRepository<Profile> implements IProfileRepository {
|
||||||
|
protected mapper: IProfileMapper;
|
||||||
|
|
||||||
|
public constructor(props: {
|
||||||
|
mapper: IProfileMapper;
|
||||||
|
adapter: ISequelizeAdapter;
|
||||||
|
transaction: Transaction;
|
||||||
|
}) {
|
||||||
|
const { adapter, mapper, transaction } = props;
|
||||||
|
super({ adapter, transaction });
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exists(id: UniqueID): Promise<boolean> {
|
||||||
|
return this._exists("Profile_Model", "id", id.toPrimitive());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getById(id: UniqueID): Promise<Profile | null> {
|
||||||
|
const rawProfile: any = await this._getById("Profile_Model", id);
|
||||||
|
|
||||||
|
if (!rawProfile === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.mapToDomain(rawProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(profile: Profile): Promise<void> {
|
||||||
|
const userData = this.mapper.mapToPersistence(profile);
|
||||||
|
|
||||||
|
// borrando y luego creando
|
||||||
|
// await this.removeById(user.id, true);
|
||||||
|
await this._save("Dealer_Model", profile.id, userData, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerProfileRepository = (context: IProfileContext) => {
|
||||||
|
const adapter = context.adapter;
|
||||||
|
const repoManager = context.repositoryManager;
|
||||||
|
|
||||||
|
repoManager.registerRepository("Profile", (params = { transaction: null }) => {
|
||||||
|
const { transaction } = params;
|
||||||
|
|
||||||
|
return new ProfileRepository({
|
||||||
|
transaction,
|
||||||
|
adapter,
|
||||||
|
mapper: createProfileMapper(context),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,23 +1,22 @@
|
|||||||
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
|
||||||
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
import { User } from "@/contexts/users/domain/entities/User";
|
import { User } from "@/contexts/users/domain/entities/User";
|
||||||
import { IGetUserResponse_DTO } from "@shared/contexts";
|
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
import { IServerError } from "@/contexts/common/domain/errors";
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
import { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
import { GetProfileUseCase } from "@/contexts/profile/application/GetProfile.useCase";
|
||||||
import { Dealer } from "@/contexts/sales/domain";
|
|
||||||
import { IProfileContext } from "../../../Profile.context";
|
import { IProfileContext } from "../../../Profile.context";
|
||||||
import { IGetProfilePresenter } from "./presenter";
|
import { IGetProfilePresenter } from "./presenter";
|
||||||
|
|
||||||
export class GetProfileController extends ExpressController {
|
export class GetProfileController extends ExpressController {
|
||||||
private useCase: GetDealerByUserUseCase;
|
private useCase: GetProfileUseCase;
|
||||||
private presenter: IGetProfilePresenter;
|
private presenter: IGetProfilePresenter;
|
||||||
private context: IProfileContext;
|
private context: IProfileContext;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
props: {
|
props: {
|
||||||
useCase: GetDealerByUserUseCase;
|
useCase: GetProfileUseCase;
|
||||||
presenter: IGetProfilePresenter;
|
presenter: IGetProfilePresenter;
|
||||||
},
|
},
|
||||||
context: IProfileContext
|
context: IProfileContext
|
||||||
@ -51,9 +50,9 @@ export class GetProfileController extends ExpressController {
|
|||||||
return this._handleExecuteError(result.error);
|
return this._handleExecuteError(result.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dealer = <Dealer>result.object;
|
const profile = result.object;
|
||||||
|
|
||||||
return this.ok<IGetUserResponse_DTO>(this.presenter.map(user, dealer, this.context));
|
return this.ok<IGetProfileResponse_DTO>(this.presenter.map(profile, this.context));
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
return this.fail(e as IServerError);
|
return this.fail(e as IServerError);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import { GetDealerByUserUseCase } from "@/contexts/sales/application";
|
import { GetProfileUseCase } from "@/contexts/profile/application/GetProfile.useCase";
|
||||||
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
|
||||||
import { IProfileContext } from "../../../Profile.context";
|
import { IProfileContext } from "../../../Profile.context";
|
||||||
|
import { registerProfileRepository } from "../../../Profile.repository";
|
||||||
import { GetProfileController } from "./GetProfile.controller";
|
import { GetProfileController } from "./GetProfile.controller";
|
||||||
import { GetUserPresenter } from "./presenter";
|
import { GetProfilePresenter } from "./presenter";
|
||||||
|
|
||||||
export const createGetProfileController = (context: IProfileContext) => {
|
export const createGetProfileController = (context: IProfileContext) => {
|
||||||
registerDealerRepository(context);
|
registerProfileRepository(context);
|
||||||
|
|
||||||
return new GetProfileController(
|
return new GetProfileController(
|
||||||
{
|
{
|
||||||
useCase: new GetDealerByUserUseCase(context),
|
useCase: new GetProfileUseCase(context),
|
||||||
presenter: GetUserPresenter,
|
presenter: GetProfilePresenter,
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { Profile } from "@/contexts/profile/domain";
|
||||||
|
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
||||||
|
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IGetProfilePresenter {
|
||||||
|
map: (profile: Profile, context: IProfileContext) => IGetProfileResponse_DTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetProfilePresenter: IGetProfilePresenter = {
|
||||||
|
map: (profile: Profile, context: IProfileContext): IGetProfileResponse_DTO => {
|
||||||
|
return {
|
||||||
|
id: profile.id.toString(),
|
||||||
|
contact_information: profile.contactInformation,
|
||||||
|
default_payment_method: profile.defaultPaymentMethod,
|
||||||
|
default_notes: profile.defaultNotes,
|
||||||
|
default_legal_terms: profile.defaultLegalTerms,
|
||||||
|
default_quote_validity: profile.defaultQuoteValidity,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
|
||||||
import { Dealer } from "@/contexts/sales/domain";
|
|
||||||
import { User } from "@/contexts/users/domain";
|
|
||||||
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
|
||||||
|
|
||||||
export interface IGetProfilePresenter {
|
|
||||||
map: (user: User, dealer: Dealer, context: IProfileContext) => IGetProfileResponse_DTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GetUserPresenter: IGetProfilePresenter = {
|
|
||||||
map: (user: User, dealer: Dealer, context: IProfileContext): IGetProfileResponse_DTO => {
|
|
||||||
return {
|
|
||||||
id: user.id.toString(),
|
|
||||||
name: user.name.toString(),
|
|
||||||
email: user.email.toString(),
|
|
||||||
language: "es",
|
|
||||||
roles: user.getRoles().map((rol) => rol.toString()),
|
|
||||||
contact_information: "",
|
|
||||||
default_payment_method: "",
|
|
||||||
default_notes: "",
|
|
||||||
default_legal_terms: "",
|
|
||||||
default_quote_validity: "",
|
|
||||||
status: "active",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1 +1 @@
|
|||||||
export * from "./GetUser.presenter";
|
export * from "./GetProfile.presenter";
|
||||||
|
|||||||
@ -0,0 +1,135 @@
|
|||||||
|
import { IUseCaseError, UseCaseError } from "@/contexts/common/application";
|
||||||
|
import { IServerError } from "@/contexts/common/domain/errors";
|
||||||
|
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
|
import { ExpressController } from "@/contexts/common/infrastructure/express";
|
||||||
|
import { UpdateProfileUseCase } from "@/contexts/profile/application";
|
||||||
|
import { User } from "@/contexts/users/domain";
|
||||||
|
import {
|
||||||
|
IUpdateProfileResponse_DTO,
|
||||||
|
IUpdateProfile_Request_DTO,
|
||||||
|
ensureUpdateProfile_Request_DTOIsValid,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import { IProfileContext } from "../../../Profile.context";
|
||||||
|
import { IUpdateProfilePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export class UpdateProfileController extends ExpressController {
|
||||||
|
private useCase: UpdateProfileUseCase;
|
||||||
|
private presenter: IUpdateProfilePresenter;
|
||||||
|
private context: IProfileContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
props: {
|
||||||
|
useCase: UpdateProfileUseCase;
|
||||||
|
presenter: IUpdateProfilePresenter;
|
||||||
|
},
|
||||||
|
context: IProfileContext
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const { useCase, presenter } = props;
|
||||||
|
this.useCase = useCase;
|
||||||
|
this.presenter = presenter;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeImpl() {
|
||||||
|
const user = <User | undefined>this.req.user;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
const errorMessage = "Unexpected missing Profile data";
|
||||||
|
const infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.UNEXCEPTED_ERROR,
|
||||||
|
errorMessage
|
||||||
|
);
|
||||||
|
return this.internalServerError(errorMessage, infraError);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const profileDTO: IUpdateProfile_Request_DTO = this.req.body;
|
||||||
|
|
||||||
|
// Validar DTO de datos
|
||||||
|
const ProfileDTOOrError = ensureUpdateProfile_Request_DTOIsValid(profileDTO);
|
||||||
|
|
||||||
|
if (ProfileDTOOrError.isFailure) {
|
||||||
|
const errorMessage = "Profile data not valid";
|
||||||
|
const infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.INVALID_INPUT_DATA,
|
||||||
|
errorMessage,
|
||||||
|
ProfileDTOOrError.error
|
||||||
|
);
|
||||||
|
return this.invalidInputError(errorMessage, infraError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llamar al caso de uso
|
||||||
|
const result = await this.useCase.execute({
|
||||||
|
id: user.id,
|
||||||
|
profileDTO,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return this._handleExecuteError(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = result.object;
|
||||||
|
|
||||||
|
return this.ok<IUpdateProfileResponse_DTO>(this.presenter.map(profile, this.context));
|
||||||
|
} 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 = "Profile has no associated profile";
|
||||||
|
|
||||||
|
infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.RESOURCE_NOT_FOUND_ERROR,
|
||||||
|
errorMessage,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.notFoundError(errorMessage, infraError);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UseCaseError.INVALID_INPUT_DATA:
|
||||||
|
errorMessage = "Profile data not valid";
|
||||||
|
|
||||||
|
infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.INVALID_INPUT_DATA,
|
||||||
|
"Datos a actualizar erróneos",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return this.invalidInputError(errorMessage, infraError);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UseCaseError.REPOSITORY_ERROR:
|
||||||
|
errorMessage = "Error updating profile";
|
||||||
|
infraError = InfrastructureError.create(
|
||||||
|
InfrastructureError.UNEXCEPTED_ERROR,
|
||||||
|
errorMessage,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return this.conflictError(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { UpdateProfileUseCase } from "@/contexts/profile/application";
|
||||||
|
import { registerDealerRepository } from "@/contexts/sales/infrastructure/Dealer.repository";
|
||||||
|
import { IProfileContext } from "../../../Profile.context";
|
||||||
|
import { UpdateProfileController } from "./UpdateProfile.controller";
|
||||||
|
import { UpdateProfilePresenter } from "./presenter";
|
||||||
|
|
||||||
|
export const createUpdateProfileController = (context: IProfileContext) => {
|
||||||
|
registerDealerRepository(context);
|
||||||
|
|
||||||
|
return new UpdateProfileController(
|
||||||
|
{
|
||||||
|
useCase: new UpdateProfileUseCase(context),
|
||||||
|
presenter: UpdateProfilePresenter,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { Profile } from "@/contexts/profile/domain";
|
||||||
|
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
||||||
|
import { IUpdateProfileResponse_DTO } from "@shared/contexts";
|
||||||
|
|
||||||
|
export interface IUpdateProfilePresenter {
|
||||||
|
map: (profile: Profile, context: IProfileContext) => IUpdateProfileResponse_DTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateProfilePresenter: IUpdateProfilePresenter = {
|
||||||
|
map: (profile: Profile, context: IProfileContext): IUpdateProfileResponse_DTO => {
|
||||||
|
return {
|
||||||
|
id: profile.id.toString(),
|
||||||
|
contact_information: profile.contactInformation,
|
||||||
|
default_payment_method: profile.defaultPaymentMethod,
|
||||||
|
default_notes: profile.defaultNotes,
|
||||||
|
default_legal_terms: profile.defaultLegalTerms,
|
||||||
|
default_quote_validity: profile.defaultQuoteValidity,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./UpdateUser.presenter";
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./profile.mapper";
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeMapper,
|
||||||
|
MapperParamsType,
|
||||||
|
SequelizeMapper,
|
||||||
|
} from "@/contexts/common/infrastructure";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
import { IProfileProps, Profile } from "../../domain";
|
||||||
|
import { IProfileContext } from "../Profile.context";
|
||||||
|
import { ProfileCreationAttributes, Profile_Model } from "../sequelize";
|
||||||
|
|
||||||
|
export interface IProfileMapper
|
||||||
|
extends ISequelizeMapper<Profile_Model, ProfileCreationAttributes, Profile> {}
|
||||||
|
|
||||||
|
class ProfileMapper
|
||||||
|
extends SequelizeMapper<Profile_Model, ProfileCreationAttributes, Profile>
|
||||||
|
implements IProfileMapper
|
||||||
|
{
|
||||||
|
public constructor(props: { context: IProfileContext }) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toDomainMappingImpl(source: Profile_Model, params: any): Profile {
|
||||||
|
const props: IProfileProps = {
|
||||||
|
contactInformation: source.contact_information,
|
||||||
|
defaultPaymentMethod: source.default_payment_method,
|
||||||
|
defaultNotes: source.default_notes,
|
||||||
|
defaultLegalTerms: source.default_legal_terms,
|
||||||
|
defaultQuoteValidity: source.default_quote_validity,
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
|
const userOrError = Profile.create(props, id);
|
||||||
|
|
||||||
|
if (userOrError.isFailure) {
|
||||||
|
throw userOrError.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userOrError.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toPersistenceMappingImpl(source: Profile, params?: MapperParamsType | undefined) {
|
||||||
|
return {
|
||||||
|
id: source.id.toPrimitive(),
|
||||||
|
|
||||||
|
contact_information: source.contactInformation,
|
||||||
|
default_payment_method: source.defaultPaymentMethod,
|
||||||
|
default_notes: source.defaultNotes,
|
||||||
|
default_legal_terms: source.defaultLegalTerms,
|
||||||
|
default_quote_validity: source.defaultQuoteValidity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createProfileMapper = (context: IProfileContext): IProfileMapper =>
|
||||||
|
new ProfileMapper({
|
||||||
|
context,
|
||||||
|
});
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./profile.model";
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
export type ProfileCreationAttributes = InferCreationAttributes<Profile_Model>;
|
||||||
|
|
||||||
|
export class Profile_Model extends Model<
|
||||||
|
InferAttributes<Profile_Model>,
|
||||||
|
InferCreationAttributes<Profile_Model>
|
||||||
|
> {
|
||||||
|
// To avoid table creation
|
||||||
|
/*static async sync(): Promise<any> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
static associate(connection: Sequelize) {}
|
||||||
|
|
||||||
|
declare id: string;
|
||||||
|
declare contact_information: string;
|
||||||
|
declare default_payment_method: string;
|
||||||
|
declare default_notes: string;
|
||||||
|
declare default_legal_terms: string;
|
||||||
|
declare default_quote_validity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (sequelize: Sequelize) => {
|
||||||
|
Profile_Model.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: new DataTypes.UUID(),
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
contact_information: DataTypes.STRING,
|
||||||
|
default_payment_method: DataTypes.STRING,
|
||||||
|
default_notes: DataTypes.STRING,
|
||||||
|
default_legal_terms: DataTypes.STRING,
|
||||||
|
default_quote_validity: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: "dealers",
|
||||||
|
|
||||||
|
paranoid: true, // softs deletes
|
||||||
|
timestamps: true,
|
||||||
|
//version: true,
|
||||||
|
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: "updated_at",
|
||||||
|
deletedAt: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Profile_Model;
|
||||||
|
};
|
||||||
@ -7,10 +7,9 @@ import {
|
|||||||
import { IRepositoryManager } from "@/contexts/common/domain";
|
import { IRepositoryManager } from "@/contexts/common/domain";
|
||||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { Result, UniqueID } from "@shared/contexts";
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
import { IDealerRepository } from "../../domain";
|
import { Dealer, IDealerRepository } from "../../domain";
|
||||||
|
|
||||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
import { Dealer } from "../../domain/entities/Dealer";
|
|
||||||
|
|
||||||
export interface IGetDealerUseCaseRequest extends IUseCaseRequest {
|
export interface IGetDealerUseCaseRequest extends IUseCaseRequest {
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
@ -31,28 +30,24 @@ export class GetDealerUseCase
|
|||||||
this._repositoryManager = props.repositoryManager;
|
this._repositoryManager = props.repositoryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRepositoryByName<T>(name: string) {
|
|
||||||
return this._repositoryManager.getRepository<T>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(request: IGetDealerUseCaseRequest): Promise<GetDealerResponseOrError> {
|
async execute(request: IGetDealerUseCaseRequest): Promise<GetDealerResponseOrError> {
|
||||||
const { id } = request;
|
const { id } = request;
|
||||||
|
|
||||||
// Validación de datos
|
// Validación de datos
|
||||||
// No hay en este caso
|
// No hay en este caso
|
||||||
|
|
||||||
return await this.findDealer(id);
|
return await this._findDealer(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findDealer(id: UniqueID) {
|
private async _findDealer(id: UniqueID) {
|
||||||
const transaction = this._adapter.startTransaction();
|
const transaction = this._adapter.startTransaction();
|
||||||
const dealerRepoBuilder = this.getRepositoryByName<IDealerRepository>("Dealer");
|
const dealerRepository = this._getDealerRepository();
|
||||||
|
|
||||||
let dealer: Dealer | null = null;
|
let dealer: Dealer | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await transaction.complete(async (t) => {
|
await transaction.complete(async (t) => {
|
||||||
const dealerRepo = dealerRepoBuilder({ transaction: t });
|
const dealerRepo = dealerRepository({ transaction: t });
|
||||||
dealer = await dealerRepo.getById(id);
|
dealer = await dealerRepo.getById(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,4 +63,8 @@ export class GetDealerUseCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getDealerRepository() {
|
||||||
|
return this._repositoryManager.getRepository<IDealerRepository>("Dealer");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import {
|
|||||||
DomainError,
|
DomainError,
|
||||||
IDomainError,
|
IDomainError,
|
||||||
IUpdateDealer_Request_DTO,
|
IUpdateDealer_Request_DTO,
|
||||||
|
KeyValueMap,
|
||||||
|
Language,
|
||||||
Name,
|
Name,
|
||||||
Result,
|
Result,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
@ -107,9 +109,24 @@ export class UpdateDealerUseCase
|
|||||||
return Result.fail(nameOrError.error);
|
return Result.fail(nameOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const languageOrError = Language.createFromCode(dealerDTO.language);
|
||||||
|
if (languageOrError.isFailure) {
|
||||||
|
return Result.fail(languageOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
return Dealer.create(
|
return Dealer.create(
|
||||||
{
|
{
|
||||||
name: nameOrError.object,
|
name: nameOrError.object,
|
||||||
|
logo: "",
|
||||||
|
language: languageOrError.object,
|
||||||
|
|
||||||
|
additionalInfo: KeyValueMap.create([
|
||||||
|
["contact_information", dealerDTO.contact_information],
|
||||||
|
["default_payment_method", dealerDTO.default_payment_method],
|
||||||
|
["default_notes", dealerDTO.default_notes],
|
||||||
|
["default_legal_terms", dealerDTO.default_legal_terms],
|
||||||
|
["default_quote_validity", dealerDTO.default_quote_validity],
|
||||||
|
]).object,
|
||||||
},
|
},
|
||||||
dealerId
|
dealerId
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
||||||
|
import { Dealer, IDealerRepository } from "@/contexts/sales/domain";
|
||||||
|
import { UniqueID } from "@shared/contexts";
|
||||||
|
|
||||||
|
export const existsDealerByID = async (
|
||||||
|
id: UniqueID,
|
||||||
|
adapter: IAdapter,
|
||||||
|
repository: RepositoryBuilder<IDealerRepository>
|
||||||
|
): Promise<boolean> => {
|
||||||
|
return await adapter
|
||||||
|
.startTransaction()
|
||||||
|
.complete(async (t) => repository({ transaction: t }).exists(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findDealerByID = async (
|
||||||
|
id: UniqueID,
|
||||||
|
adapter: IAdapter,
|
||||||
|
repository: RepositoryBuilder<IDealerRepository>
|
||||||
|
): Promise<Dealer | null> => {
|
||||||
|
return await adapter
|
||||||
|
.startTransaction()
|
||||||
|
.complete(async (t) => repository({ transaction: t }).getById(id));
|
||||||
|
};
|
||||||
@ -4,3 +4,5 @@ export * from "./GetDealer.useCase";
|
|||||||
export * from "./GetDealerByUser.useCase";
|
export * from "./GetDealerByUser.useCase";
|
||||||
export * from "./ListDealers.useCase";
|
export * from "./ListDealers.useCase";
|
||||||
export * from "./UpdateDealer.useCase";
|
export * from "./UpdateDealer.useCase";
|
||||||
|
|
||||||
|
export * from "./dealerServices";
|
||||||
|
|||||||
@ -1,14 +1,34 @@
|
|||||||
import { AggregateRoot, IDomainError, Name, Result, UniqueID } from "@shared/contexts";
|
import {
|
||||||
|
AggregateRoot,
|
||||||
|
IDomainError,
|
||||||
|
KeyValueMap,
|
||||||
|
Language,
|
||||||
|
Name,
|
||||||
|
Result,
|
||||||
|
UniqueID,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import { DealerRole } from "./DealerRole";
|
||||||
|
import { DealerStatus } from "./DealerStatus";
|
||||||
|
|
||||||
export interface IDealerProps {
|
export interface IDealerProps {
|
||||||
name: Name;
|
|
||||||
user_id: UniqueID;
|
user_id: UniqueID;
|
||||||
|
name: Name;
|
||||||
|
logo: string;
|
||||||
|
language: Language;
|
||||||
|
roles: DealerRole[];
|
||||||
|
additionalInfo: KeyValueMap;
|
||||||
|
status: DealerStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDealer {
|
export interface IDealer {
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
name: Name;
|
|
||||||
user_id: UniqueID;
|
user_id: UniqueID;
|
||||||
|
name: Name;
|
||||||
|
language: Language;
|
||||||
|
|
||||||
|
additionalInfo: KeyValueMap;
|
||||||
|
status: DealerStatus;
|
||||||
|
getRoles: () => DealerRole[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
||||||
@ -17,11 +37,47 @@ export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
|
|||||||
return Result.ok<Dealer>(user);
|
return Result.ok<Dealer>(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): Name {
|
private roles: DealerRole[];
|
||||||
return this.props.name;
|
|
||||||
|
constructor(props: IDealerProps, id?: UniqueID) {
|
||||||
|
const { roles } = props;
|
||||||
|
super(props, id);
|
||||||
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
get user_id(): UniqueID {
|
get user_id(): UniqueID {
|
||||||
return this.props.user_id;
|
return this.props.user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get name(): Name {
|
||||||
|
return this.props.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get language(): Language {
|
||||||
|
return this.props.language;
|
||||||
|
}
|
||||||
|
|
||||||
|
get status(): DealerStatus {
|
||||||
|
return this.props.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalInfo(): KeyValueMap {
|
||||||
|
return this.props.additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isUser(): boolean {
|
||||||
|
return this._hasRole(DealerRole.ROLE_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAdmin(): boolean {
|
||||||
|
return this._hasRole(DealerRole.ROLE_ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRoles(): DealerRole[] {
|
||||||
|
return this.roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hasRole(role: DealerRole): boolean {
|
||||||
|
return (this.roles || []).some((r) => r.equals(role));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
server/src/contexts/sales/domain/entities/DealerRole.ts
Normal file
3
server/src/contexts/sales/domain/entities/DealerRole.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { AuthUserRole } from "@/contexts/auth/domain";
|
||||||
|
|
||||||
|
export class DealerRole extends AuthUserRole {}
|
||||||
88
server/src/contexts/sales/domain/entities/DealerStatus.ts
Normal file
88
server/src/contexts/sales/domain/entities/DealerStatus.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
IValueObjectOptions,
|
||||||
|
Result,
|
||||||
|
RuleValidator,
|
||||||
|
ValueObject,
|
||||||
|
handleDomainError,
|
||||||
|
} from "@shared/contexts";
|
||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
|
export enum DEALER_STATUS {
|
||||||
|
ACTIVE = "active",
|
||||||
|
BLOQUED = "bloqued",
|
||||||
|
DISABLED = "disabled",
|
||||||
|
}
|
||||||
|
export interface IDealerStatusOptions extends IValueObjectOptions {}
|
||||||
|
|
||||||
|
export class DealerStatus extends ValueObject<string> {
|
||||||
|
public static readonly DEALER_STATUS = DEALER_STATUS;
|
||||||
|
|
||||||
|
protected static validate(value: string, options: IValueObjectOptions) {
|
||||||
|
const rule = Joi.string()
|
||||||
|
.valid(DEALER_STATUS.ACTIVE, DEALER_STATUS.BLOQUED, DEALER_STATUS.DISABLED)
|
||||||
|
.label(options.label ? options.label : "status");
|
||||||
|
|
||||||
|
return RuleValidator.validate<string>(rule, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sanitize(status: string): string {
|
||||||
|
return String(status).trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createActive(): DealerStatus {
|
||||||
|
return new DealerStatus(DEALER_STATUS.ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createBloqued(): DealerStatus {
|
||||||
|
return new DealerStatus(DEALER_STATUS.BLOQUED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createDisabled(): DealerStatus {
|
||||||
|
return new DealerStatus(DEALER_STATUS.DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(status: string, options: IDealerStatusOptions = {}) {
|
||||||
|
const _options = {
|
||||||
|
label: "status",
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
const validationResult = DealerStatus.validate(status, _options);
|
||||||
|
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(
|
||||||
|
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new DealerStatus(DealerStatus.sanitize(validationResult.object)));
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): string {
|
||||||
|
return String(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return String(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): string {
|
||||||
|
return this.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isActive(): boolean {
|
||||||
|
return this.equals(DealerStatus.createActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
public isBloqued(): boolean {
|
||||||
|
return this.equals(DealerStatus.createBloqued());
|
||||||
|
}
|
||||||
|
|
||||||
|
public isDisabled(): boolean {
|
||||||
|
return this.equals(DealerStatus.createDisabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public isEmpty(): boolean {
|
||||||
|
return this.props === "undefined";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./Dealer";
|
export * from "./Dealer";
|
||||||
|
export * from "./DealerStatus";
|
||||||
export * from "./Quote";
|
export * from "./Quote";
|
||||||
export * from "./QuoteItem";
|
export * from "./QuoteItem";
|
||||||
export * from "./QuoteStatus";
|
export * from "./QuoteStatus";
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import {
|
|||||||
MapperParamsType,
|
MapperParamsType,
|
||||||
SequelizeMapper,
|
SequelizeMapper,
|
||||||
} from "@/contexts/common/infrastructure";
|
} from "@/contexts/common/infrastructure";
|
||||||
import { Name, UniqueID } from "@shared/contexts";
|
import { KeyValueMap, Language, Name, UniqueID } from "@shared/contexts";
|
||||||
import { Dealer, IDealerProps } from "../../domain/entities";
|
import { Dealer, DealerStatus, IDealerProps } from "../../domain/entities";
|
||||||
|
import { DealerRole } from "../../domain/entities/DealerRole";
|
||||||
import { ISalesContext } from "../Sales.context";
|
import { ISalesContext } from "../Sales.context";
|
||||||
import { DEALER_STATUS, DealerCreationAttributes, Dealer_Model } from "../sequelize";
|
import { DealerCreationAttributes, Dealer_Model } from "../sequelize";
|
||||||
|
|
||||||
export interface IDealerMapper
|
export interface IDealerMapper
|
||||||
extends ISequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer> {}
|
extends ISequelizeMapper<Dealer_Model, DealerCreationAttributes, Dealer> {}
|
||||||
@ -21,8 +22,19 @@ class DealerMapper
|
|||||||
|
|
||||||
protected toDomainMappingImpl(source: Dealer_Model, params: any): Dealer {
|
protected toDomainMappingImpl(source: Dealer_Model, params: any): Dealer {
|
||||||
const props: IDealerProps = {
|
const props: IDealerProps = {
|
||||||
name: this.mapsValue(source, "name", Name.create),
|
|
||||||
user_id: this.mapsValue(source, "user_id", UniqueID.create),
|
user_id: this.mapsValue(source, "user_id", UniqueID.create),
|
||||||
|
logo: "",
|
||||||
|
name: this.mapsValue(source, "name", Name.create),
|
||||||
|
status: this.mapsValue(source, "status", DealerStatus.create),
|
||||||
|
roles: this.mapsValue(source, "roles", DealerRole.create),
|
||||||
|
language: this.mapsValue(source, "language", Language.createFromCode),
|
||||||
|
additionalInfo: KeyValueMap.create([
|
||||||
|
["contact_information", source.contact_information],
|
||||||
|
["default_payment_method", source.default_payment_method],
|
||||||
|
["default_notes", source.default_notes],
|
||||||
|
["default_legal_terms", source.default_legal_terms],
|
||||||
|
["default_quote_validity", source.default_quote_validity],
|
||||||
|
]).object,
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = this.mapsValue(source, "id", UniqueID.create);
|
const id = this.mapsValue(source, "id", UniqueID.create);
|
||||||
@ -39,15 +51,16 @@ class DealerMapper
|
|||||||
return {
|
return {
|
||||||
id: source.id.toPrimitive(),
|
id: source.id.toPrimitive(),
|
||||||
user_id: source.user_id.toPrimitive(),
|
user_id: source.user_id.toPrimitive(),
|
||||||
contact_id: undefined,
|
//contact_id: undefined,
|
||||||
|
logo: "",
|
||||||
name: source.name.toPrimitive(),
|
name: source.name.toPrimitive(),
|
||||||
contact_information: "",
|
status: source.status.toPrimitive(),
|
||||||
default_payment_method: "",
|
language: source.language.toPrimitive(),
|
||||||
default_notes: "",
|
contact_information: source.additionalInfo.get("contact_information")?.toString() ?? "",
|
||||||
default_legal_terms: "",
|
default_payment_method: source.additionalInfo.get("default_payment_method")?.toString() ?? "",
|
||||||
default_quote_validity: "",
|
default_notes: source.additionalInfo.get("default_notes")?.toString() ?? "",
|
||||||
status: DEALER_STATUS.STATUS_ACTIVE,
|
default_legal_terms: source.additionalInfo.get("default_legal_terms")?.toString() ?? "",
|
||||||
language: "",
|
default_quote_validity: source.additionalInfo.get("default_quote_validity")?.toString() ?? "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,11 +10,6 @@ import {
|
|||||||
} from "sequelize";
|
} from "sequelize";
|
||||||
import { Quote_Model } from "./quote.model";
|
import { Quote_Model } from "./quote.model";
|
||||||
|
|
||||||
export enum DEALER_STATUS {
|
|
||||||
STATUS_ACTIVE = "active",
|
|
||||||
STATUS_BLOCKED = "blocked",
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DealerCreationAttributes = InferCreationAttributes<
|
export type DealerCreationAttributes = InferCreationAttributes<
|
||||||
Dealer_Model,
|
Dealer_Model,
|
||||||
{ omit: "user" | "quotes" }
|
{ omit: "user" | "quotes" }
|
||||||
@ -54,7 +49,7 @@ export class Dealer_Model extends Model<
|
|||||||
declare default_notes: string;
|
declare default_notes: string;
|
||||||
declare default_legal_terms: string;
|
declare default_legal_terms: string;
|
||||||
declare default_quote_validity: string;
|
declare default_quote_validity: string;
|
||||||
declare status: DEALER_STATUS;
|
declare status: string;
|
||||||
declare language: string;
|
declare language: string;
|
||||||
|
|
||||||
declare user?: NonAttribute<User_Model>;
|
declare user?: NonAttribute<User_Model>;
|
||||||
@ -87,7 +82,7 @@ export default (sequelize: Sequelize) => {
|
|||||||
language: DataTypes.STRING,
|
language: DataTypes.STRING,
|
||||||
|
|
||||||
status: {
|
status: {
|
||||||
type: DataTypes.ENUM(...Object.values(DEALER_STATUS)),
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { checkUser } from "@/contexts/auth";
|
import { checkUser } from "@/contexts/auth";
|
||||||
import { createGetProfileController } from "@/contexts/profile/infrastructure";
|
import { createGetProfileController } from "@/contexts/profile/infrastructure";
|
||||||
import { createUpdateUserController } from "@/contexts/users";
|
|
||||||
import Express from "express";
|
import Express from "express";
|
||||||
|
|
||||||
export const profileRouter = (appRouter: Express.Router) => {
|
export const profileRouter = (appRouter: Express.Router) => {
|
||||||
@ -17,7 +16,7 @@ export const profileRouter = (appRouter: Express.Router) => {
|
|||||||
"/",
|
"/",
|
||||||
checkUser,
|
checkUser,
|
||||||
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
|
||||||
createUpdateUserController(res.locals["context"]).execute(req, res, next)
|
createUpdateProfileController(res.locals["context"]).execute(req, res, next)
|
||||||
);
|
);
|
||||||
|
|
||||||
appRouter.use("/profile", profileRoutes);
|
appRouter.use("/profile", profileRoutes);
|
||||||
|
|||||||
35
shared/lib/contexts/common/domain/entities/KeyValueMap.ts
Normal file
35
shared/lib/contexts/common/domain/entities/KeyValueMap.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Result } from "./Result";
|
||||||
|
import { Primitive, ValueObject } from "./ValueObject";
|
||||||
|
|
||||||
|
type KeyValueMapProps = Map<string, Primitive>;
|
||||||
|
|
||||||
|
export class KeyValueMap extends ValueObject<KeyValueMapProps> {
|
||||||
|
public static create(entries?: [string, Primitive][]) {
|
||||||
|
const map = new Map<string, Primitive>(entries);
|
||||||
|
return Result.ok(new KeyValueMap(map));
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string): Primitive | undefined {
|
||||||
|
return this.props.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: string, value: Primitive): KeyValueMap {
|
||||||
|
const newMap = new Map(this.props);
|
||||||
|
newMap.set(key, value);
|
||||||
|
return new KeyValueMap(newMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key: string): KeyValueMap {
|
||||||
|
const newMap = new Map(this.props);
|
||||||
|
newMap.delete(key);
|
||||||
|
return new KeyValueMap(newMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPrimitive(): string {
|
||||||
|
return JSON.stringify(Object.fromEntries(this.props.entries()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public entries(): [string, Primitive][] {
|
||||||
|
return Array.from(this.props.entries());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { shallowEqual } from "shallow-equal-object";
|
import { shallowEqual } from "shallow-equal-object";
|
||||||
|
|
||||||
type Primitive = string | boolean | number;
|
export type Primitive = string | boolean | number;
|
||||||
|
|
||||||
export interface IValueObjectOptions {
|
export interface IValueObjectOptions {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export * from "./Currency";
|
|||||||
export * from "./Description";
|
export * from "./Description";
|
||||||
export * from "./Email";
|
export * from "./Email";
|
||||||
export * from "./Entity";
|
export * from "./Entity";
|
||||||
|
export * from "./KeyValueMap";
|
||||||
export * from "./Language";
|
export * from "./Language";
|
||||||
export * from "./Measure";
|
export * from "./Measure";
|
||||||
export * from "./MoneyValue";
|
export * from "./MoneyValue";
|
||||||
|
|||||||
@ -1,13 +1,8 @@
|
|||||||
export interface IGetProfileResponse_DTO {
|
export interface IGetProfileResponse_DTO {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
language: string;
|
|
||||||
roles: string[];
|
|
||||||
contact_information: string;
|
contact_information: string;
|
||||||
default_payment_method: string;
|
default_payment_method: string;
|
||||||
default_notes: string;
|
default_notes: string;
|
||||||
default_legal_terms: string;
|
default_legal_terms: string;
|
||||||
default_quote_validity: string;
|
default_quote_validity: string;
|
||||||
status: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { Result, RuleValidator } from "../../../../common";
|
||||||
|
|
||||||
|
export interface IUpdateProfile_Request_DTO {
|
||||||
|
contact_information: string;
|
||||||
|
default_payment_method: string;
|
||||||
|
default_notes: string;
|
||||||
|
default_legal_terms: string;
|
||||||
|
default_quote_validity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_Request_DTO) {
|
||||||
|
const schema = Joi.object({
|
||||||
|
contact_information: Joi.string(),
|
||||||
|
default_payment_method: Joi.string(),
|
||||||
|
default_notes: Joi.string(),
|
||||||
|
default_legal_terms: Joi.string(),
|
||||||
|
default_quote_validity: Joi.string(),
|
||||||
|
}).unknown(true);
|
||||||
|
|
||||||
|
const result = RuleValidator.validate<IUpdateProfile_Request_DTO>(schema, userDTO);
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(true);
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
export interface IUpdateProfileResponse_DTO {
|
||||||
|
id: string;
|
||||||
|
contact_information: string;
|
||||||
|
default_payment_method: string;
|
||||||
|
default_notes: string;
|
||||||
|
default_legal_terms: string;
|
||||||
|
default_quote_validity: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./IUpdateProfile_Request.dto";
|
||||||
|
export * from "./IUpdateProfile_Response.dto";
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./GetProfile.dto";
|
export * from "./GetProfile.dto";
|
||||||
|
export * from "./UpdateProfile.dto";
|
||||||
|
|||||||
@ -3,11 +3,23 @@ import { Result, RuleValidator } from "../../../../../common";
|
|||||||
|
|
||||||
export interface IUpdateDealer_Request_DTO {
|
export interface IUpdateDealer_Request_DTO {
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
|
contact_information: string;
|
||||||
|
default_payment_method: string;
|
||||||
|
default_notes: string;
|
||||||
|
default_legal_terms: string;
|
||||||
|
default_quote_validity: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureUpdateDealer_Request_DTOIsValid(dealerDTO: IUpdateDealer_Request_DTO) {
|
export function ensureUpdateDealer_Request_DTOIsValid(dealerDTO: IUpdateDealer_Request_DTO) {
|
||||||
const schema = Joi.object({
|
const schema = Joi.object({
|
||||||
name: Joi.string(),
|
name: Joi.string(),
|
||||||
|
language: Joi.string(),
|
||||||
|
contact_information: Joi.string(),
|
||||||
|
default_payment_method: Joi.string(),
|
||||||
|
default_notes: Joi.string(),
|
||||||
|
default_legal_terms: Joi.string(),
|
||||||
|
default_quote_validity: Joi.string(),
|
||||||
}).unknown(true);
|
}).unknown(true);
|
||||||
|
|
||||||
const result = RuleValidator.validate<IUpdateDealer_Request_DTO>(schema, dealerDTO);
|
const result = RuleValidator.validate<IUpdateDealer_Request_DTO>(schema, dealerDTO);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user