diff --git a/client/.env.development b/client/.env.development index 6521d95..086934f 100644 --- a/client/.env.development +++ b/client/.env.development @@ -1,2 +1,2 @@ -VITE_API_URL=http://127.0.0.1:4001/api/v1 +VITE_API_URL=http://192.168.0.111:4001/api/v1 VITE_API_KEY=e175f809ba71fb2765ad5e60f9d77596-es19 \ No newline at end of file diff --git a/client/src/Routes.tsx b/client/src/Routes.tsx index 0944408..3aea4e1 100644 --- a/client/src/Routes.tsx +++ b/client/src/Routes.tsx @@ -5,12 +5,12 @@ import { LoginPage, LogoutPage, QuoteCreate, + QuoteEdit, SettingsEditor, SettingsLayout, StartPage, } from "./app"; import { CatalogLayout, CatalogList } from "./app/catalog"; -import { DashboardPage } from "./app/dashboard"; import { QuotesLayout } from "./app/quotes/layout"; import { QuotesList } from "./app/quotes/list"; import { ProtectedRoute } from "./components"; @@ -26,14 +26,6 @@ export const Routes = () => { // Define routes accessible only to authenticated users const routesForAuthenticatedOnly = [ - { - path: "/home", - element: ( - - - - ), - }, { path: "/catalog", element: ( @@ -84,6 +76,10 @@ export const Routes = () => { path: "add", element: , }, + { + path: "edit", + element: , + }, ], }, { diff --git a/client/src/app/ErrorPage.tsx b/client/src/app/ErrorPage.tsx index c910633..f8f38e6 100644 --- a/client/src/app/ErrorPage.tsx +++ b/client/src/app/ErrorPage.tsx @@ -32,7 +32,7 @@ export const ErrorPage = (props: ErrorPageProps) => { - - - {t("quotes.create.buttons.save_quote")} - + + +
+ + + +
+
- - - {t("quotes.create.tabs.general")} - {t("quotes.create.tabs.items")} - {t("quotes.create.tabs.documents")} - {t("quotes.create.tabs.history")} - - - - - - - - -
- - + +
+ + +
diff --git a/client/src/app/quotes/edit.tsx b/client/src/app/quotes/edit.tsx new file mode 100644 index 0000000..38668db --- /dev/null +++ b/client/src/app/quotes/edit.tsx @@ -0,0 +1,128 @@ +import { ChevronLeft } from "lucide-react"; + +import { SubmitButton } from "@/components"; +import { useGetIdentity } from "@/lib/hooks"; +import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui"; +import { t } from "i18next"; +import { useState } from "react"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { + QuoteDetailsCardEditor, + QuoteDocumentsCardEditor, + QuoteGeneralCardEditor, +} from "./components/editors"; +import { useQuotes } from "./hooks"; + +type QuoteDataForm = { + id: string; + status: string; + date: string; + reference: string; + customer_information: string; + lang_code: string; + currency_code: string; + payment_method: string; + notes: string; + validity: string; + items: any[]; +}; + +type QuoteCreateProps = { + isOverModal?: boolean; +}; + +export const QuoteEdit = ({ isOverModal }: QuoteCreateProps) => { + const [loading, setLoading] = useState(false); + + const { data: userIdentity } = useGetIdentity(); + console.log(userIdentity); + + const { useQuery, useMutation } = useQuotes(); + + const { data } = useQuery; + const { mutate } = useMutation; + + const form = useForm({ + mode: "onBlur", + values: data, + defaultValues: { + date: "", + reference: "", + customer_information: "", + lang_code: "", + currency_code: "", + payment_method: "", + notes: "", + validity: "", + items: [], + }, + }); + + const onSubmit: SubmitHandler = async (data) => { + alert(JSON.stringify(data)); + + try { + setLoading(true); + data.currency_code = "EUR"; + data.lang_code = String(userIdentity?.language); + + mutate(data); + } finally { + setLoading(false); + } + }; + + return ( +
+ +
+
+ +

+ {t("quotes.create.title")} +

