({
- url: `${apiUrl}/${resource}/${id}`,
- method: "GET",
- });
-
- return response.data;*/
- },
-
- createOne: (params: ICreateOneDataProviderParams
) => {
- throw Error;
- },
- updateOne:
(params: IUpdateOneDataProviderParams
) => {
- throw Error;
- },
- removeOne: (params: IRemoveOneDataProviderParams) => {
- throw Error;
- },
-});
-
-const extractPaginationParams = (pagination?: IPaginationDataProviderParam) => {
- const { pageIndex = INITIAL_PAGE_INDEX, pageSize = INITIAL_PAGE_SIZE } = pagination || {};
-
- return {
- page: pageIndex,
- limit: pageSize,
- };
-};
diff --git a/client/src/lib/axios/index.ts b/client/src/lib/axios/index.ts
index 63d0c32..9996379 100644
--- a/client/src/lib/axios/index.ts
+++ b/client/src/lib/axios/index.ts
@@ -1,3 +1,2 @@
-export * from "./HttpError";
export * from "./createAxiosAuthActions";
export * from "./createAxiosDataProvider";
diff --git a/client/src/lib/axios/setupInterceptors.ts b/client/src/lib/axios/setupInterceptors.ts
index 7ec94ff..6ec4c8a 100644
--- a/client/src/lib/axios/setupInterceptors.ts
+++ b/client/src/lib/axios/setupInterceptors.ts
@@ -59,7 +59,7 @@ const onResponseError = (error: AxiosError): Promise => {
break;
case 401:
console.error("UnAuthorized");
- //return (window.location.href = "/logout");
+ return (window.location.href = "/logout");
break;
case 403:
console.error("Forbidden");
diff --git a/client/src/lib/calc.ts b/client/src/lib/calc.ts
new file mode 100644
index 0000000..28b7f0b
--- /dev/null
+++ b/client/src/lib/calc.ts
@@ -0,0 +1,59 @@
+import { MoneyValue, Percentage, Quantity } from "@shared/contexts";
+import { IMoney, IPercentage, IQuantity } from "./types";
+
+export const calculateItemTotals = (item: {
+ quantity?: IQuantity;
+ retail_price?: IMoney;
+ discount?: IPercentage;
+}): {
+ quantity: Quantity;
+ retailPrice: MoneyValue;
+ price: MoneyValue;
+ discount: Percentage;
+ total: MoneyValue;
+} => {
+ const { quantity: quantityValue, retail_price: retailPriceValue, discount: discountValue } = item;
+
+ console.log({
+ quantityValue,
+ retailPriceValue,
+ discountValue,
+ });
+
+ const quantityOrError = Quantity.create(quantityValue);
+ if (quantityOrError.isFailure) {
+ throw quantityOrError.error;
+ }
+ const quantity = quantityOrError.object;
+
+ const retailPriceOrError = MoneyValue.create(retailPriceValue);
+ if (retailPriceOrError.isFailure) {
+ throw retailPriceOrError.error;
+ }
+ const retailPrice = retailPriceOrError.object;
+
+ const discountOrError = Percentage.create(discountValue);
+ if (discountOrError.isFailure) {
+ throw discountOrError.error;
+ }
+ const discount = discountOrError.object;
+
+ const price = retailPrice.multiply(quantity.toNumber());
+ const total = price.subtract(price.percentage(discount.toNumber()));
+
+ return {
+ quantity,
+ retailPrice,
+ price,
+ discount,
+ total,
+ };
+
+ /*return {
+ quantity: quantity.toObject(),
+ retail_price: retailPrice.toObject(),
+ price: price.toObject(),
+ discount: discount.toObject(),
+ total: total.toObject(),
+ };*/
+};
diff --git a/client/src/lib/hooks/index.ts b/client/src/lib/hooks/index.ts
index 0ad5e35..564a872 100644
--- a/client/src/lib/hooks/index.ts
+++ b/client/src/lib/hooks/index.ts
@@ -13,7 +13,9 @@ export * from "./useUrlId";
export * from "./useAuth";
export * from "./useCustomDialog";
+export * from "./useDataSource";
export * from "./useDataTable";
export * from "./useLocalization";
export * from "./usePagination";
export * from "./useTheme";
+export * from "./useUnsavedChangesNotifier";
diff --git a/client/src/lib/hooks/useAuth/AuthActions.ts b/client/src/lib/hooks/useAuth/AuthActions.ts
index 4c11ca3..142d648 100644
--- a/client/src/lib/hooks/useAuth/AuthActions.ts
+++ b/client/src/lib/hooks/useAuth/AuthActions.ts
@@ -7,7 +7,7 @@ export type SuccessNotificationResponse = {
export type PermissionResponse = unknown;
-export type IdentityResponse = IIdentity_Response_DTO;
+export type IdentityResponse = IIdentity_Response_DTO | null;
export type AuthActionCheckResponse = {
authenticated: boolean;
diff --git a/client/src/lib/hooks/useAuth/AuthContext.tsx b/client/src/lib/hooks/useAuth/AuthContext.tsx
index e8a0c85..45f3497 100644
--- a/client/src/lib/hooks/useAuth/AuthContext.tsx
+++ b/client/src/lib/hooks/useAuth/AuthContext.tsx
@@ -28,6 +28,7 @@ export const AuthProvider = ({
};
const handleCheck = async () => {
+ console.trace("check");
try {
return Promise.resolve(authActions.check?.());
} catch (error) {
diff --git a/client/src/lib/hooks/useAuth/useIsLoggedIn.tsx b/client/src/lib/hooks/useAuth/useIsLoggedIn.tsx
index b00a6ea..eca773b 100644
--- a/client/src/lib/hooks/useAuth/useIsLoggedIn.tsx
+++ b/client/src/lib/hooks/useAuth/useIsLoggedIn.tsx
@@ -9,6 +9,7 @@ export const useIsLoggedIn = (queryOptions?: UseQueryOptions({
queryKey: keys().auth().action("check").get(),
queryFn: check,
+ retry: false,
...queryOptions,
});
diff --git a/client/src/lib/hooks/useBreadcrumbs.tsx b/client/src/lib/hooks/useBreadcrumbs.tsx
deleted file mode 100644
index 65e1a1e..0000000
--- a/client/src/lib/hooks/useBreadcrumbs.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useEffect, useState } from "react";
-import { useMatches } from "react-router";
-
-export const useBreadcrumbs = (): any[] => {
- const [crumbs, setCrumbs] = useState([]);
- let matches = useMatches();
-
- useEffect(() => {
- const _crumbs = matches
- // @ts-ignore
- .filter((match) => Boolean(match.handle?.crumb))
- // @ts-ignore
- .map((match) => match.handle?.crumb(match.data));
-
- setCrumbs(_crumbs);
- }, matches);
-
- return crumbs;
-};
diff --git a/client/src/lib/hooks/useDataSource/useMany.tsx b/client/src/lib/hooks/useDataSource/useMany.tsx
index 50a9d34..51abf6d 100644
--- a/client/src/lib/hooks/useDataSource/useMany.tsx
+++ b/client/src/lib/hooks/useDataSource/useMany.tsx
@@ -1,37 +1,30 @@
-import {
- QueryFunction,
- QueryKey,
- UseQueryResult,
- useQuery,
-} from '@tanstack/react-query';
-
+import { QueryFunction, QueryKey, UseQueryResult, useQuery } from "@tanstack/react-query";
export interface IUseManyQueryOptions<
- TUseManyQueryData = unknown,
- TUseManyQueryError = unknown
+ TUseManyQueryData = unknown,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ TUseManyQueryError = unknown
> {
- queryKey: QueryKey;
- queryFn: QueryFunction;
- enabled?: boolean;
- select?: (data: TUseManyQueryData) => TUseManyQueryData;
- queryOptions?: any;
+ queryKey: QueryKey;
+ queryFn: QueryFunction;
+ enabled?: boolean;
+ select?: (data: TUseManyQueryData) => TUseManyQueryData;
+ queryOptions?: any;
}
export function useMany(
- options: IUseManyQueryOptions
+ options: IUseManyQueryOptions
): UseQueryResult {
- const { queryKey, queryFn, enabled, select, queryOptions } = options;
-
- const queryResponse = useQuery({
- queryKey,
- queryFn,
- keepPreviousData: true,
- ...queryOptions,
- enabled,
- select,
+ const { queryKey, queryFn, enabled, select, queryOptions } = options;
- });
+ const queryResponse = useQuery({
+ queryKey,
+ queryFn,
+ keepPreviousData: true,
+ ...queryOptions,
+ enabled,
+ select,
+ });
- return queryResponse;
+ return queryResponse;
}
-
diff --git a/client/src/lib/hooks/useDataSource/useRemove.tsx b/client/src/lib/hooks/useDataSource/useRemove.tsx
index d892dc1..4ad4f36 100644
--- a/client/src/lib/hooks/useDataSource/useRemove.tsx
+++ b/client/src/lib/hooks/useDataSource/useRemove.tsx
@@ -1,30 +1,29 @@
-import { useMutation } from '@tanstack/react-query';
+import { useMutation } from "@tanstack/react-query";
export interface IUseRemoveMutationOptions<
- TUseRemoveMutationData,
- TUseRemoveMutationError,
- TUseRemoveMutationVariables
+ TUseRemoveMutationData,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ TUseRemoveMutationError,
+ TUseRemoveMutationVariables
> {
- mutationFn: (
- variables: TUseRemoveMutationVariables,
- ) => Promise;
+ mutationFn: (variables: TUseRemoveMutationVariables) => Promise;
}
export function useRemove<
+ TUseRemoveMutationData,
+ TUseRemoveMutationError,
+ TUseRemoveMutationVariables
+>(
+ options: IUseRemoveMutationOptions<
TUseRemoveMutationData,
TUseRemoveMutationError,
- TUseRemoveMutationVariables>(options: IUseRemoveMutationOptions<
- TUseRemoveMutationData,
- TUseRemoveMutationError,
- TUseRemoveMutationVariables
- >) {
- const { mutationFn, ...params } = options;
+ TUseRemoveMutationVariables
+ >
+) {
+ const { mutationFn, ...params } = options;
- return useMutation<
- TUseRemoveMutationData,
- TUseRemoveMutationError,
- TUseRemoveMutationVariables>({
- mutationFn,
- ...params
- });
+ return useMutation({
+ mutationFn,
+ ...params,
+ });
}
diff --git a/client/src/lib/hooks/useDataTable/DataTableContext.tsx b/client/src/lib/hooks/useDataTable/DataTableContext.tsx
index 4df1a2c..49fc4bb 100644
--- a/client/src/lib/hooks/useDataTable/DataTableContext.tsx
+++ b/client/src/lib/hooks/useDataTable/DataTableContext.tsx
@@ -1,12 +1,21 @@
-import { PaginationState, usePaginationParams } from "@/lib/hooks";
+import { PaginationState } from "@/lib/hooks";
import { SortingState } from "@tanstack/react-table";
-import { PropsWithChildren, createContext, useCallback, useMemo, useState } from "react";
+import {
+ Dispatch,
+ PropsWithChildren,
+ SetStateAction,
+ createContext,
+ useCallback,
+ useMemo,
+ useState,
+} from "react";
+import { useSyncedPagination } from "./useSyncedPagination";
export interface IDataTableContextState {
pagination: PaginationState;
setPagination: (newPagination: PaginationState) => void;
- sorting: [];
- setSorting: () => void;
+ sorting: SortingState;
+ setSorting: Dispatch>;
globalFilter: string;
setGlobalFilter: (newGlobalFilter: string) => void;
resetGlobalFilter: () => void;
@@ -16,12 +25,14 @@ export interface IDataTableContextState {
export const DataTableContext = createContext(null);
export const DataTableProvider = ({
+ syncWithLocation = true,
initialGlobalFilter = "",
children,
}: PropsWithChildren<{
+ syncWithLocation?: boolean;
initialGlobalFilter?: string;
}>) => {
- const [pagination, setPagination] = usePaginationParams();
+ const [pagination, setPagination] = useSyncedPagination(syncWithLocation);
const [globalFilter, setGlobalFilter] = useState(initialGlobalFilter);
const [sorting, setSorting] = useState([]);
diff --git a/client/src/lib/hooks/useDataTable/helper.ts b/client/src/lib/hooks/useDataTable/helper.ts
deleted file mode 100644
index fd4a9f5..0000000
--- a/client/src/lib/hooks/useDataTable/helper.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import { differenceWith, isEmpty, isEqual, reverse, unionWith } from 'lodash';
-
-export const parseQueryString = (queryString: Record) => {
- // pagination
- const pageIndex = parseInt(queryString.page ?? '0', 0) - 1;
- const pageSize = Math.min(parseInt(queryString.limit ?? '10', 10), 100);
- const pagination =
- pageIndex >= 0 && pageSize > 0 ? { pageIndex, pageSize } : undefined;
-
- // pagination
- /*let pagination = undefined;
- if (page !== undefined && limit !== undefined) {
- let parsedPage = toSafeInteger(queryString['page']) - 1;
- if (parsedPage < 0) parsedPage = 0;
-
- let parsedPageSize = toSafeInteger(queryString['limit']);
- if (parsedPageSize > 100) parsedPageSize = 100;
- if (parsedPageSize < 5) parsedPageSize = 5;
-
- pagination = {
- pageIndex: parsedPage,
- pageSize: parsedPageSize,
- };
- }*/
-
- // sorter
- // sorter
- const sorter = (queryString.sort ?? '')
- .split(',')
- .map((token) => token.match(/([+-]?)([\w_]+)/i))
- .slice(0, 3)
- .map((item) => (item ? { id: item[2], desc: item[1] === '-' } : null))
- .filter(Boolean);
-
- /*let sorter = [];
- if (sort !== undefined) {
- sorter = sort
- .split(',')
- .map((token) => token.match(/([+-]?)([\w_]+)/i))
- .slice(0, 3)
- .map((item) =>
- item ? { id: item[2], desc: item[1] === '-' } : null
- );
- }*/
-
- // filters
- const filters = Object.entries(queryString)
- .filter(([key]) => key !== 'page' && key !== 'limit' && key !== 'sort')
- .map(([key, value]) => {
- const [, field, , , operator] =
- key.match(/([\w]+)(([\[])([\w]+)([\]]))*/i) ?? [];
- const sanitizedOperator = _sanitizeOperator(operator ?? '');
- return !isEmpty(value)
- ? { field, operator: sanitizedOperator, value }
- : null;
- })
- .filter(Boolean);
-
- /*let filters = [];
- if (filterCandidates !== undefined) {
- Object.keys(filterCandidates).map((token) => {
- const [, field, , , operator] = token.match( */
- // /([\w]+)(([\[])([\w]+)([\]]))*/i
- /* );
- const value = filterCandidates[token];
-
- if (!isEmpty(value)) {
- filters.push({
- field,
- operator: _sanitizeOperator(operator),
- value,
- });
- }
- });
- }*/
-
- return {
- pagination,
- sorter,
- filters,
- };
-};
-
-export const buildQueryString = ({ pagination, sorter, filters }) => {
- const params = new URLSearchParams();
-
- if (
- pagination &&
- pagination.pageIndex !== undefined &&
- pagination.pageSize !== undefined
- ) {
- params.append('page', String(pagination.pageIndex + 1));
- params.append('limit', String(pagination.pageSize));
- }
-
- if (sorter && Array.isArray(sorter) && sorter.length > 0) {
- params.append(
- 'sort',
- sorter.map(({ id, desc }) => `${desc ? '-' : ''}${id}`).toString()
- );
- }
-
- if (filters && Array.isArray(filters) && filters.length > 0) {
- filters.forEach((filterItem) => {
- if (filterItem.value !== undefined) {
- let operator = _mapFilterOperator(filterItem.operator);
- if (operator === 'eq') {
- params.append(`${filterItem.field}`, filterItem.value);
- } else {
- params.append(
- `${filterItem.field}[${_mapFilterOperator(
- filterItem.operator
- )}]`,
- filterItem.value
- );
- }
- }
- });
- }
-
- return params.toString();
-};
-
-export const combineFilters = (requiredFilters = [], otherFilters = []) => [
- ...differenceWith(otherFilters, requiredFilters, isEqual),
- ...requiredFilters,
-];
-
-export const unionFilters = (
- permanentFilter = [],
- newFilters = [],
- prevFilters = []
-) =>
- reverse(
- unionWith(
- permanentFilter,
- newFilters,
- prevFilters,
- (left, right) =>
- left.field == right.field && left.operator == right.operator
- )
- ).filter(
- (crudFilter) =>
- crudFilter.value !== undefined && crudFilter.value !== null
- );
-
-export const extractTableSortPropertiesFromColumn = (columns) => {
- const _extractColumnSortProperies = (column) => {
- const { canSort, isSorted, sortedIndex, isSortedDesc } = column;
- if (!isSorted || !canSort) {
- return undefined;
- } else {
- return {
- index: sortedIndex,
- field: column.id,
- order: isSortedDesc ? 'DESC' : 'ASC',
- };
- }
- };
-
- return columns
- .map((column) => _extractColumnSortProperies(column))
- .filter((item) => item)
- .sort((a, b) => a.index - b.index);
-};
-
-export const extractTableSortProperties = (sorter) => {
- return sorter.map((sortItem, index) => ({
- index,
- field: sortItem.id,
- order: sortItem.desc ? 'DESC' : 'ASC',
- }));
-};
-
-export const extractTableFilterProperties = (filters) =>
- filters.filter((item) => !isEmpty(item.value));
-
-const _sanitizeOperator = (operator) =>
- ['eq', 'ne', 'gte', 'lte', 'like'].includes(operator) ? operator : 'eq';
-
-const _mapFilterOperator = (operator) => {
- switch (operator) {
- case 'ne':
- case 'gte':
- case 'lte':
- return `[${operator}]`;
- case 'contains':
- return '[like]';
- default:
- return 'eq';
- }
-};
diff --git a/client/src/lib/hooks/useDataTable/useQueryDataTable.tsx b/client/src/lib/hooks/useDataTable/useQueryDataTable.tsx
index 525922f..1d3ceaf 100644
--- a/client/src/lib/hooks/useDataTable/useQueryDataTable.tsx
+++ b/client/src/lib/hooks/useDataTable/useQueryDataTable.tsx
@@ -1,7 +1,4 @@
-import {
- DataTableColumnProps,
- getDataTableSelectionColumn,
-} from "@/components";
+import { DataTableColumnProps, getDataTableSelectionColumn } from "@/components";
import { IListResponse_DTO } from "@shared/contexts";
import {
OnChangeFn,
@@ -12,12 +9,9 @@ import {
} from "@tanstack/react-table";
import { useCallback, useMemo, useState } from "react";
import { UseListQueryResult } from "../useDataSource";
-import { usePaginationParams } from "../usePagination";
+import { usePaginationSyncWithLocation } from "../usePagination";
-type TUseDataTableQueryResult = UseListQueryResult<
- IListResponse_DTO,
- TError
->;
+type TUseDataTableQueryResult = UseListQueryResult, TError>;
type TUseDataTableQuery = (params: {
pagination: {
@@ -44,16 +38,9 @@ type DataTableColumnsOptionsProps = {
columns: DataTableColumnsProps;
};
-type DataTableColumnsProps = DataTableColumnProps<
- TData,
- TValue
->[];
+type DataTableColumnsProps = DataTableColumnProps[];
-export const useQueryDataTable = <
- TData = unknown,
- TValue = unknown,
- TError = Error
->({
+export const useQueryDataTable = ({
fetchQuery,
enabled = true,
@@ -64,7 +51,7 @@ export const useQueryDataTable = <
const defaultData = useMemo(() => [], []);
const [rowSelection, setRowSelection] = useState({});
- const [pagination, setPagination] = usePaginationParams();
+ const [pagination, setPagination] = usePaginationSyncWithLocation();
const [sorting, setSorting] = useState([]);
const paginationUpdater: OnChangeFn = (updater) => {
diff --git a/client/src/lib/hooks/useDataTable/useSyncedPagination.tsx b/client/src/lib/hooks/useDataTable/useSyncedPagination.tsx
new file mode 100644
index 0000000..85a9153
--- /dev/null
+++ b/client/src/lib/hooks/useDataTable/useSyncedPagination.tsx
@@ -0,0 +1,12 @@
+import { usePagination, usePaginationSyncWithLocation } from "../usePagination";
+
+export const useSyncedPagination = (syncWithLocation: boolean) => {
+ const [paginationWithLocation, setPaginationWithLocation] = usePaginationSyncWithLocation();
+ const [paginationWithoutLocation, setPaginationWithoutLocation] = usePagination();
+
+ if (syncWithLocation) {
+ return [paginationWithLocation, setPaginationWithLocation] as const;
+ } else {
+ return [paginationWithoutLocation, setPaginationWithoutLocation] as const;
+ }
+};
diff --git a/client/src/lib/hooks/useLocalization/useLocatlization.tsx b/client/src/lib/hooks/useLocalization/useLocatlization.tsx
index f961510..db6b4e2 100644
--- a/client/src/lib/hooks/useLocalization/useLocatlization.tsx
+++ b/client/src/lib/hooks/useLocalization/useLocatlization.tsx
@@ -1,7 +1,6 @@
/* https://github.com/mayank8aug/use-localization/blob/main/src/index.ts */
import { useCallback, useMemo } from "react";
-import { useTranslation } from "react-i18next";
import { LocaleToCurrencyTable, rtlLangsList } from "./utils";
type UseLocalizationProps = {
@@ -12,10 +11,10 @@ export const useLocalization = (props: UseLocalizationProps) => {
const { locale } = props;
const [lang, loc] = locale.split("-");
- const { i18n } = useTranslation();
+ //const { i18n } = useTranslation();
// Obtener el idioma actual
- const currentLanguage = i18n.language;
+ // const currentLanguage = i18n.language;
const formatCurrency = useCallback(
(value: number) => {
diff --git a/client/src/lib/hooks/usePagination/index.ts b/client/src/lib/hooks/usePagination/index.ts
index 1d72ed7..ba7429d 100644
--- a/client/src/lib/hooks/usePagination/index.ts
+++ b/client/src/lib/hooks/usePagination/index.ts
@@ -1,2 +1,2 @@
export * from "./usePagination";
-export * from "./usePaginationParams";
+export * from "./usePaginationSyncWithLocation";
diff --git a/client/src/lib/hooks/usePagination/usePaginationParams.tsx b/client/src/lib/hooks/usePagination/usePaginationSyncWithLocation.tsx
similarity index 94%
rename from client/src/lib/hooks/usePagination/usePaginationParams.tsx
rename to client/src/lib/hooks/usePagination/usePaginationSyncWithLocation.tsx
index 154e330..8592fd0 100644
--- a/client/src/lib/hooks/usePagination/usePaginationParams.tsx
+++ b/client/src/lib/hooks/usePagination/usePaginationSyncWithLocation.tsx
@@ -9,7 +9,7 @@ import { useMemo } from "react";
import { useSearchParams } from "react-router-dom";
import { usePagination } from "./usePagination";
-export const usePaginationParams = (
+export const usePaginationSyncWithLocation = (
initialPageIndex: number = INITIAL_PAGE_INDEX,
initialPageSize: number = INITIAL_PAGE_SIZE
) => {
@@ -50,5 +50,5 @@ export const usePaginationParams = (
});
};
- return [pagination, updatePagination];
+ return [pagination, updatePagination] as const;
};
diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx
new file mode 100644
index 0000000..c49d5aa
--- /dev/null
+++ b/client/src/lib/hooks/useUnsavedChangesNotifier/WarnAboutChangeContext.tsx
@@ -0,0 +1,18 @@
+import { PropsWithChildren, createContext, useState } from "react";
+
+export interface IUnsavedWarnContextState {
+ warnWhen?: boolean;
+ setWarnWhen?: (value: boolean) => void;
+}
+
+export const UnsavedWarnContext = createContext({});
+
+export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => {
+ const [warnWhen, setWarnWhen] = useState(false);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/index.ts b/client/src/lib/hooks/useUnsavedChangesNotifier/index.ts
index 5cdc87b..3d00b5e 100644
--- a/client/src/lib/hooks/useUnsavedChangesNotifier/index.ts
+++ b/client/src/lib/hooks/useUnsavedChangesNotifier/index.ts
@@ -1 +1,3 @@
+export * from "./WarnAboutChangeContext";
export * from "./useUnsavedChangesNotifier";
+export * from "./useWarnAboutChange";
diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/usePromptWorkaround.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/usePromptWorkaround.tsx
new file mode 100644
index 0000000..d435857
--- /dev/null
+++ b/client/src/lib/hooks/useUnsavedChangesNotifier/usePromptWorkaround.tsx
@@ -0,0 +1,78 @@
+/**
+ * `useBlocker` and `usePrompt` is no longer part of react-router-dom for the routers other than `DataRouter`.
+ *
+ * The previous workaround ( boolean, when = true) {
+ const { navigator } = React.useContext(NavigationContext);
+
+ React.useEffect(() => {
+ if (!when) {
+ return;
+ }
+
+ const go = navigator.go;
+ const push = navigator.push;
+
+ navigator.push = (...args: Parameters) => {
+ const result = confirmExit();
+ if (result !== false) {
+ push(...args);
+ }
+ };
+
+ navigator.go = (...args: Parameters) => {
+ const result = confirmExit();
+ if (result !== false) {
+ go(...args);
+ }
+ };
+
+ return () => {
+ navigator.push = push;
+ navigator.go = go;
+ };
+ }, [navigator, confirmExit, when]);
+}
+
+export function usePrompt(message: string, when = true, onConfirm?: () => void, legacy = false) {
+ const warnWhenListener = React.useCallback(
+ (e: { preventDefault: () => void; returnValue: string }) => {
+ e.preventDefault();
+
+ e.returnValue = message;
+
+ return e.returnValue;
+ },
+ [message]
+ );
+
+ React.useEffect(() => {
+ if (when && !legacy) {
+ window.addEventListener("beforeunload", warnWhenListener);
+ }
+
+ return () => {
+ window.removeEventListener("beforeunload", warnWhenListener);
+ };
+ }, [warnWhenListener, when, legacy]);
+
+ const confirmExit = React.useCallback(() => {
+ const confirm = window.confirm(message);
+ if (confirm && onConfirm) {
+ onConfirm();
+ }
+ return confirm;
+ }, [message]);
+
+ console.log(when);
+
+ useConfirmExit(confirmExit, when);
+}
diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx
index dc506ce..c73aa56 100644
--- a/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx
+++ b/client/src/lib/hooks/useUnsavedChangesNotifier/useUnsavedChangesNotifier.tsx
@@ -1,16 +1,44 @@
-import React from "react";
-import { useCustomDialog } from "../useCustomDialog";
+import { CustomDialog } from "@/components";
+import { t } from "i18next";
+import { useEffect, useMemo } from "react";
+import { useLocation } from "react-router-dom";
+import { useWarnAboutChange } from "./useWarnAboutChange";
type UnsavedChangesNotifierProps = {
- translationKey?: string;
message?: string;
};
-export const UnsavedChangesNotifier: React.FC = () => {
- const { openDialog: openWarmDialog, DialogComponent: WarmDialog } = useCustomDialog({
- title: "Hay cambios sin guardar",
- description: "Are you sure you want to leave? You have unsaved changes.",
+export const UnsavedChangesNotifier = ({
+ message = t("unsaved_changes_prompt"),
+}: UnsavedChangesNotifierProps) => {
+ const { pathname } = useLocation();
+ const { warnWhen, setWarnWhen } = useWarnAboutChange();
+
+ useEffect(() => {
+ return () => setWarnWhen?.(false);
+ }, [pathname]);
+
+ const warnMessage = useMemo(() => {
+ return t(message);
+ }, [message]);
+
+ return (
+ {}}
+ title='titulo'
+ isOpen={warnWhen}
+ onConfirm={() => {
+ setWarnWhen?.(false);
+ }}
+ description={warnMessage}
+ />
+ );
+
+ /*usePrompt(warnMessage, warnWhen, () => {
+ setWarnWhen?.(false);
});
- return <>{WarmDialog}>;
+ return null;*/
};
diff --git a/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx b/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx
new file mode 100644
index 0000000..4c58d49
--- /dev/null
+++ b/client/src/lib/hooks/useUnsavedChangesNotifier/useWarnAboutChange.tsx
@@ -0,0 +1,15 @@
+import { useContext } from "react";
+import { UnsavedWarnContext } from "./WarnAboutChangeContext";
+
+export const useWarnAboutChange = () => {
+ const context = useContext(UnsavedWarnContext);
+ if (context === null)
+ throw new Error("useWarnAboutChange must be used within a UnsavedWarnProvider");
+
+ const { warnWhen, setWarnWhen } = context;
+
+ return {
+ warnWhen: Boolean(warnWhen),
+ setWarnWhen: setWarnWhen ?? (() => undefined),
+ };
+};
diff --git a/client/src/lib/types.ts b/client/src/lib/types.ts
new file mode 100644
index 0000000..e7e941b
--- /dev/null
+++ b/client/src/lib/types.ts
@@ -0,0 +1,9 @@
+import {
+ IMoney_Response_DTO,
+ IPercentage_Response_DTO,
+ IQuantity_Response_DTO,
+} from "@shared/contexts";
+
+export interface IMoney extends IMoney_Response_DTO {}
+export interface IQuantity extends IQuantity_Response_DTO {}
+export interface IPercentage extends IPercentage_Response_DTO {}
diff --git a/client/src/locales/es.json b/client/src/locales/es.json
index f9a19cc..0eae66f 100644
--- a/client/src/locales/es.json
+++ b/client/src/locales/es.json
@@ -30,7 +30,8 @@
"duplicate_rows": "Duplicar",
"duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
"pick_date": "Elige una fecha",
- "required_field": "Este campo es obligatorio"
+ "required_field": "Este campo es obligatorio",
+ "unsaved_changes_prompt": "Los últimos cambios no se han guardado. Si continúas, se perderán"
},
"main_menu": {
"home": "Inicio",
@@ -146,6 +147,9 @@
"desc": "desc"
}
}
+ },
+ "edit": {
+ "title": "Cotización"
}
},
"settings": {
diff --git a/client/src/ui/use-autosize-textarea.tsx b/client/src/ui/use-autosize-textarea.tsx
index f4d4baf..48844e9 100644
--- a/client/src/ui/use-autosize-textarea.tsx
+++ b/client/src/ui/use-autosize-textarea.tsx
@@ -15,6 +15,7 @@ export const useAutosizeTextArea = ({
minHeight = 0,
}: UseAutosizeTextAreaProps) => {
const [init, setInit] = React.useState(true);
+
React.useEffect(() => {
// We need to reset the height momentarily to get the correct scrollHeight for the textarea
const offsetBorder = 2;
@@ -36,5 +37,5 @@ export const useAutosizeTextArea = ({
textAreaRef.style.height = `${scrollHeight + offsetBorder}px`;
}
}
- }, [textAreaRef, triggerAutoSize]);
+ }, [textAreaRef, triggerAutoSize, init, maxHeight, minHeight]);
};
diff --git a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts
index 67e01de..a9a8b16 100644
--- a/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts
+++ b/server/src/contexts/sales/application/Quote/CreateQuote.useCase.ts
@@ -4,13 +4,14 @@ import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import {
Collection,
- Currency,
+ CurrencyData,
Description,
DomainError,
ICreateQuote_Request_DTO,
IDomainError,
Language,
Note,
+ Percentage,
Quantity,
Result,
UTCDateValue,
@@ -153,7 +154,7 @@ export class CreateQuoteUseCase
return Result.fail(customerOrError.error);
}
- const currencyOrError = Currency.createFromCode(quoteDTO.currency_code);
+ const currencyOrError = CurrencyData.createFromCode(quoteDTO.currency_code);
if (currencyOrError.isFailure) {
return Result.fail(currencyOrError.error);
}
@@ -177,13 +178,15 @@ export class CreateQuoteUseCase
quoteDTO.items?.map(
(item) =>
QuoteItem.create({
+ articleId: item.article_id,
description: Description.create(item.description).object,
- quantity: Quantity.create({ amount: item.quantity, precision: 4 }).object,
+ quantity: Quantity.create(item.quantity).object,
unitPrice: UnitPrice.create({
amount: item.unit_price.amount,
- currencyCode: item.unit_price.currency,
+ currencyCode: item.unit_price.currency_code,
precision: item.unit_price.precision,
}).object,
+ discount: Percentage.create(item.discount.amount).object,
}).object
)
);
diff --git a/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts b/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts
index 74a88ed..d0f1e0c 100644
--- a/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts
+++ b/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts
@@ -7,10 +7,11 @@ import {
import { IRepositoryManager } from "@/contexts/common/domain";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import { Result, UniqueID } from "@shared/contexts";
-import { IQuoteRepository } from "../../domain";
+import { Dealer, IQuoteRepository } from "../../domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { Quote } from "../../domain/entities/Quotes/Quote";
+import { ISalesContext } from "../../infrastructure";
export interface IGetQuoteUseCaseRequest extends IUseCaseRequest {
id: UniqueID;
@@ -25,14 +26,12 @@ export class GetQuoteUseCase
{
private _adapter: ISequelizeAdapter;
private _repositoryManager: IRepositoryManager;
+ private _dealer?: Dealer;
- constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
- this._adapter = props.adapter;
- this._repositoryManager = props.repositoryManager;
- }
-
- private getRepositoryByName(name: string) {
- return this._repositoryManager.getRepository(name);
+ constructor(context: ISalesContext) {
+ this._adapter = context.adapter;
+ this._repositoryManager = context.repositoryManager;
+ this._dealer = context.dealer;
}
async execute(request: IGetQuoteUseCaseRequest): Promise {
@@ -63,9 +62,7 @@ export class GetQuoteUseCase
return Result.ok(Quote!);
} catch (error: unknown) {
const _error = error as IInfrastructureError;
- return Result.fail(
- UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Error al consultar el usuario", _error)
- );
+ return Result.fail(UseCaseError.create(UseCaseError.REPOSITORY_ERROR, "Query error", _error));
}
}
diff --git a/server/src/contexts/sales/application/Quote/ListQuotes.useCase.ts b/server/src/contexts/sales/application/Quote/ListQuotes.useCase.ts
index e8fe123..8b8b8c1 100644
--- a/server/src/contexts/sales/application/Quote/ListQuotes.useCase.ts
+++ b/server/src/contexts/sales/application/Quote/ListQuotes.useCase.ts
@@ -58,13 +58,7 @@ export class ListQuotesUseCase implements IUseCase;
+ //subtotalPrice: MoneyValue;
+ discount: Percentage;
+ //totalPrice: MoneyValue;
+
dealerId: UniqueID;
}
@@ -38,10 +44,15 @@ export interface IQuote {
reference: QuoteReference;
customer: QuoteCustomer;
language: Language;
- currency: Currency;
+ currency: CurrencyData;
paymentMethod: Note;
notes: Note;
validity: Note;
+
+ subtotalPrice: MoneyValue;
+ discount: Percentage;
+ totalPrice: MoneyValue;
+
items: ICollection;
dealerId: UniqueID;
@@ -60,6 +71,14 @@ export class Quote extends AggregateRoot implements IQuote {
protected _items: ICollection;
+ protected _calculateTotalPriceItems = (): MoneyValue =>
+ this.props.items
+ .toArray()
+ .reduce(
+ (accumulator, currentItem) => accumulator.add(currentItem.subtotalPrice),
+ MoneyValue.create({ amount: 0, precision: 2, currencyCode: this.currency.code }).object
+ );
+
protected constructor(props: IQuoteProps, id?: UniqueID) {
super(props, id);
@@ -113,4 +132,16 @@ export class Quote extends AggregateRoot implements IQuote {
get dealerId() {
return this.props.dealerId;
}
+
+ get subtotalPrice(): MoneyValue {
+ return this._calculateTotalPriceItems();
+ }
+
+ get discount(): Percentage {
+ return this.props.discount;
+ }
+
+ get totalPrice(): MoneyValue {
+ return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
+ }
}
diff --git a/server/src/contexts/sales/domain/entities/Quotes/QuoteItem.ts b/server/src/contexts/sales/domain/entities/Quotes/QuoteItem.ts
index c497ef2..cdab59b 100644
--- a/server/src/contexts/sales/domain/entities/Quotes/QuoteItem.ts
+++ b/server/src/contexts/sales/domain/entities/Quotes/QuoteItem.ts
@@ -4,21 +4,30 @@ import {
IDomainError,
IEntityProps,
MoneyValue,
+ Percentage,
Quantity,
Result,
UniqueID,
} from "@shared/contexts";
export interface IQuoteItemProps extends IEntityProps {
+ articleId: string;
description: Description; // Descripción del artículo o servicio
quantity: Quantity; // Cantidad de unidades
unitPrice: MoneyValue; // Precio unitario en la moneda de la factura
+ // subtotalPrice: MoneyValue; // Precio unitario * Cantidad
+ discount: Percentage; // % descuento
+ // totalPrice: MoneyValue;
}
export interface IQuoteItem {
+ articleId: string;
description: Description;
quantity: Quantity;
unitPrice: MoneyValue;
+ subtotalPrice: MoneyValue;
+ discount: Percentage;
+ totalPrice: MoneyValue;
}
export class QuoteItem extends Entity implements IQuoteItem {
@@ -26,6 +35,10 @@ export class QuoteItem extends Entity implements IQuoteItem {
return Result.ok(new QuoteItem(props, id));
}
+ get articleId(): string {
+ return this.props.articleId;
+ }
+
get description(): Description {
return this.props.description;
}
@@ -37,4 +50,16 @@ export class QuoteItem extends Entity implements IQuoteItem {
get unitPrice(): MoneyValue {
return this.props.unitPrice;
}
+
+ get subtotalPrice(): MoneyValue {
+ return this.unitPrice.multiply(this.quantity.toNumber());
+ }
+
+ get discount(): Percentage {
+ return this.props.discount;
+ }
+
+ get totalPrice(): MoneyValue {
+ return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
+ }
}
diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts
index ef23271..b6caac0 100644
--- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts
+++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/getQuote/presenter/GetQuote.presenter.ts
@@ -1,4 +1,8 @@
-import { ICollection, IGetQuote_Response_DTO } from "@shared/contexts";
+import {
+ ICollection,
+ IGetQuote_QuoteItem_Response_DTO,
+ IGetQuote_Response_DTO,
+} from "@shared/contexts";
import { Quote, QuoteItem } from "../../../../../../domain";
import { ISalesContext } from "../../../../../Sales.context";
@@ -13,44 +17,38 @@ export const GetQuotePresenter: IGetQuotePresenter = {
id: quote.id.toString(),
status: quote.status.toString(),
date: quote.date.toString(),
- language_code: quote.date.toString(),
+ reference: quote.reference.toString(),
+ customer_information: quote.customer.toString(),
+ lang_code: quote.language.code,
currency_code: quote.currency.toString(),
- subtotal: {
- amount: 0,
- precision: 2,
- currency: "EUR",
- },
- total: {
- amount: 0,
- precision: 2,
- currency: "EUR",
- },
+
+ payment_method: quote.paymentMethod.toString(),
+ validity: quote.validity.toString(),
+ notes: quote.notes.toString(),
+
+ subtotal_price: quote.subtotalPrice.toObject(),
+ discount: quote.discount.toObject(),
+ total_price: quote.totalPrice.toObject(),
+
items: quoteItemPresenter(quote.items, context),
+ dealer_id: quote.dealerId.toString(),
};
},
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const quoteItemPresenter = (items: ICollection, context: ISalesContext) =>
+const quoteItemPresenter = (
+ items: ICollection,
+ context: ISalesContext
+): IGetQuote_QuoteItem_Response_DTO[] =>
items.totalCount > 0
? items.items.map((item: QuoteItem) => ({
+ article_id: item.articleId,
description: item.description.toString(),
- quantity: item.quantity.toString(),
- unit_measure: "",
- unit_price: {
- amount: 0,
- precision: 2,
- currency: "EUR",
- },
- subtotal: {
- amount: 0,
- precision: 2,
- currency: "EUR",
- },
- total: {
- amount: 0,
- precision: 2,
- currency: "EUR",
- },
+ quantity: item.quantity.toObject(),
+ unit_price: item.unitPrice.toObject(),
+ subtotal_price: item.subtotalPrice.toObject(),
+ discount: item.discount.toObject(),
+ total_price: item.totalPrice.toObject(),
}))
: [];
diff --git a/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/index.ts b/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/index.ts
index bd19c2c..33b59ce 100644
--- a/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/index.ts
+++ b/server/src/contexts/sales/infrastructure/express/controllers/quotes/listQuotes/index.ts
@@ -1,5 +1,6 @@
import { ListQuotesUseCase } from "@/contexts/sales/application";
import { registerQuoteRepository } from "@/contexts/sales/infrastructure/Quote.repository";
+import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
import Express from "express";
import { ListQuotesController } from "./ListQuotes.controller";
import { ListQuotesPresenter } from "./presenter";
@@ -9,7 +10,7 @@ export const listQuotesController = (
res: Express.Response,
next: Express.NextFunction
) => {
- const context = res.locals.context;
+ const context: ISalesContext = res.locals.context;
registerQuoteRepository(context);
diff --git a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts
index dedb230..0c846a2 100644
--- a/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts
+++ b/server/src/contexts/sales/infrastructure/mappers/quote.mapper.ts
@@ -1,4 +1,4 @@
-import { Currency, Language, Note, UTCDateValue, UniqueID } from "@shared/contexts";
+import { CurrencyData, Language, Note, Percentage, UTCDateValue, UniqueID } from "@shared/contexts";
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
import { IQuoteProps, Quote, QuoteCustomer, QuoteReference } from "../../domain";
@@ -33,18 +33,41 @@ class QuoteMapper
const props: IQuoteProps = {
status: this.mapsValue(source, "status", QuoteStatus.create),
- date: this.mapsValue(source, "issue_date", UTCDateValue.create),
+ date: this.mapsValue(source, "date", UTCDateValue.create),
reference: this.mapsValue(source, "reference", QuoteReference.create),
- currency: this.mapsValue(source, "quote_currency", Currency.createFromCode),
- language: this.mapsValue(source, "quote_language", Language.createFromCode),
- customer: this.mapsValue(source, "customer", QuoteCustomer.create),
+ currency: this.mapsValue(source, "currency_code", CurrencyData.createFromCode),
+ language: this.mapsValue(source, "lang_code", Language.createFromCode),
+ customer: this.mapsValue(source, "customer_information", QuoteCustomer.create),
validity: this.mapsValue(source, "validity", Note.create),
- paymentMethod: this.mapsValue(source, "paymentMethod", Note.create),
+ paymentMethod: this.mapsValue(source, "payment_method", Note.create),
notes: this.mapsValue(source, "notes", Note.create),
items,
+ /*subtotal: this.mapsValue(source, "subtotal_price", (subtotal_price) =>
+ MoneyValue.create({
+ amount: subtotal_price,
+ currencyCode: source.currency_code,
+ precision: 2,
+ })
+ ),*/
+
+ discount: this.mapsValue(source, "discount", (discount) =>
+ Percentage.create({
+ amount: discount,
+ precision: Percentage.DEFAULT_PRECISION,
+ })
+ ),
+
+ /*totalPrice: this.mapsValue(source, "total_price", (total_price) =>
+ MoneyValue.create({
+ amount: total_price,
+ currencyCode: source.currency_code,
+ precision: 2,
+ })
+ ),*/
+
dealerId: this.mapsValue(source, "dealer_id", UniqueID.create),
};
@@ -75,9 +98,9 @@ class QuoteMapper
payment_method: source.paymentMethod.toPrimitive(),
notes: source.notes.toPrimitive(),
- discount: 0,
- subtotal: 0,
- total: 0,
+ subtotal_price: source.subtotalPrice.toPrimitive(),
+ discount: source.discount.toPrimitive(),
+ total_price: source.totalPrice.toPrimitive(),
items,
dealer_id: source.dealerId.toPrimitive(),
diff --git a/server/src/contexts/sales/infrastructure/mappers/quoteItem.mapper.ts b/server/src/contexts/sales/infrastructure/mappers/quoteItem.mapper.ts
index 12dbbf4..c732807 100644
--- a/server/src/contexts/sales/infrastructure/mappers/quoteItem.mapper.ts
+++ b/server/src/contexts/sales/infrastructure/mappers/quoteItem.mapper.ts
@@ -1,5 +1,5 @@
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
-import { Description, Quantity, UniqueID, UnitPrice } from "@shared/contexts";
+import { Description, MoneyValue, Percentage, Quantity, UniqueID } from "@shared/contexts";
import { IQuoteItemProps, Quote, QuoteItem } from "../../domain";
import { ISalesContext } from "../Sales.context";
import { Quote_Model } from "../sequelize";
@@ -23,15 +23,40 @@ class QuoteItemMapper
const id = this.mapsValue(source, "item_id", UniqueID.create);
const props: IQuoteItemProps = {
+ articleId: source.id_article,
description: this.mapsValue(source, "description", Description.create),
quantity: this.mapsValue(source, "quantity", Quantity.create),
unitPrice: this.mapsValue(source, "unit_price", (unit_price) =>
- UnitPrice.create({
+ MoneyValue.create({
amount: unit_price,
currencyCode: sourceParent.currency_code,
precision: 4,
})
),
+
+ /*subtotalPrice: this.mapsValue(source, "subtotal_price", (subtotal_price) =>
+ MoneyValue.create({
+ amount: subtotal_price,
+ currencyCode: sourceParent.currency_code,
+ precision: 4,
+ })
+ ),
+ */
+
+ discount: this.mapsValue(source, "discount", (discount) =>
+ Percentage.create({
+ amount: discount,
+ precision: Percentage.DEFAULT_PRECISION,
+ })
+ ),
+
+ /*totalPrice: this.mapsValue(source, "total_price", (total_price) =>
+ MoneyValue.create({
+ amount: total_price,
+ currencyCode: sourceParent.currency_code,
+ precision: 2,
+ })
+ ),*/
};
const quoteItemOrError = QuoteItem.create(props, id);
@@ -50,16 +75,16 @@ class QuoteItemMapper
const { index, sourceParent } = params;
return {
+ item_id: source.id.toString(),
quote_id: sourceParent.id.toPrimitive(),
position: index,
- item_id: "", //article_id: source.id.toPrimitive(),
+ id_article: source.articleId,
description: source.description.toPrimitive(),
quantity: source.quantity.toPrimitive(),
unit_price: source.unitPrice.toPrimitive(),
- subtotal: 0,
- total: 0,
- //subtotal: source.calculateSubtotal().toPrimitive(),
- //total: source.calculateTotal().toPrimitive(),
+ subtotal_price: source.subtotalPrice.toPrimitive(),
+ discount: source.discount.toPrimitive(),
+ total_price: source.totalPrice.toPrimitive(),
};
}
}
diff --git a/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts b/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts
index ad48b04..2415eb5 100644
--- a/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts
+++ b/server/src/contexts/sales/infrastructure/sequelize/quote.model.ts
@@ -50,9 +50,9 @@ export class Quote_Model extends Model<
declare notes: CreationOptional;
declare validity: CreationOptional;
- declare subtotal: CreationOptional;
+ declare subtotal_price: CreationOptional;
declare discount: CreationOptional;
- declare total: CreationOptional;
+ declare total_price: CreationOptional;
declare items: NonAttribute;
declare dealer: NonAttribute;
@@ -108,7 +108,7 @@ export default (sequelize: Sequelize) => {
type: DataTypes.TEXT,
},
- subtotal: {
+ subtotal_price: {
type: new DataTypes.BIGINT(),
allowNull: true,
},
@@ -118,7 +118,7 @@ export default (sequelize: Sequelize) => {
allowNull: true,
},
- total: {
+ total_price: {
type: new DataTypes.BIGINT(),
allowNull: true,
},
diff --git a/server/src/contexts/sales/infrastructure/sequelize/quoteItem.model.ts b/server/src/contexts/sales/infrastructure/sequelize/quoteItem.model.ts
index 297f5cc..3153a34 100644
--- a/server/src/contexts/sales/infrastructure/sequelize/quoteItem.model.ts
+++ b/server/src/contexts/sales/infrastructure/sequelize/quoteItem.model.ts
@@ -30,12 +30,14 @@ export class QuoteItem_Model extends Model<
declare quote_id: string;
declare item_id: string;
+ declare id_article: string; // number ??
declare position: number;
declare description: CreationOptional;
declare quantity: CreationOptional;
declare unit_price: CreationOptional;
- declare subtotal: CreationOptional;
- declare total: CreationOptional;
+ declare subtotal_price: CreationOptional;
+ declare discount: CreationOptional;
+ declare total_price: CreationOptional;
declare quote: NonAttribute;
}
@@ -51,6 +53,10 @@ export default (sequelize: Sequelize) => {
type: new DataTypes.UUID(),
primaryKey: true,
},
+ id_article: {
+ type: DataTypes.BIGINT().UNSIGNED,
+ allowNull: false,
+ },
position: {
type: new DataTypes.MEDIUMINT(),
autoIncrement: false,
@@ -68,11 +74,15 @@ export default (sequelize: Sequelize) => {
type: new DataTypes.BIGINT(),
allowNull: true,
},
- subtotal: {
+ subtotal_price: {
type: new DataTypes.BIGINT(),
allowNull: true,
},
- total: {
+ discount: {
+ type: DataTypes.BIGINT(),
+ allowNull: true,
+ },
+ total_price: {
type: new DataTypes.BIGINT(),
allowNull: true,
},
diff --git a/server/src/infrastructure/express/api/routes/quote.routes.ts b/server/src/infrastructure/express/api/routes/quote.routes.ts
index b02bf55..3a9a50b 100644
--- a/server/src/infrastructure/express/api/routes/quote.routes.ts
+++ b/server/src/infrastructure/express/api/routes/quote.routes.ts
@@ -1,7 +1,9 @@
import { checkUser } from "@/contexts/auth";
import {
createQuoteController,
+ getQuoteController,
listQuotesController,
+ updateQuoteController,
} from "@/contexts/sales/infrastructure/express/controllers";
import { getDealerMiddleware } from "@/contexts/sales/infrastructure/express/middlewares/dealerMiddleware";
import Express from "express";
@@ -10,11 +12,11 @@ export const QuoteRouter = (appRouter: Express.Router) => {
const quoteRoutes: Express.Router = Express.Router({ mergeParams: true });
quoteRoutes.get("/", checkUser, getDealerMiddleware, listQuotesController);
+ quoteRoutes.get("/:quoteId", checkUser, getDealerMiddleware, getQuoteController);
quoteRoutes.post("/", checkUser, getDealerMiddleware, createQuoteController);
+ quoteRoutes.put("/:quoteId", checkUser, updateQuoteController);
- //quoteRoutes.put("/:quoteId", checkUser, updateQuoteController);
-
- /*quoteRoutes.get("/:quoteId", isUser, getQuoteMiddleware, getQuoteController);
+ /*
quoteRoutes.post("/", isAdmin, createQuoteController);
quoteRoutes.delete("/:quoteId", isAdmin, deleteQuoteController);*/
diff --git a/shared/lib/contexts/catalog/application/dto/IListArticles.dto/IListArticles_Response.dto.ts b/shared/lib/contexts/catalog/application/dto/IListArticles.dto/IListArticles_Response.dto.ts
index 11d07f7..f05596d 100644
--- a/shared/lib/contexts/catalog/application/dto/IListArticles.dto/IListArticles_Response.dto.ts
+++ b/shared/lib/contexts/catalog/application/dto/IListArticles.dto/IListArticles_Response.dto.ts
@@ -1,4 +1,4 @@
-import { IMoney_Response_DTO } from "../../../../common";
+import { IQuantuty_Response_DTO } from "../../../../common";
export interface IListArticles_Response_DTO {
id: string;
@@ -10,5 +10,5 @@ export interface IListArticles_Response_DTO {
description: string;
points: number;
- retail_price: IMoney_Response_DTO;
+ retail_price: IQuantuty_Response_DTO;
}
diff --git a/shared/lib/contexts/common/application/Common.service.ts b/shared/lib/contexts/common/application/Common.service.ts
index 073baed..7207d32 100644
--- a/shared/lib/contexts/common/application/Common.service.ts
+++ b/shared/lib/contexts/common/application/Common.service.ts
@@ -1,5 +1,5 @@
import {
- Currency,
+ CurrencyData,
Description,
Email,
Language,
@@ -46,7 +46,7 @@ export const ensureDateIsValid = (value: string): Result => {
};
export const ensureCurrencyCodeIsValid = (value: string): Result => {
- const currencyOrError = Currency.createFromCode(value);
+ const currencyOrError = CurrencyData.createFromCode(value);
return currencyOrError.isSuccess ? Result.ok(true) : Result.fail(currencyOrError.error);
};
diff --git a/shared/lib/contexts/common/application/dto/IMoney.dto.ts b/shared/lib/contexts/common/application/dto/IMoney.dto.ts
index b092ec8..fff443f 100644
--- a/shared/lib/contexts/common/application/dto/IMoney.dto.ts
+++ b/shared/lib/contexts/common/application/dto/IMoney.dto.ts
@@ -4,14 +4,14 @@ import { Result, RuleValidator } from "../../domain";
export interface IMoney_Request_DTO {
amount: number;
precision: number;
- currency: string;
+ currency_code: string;
}
export function ensureMoney_DTOIsValid(money: IMoney_Request_DTO) {
const schema = Joi.object({
amount: Joi.number(),
precision: Joi.number(),
- currency: Joi.string(),
+ currency_code: Joi.string(),
});
const result = RuleValidator.validate(schema, money);
diff --git a/shared/lib/contexts/common/application/dto/IPercentage.dto.ts b/shared/lib/contexts/common/application/dto/IPercentage.dto.ts
index 3ff7d88..3067870 100644
--- a/shared/lib/contexts/common/application/dto/IPercentage.dto.ts
+++ b/shared/lib/contexts/common/application/dto/IPercentage.dto.ts
@@ -1,6 +1,7 @@
export interface IPercentage_DTO {
- amount: number;
- precision: number;
+ amount: number;
+ precision: number;
}
+export interface IPercentage_Request_DTO extends IPercentage_DTO {}
export interface IPercentage_Response_DTO extends IPercentage_DTO {}
diff --git a/shared/lib/contexts/common/application/dto/IQuantity.dto.ts b/shared/lib/contexts/common/application/dto/IQuantity.dto.ts
new file mode 100644
index 0000000..7ef36f7
--- /dev/null
+++ b/shared/lib/contexts/common/application/dto/IQuantity.dto.ts
@@ -0,0 +1,24 @@
+import Joi from "joi";
+import { Result, RuleValidator } from "../../domain";
+
+export interface IQuantity_Request_DTO {
+ amount: number;
+ precision: number;
+}
+
+export function ensureQuantity_DTOIsValid(quantity: IQuantity_Request_DTO) {
+ const schema = Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ });
+
+ const result = RuleValidator.validate(schema, quantity);
+
+ if (result.isFailure) {
+ return Result.fail(result.error);
+ }
+
+ return Result.ok(true);
+}
+
+export interface IQuantity_Response_DTO extends IQuantity_Request_DTO {}
diff --git a/shared/lib/contexts/common/application/dto/index.ts b/shared/lib/contexts/common/application/dto/index.ts
index 9db54ca..6cb4022 100644
--- a/shared/lib/contexts/common/application/dto/index.ts
+++ b/shared/lib/contexts/common/application/dto/index.ts
@@ -1,4 +1,5 @@
export * from "./IError_Response.dto";
export * from "./IMoney.dto";
export * from "./IPercentage.dto";
+export * from "./IQuantity.dto";
export * from "./ITaxType.dto";
diff --git a/shared/lib/contexts/common/domain/entities/Collection.ts b/shared/lib/contexts/common/domain/entities/Collection.ts
index fe8e9c2..4c5321a 100644
--- a/shared/lib/contexts/common/domain/entities/Collection.ts
+++ b/shared/lib/contexts/common/domain/entities/Collection.ts
@@ -1,6 +1,8 @@
export interface ICollection {
items: T[];
totalCount: number;
+
+ toArray(): T[];
}
export class Collection implements ICollection {
@@ -15,7 +17,7 @@ export class Collection implements ICollection {
return Array.from(this._items.values()).reduce(
(total, item) => (item !== undefined ? total + 1 : total),
- 0,
+ 0
);
}
@@ -26,10 +28,8 @@ export class Collection implements ICollection {
constructor(initialValues?: T[], totalCount?: number) {
this._items = new Map(
initialValues
- ? initialValues.map(
- (value: any, index: number) => [index, value] as [number, T],
- )
- : [],
+ ? initialValues.map((value: any, index: number) => [index, value] as [number, T])
+ : []
);
this._totalCountIsProvided = typeof totalCount === "number";
@@ -89,9 +89,7 @@ export class Collection implements ICollection {
}
}
- public find(
- predicate: (value: T, index: number, obj: T[]) => unknown,
- ): T | undefined {
+ public find(predicate: (value: T, index: number, obj: T[]) => unknown): T | undefined {
return Array.from(this._items.values()).find(predicate);
}
@@ -106,8 +104,8 @@ export class Collection implements ICollection {
}
public toStringArray(): string[] {
- return Array.from(this._items.values(), (element) =>
- JSON.stringify(element),
- ).filter((element) => element.length > 0);
+ return Array.from(this._items.values(), (element) => JSON.stringify(element)).filter(
+ (element) => element.length > 0
+ );
}
}
diff --git a/shared/lib/contexts/common/domain/entities/Currency/index.ts b/shared/lib/contexts/common/domain/entities/Currency/index.ts
deleted file mode 100644
index 0748ff3..0000000
--- a/shared/lib/contexts/common/domain/entities/Currency/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./Currency";
diff --git a/shared/lib/contexts/common/domain/entities/Currency/Currency.ts b/shared/lib/contexts/common/domain/entities/CurrencyData/CurrencyData.ts
similarity index 76%
rename from shared/lib/contexts/common/domain/entities/Currency/Currency.ts
rename to shared/lib/contexts/common/domain/entities/CurrencyData/CurrencyData.ts
index c3c1950..7ca48b6 100644
--- a/shared/lib/contexts/common/domain/entities/Currency/Currency.ts
+++ b/shared/lib/contexts/common/domain/entities/CurrencyData/CurrencyData.ts
@@ -5,7 +5,7 @@ import { DomainError, handleDomainError } from "../../errors";
import { INullableValueObjectOptions, NullableValueObject } from "../NullableValueObject";
import { Result } from "../Result";
-export interface ICurrency {
+export interface ICurrencyData {
symbol: string;
name: string;
symbol_native: string;
@@ -15,9 +15,9 @@ export interface ICurrency {
name_plural: string;
}
-export interface ICurrencyOptions extends INullableValueObjectOptions {}
+export interface ICurrencyDataOptions extends INullableValueObjectOptions {}
-export class Currency extends NullableValueObject {
+export class CurrencyData extends NullableValueObject {
public static readonly DEFAULT_CURRENCY_CODE = "EUR";
public static readonly CURRENCIES = Currencies;
@@ -29,7 +29,7 @@ export class Currency extends NullableValueObject {
return this.props ? String(this.props.code) : "";
}
- protected static validate(value: string, options: ICurrencyOptions) {
+ protected static validate(value: string, options: ICurrencyDataOptions) {
const rule = Joi.alternatives(
RuleValidator.RULE_ALLOW_EMPTY.default(""),
Joi.string()
@@ -41,24 +41,24 @@ export class Currency extends NullableValueObject {
return RuleValidator.validate(rule, value);
}
- public static createFromCode(currencyCode: string, options: ICurrencyOptions = {}) {
+ public static createFromCode(currencyCode: string, options: ICurrencyDataOptions = {}) {
const _options = {
...options,
label: options.label ? options.label : "current_code",
};
- const validationResult = Currency.validate(currencyCode, _options);
+ const validationResult = CurrencyData.validate(currencyCode, _options);
if (validationResult.isFailure) {
return Result.fail(
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
);
}
- return Result.ok(new Currency(Currencies[validationResult.object]));
+ return Result.ok(new CurrencyData(Currencies[validationResult.object]));
}
public static createDefaultCode() {
- return Currency.createFromCode(Currency.DEFAULT_CURRENCY_CODE);
+ return CurrencyData.createFromCode(CurrencyData.DEFAULT_CURRENCY_CODE);
}
public isEmpty(): boolean {
diff --git a/shared/lib/contexts/common/domain/entities/CurrencyData/index.ts b/shared/lib/contexts/common/domain/entities/CurrencyData/index.ts
new file mode 100644
index 0000000..50aaf79
--- /dev/null
+++ b/shared/lib/contexts/common/domain/entities/CurrencyData/index.ts
@@ -0,0 +1 @@
+export * from "./CurrencyData";
diff --git a/shared/lib/contexts/common/domain/entities/MoneyValue.ts b/shared/lib/contexts/common/domain/entities/MoneyValue.ts
index 6517052..04cf3c9 100644
--- a/shared/lib/contexts/common/domain/entities/MoneyValue.ts
+++ b/shared/lib/contexts/common/domain/entities/MoneyValue.ts
@@ -1,27 +1,28 @@
/* eslint-disable no-use-before-define */
import DineroFactory, { Dinero } from "dinero.js";
-import { Currency } from "./Currency";
-
import Joi from "joi";
import { isNull } from "lodash";
import { NullOr } from "../../../../utilities";
import { RuleValidator } from "../RuleValidator";
+import { CurrencyData } from "./CurrencyData";
import { Result } from "./Result";
import { IValueObjectOptions, ValueObject } from "./ValueObject";
export interface IMoneyValueOptions extends IValueObjectOptions {
+ defaultValue?: number;
locale: string;
}
export const defaultMoneyValueOptions: IMoneyValueOptions = {
+ defaultValue: 0,
locale: "es-ES",
};
export interface MoneyValueObject {
amount: number;
precision: number;
- currency: string;
+ currency_code: string;
}
type RoundingMode =
@@ -43,7 +44,7 @@ export interface IMoneyValueProps {
const defaultMoneyValueProps = {
amount: 0,
- currencyCode: Currency.DEFAULT_CURRENCY_CODE,
+ currencyCode: CurrencyData.DEFAULT_CURRENCY_CODE,
precision: 2,
};
@@ -56,13 +57,10 @@ interface IMoneyValue {
isNull(): boolean;
getAmount(): number;
- getCurrency(): Currency;
+ getCurrency(): CurrencyData;
getLocale(): string;
getPrecision(): number;
- convertPrecision(
- newPrecision: number,
- roundingMode?: RoundingMode,
- ): MoneyValue;
+ convertPrecision(newPrecision: number, roundingMode?: RoundingMode): MoneyValue;
add(addend: MoneyValue): MoneyValue;
subtract(subtrahend: MoneyValue): MoneyValue;
@@ -90,16 +88,16 @@ interface IMoneyValue {
}
export class MoneyValue extends ValueObject implements IMoneyValue {
+ public static readonly DEFAULT_PRECISION = defaultMoneyValueProps.precision;
+ public static readonly DEFAULT_CURRENCY_CODE = defaultMoneyValueProps.currencyCode;
+
private static readonly MIN_VALUE = Number.MIN_VALUE;
private static readonly MAX_VALUE = Number.MAX_VALUE;
private readonly _isNull: boolean;
private readonly _options: IMoneyValueOptions;
- protected static validate(
- amount: NullOr,
- options: IMoneyValueOptions,
- ) {
+ protected static validate(amount: NullOr, options: IMoneyValueOptions) {
const ruleNull = Joi.any()
.optional() // <- undefined
.valid(null); // <- null
@@ -131,7 +129,7 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
public static create(
props: IMoneyValueProps = defaultMoneyValueProps,
- options = defaultMoneyValueOptions,
+ options = defaultMoneyValueOptions
) {
if (props === null) {
throw new Error(`InvalidParams: props params is missing`);
@@ -141,7 +139,7 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
amount = defaultMoneyValueProps.amount,
currencyCode = defaultMoneyValueProps.currencyCode,
precision = defaultMoneyValueProps.precision,
- } = props;
+ } = props || {};
const validationResult = MoneyValue.validate(amount, options);
@@ -149,13 +147,11 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
return Result.fail(validationResult.error);
}
- const _amount: NullOr = MoneyValue.sanitize(
- validationResult.object,
- );
+ const _amount: NullOr = MoneyValue.sanitize(validationResult.object);
const prop = DineroFactory({
- amount: !isNull(_amount) ? _amount : 0,
- currency: Currency.DEFAULT_CURRENCY_CODE,
+ amount: !isNull(_amount) ? _amount : options.defaultValue,
+ currency: CurrencyData.createFromCode(currencyCode).object.code,
precision,
}).setLocale(options.locale);
@@ -175,29 +171,23 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
}
protected static createFromDinero(dinero: Dinero) {
- return Result.ok(
- new MoneyValue(dinero, false, defaultMoneyValueOptions),
+ return Result.ok(new MoneyValue(dinero, false, defaultMoneyValueOptions));
+ }
+
+ public static normalizePrecision(objects: ReadonlyArray): MoneyValue[] {
+ return DineroFactory.normalizePrecision(objects.map((object) => object.props)).map(
+ (dinero) => MoneyValue.createFromDinero(dinero).object
);
}
- public static normalizePrecision(
- objects: ReadonlyArray,
- ): MoneyValue[] {
- return DineroFactory.normalizePrecision(
- objects.map((object) => object.props),
- ).map((dinero) => MoneyValue.createFromDinero(dinero).object);
- }
-
public static minimum(objects: ReadonlyArray): MoneyValue {
- return MoneyValue.createFromDinero(
- DineroFactory.minimum(objects.map((object) => object.props)),
- ).object;
+ return MoneyValue.createFromDinero(DineroFactory.minimum(objects.map((object) => object.props)))
+ .object;
}
public static maximum(objects: ReadonlyArray): MoneyValue {
- return MoneyValue.createFromDinero(
- DineroFactory.maximum(objects.map((object) => object.props)),
- ).object;
+ return MoneyValue.createFromDinero(DineroFactory.maximum(objects.map((object) => object.props)))
+ .object;
}
constructor(value: Dinero, isNull: boolean, options: IMoneyValueOptions) {
@@ -238,17 +228,13 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
return this.props.getPrecision();
}
- public convertPrecision(
- newPrecision: number,
- roundingMode?: RoundingMode,
- ): MoneyValue {
- return MoneyValue.createFromDinero(
- this.props.convertPrecision(newPrecision, roundingMode),
- ).object;
+ public convertPrecision(newPrecision: number, roundingMode?: RoundingMode): MoneyValue {
+ return MoneyValue.createFromDinero(this.props.convertPrecision(newPrecision, roundingMode))
+ .object;
}
- public getCurrency(): Currency {
- return Currency.createFromCode(this.props.getCurrency()).object;
+ public getCurrency(): CurrencyData {
+ return CurrencyData.createFromCode(this.props.getCurrency()).object;
}
public getLocale(): string {
@@ -260,34 +246,23 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
}
public subtract(subtrahend: MoneyValue): MoneyValue {
- return MoneyValue.createFromDinero(this.props.subtract(subtrahend.props))
- .object;
+ return MoneyValue.createFromDinero(this.props.subtract(subtrahend.props)).object;
}
public multiply(multiplier: number, roundingMode?: RoundingMode): MoneyValue {
- return MoneyValue.createFromDinero(
- this.props.multiply(multiplier, roundingMode),
- ).object;
+ return MoneyValue.createFromDinero(this.props.multiply(multiplier, roundingMode)).object;
}
public divide(divisor: number, roundingMode?: RoundingMode): MoneyValue {
- return MoneyValue.createFromDinero(this.props.divide(divisor, roundingMode))
- .object;
+ return MoneyValue.createFromDinero(this.props.divide(divisor, roundingMode)).object;
}
- public percentage(
- percentage: number,
- roundingMode?: RoundingMode,
- ): MoneyValue {
- return MoneyValue.createFromDinero(
- this.props.percentage(percentage, roundingMode),
- ).object;
+ public percentage(percentage: number, roundingMode?: RoundingMode): MoneyValue {
+ return MoneyValue.createFromDinero(this.props.percentage(percentage, roundingMode)).object;
}
public allocate(ratios: ReadonlyArray): MoneyValue[] {
- return this.props
- .allocate(ratios)
- .map((dinero) => MoneyValue.createFromDinero(dinero).object);
+ return this.props.allocate(ratios).map((dinero) => MoneyValue.createFromDinero(dinero).object);
}
public equalsTo(comparator: MoneyValue): boolean {
@@ -347,7 +322,7 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
return {
amount: obj.amount,
precision: obj.precision,
- currency: String(obj.currency),
+ currency_code: String(obj.currency),
};
}
diff --git a/shared/lib/contexts/common/domain/entities/Percentage.ts b/shared/lib/contexts/common/domain/entities/Percentage.ts
index be8e8ae..611990c 100644
--- a/shared/lib/contexts/common/domain/entities/Percentage.ts
+++ b/shared/lib/contexts/common/domain/entities/Percentage.ts
@@ -1,57 +1,160 @@
import Joi from "joi";
+import { isNull } from "lodash";
+import { NullOr } from "../../../../utilities";
import { RuleValidator } from "../RuleValidator";
-import {
- INullableValueObjectOptions,
- NullableValueObject,
-} from "./NullableValueObject";
+import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject";
import { Result } from "./Result";
-export class Percentage extends NullableValueObject {
- private static readonly MIN_VALUE = 0;
- private static readonly MAX_VALUE = 100;
+export interface IPercentageOptions extends INullableValueObjectOptions {}
+
+export interface IPercentageProps {
+ amount: NullOr;
+ precision?: number;
+}
+
+interface IPercentage {
+ amount: NullOr;
+ precision: number;
+}
+
+export interface PercentageObject {
+ amount: number;
+ precision: number;
+}
+
+const defaultPercentageProps = {
+ amount: 0,
+ precision: 0,
+};
+
+export class Percentage extends NullableValueObject {
+ public static readonly DEFAULT_PRECISION = 2;
+ public static readonly MIN_VALUE = 0;
+ public static readonly MAX_VALUE = 100;
+
+ private readonly _isNull: boolean;
+ private readonly _options: IPercentageOptions;
+
+ protected static validate(value: NullOr, options: IPercentageOptions) {
+ const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(
+ defaultPercentageProps.amount
+ );
- protected static validate(
- value: number,
- options: INullableValueObjectOptions,
- ) {
const rule = Joi.number()
.min(Percentage.MIN_VALUE)
.max(Percentage.MAX_VALUE)
- .label(options.label ? options.label : "value");
+ .label(options.label ? options.label : "percentage");
- return RuleValidator.validate(rule, value);
+ const rules = Joi.alternatives(ruleNull, rule);
+
+ return RuleValidator.validate>(rules, value);
}
public static create(
- value: number,
- options: INullableValueObjectOptions = {},
+ props: IPercentageProps = defaultPercentageProps,
+ options: IPercentageOptions = {}
) {
+ if (props === null) {
+ throw new Error(`InvalidParams: props params is missing`);
+ }
+
+ const { amount = defaultPercentageProps.amount, precision = defaultPercentageProps.precision } =
+ props;
+
const _options = {
label: "percentage",
...options,
};
- const validationResult = Percentage.validate(value, _options);
+ const validationResult = Percentage.validate(amount, _options);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
- return Result.ok(new Percentage(value));
+ let _amount: NullOr = Percentage.sanitize(validationResult.object);
+
+ const _props = {
+ amount: isNull(_amount) ? 0 : _amount,
+ precision,
+ };
+
+ return Result.ok(new this(_props, isNull(_amount), options));
}
+ private static sanitize(value: NullOr): NullOr {
+ let _value: NullOr = null;
+
+ if (typeof value === "string") {
+ _value = parseInt(value, 10);
+ } else {
+ _value = value;
+ }
+
+ return _value;
+ }
+
+ constructor(percentage: IPercentage, isNull: boolean, options: IPercentageOptions) {
+ super(percentage);
+ this._isNull = Object.freeze(isNull);
+ this._options = Object.freeze(options);
+ }
+
+ get amount(): NullOr {
+ return this.isNull() ? null : Number(this.props?.amount);
+ }
+
+ get precision(): number {
+ return this.isNull() ? 0 : Number(this.props?.precision);
+ }
+
+ public getAmount(): NullOr {
+ return this.isNull() ? null : Number(this.props?.amount);
+ }
+
+ public getPrecision(): number {
+ return this.isNull() ? 0 : Number(this.props?.precision);
+ }
+
+ public isEmpty = (): boolean => {
+ return this.isNull();
+ };
+
+ public isNull = (): boolean => {
+ return this._isNull;
+ };
+
public toNumber(): number {
- return this.isNull() ? 0 : Number(this.value);
+ if (this.isNull()) {
+ return 0;
+ }
+
+ const factor = Math.pow(10, this.precision);
+ const amount = Number(this.amount) / factor;
+ return Number(amount.toFixed(this.precision));
}
public toString(): string {
- return this.isNull() ? "" : String(this.value);
+ return this.isNull() ? "" : String(this.toNumber());
}
public toPrimitive(): number {
return this.toNumber();
}
-}
-export class InvalidPercentageError extends Error {}
+ public toPrimitives() {
+ return this.toObject();
+ }
+
+ public toObject(): PercentageObject {
+ return {
+ amount: this.amount ? this.amount : 0,
+ precision: this.precision,
+ };
+ }
+
+ public hasSamePrecision(quantity: Percentage) {
+ return this.precision === quantity.precision;
+ }
+}
diff --git a/shared/lib/contexts/common/domain/entities/Quantity.ts b/shared/lib/contexts/common/domain/entities/Quantity.ts
index 81d0218..babbd6e 100644
--- a/shared/lib/contexts/common/domain/entities/Quantity.ts
+++ b/shared/lib/contexts/common/domain/entities/Quantity.ts
@@ -17,17 +17,26 @@ interface IQuantity {
precision: number;
}
+export interface QuantityObject {
+ amount: number;
+ precision: number;
+}
+
const defaultQuantityProps = {
- amount: 1,
+ amount: 0,
precision: 0,
};
export class Quantity extends NullableValueObject {
+ public static readonly DEFAULT_PRECISION = defaultQuantityProps.precision;
+
private readonly _isNull: boolean;
private readonly _options: IQuantityOptions;
protected static validate(value: NullOr, options: IQuantityOptions = {}) {
- const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(null);
+ const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(
+ defaultQuantityProps.amount
+ );
const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
options.label ? options.label : "quantity"
@@ -100,6 +109,14 @@ export class Quantity extends NullableValueObject {
return this.isNull() ? 0 : Number(this.props?.precision);
}
+ public getAmount(): NullOr {
+ return this.isNull() ? null : Number(this.props?.amount);
+ }
+
+ public getPrecision(): number {
+ return this.isNull() ? 0 : Number(this.props?.precision);
+ }
+
public isEmpty = (): boolean => {
return this.isNull();
};
@@ -130,9 +147,9 @@ export class Quantity extends NullableValueObject {
return this.toObject();
}
- public toObject(): IQuantityProps {
+ public toObject(): QuantityObject {
return {
- amount: this.amount,
+ amount: this.amount ? this.amount : 0,
precision: this.precision,
};
}
diff --git a/shared/lib/contexts/common/domain/entities/index.ts b/shared/lib/contexts/common/domain/entities/index.ts
index ea565be..0738ba5 100644
--- a/shared/lib/contexts/common/domain/entities/index.ts
+++ b/shared/lib/contexts/common/domain/entities/index.ts
@@ -1,7 +1,7 @@
export * from "./Address";
export * from "./AggregateRoot";
export * from "./Collection";
-export * from "./Currency";
+export * from "./CurrencyData";
export * from "./Description";
export * from "./Email";
export * from "./Entity";
@@ -19,11 +19,11 @@ export * from "./Result";
export * from "./ResultCollection";
export * from "./Slug";
export * from "./StringValueObject";
-export * from "./TINNumber";
export * from "./TextValueObject";
-export * from "./UTCDateValue";
+export * from "./TINNumber";
export * from "./UniqueID";
-export * from "./UnitPrice";
+//export * from "./UnitPrice";
+export * from "./UTCDateValue";
export * from "./ValueObject";
export * from "./QueryCriteria";
diff --git a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts
index 041f003..ef62deb 100644
--- a/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts
+++ b/shared/lib/contexts/sales/application/dto/Quote/CreateQuote.dto/ICreateQuote_Request.dto.ts
@@ -1,5 +1,11 @@
import Joi from "joi";
-import { IMoney_Request_DTO, Result, RuleValidator } from "../../../../../common";
+import {
+ IMoney_Response_DTO,
+ IPercentage_Response_DTO,
+ IQuantity_Response_DTO,
+ Result,
+ RuleValidator,
+} from "../../../../../common";
export interface ICreateQuote_Request_DTO {
id: string;
@@ -13,14 +19,23 @@ export interface ICreateQuote_Request_DTO {
notes: string;
validity: string;
+ subtotal: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total: IMoney_Response_DTO;
+
items: ICreateQuoteItem_Request_DTO[];
+
+ dealer_id: string;
}
export interface ICreateQuoteItem_Request_DTO {
+ article_id: string;
+ quantity: IQuantity_Response_DTO;
description: string;
- quantity: string;
- unit_measure: string;
- unit_price: IMoney_Request_DTO;
+ unit_price: IMoney_Response_DTO;
+ price: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total_price: IMoney_Response_DTO;
}
export function ensureCreateQuote_Request_DTOIsValid(quoteDTO: ICreateQuote_Request_DTO) {
@@ -37,14 +52,21 @@ export function ensureCreateQuote_Request_DTOIsValid(quoteDTO: ICreateQuote_Requ
items: Joi.array().items(
Joi.object({
+ article_id: Joi.string(),
description: Joi.string(),
- quantity: Joi.string(),
- unit_measure: Joi.string(),
+ quantity: {
+ amount: Joi.number(),
+ precision: Joi.number(),
+ },
unit_price: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
}),
+ discount: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ }),
}).unknown(true)
),
}).unknown(true);
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 40377e0..50c90a8 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,23 +1,31 @@
-import { IMoney_Response_DTO } from "../../../../../common";
+import {
+ IMoney_Response_DTO,
+ IPercentage_Response_DTO,
+ IQuantity_Response_DTO,
+} from "../../../../../common";
export interface ICreateQuote_Response_DTO {
id: string;
status: string;
date: string;
+ reference: string;
+ customer_information: string;
lang_code: string;
currency_code: string;
+ payment_method: string;
+ notes: string;
+ validity: string;
- subtotal: IMoney_Response_DTO;
- total: IMoney_Response_DTO;
-
+ discount: IPercentage_Response_DTO;
items: ICreateQuote_QuoteItem_Response_DTO[];
}
export interface ICreateQuote_QuoteItem_Response_DTO {
+ article_id: string;
+ quantity: IQuantity_Response_DTO;
description: string;
- quantity: string;
- unit_measure: string;
unit_price: IMoney_Response_DTO;
- subtotal: IMoney_Response_DTO;
- total: IMoney_Response_DTO;
+ price: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total_price: IMoney_Response_DTO;
}
diff --git a/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts
index 7007171..17e50de 100644
--- a/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts
+++ b/shared/lib/contexts/sales/application/dto/Quote/GetQuote.dto/IGetQuote_Response.dto.ts
@@ -1,4 +1,8 @@
-import { IMoney_Response_DTO } from "../../../../../common";
+import {
+ IMoney_Response_DTO,
+ IPercentage_Response_DTO,
+ IQuantity_Response_DTO,
+} from "../../../../../common";
export interface IGetQuote_Response_DTO {
id: string;
@@ -8,21 +12,26 @@ export interface IGetQuote_Response_DTO {
customer_information: string;
lang_code: string;
currency_code: string;
+
payment_method: string;
notes: string;
validity: string;
- subtotal: IMoney_Response_DTO;
- total: IMoney_Response_DTO;
+ subtotal_price: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total_price: IMoney_Response_DTO;
items: IGetQuote_QuoteItem_Response_DTO[];
+
+ dealer_id: string;
}
export interface IGetQuote_QuoteItem_Response_DTO {
+ article_id: string;
+ quantity: IQuantity_Response_DTO;
description: string;
- quantity: string;
- unit_measure: string;
unit_price: IMoney_Response_DTO;
- subtotal: IMoney_Response_DTO;
- total: IMoney_Response_DTO;
+ subtotal_price: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total_price: IMoney_Response_DTO;
}
diff --git a/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts
index f0dbdd3..62b88f1 100644
--- a/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts
+++ b/shared/lib/contexts/sales/application/dto/Quote/ListQuotes.dto/IListQuotes_Response.dto.ts
@@ -1,4 +1,4 @@
-import { IMoney_Response_DTO } from "../../../../../common";
+import { IQuantuty_Response_DTO } from "../../../../../common";
export interface IListQuotes_Response_DTO {
id: string;
@@ -9,6 +9,6 @@ export interface IListQuotes_Response_DTO {
lang_code: string;
currency_code: string;
- subtotal: IMoney_Response_DTO;
- total: IMoney_Response_DTO;
+ subtotal: IQuantuty_Response_DTO;
+ total: IQuantuty_Response_DTO;
}
diff --git a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts
index 53b1495..ea348c4 100644
--- a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts
+++ b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Request.dto.ts
@@ -1,5 +1,13 @@
import Joi from "joi";
-import { IMoney_Request_DTO, Result, RuleValidator } from "../../../../../common";
+import {
+ IMoney_Request_DTO,
+ IMoney_Response_DTO,
+ IPercentage_Request_DTO,
+ IPercentage_Response_DTO,
+ IQuantity_Response_DTO,
+ Result,
+ RuleValidator,
+} from "../../../../../common";
export interface IUpdateQuote_Request_DTO {
status: string;
@@ -12,37 +20,71 @@ export interface IUpdateQuote_Request_DTO {
notes: string;
validity: string;
+ subtotal: IMoney_Request_DTO;
+ discount: IPercentage_Request_DTO;
items: IUpdateQuoteItem_Request_DTO[];
}
export interface IUpdateQuoteItem_Request_DTO {
+ article_id: string;
+ quantity: IQuantity_Response_DTO;
description: string;
- quantity: string;
- unit_measure: string;
- unit_price: IMoney_Request_DTO;
+ unit_price: IMoney_Response_DTO;
+ subtotal_price: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total_price: IMoney_Response_DTO;
}
export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Request_DTO) {
const schema = Joi.object({
+ status: Joi.string(),
date: Joi.string(),
reference: Joi.string(),
- lang_code: Joi.string(),
customer_information: Joi.string(),
+ lang_code: Joi.string(),
currency_code: Joi.string(),
payment_method: Joi.string(),
notes: Joi.string(),
validity: Joi.string(),
+ subtotal: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ currency: Joi.string(),
+ }),
+
+ discount: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ }),
+
items: Joi.array().items(
Joi.object({
+ article_id: Joi.string(),
+ quantity: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ }),
description: Joi.string(),
- quantity: Joi.string(),
- unit_measure: Joi.string(),
unit_price: Joi.object({
amount: Joi.number(),
precision: Joi.number(),
currency: Joi.string(),
}),
+ subtotal_price: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ currency: Joi.string(),
+ }),
+ discount: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ }),
+ total_price: Joi.object({
+ amount: Joi.number(),
+ precision: Joi.number(),
+ currency: Joi.string(),
+ }),
}).unknown(true)
),
}).unknown(true);
diff --git a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts
index b191002..9ec3921 100644
--- a/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts
+++ b/shared/lib/contexts/sales/application/dto/Quote/UpdateQuote.dto/IUpdateQuote_Response.dto.ts
@@ -1,23 +1,33 @@
import { IMoney_Response_DTO } from "shared/lib/contexts/common";
+import { IPercentage_Response_DTO, IQuantity_Response_DTO } from "../../../../../common";
export interface IUpdateQuote_Response_DTO {
id: string;
status: string;
date: string;
- language_code: string;
+ reference: string;
+ customer_information: string;
+ lang_code: string;
currency_code: string;
+ payment_method: string;
+ notes: string;
+ validity: string;
subtotal: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
total: IMoney_Response_DTO;
items: IUpdateQuote_QuoteItem_Response_DTO[];
+
+ dealer_id: string;
}
export interface IUpdateQuote_QuoteItem_Response_DTO {
+ article_id: string;
+ quantity: IQuantity_Response_DTO;
description: string;
- quantity: string;
- unit_measure: string;
unit_price: IMoney_Response_DTO;
- subtotal: IMoney_Response_DTO;
- total: IMoney_Response_DTO;
+ price: IMoney_Response_DTO;
+ discount: IPercentage_Response_DTO;
+ total_price: IMoney_Response_DTO;
}