+ + {t("quotes.status.draft")} + +
+ + + {t("quotes.create.buttons.save_quote")} + +
+
+ + + {t("quotes.create.tabs.general")} + {t("quotes.create.tabs.items")} + {t("quotes.create.tabs.documents")} + {t("quotes.create.tabs.history")} + + + + + + + + + + + + + +
+ + +
+
+
+ + ); +}; diff --git a/client/src/app/quotes/hooks/useQuotes.tsx b/client/src/app/quotes/hooks/useQuotes.tsx index fae761f..2c2edd2 100644 --- a/client/src/app/quotes/hooks/useQuotes.tsx +++ b/client/src/app/quotes/hooks/useQuotes.tsx @@ -37,10 +37,12 @@ export const useQuotes = (params?: UseQuotesGetParamsType) => { id = UniqueID.generateNewID().object.toString(); } - return dataSource.updateOne({ + return dataSource.createOne({ resource: "quotes", - data, - id, + data: { + ...data, + id, + }, }); }, }), diff --git a/client/src/app/quotes/index.ts b/client/src/app/quotes/index.ts index d1c12f2..ca5d8e1 100644 --- a/client/src/app/quotes/index.ts +++ b/client/src/app/quotes/index.ts @@ -1,2 +1,3 @@ export * from "./create"; +export * from "./edit"; export * from "./list"; diff --git a/client/src/app/quotes/list.tsx b/client/src/app/quotes/list.tsx index 6166871..72a61b1 100644 --- a/client/src/app/quotes/list.tsx +++ b/client/src/app/quotes/list.tsx @@ -1,15 +1,443 @@ import { DataTableProvider } from "@/lib/hooks"; +import { + ChevronLeft, + ChevronRight, + Copy, + CreditCard, + File, + ListFilter, + MoreVertical, + Truck, +} from "lucide-react"; import { Trans } from "react-i18next"; import { QuotesDataTable } from "./components"; -export const QuotesList = () => ( - -
-

- -

-
+import { + Badge, + Button, + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, + Pagination, + PaginationContent, + PaginationItem, + Progress, + Separator, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/ui"; +import { useNavigate } from "react-router-dom"; - -
-); +export const QuotesList = () => { + const navigate = useNavigate(); + + return ( + +
+
+

+ +

+

descripción

+
+
+ + + +
+
+
+ + + Tus Cotizaciones + + Introducing Our Dynamic Orders Dashboard for Seamless Management and Insightful + Analysis. + + + + + + + + + This Week + $1,329 + + +
+25% from last week
+
+ + + +
+ + + This Month + $5,329 + + +
+10% from last month
+
+ + + +
+
+ +
+ + Week + Month + Year + +
+ + + + + + Filter by + + Fulfilled + Declined + Refunded + + + +
+
+ + + + Orders + Recent orders from your store. + + + + + + Customer + Type + Status + Date + Amount + + + + + +
Liam Johnson
+
+ liam@example.com +
+
+ Sale + + + Fulfilled + + + 2023-06-23 + $250.00 +
+ + +
Olivia Smith
+
+ olivia@example.com +
+
+ Refund + + + Declined + + + 2023-06-24 + $150.00 +
+ + +
Noah Williams
+
+ noah@example.com +
+
+ Subscription + + + Fulfilled + + + 2023-06-25 + $350.00 +
+ + +
Emma Brown
+
+ emma@example.com +
+
+ Sale + + + Fulfilled + + + 2023-06-26 + $450.00 +
+ + +
Liam Johnson
+
+ liam@example.com +
+
+ Sale + + + Fulfilled + + + 2023-06-23 + $250.00 +
+ + +
Liam Johnson
+
+ liam@example.com +
+
+ Sale + + + Fulfilled + + + 2023-06-23 + $250.00 +
+ + +
Olivia Smith
+
+ olivia@example.com +
+
+ Refund + + + Declined + + + 2023-06-24 + $150.00 +
+ + +
Emma Brown
+
+ emma@example.com +
+
+ Sale + + + Fulfilled + + + 2023-06-26 + $450.00 +
+
+
+
+
+
+
+
+
+ + +
+ + Order Oe31b70H + + + Date: November 23, 2023 +
+
+ + + + + + + Edit + Export + + Trash + + +
+
+ +
+
Order Details
+
    +
  • + + Glimmer Lamps x 2 + + $250.00 +
  • +
  • + + Aqua Filters x 1 + + $49.00 +
  • +
+ +
    +
  • + Subtotal + $299.00 +
  • +
  • + Shipping + $5.00 +
  • +
  • + Tax + $25.00 +
  • +
  • + Total + $329.00 +
  • +
+
+ +
+
+
Shipping Information
+
+ Liam Johnson + 1234 Main St. + Anytown, CA 12345 +
+
+
+
Billing Information
+
Same as shipping address
+
+
+ +
+
Customer Information
+
+
+
Customer
+
Liam Johnson
+
+
+
Email
+
+ liam@acme.com +
+
+
+
Phone
+
+ +1 234 567 890 +
+
+
+
+ +
+
Payment Information
+
+
+
+ + Visa +
+
**** **** **** 4532
+
+
+
+
+ +
+ Updated +
+ + + + + + + + + + +
+
+
+
+
+ ); +}; diff --git a/client/src/components/Layout/LayoutHeader.tsx b/client/src/components/Layout/LayoutHeader.tsx index 3589e1e..7e948c2 100644 --- a/client/src/components/Layout/LayoutHeader.tsx +++ b/client/src/components/Layout/LayoutHeader.tsx @@ -15,9 +15,6 @@ export const LayoutHeader = () => { Uecko - - - { Uecko - - - diff --git a/client/src/lib/axios/axiosInstance.ts b/client/src/lib/axios/axiosInstance.ts index b34e895..8e9f987 100644 --- a/client/src/lib/axios/axiosInstance.ts +++ b/client/src/lib/axios/axiosInstance.ts @@ -18,6 +18,7 @@ export const defaultAxiosRequestConfig = { Accept: "application/json", "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache", + "Access-Control-Allow-Origin": "*", // Could work and fix the previous problem, but not in all APIs //'api-key': SERVER_API_KEY, }, //timeout: 300, diff --git a/client/src/lib/axios/createAxiosAuthActions.ts b/client/src/lib/axios/createAxiosAuthActions.ts index 6381234..60ac56b 100644 --- a/client/src/lib/axios/createAxiosAuthActions.ts +++ b/client/src/lib/axios/createAxiosAuthActions.ts @@ -25,7 +25,7 @@ export const createAxiosAuthActions = ( return { success: true, data, - redirectTo: "/home", + redirectTo: "/", }; } catch (error) { return { diff --git a/client/src/lib/hooks/useAuth/useLogin.tsx b/client/src/lib/hooks/useAuth/useLogin.tsx index 6c99002..c480dc4 100644 --- a/client/src/lib/hooks/useAuth/useLogin.tsx +++ b/client/src/lib/hooks/useAuth/useLogin.tsx @@ -17,7 +17,7 @@ export const useLogin = (params?: UseMutationOptions { const { success, redirectTo } = data; if (success && redirectTo) { - navigate(redirectTo, { replace: true }); + navigate(redirectTo || "/", { replace: true }); } if (onSuccess) { onSuccess(data, variables, context); diff --git a/client/src/lib/hooks/useAuth/useLogout.tsx b/client/src/lib/hooks/useAuth/useLogout.tsx index 784e632..ae906de 100644 --- a/client/src/lib/hooks/useAuth/useLogout.tsx +++ b/client/src/lib/hooks/useAuth/useLogout.tsx @@ -13,10 +13,11 @@ export const useLogout = (params?: UseMutationOptions return useMutation({ mutationKey: keys().auth().action("logout").get(), mutationFn: logout, + onSuccess: async (data, variables, context) => { const { success, redirectTo } = data; if (success && redirectTo) { - navigate(redirectTo); + navigate(redirectTo || "/", { replace: true }); } if (onSuccess) { onSuccess(data, variables, context); diff --git a/client/src/locales/es.json b/client/src/locales/es.json index dabb5a9..2924926 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -9,6 +9,7 @@ "hide": "Ocultar", "back": "Volver", "upload": "Cargar", + "continue": "Continuar", "sort_asc": "Asc", "sort_asc_description": "En order ascendente. Click para ordenar descendentemente.", "sort_desc": "Desc", @@ -62,8 +63,8 @@ "welcome": "Bienvenido" }, "catalog": { - "title": "Catálogo de artículos", "list": { + "title": "Catálogo de artículos", "columns": { "description": "Descripción", "points": "Puntos", diff --git a/server/src/contexts/sales/application/Dealer/GetDealerByUser.useCase.ts b/server/src/contexts/sales/application/Dealer/GetDealerByUser.useCase.ts index b89e439..c594911 100644 --- a/server/src/contexts/sales/application/Dealer/GetDealerByUser.useCase.ts +++ b/server/src/contexts/sales/application/Dealer/GetDealerByUser.useCase.ts @@ -10,7 +10,7 @@ import { Result, UniqueID } from "@shared/contexts"; import { IDealerRepository } from "../../domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; -import { Dealer } from "../../domain/entities/Dealer"; +import { Dealer } from "../../domain/entities/Dealer/Dealer"; export interface IGetDealerByUserByUserUseCaseRequest extends IUseCaseRequest { userId: UniqueID; diff --git a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts index 1d8db62..2949b54 100644 --- a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts +++ b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts @@ -10,6 +10,7 @@ import { ICreateQuote_Request_DTO, IDomainError, Language, + Note, Quantity, Result, UTCDateValue, @@ -17,7 +18,7 @@ import { UnitPrice, ensureIdIsValid, } from "@shared/contexts"; -import { IQuoteRepository, Quote, QuoteItem, QuoteStatus } from "../../domain"; +import { IQuoteRepository, Quote, QuoteCustomer, QuoteItem, QuoteStatus } from "../../domain"; export type CreateQuoteResponseOrError = | Result // Misc errors (value objects) @@ -118,22 +119,47 @@ export class CreateQuoteUseCase return Result.fail(dateOrError.error); } - const languageOrError = Language.createFromCode(quoteDTO.language_code); + const referenceOrError = QuoteStatus.create(quoteDTO.reference); + if (referenceOrError.isFailure) { + return Result.fail(referenceOrError.error); + } + + const languageOrError = Language.createFromCode(quoteDTO.lang_code); if (languageOrError.isFailure) { return Result.fail(languageOrError.error); } + const customerOrError = QuoteCustomer.create(quoteDTO.customer_information); + if (customerOrError.isFailure) { + return Result.fail(customerOrError.error); + } + const currencyOrError = Currency.createFromCode(quoteDTO.currency_code); if (currencyOrError.isFailure) { return Result.fail(currencyOrError.error); } + const paymentOrError = Note.create(quoteDTO.payment_method); + if (paymentOrError.isFailure) { + return Result.fail(paymentOrError.error); + } + + const notesOrError = Note.create(quoteDTO.notes); + if (notesOrError.isFailure) { + return Result.fail(notesOrError.error); + } + + const validityOrError = Note.create(quoteDTO.validity); + if (validityOrError.isFailure) { + return Result.fail(validityOrError.error); + } + const items = new Collection( quoteDTO.items?.map( (item) => QuoteItem.create({ description: Description.create(item.description).object, - quantity: Quantity.create(item.quantity).object, + quantity: Quantity.create({ amount: item.quantity, precision: 4 }).object, unitPrice: UnitPrice.create({ amount: item.unit_price.amount, currencyCode: item.unit_price.currency, @@ -147,8 +173,14 @@ export class CreateQuoteUseCase { status: statusOrError.object, date: dateOrError.object, + reference: referenceOrError.object, language: languageOrError.object, + customer: customerOrError.object, currency: currencyOrError.object, + paymentMethod: paymentOrError.object, + notes: notesOrError.object, + validity: validityOrError.object, + items, }, quoteId diff --git a/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts b/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts index e7dbf0c..74a88ed 100644 --- a/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts +++ b/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts @@ -10,7 +10,7 @@ import { Result, UniqueID } from "@shared/contexts"; import { IQuoteRepository } from "../../domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; -import { Quote } from "../../domain/entities/Quote"; +import { Quote } from "../../domain/entities/Quotes/Quote"; export interface IGetQuoteUseCaseRequest extends IUseCaseRequest { id: UniqueID; diff --git a/server/src/contexts/sales/application/Quote/GetQuoteByUser.useCase.ts b/server/src/contexts/sales/application/Quote/GetQuoteByUser.useCase.ts index 26c6959..bb7c0d2 100644 --- a/server/src/contexts/sales/application/Quote/GetQuoteByUser.useCase.ts +++ b/server/src/contexts/sales/application/Quote/GetQuoteByUser.useCase.ts @@ -10,7 +10,7 @@ import { Result, UniqueID } from "@shared/contexts"; import { IQuoteRepository } from "../../domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; -import { Quote } from "../../domain/entities/Quote"; +import { Quote } from "../../domain/entities/Quotes/Quote"; export interface IGetQuoteByUserByUserUseCaseRequest extends IUseCaseRequest { userId: UniqueID; diff --git a/server/src/contexts/sales/domain/entities/Dealer.ts b/server/src/contexts/sales/domain/entities/Dealer/Dealer.ts similarity index 100% rename from server/src/contexts/sales/domain/entities/Dealer.ts rename to server/src/contexts/sales/domain/entities/Dealer/Dealer.ts diff --git a/server/src/contexts/sales/domain/entities/DealerRole.ts b/server/src/contexts/sales/domain/entities/Dealer/DealerRole.ts similarity index 100% rename from server/src/contexts/sales/domain/entities/DealerRole.ts rename to server/src/contexts/sales/domain/entities/Dealer/DealerRole.ts diff --git a/server/src/contexts/sales/domain/entities/DealerStatus.ts b/server/src/contexts/sales/domain/entities/Dealer/DealerStatus.ts similarity index 100% rename from server/src/contexts/sales/domain/entities/DealerStatus.ts rename to server/src/contexts/sales/domain/entities/Dealer/DealerStatus.ts diff --git a/server/src/contexts/sales/domain/entities/Dealer/index.ts b/server/src/contexts/sales/domain/entities/Dealer/index.ts new file mode 100644 index 0000000..dfb596c --- /dev/null +++ b/server/src/contexts/sales/domain/entities/Dealer/index.ts @@ -0,0 +1,3 @@ +export * from "./Dealer"; +export * from "./DealerRole"; +export * from "./DealerStatus"; diff --git a/server/src/contexts/sales/domain/entities/Quote.ts b/server/src/contexts/sales/domain/entities/Quotes/Quote.ts similarity index 68% rename from server/src/contexts/sales/domain/entities/Quote.ts rename to server/src/contexts/sales/domain/entities/Quotes/Quote.ts index e10a41e..ba32f39 100644 --- a/server/src/contexts/sales/domain/entities/Quote.ts +++ b/server/src/contexts/sales/domain/entities/Quotes/Quote.ts @@ -4,23 +4,26 @@ import { ICollection, IDomainError, Language, + Note, Result, UTCDateValue, UniqueID, } from "@shared/contexts"; +import { QuoteCustomer } from "./QuoteCustomer"; import { QuoteItem } from "./QuoteItem"; +import { QuoteReference } from "./QuoteReference"; import { QuoteStatus } from "./QuoteStatus"; export interface IQuoteProps { status: QuoteStatus; date: UTCDateValue; - reference: string; + reference: QuoteReference; + customer: QuoteCustomer; language: Language; - customer: string; currency: Currency; - paymentMethod: string; - notes: string; - validity: string; + paymentMethod: Note; + notes: Note; + validity: Note; items: ICollection; } @@ -30,9 +33,13 @@ export interface IQuote { status: QuoteStatus; date: UTCDateValue; - + reference: QuoteReference; + customer: QuoteCustomer; language: Language; currency: Currency; + paymentMethod: Note; + notes: Note; + validity: Note; items: ICollection; } @@ -67,6 +74,14 @@ export class Quote extends AggregateRoot implements IQuote { return this.props.status; } + get reference() { + return this.props.reference; + } + + get customer() { + return this.props.customer; + } + get language() { return this.props.language; } @@ -75,6 +90,18 @@ export class Quote extends AggregateRoot implements IQuote { return this.props.currency; } + get paymentMethod() { + return this.props.paymentMethod; + } + + get notes() { + return this.props.notes; + } + + get validity() { + return this.props.validity; + } + get items() { return this._items; } diff --git a/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts new file mode 100644 index 0000000..c2f8472 --- /dev/null +++ b/server/src/contexts/sales/domain/entities/Quotes/QuoteCustomer.ts @@ -0,0 +1,45 @@ +import { + DomainError, + IStringValueObjectOptions, + Result, + RuleValidator, + StringValueObject, + handleDomainError, +} from "@shared/contexts"; +import { UndefinedOr } from "@shared/utilities"; +import Joi from "joi"; + +export interface IQuoteCustomerOptions extends IStringValueObjectOptions {} + +export class QuoteCustomer extends StringValueObject { + private static readonly MAX_LENGTH = 255; + + protected static validate(value: UndefinedOr, options: IQuoteCustomerOptions) { + const rule = Joi.string() + .allow(null) + .allow("") + .default("") + .trim() + .max(QuoteCustomer.MAX_LENGTH) + .label(options.label ? options.label : "value"); + + return RuleValidator.validate(rule, value); + } + + public static create(value: UndefinedOr, options: IQuoteCustomerOptions = {}) { + const _options = { + label: "customer", + ...options, + }; + + const validationResult = QuoteCustomer.validate(value, _options); + + if (validationResult.isFailure) { + return Result.fail( + handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options) + ); + } + + return Result.ok(new QuoteCustomer(validationResult.object)); + } +} diff --git a/server/src/contexts/sales/domain/entities/QuoteItem.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteItem.ts similarity index 100% rename from server/src/contexts/sales/domain/entities/QuoteItem.ts rename to server/src/contexts/sales/domain/entities/Quotes/QuoteItem.ts diff --git a/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts new file mode 100644 index 0000000..de023f9 --- /dev/null +++ b/server/src/contexts/sales/domain/entities/Quotes/QuoteReference.ts @@ -0,0 +1,45 @@ +import { + DomainError, + IStringValueObjectOptions, + Result, + RuleValidator, + StringValueObject, + handleDomainError, +} from "@shared/contexts"; +import { UndefinedOr } from "@shared/utilities"; +import Joi from "joi"; + +export interface IQuoteReferenceOptions extends IStringValueObjectOptions {} + +export class QuoteReference extends StringValueObject { + private static readonly MAX_LENGTH = 255; + + protected static validate(value: UndefinedOr, options: IQuoteReferenceOptions) { + const rule = Joi.string() + .allow(null) + .allow("") + .default("") + .trim() + .max(QuoteReference.MAX_LENGTH) + .label(options.label ? options.label : "value"); + + return RuleValidator.validate(rule, value); + } + + public static create(value: UndefinedOr, options: IQuoteReferenceOptions = {}) { + const _options = { + label: "customer", + ...options, + }; + + const validationResult = QuoteReference.validate(value, _options); + + if (validationResult.isFailure) { + return Result.fail( + handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options) + ); + } + + return Result.ok(new QuoteReference(validationResult.object)); + } +} diff --git a/server/src/contexts/sales/domain/entities/QuoteStatus.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteStatus.ts similarity index 100% rename from server/src/contexts/sales/domain/entities/QuoteStatus.ts rename to server/src/contexts/sales/domain/entities/Quotes/QuoteStatus.ts diff --git a/server/src/contexts/sales/domain/entities/Quotes/index.ts b/server/src/contexts/sales/domain/entities/Quotes/index.ts new file mode 100644 index 0000000..aebae89 --- /dev/null +++ b/server/src/contexts/sales/domain/entities/Quotes/index.ts @@ -0,0 +1,5 @@ +export * from "./Quote"; +export * from "./QuoteCustomer"; +export * from "./QuoteItem"; +export * from "./QuoteReference"; +export * from "./QuoteStatus"; diff --git a/server/src/contexts/sales/domain/entities/index.ts b/server/src/contexts/sales/domain/entities/index.ts index 2da3d7c..5b5cfef 100644 --- a/server/src/contexts/sales/domain/entities/index.ts +++ b/server/src/contexts/sales/domain/entities/index.ts @@ -1,5 +1,2 @@ -export * from "./Dealer"; -export * from "./DealerStatus"; -export * from "./Quote"; -export * from "./QuoteItem"; -export * from "./QuoteStatus"; +export * from "./Dealer/Dealer"; +export * from "./Quotes"; diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts index cd6c444..a7138d5 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/CreateQuote.controller.ts @@ -35,6 +35,16 @@ export class CreateQuoteController extends ExpressController { async executeImpl() { try { const quoteDTO: ICreateQuote_Request_DTO = this.req.body; + /*const user = this.req.user; + + if (!user) { + const errorMessage = "Unexpected missing user data"; + const infraError = InfrastructureError.create( + InfrastructureError.UNEXCEPTED_ERROR, + errorMessage + ); + return this.internalServerError(errorMessage, infraError); + }*/ // Validaciones de DTO const quoteDTOOrError = ensureCreateQuote_Request_DTOIsValid(quoteDTO); diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts index 076feee..b35524d 100644 --- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts +++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/createQuote/presenter/CreateQuote.presenter.ts @@ -11,10 +11,12 @@ export const CreateQuotePresenter: ICreateQuotePresenter = { map: (quote: Quote, context: ISalesContext): ICreateQuote_Response_DTO => { return { id: quote.id.toString(), + //reference: quote.refe status: quote.status.toString(), date: quote.date.toString(), - language_code: quote.date.toString(), + lang_code: quote.language.toString(), currency_code: quote.currency.toString(), + customer_information: quote.customer, subtotal: { amount: 0, precision: 2, @@ -35,7 +37,7 @@ const quoteItemPresenter = (items: ICollection, context: ISalesContex items.totalCount > 0 ? items.items.map((item: QuoteItem) => ({ description: item.description.toString(), - quantity: item.quantity.toString(), + quantity: item.quantity.toObject(), unit_measure: "", unit_price: { amount: 0, diff --git a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts index 6f2c6e4..9ede6ec 100644 --- a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts +++ b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts @@ -2,7 +2,7 @@ import { Currency, Language, UTCDateValue, UniqueID } from "@shared/contexts"; import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; import { IQuoteProps, Quote } from "../../domain"; -import { QuoteStatus } from "../../domain/entities/QuoteStatus"; +import { QuoteStatus } from "../../domain/entities/Quotes/QuoteStatus"; import { ISalesContext } from "../Sales.context"; import { QuoteCreationAttributes, Quote_Model } from "../sequelize"; import { IQuoteItemMapper, createQuoteItemMapper } from "./quoteItem.mapper"; @@ -36,6 +36,7 @@ class QuoteMapper date: this.mapsValue(source, "issue_date", UTCDateValue.create), currency: this.mapsValue(source, "quote_currency", Currency.createFromCode), language: this.mapsValue(source, "quote_language", Language.createFromCode), + customer: source.customer_information, items, }; @@ -60,7 +61,8 @@ class QuoteMapper status: source.status.toPrimitive(), date: source.date.toPrimitive(), currency_code: source.currency.toPrimitive(), - language_code: source.language.toPrimitive(), + lang_code: source.language.toPrimitive(), + customer_information: source.customer, subtotal: 0, total: 0, items, diff --git a/server/src/infrastructure/express/api/routes/dealers.routes.ts b/server/src/infrastructure/express/api/routes/dealers.routes.ts index 745b512..a9d9c58 100644 --- a/server/src/infrastructure/express/api/routes/dealers.routes.ts +++ b/server/src/infrastructure/express/api/routes/dealers.routes.ts @@ -8,8 +8,6 @@ import { } from "@/contexts/sales/infrastructure/express/controllers/dealers"; import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/dealerMiddleware"; import Express from "express"; -import { quoteRoutes } from "./quote.routes"; -5; export const DealerRouter = (appRouter: Express.Router) => { const dealerRoutes: Express.Router = Express.Router({ mergeParams: true }); @@ -21,7 +19,7 @@ export const DealerRouter = (appRouter: Express.Router) => { dealerRoutes.delete("/:dealerId", checkisAdmin, deleteDealerController); // Anidar quotes en /dealers/:dealerId - dealerRoutes.use("/:dealerId/quotes", quoteRoutes); + //dealerRoutes.use("/:dealerId/quotes", quoteRoutes); appRouter.use("/dealers", dealerRoutes); }; diff --git a/server/src/infrastructure/express/api/routes/index.ts b/server/src/infrastructure/express/api/routes/index.ts index 501b213..e4367c0 100644 --- a/server/src/infrastructure/express/api/routes/index.ts +++ b/server/src/infrastructure/express/api/routes/index.ts @@ -3,5 +3,4 @@ export * from "./catalog.routes"; export * from "./dealers.routes"; export * from "./profile.routes"; export * from "./quote.routes"; -export * from "./sales.routes"; export * from "./users.routes"; diff --git a/server/src/infrastructure/express/api/routes/quote.routes.ts b/server/src/infrastructure/express/api/routes/quote.routes.ts index 4ff8f21..e4a2bb6 100644 --- a/server/src/infrastructure/express/api/routes/quote.routes.ts +++ b/server/src/infrastructure/express/api/routes/quote.routes.ts @@ -1,20 +1,28 @@ import { checkUser } from "@/contexts/auth"; -import { listQuotesController } from "@/contexts/sales/infrastructure/express/controllers"; +import { + createQuoteController, + listQuotesController, +} from "@/contexts/sales/infrastructure/express/controllers"; import Express from "express"; -export const quoteRoutes: Express.Router = Express.Router({ mergeParams: true }); - -quoteRoutes.get( - "/", - checkUser, - (req: Express.Request, res: Express.Response, next: Express.NextFunction) => - listQuotesController(res.locals["context"]).execute(req, res, next) -); -/*quoteRoutes.get("/:quoteId", isUser, getQuoteMiddleware, getQuoteController); -quoteRoutes.post("/", isAdmin, createQuoteController); -quoteRoutes.put("/:quoteId", isAdmin, updateQuoteController); -quoteRoutes.delete("/:quoteId", isAdmin, deleteQuoteController);*/ - export const QuoteRouter = (appRouter: Express.Router) => { + const quoteRoutes: Express.Router = Express.Router({ mergeParams: true }); + + quoteRoutes.get( + "/", + checkUser, + (req: Express.Request, res: Express.Response, next: Express.NextFunction) => + listQuotesController(res.locals["context"]).execute(req, res, next) + ); + + quoteRoutes.post("/", checkUser, createQuoteController); + + //quoteRoutes.put("/:quoteId", checkUser, updateQuoteController); + + /*quoteRoutes.get("/:quoteId", isUser, getQuoteMiddleware, getQuoteController); + quoteRoutes.post("/", isAdmin, createQuoteController); + + quoteRoutes.delete("/:quoteId", isAdmin, deleteQuoteController);*/ + appRouter.use("/quotes", quoteRoutes); }; diff --git a/server/src/infrastructure/express/api/routes/sales.routes.ts b/server/src/infrastructure/express/api/routes/sales.routes.ts deleted file mode 100644 index 6d018a4..0000000 --- a/server/src/infrastructure/express/api/routes/sales.routes.ts +++ /dev/null @@ -1,8 +0,0 @@ -import Express from "express"; -import { DealerRouter } from "./dealers.routes"; -import { QuoteRouter } from "./quote.routes"; - -export const salesRouter = (appRouter: Express.Router) => { - DealerRouter(appRouter); - QuoteRouter(appRouter); -}; diff --git a/server/src/infrastructure/express/api/v1.ts b/server/src/infrastructure/express/api/v1.ts index 63f9c98..f7379f9 100644 --- a/server/src/infrastructure/express/api/v1.ts +++ b/server/src/infrastructure/express/api/v1.ts @@ -1,7 +1,13 @@ -import { salesRouter } from "@/contexts/sales/infrastructure/express"; import Express from "express"; import { createContextMiddleware } from "./context.middleware"; -import { authRouter, catalogRouter, profileRouter, usersRouter } from "./routes"; +import { + DealerRouter, + authRouter, + catalogRouter, + profileRouter, + quoteRoutes, + usersRouter, +} from "./routes"; export const v1Routes = () => { const routes = Express.Router({ mergeParams: true }); @@ -25,7 +31,8 @@ export const v1Routes = () => { profileRouter(routes); usersRouter(routes); catalogRouter(routes); - salesRouter(routes); + DealerRouter(routes); + quoteRoutes(routes); return routes; }; diff --git a/server/src/infrastructure/express/app.ts b/server/src/infrastructure/express/app.ts index e607fcc..79f3e16 100644 --- a/server/src/infrastructure/express/app.ts +++ b/server/src/infrastructure/express/app.ts @@ -25,7 +25,7 @@ app.use(responseTime()); // enable CORS - Cross Origin Resource Sharing app.use( cors({ - origin: "http://localhost:5173", + origin: "*", //"http://localhost:5173", credentials: true, exposedHeaders: [ diff --git a/shared/lib/contexts/common/domain/entities/TextValueObject.ts b/shared/lib/contexts/common/domain/entities/TextValueObject.ts new file mode 100644 index 0000000..090d0ac --- /dev/null +++ b/shared/lib/contexts/common/domain/entities/TextValueObject.ts @@ -0,0 +1,34 @@ +import Joi from "joi"; + +import { DomainError, Result, RuleValidator, handleDomainError } from ".."; +import { UndefinedOr } from "../../../../utilities"; +import { IStringValueObjectOptions, StringValueObject } from "./StringValueObject"; + +export class TextValueObject extends StringValueObject { + protected static validate(value: UndefinedOr, options: IStringValueObjectOptions) { + const rule = Joi.string() + .allow(null) + .allow("") + .default("") + .trim() + .label(options.label ? options.label : "value"); + + return RuleValidator.validate(rule, value); + } + + public static create(value: UndefinedOr, options: IStringValueObjectOptions = {}) { + const _options = { + label: "text", + ...options, + }; + + const validationResult = TextValueObject.validate(value, _options); + + if (validationResult.isFailure) { + return Result.fail( + handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options) + ); + } + return Result.ok(new TextValueObject(validationResult.object)); + } +} diff --git a/shared/lib/contexts/common/domain/entities/index.ts b/shared/lib/contexts/common/domain/entities/index.ts index ef38a3e..ea565be 100644 --- a/shared/lib/contexts/common/domain/entities/index.ts +++ b/shared/lib/contexts/common/domain/entities/index.ts @@ -20,6 +20,7 @@ export * from "./ResultCollection"; export * from "./Slug"; export * from "./StringValueObject"; export * from "./TINNumber"; +export * from "./TextValueObject"; export * from "./UTCDateValue"; export * from "./UniqueID"; export * from "./UnitPrice"; diff --git a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts index a8e3643..40377e0 100644 --- a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts +++ b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Response.dto.ts @@ -1,10 +1,10 @@ -import { IMoney_Response_DTO } from "shared/lib/contexts/common"; +import { IMoney_Response_DTO } from "../../../../../common"; export interface ICreateQuote_Response_DTO { id: string; status: string; date: string; - language_code: string; + lang_code: string; currency_code: string; subtotal: IMoney_Response_DTO;