This commit is contained in:
David Arranz 2024-06-19 18:54:25 +02:00
parent 1caa778374
commit 6ead69908c
15 changed files with 499 additions and 371 deletions

View File

@ -27,7 +27,6 @@ export const useCatalogList = (params: UseCatalogListParams): UseCatalogListResp
return useList({
queryKey: keys().data().resource("catalog").action("list").params(params).get(),
queryFn: () => {
console.log(pagination);
return dataSource.getList({
resource: "catalog",
quickSearchTerm: searchTerm,

View File

@ -0,0 +1,87 @@
import { Card, CardContent } from "@/ui";
import { DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
import { DataTable } from "@/components";
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
import { useDataTable, useDataTableContext } from "@/lib/hooks";
import { IListArticles_Response_DTO } from "@shared/contexts";
import { ColumnDef } from "@tanstack/react-table";
import { useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useDealerList } from "../hooks";
export const DealerDataTable = () => {
const navigate = useNavigate();
const { pagination, globalFilter, isFiltered } = useDataTableContext();
const { data, isPending, isError, error } = useDealerList({
pagination: {
pageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
},
searchTerm: globalFilter,
});
const columns = useMemo<ColumnDef<IListArticles_Response_DTO, any>[]>(
() => [
{
id: "id" as const,
accessorKey: "id",
enableResizing: false,
size: 10,
},
{
id: "name" as const,
accessorKey: "name",
enableResizing: false,
size: 10,
},
],
[]
);
const { table } = useDataTable({
data: data?.items ?? [],
columns: columns,
pageCount: data?.total_pages ?? -1,
});
if (isError) {
return <ErrorOverlay subtitle={(error as Error).message} />;
}
if (isPending) {
return (
<Card>
<CardContent>
<DataTableSkeleton
columnCount={6}
searchableColumnCount={1}
filterableColumnCount={2}
//cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem"]}
shrinkZero
/>
</CardContent>
</Card>
);
}
if (data?.total_items === 0 && !isFiltered) {
return (
<SimpleEmptyState
subtitle='Empieza cargando los artículos del catálogo'
buttonText=''
onButtonClick={() => navigate("/catalog/add")}
/>
);
}
return (
<>
<DataTable table={table} paginationOptions={{ visible: true }}>
<DataTableToolbar table={table} />
</DataTable>
</>
);
};

View File

@ -0,0 +1 @@
export * from "./DealerDataTable";

View File

@ -0,0 +1,77 @@
import { IListArticles_Response_DTO } from "@shared/contexts";
import { ColumnDef } from "@tanstack/react-table";
import { DataTablaRowActionFunction, DataTableRowActions } from "@/components";
import { Badge } from "@/ui";
import { useMemo } from "react";
export const useCustomerInvoiceDataTableColumns = (
actions: DataTablaRowActionFunction<IListArticles_Response_DTO>
): ColumnDef<IListArticles_Response_DTO>[] => {
const customerColumns: ColumnDef<IListArticles_Response_DTO>[] = useMemo(
() => [
/*{
id: "complete_name",
header: "Nombre",
accessorFn: (row) => (
<div className="flex items-center justify-between space-x-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src={row.photo_url} />
<AvatarFallback>
{acronym(`${row.first_name} ${row.last_name}`)}
</AvatarFallback>
</Avatar>
<div>
<p className="text-base font-semibold">{`${row.first_name} ${row.last_name}`}</p>
<p className="mt-1">{row.job_title}</p>
</div>
</div>
</div>
),
enableSorting: true,
sortingFn: "alphanumeric",
enableHiding: false,
cell: ({ renderValue }) => (
<span className="w-full">
<>{renderValue()}</>
</span>
),
},*/
{
id: "company",
accessorKey: "company_name",
header: "Compañía",
enableSorting: true,
sortingFn: "alphanumeric",
},
{
id: "state",
accessorKey: "state",
header: "Estado",
cell: ({ renderValue }) => (
<Badge variant={"destructive"}>
<>{renderValue()}</>
</Badge>
),
},
{
id: "phone",
accessorKey: "phone",
header: "Phone",
enableSorting: true,
sortingFn: "alphanumeric",
},
{
id: "actions",
header: "Acciones",
cell: ({ row }) => {
return <DataTableRowActions row={row} actions={actions} />;
},
},
],
[actions]
);
return customerColumns;
};

View File

@ -0,0 +1 @@
export * from "./useDealerList";

View File

@ -0,0 +1,39 @@
import { UseListQueryResult, useList } from "@/lib/hooks/useDataSource";
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
import { useQueryKey } from "@/lib/hooks/useQueryKey";
import { IListArticles_Response_DTO, IListResponse_DTO } from "@shared/contexts";
export type UseDealerListParams = {
pagination: {
pageIndex: number;
pageSize: number;
};
searchTerm?: string;
enabled?: boolean;
queryOptions?: Record<string, unknown>;
};
export type UseDealerListResponse = UseListQueryResult<
IListResponse_DTO<IListArticles_Response_DTO>,
unknown
>;
export const useDealerList = (params: UseDealerListParams): UseDealerListResponse => {
const dataSource = useDataSource();
const keys = useQueryKey();
const { pagination, searchTerm = undefined, enabled = true, queryOptions } = params;
return useList({
queryKey: keys().data().resource("dealer").action("list").params(params).get(),
queryFn: () => {
return dataSource.getList({
resource: "dealers",
quickSearchTerm: searchTerm,
pagination,
});
},
enabled,
queryOptions,
});
};

View File

@ -1,311 +1,10 @@
import {
Badge,
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/ui";
import { File, ListFilter, MoreHorizontal, PlusCircle } from "lucide-react";
import { Trans } from "react-i18next";
import { DataTableProvider } from "@/lib/hooks";
import { DealerDataTable } from "./components";
export const DealersList = () => {
return (
<>
<Tabs defaultValue='all'>
<div className='flex items-center'>
<TabsList>
<TabsTrigger value='all'>All</TabsTrigger>
<TabsTrigger value='active'>Active</TabsTrigger>
<TabsTrigger value='draft'>Draft</TabsTrigger>
<TabsTrigger value='archived' className='hidden sm:flex'>
Archived
</TabsTrigger>
</TabsList>
<div className='flex items-center gap-2 ml-auto'>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm' className='h-8 gap-1'>
<ListFilter className='h-3.5 w-3.5' />
<span className='sr-only sm:not-sr-only sm:whitespace-nowrap'>Filter</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Filter by</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem checked>Active</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Draft</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Archived</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
<Button size='sm' variant='outline' className='h-8 gap-1'>
<File className='h-3.5 w-3.5' />
<span className='sr-only sm:not-sr-only sm:whitespace-nowrap'>Export</span>
</Button>
<Button size='sm' className='h-8 gap-1'>
<PlusCircle className='h-3.5 w-3.5' />
<span className='sr-only sm:not-sr-only sm:whitespace-nowrap'>Add Product</span>
</Button>
</div>
</div>
<TabsContent value='all'>
<Card x-chunk='dashboard-06-chunk-0'>
<CardHeader>
<CardTitle>
<Trans i18nKey='catalog.title' />
</CardTitle>
<CardDescription>
Manage your products and view their sales performance.
</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className='hidden w-[100px] sm:table-cell'>
<span className='sr-only'>Image</span>
</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead className='hidden md:table-cell'>Price</TableHead>
<TableHead className='hidden md:table-cell'>Total Sales</TableHead>
<TableHead className='hidden md:table-cell'>Created at</TableHead>
<TableHead>
<span className='sr-only'>Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Laser Lemonade Machine</TableCell>
<TableCell>
<Badge variant='outline'>Draft</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$499.99</TableCell>
<TableCell className='hidden md:table-cell'>25</TableCell>
<TableCell className='hidden md:table-cell'>2023-07-12 10:42 AM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Hypernova Headphones</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$129.99</TableCell>
<TableCell className='hidden md:table-cell'>100</TableCell>
<TableCell className='hidden md:table-cell'>2023-10-18 03:21 PM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>AeroGlow Desk Lamp</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$39.99</TableCell>
<TableCell className='hidden md:table-cell'>50</TableCell>
<TableCell className='hidden md:table-cell'>2023-11-29 08:15 AM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>TechTonic Energy Drink</TableCell>
<TableCell>
<Badge variant='secondary'>Draft</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$2.99</TableCell>
<TableCell className='hidden md:table-cell'>0</TableCell>
<TableCell className='hidden md:table-cell'>2023-12-25 11:59 PM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Gamer Gear Pro Controller</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$59.99</TableCell>
<TableCell className='hidden md:table-cell'>75</TableCell>
<TableCell className='hidden md:table-cell'>2024-01-01 12:00 AM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Luminous VR Headset</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$199.99</TableCell>
<TableCell className='hidden md:table-cell'>30</TableCell>
<TableCell className='hidden md:table-cell'>2024-02-14 02:14 PM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
<CardFooter>
<div className='text-xs text-muted-foreground'>
Showing <strong>1-10</strong> of <strong>32</strong> products
</div>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</>
<DataTableProvider>
<DealerDataTable />
</DataTableProvider>
);
};

View File

@ -32,7 +32,7 @@ export function DataTableToolbar<TData>({
<div className='flex items-center flex-1 space-x-2'>
<Input
key='global-filter'
placeholder={t("catalog.list.global_filter_placeholder")}
placeholder={t("common.filter_placeholder")}
value={globalFilter}
onChange={(event) => setGlobalFilter(String(event.target.value))}
className='w-3/12 h-8 lg:w-6/12'

View File

@ -19,6 +19,7 @@
"go_to_prev_page": "Ir a la página anterior",
"go_to_next_page": "Ir a la página siguiente",
"go_to_last_page": "Ir a la última página",
"filter_placeholder": "Escribe aquí para filtrar...",
"reset_filter": "Quitar el filtro",
"error": "Error"
},
@ -56,7 +57,6 @@
"catalog": {
"title": "Catálogo de artículos",
"list": {
"global_filter_placeholder": "Escribe aquí para filtrar los artículos...",
"columns": {
"description": "Descripción",
"points": "Puntos",

14
shared/jest.config.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
globals: {},
moduleFileExtensions: ["ts", "js"],
transform: {
"^.+\\.(ts|tsx)$": [
"ts-jest",
{
tsconfig: "tsconfig.json",
},
],
},
testMatch: ["**/*.test.(ts|js)"],
testEnvironment: "node",
};

View File

@ -3,52 +3,61 @@ import { Quantity } from "./Quantity"; // Asegúrate de importar correctamente l
describe("Quantity Value Object", () => {
// Prueba la creación de una cantidad válida.
it("Should create a valid quantity (number)", () => {
const validQuantity = Quantity.create(5);
const validQuantity = Quantity.create({ amount: 5 });
expect(validQuantity.isSuccess).toBe(true);
expect(validQuantity.object.toNumber()).toBe(5);
});
it("Should create a valid quantity (string)", () => {
const validQuantity = Quantity.create("99");
const validQuantity = Quantity.create({ amount: "99" });
expect(validQuantity.isSuccess).toBe(true);
expect(validQuantity.object.toNumber()).toBe(99);
});
it("Should create a valid quantity (null)", () => {
const validQuantity = Quantity.create(null);
const validQuantity = Quantity.create({ amount: null });
expect(validQuantity.isSuccess).toBe(true);
expect(validQuantity.object.isNull).toBeTruthy();
});
// Prueba la creación de una cantidad nula.
it("Should create a valid null quantity", () => {
const nullQuantity = Quantity.create(null);
it("Should create a default quantity", () => {
const nullQuantity = Quantity.create();
expect(nullQuantity.isSuccess).toBe(true);
expect(nullQuantity.object.isNull()).toBe(true);
expect(nullQuantity.object.amount).toBe(1);
expect(nullQuantity.object.precision).toBe(0);
});
// Prueba la creación de una cantidad válida a partir de una cadena.
it("Should create a valid quantity from string", () => {
const validQuantityFromString = Quantity.create("10");
const validQuantityFromString = Quantity.create({ amount: "10" });
expect(validQuantityFromString.isSuccess).toBe(true);
expect(validQuantityFromString.object.toNumber()).toBe(10);
});
// Prueba la creación de una cantidad válida a partir de una cadena con decimales.
it("Should create a valid quantity from string", () => {
const validQuantityFromString = Quantity.create({ amount: "123456", precision: 2 });
expect(validQuantityFromString.isSuccess).toBe(true);
expect(validQuantityFromString.object.toNumber()).toBe(1234.56);
});
// Prueba la creación de una cantidad con una cadena no válida.
it("Should fail to create quantity from invalid string", () => {
const invalidQuantityFromString = Quantity.create("invalid");
const invalidQuantityFromString = Quantity.create({ amount: "invalid" });
expect(invalidQuantityFromString.isFailure).toBe(true);
});
// Prueba la conversión a número.
it("Should convert to number", () => {
const quantity = Quantity.create(7).object;
const quantity = Quantity.create({ amount: 7 }).object;
const result = quantity.toNumber();
expect(result).toBe(7);
@ -56,25 +65,41 @@ describe("Quantity Value Object", () => {
// Prueba la conversión a cadena.
it("Should convert to string", () => {
const quantity = Quantity.create(15).object;
const quantity = Quantity.create({ amount: 15 }).object;
const result = quantity.toString();
expect(result).toBe("15");
});
// Prueba la operación de incremento.
it("Should increment quantity", () => {
const quantity = Quantity.create(5).object;
const incrementedQuantity = quantity.increment(3).object;
it("Should increment", () => {
const quantity = Quantity.create({ amount: 5 }).object;
const incrementedQuantity = quantity.increment().object;
expect(incrementedQuantity.toNumber()).toBe(8);
expect(incrementedQuantity.toNumber()).toBe(6);
});
it("Should increment quantity", () => {
const firstQ = Quantity.create({ amount: -5 }).object;
const secountQ = Quantity.create({ amount: 105 }).object;
const incrementedQuantity = firstQ.increment(secountQ).object;
expect(incrementedQuantity.toNumber()).toBe(100);
});
// Prueba la operación de decremento.
it("Should decrement", () => {
const quantity = Quantity.create({ amount: 0 }).object;
const decrementedQuantity = quantity.decrement().object;
expect(decrementedQuantity.toNumber()).toBe(-1);
});
// Prueba la operación de decremento.
it("Should decrement quantity", () => {
const quantity = Quantity.create(10).object;
const decrementedQuantity = quantity.decrement(4).object;
const quantity = Quantity.create({ amount: 10 }).object;
const decrementedQuantity = quantity.decrement(Quantity.create({ amount: 110 }).object).object;
expect(decrementedQuantity.toNumber()).toBe(6);
expect(decrementedQuantity.toNumber()).toBe(-100);
});
});

View File

@ -1,28 +1,41 @@
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 interface IQuantityOptions extends INullableValueObjectOptions {}
export class Quantity extends NullableValueObject<number> {
protected static validate(
value: NullOr<number | string>,
options: IQuantityOptions = {},
) {
export interface IQuantityProps {
amount: NullOr<number | string>;
precision?: number;
}
interface IQuantity {
amount: NullOr<number>;
precision: number;
}
const defaultQuantityProps = {
amount: 1,
precision: 0,
};
export class Quantity extends NullableValueObject<IQuantity> {
private readonly _isNull: boolean;
private readonly _options: IQuantityOptions;
protected static validate(value: NullOr<number | string>, options: IQuantityOptions = {}) {
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(null);
const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
options.label ? options.label : "quantity",
options.label ? options.label : "quantity"
);
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(
/^[-]?\d+$/,
).label(options.label ? options.label : "quantity");
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
options.label ? options.label : "quantity"
);
const rules = Joi.alternatives(ruleNull, ruleNumber, ruleString);
@ -30,68 +43,157 @@ export class Quantity extends NullableValueObject<number> {
}
public static create(
value: NullOr<number | string>,
options: IQuantityOptions = {},
props: IQuantityProps = defaultQuantityProps,
options: IQuantityOptions = {}
) {
if (props === null) {
throw new Error(`InvalidParams: props params is missing`);
}
const { amount = defaultQuantityProps.amount, precision = defaultQuantityProps.precision } =
props;
const _options = {
label: "quantity",
...options,
};
const validationResult = Quantity.validate(value, _options);
const validationResult = Quantity.validate(amount, _options);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
let _value: NullOr<number> = null;
let _amount: NullOr<number> = Quantity.sanitize(validationResult.object);
if (typeof validationResult.object === "string") {
_value = parseInt(validationResult.object, 10);
} else {
_value = validationResult.object;
}
const _props = {
amount: isNull(_amount) ? 0 : _amount,
precision,
};
return Result.ok<Quantity>(new Quantity(_value));
return Result.ok<Quantity>(new this(_props, isNull(_amount), options));
}
private static sanitize(value: NullOr<number | string>): NullOr<number> {
let _value: NullOr<number> = null;
if (typeof value === "string") {
_value = parseInt(value, 10);
} else {
_value = value;
}
return _value;
}
constructor(quantity: IQuantity, isNull: boolean, options: IQuantityOptions) {
super(quantity);
this._isNull = Object.freeze(isNull);
this._options = Object.freeze(options);
}
get amount(): NullOr<number> {
return this.isNull() ? null : Number(this.props?.amount);
}
get precision(): 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();
}
public increment(amount: number = 1) {
const validationResult = Quantity.validate(amount);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
if (this.value === null) {
return Quantity.create(amount);
}
return Quantity.create(this.value + amount);
public toPrimitives() {
return this.toObject();
}
public decrement(amount: number = 1) {
const validationResult = Quantity.validate(amount);
public toObject(): IQuantityProps {
return {
amount: this.amount,
precision: this.precision,
};
}
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
public hasSamePrecision(quantity: Quantity) {
return this.precision === quantity.precision;
}
public increment(anotherQuantity?: Quantity) {
if (!anotherQuantity) {
return Quantity.create(
{
amount: this.toNumber() + 1,
precision: this.precision,
},
this._options
);
}
if (this.value === null) {
return Quantity.create(amount);
if (!this.hasSamePrecision(anotherQuantity)) {
return Result.fail(Error("No se pueden sumar cantidades con diferentes precisiones."));
}
return Quantity.create(this.value - amount);
if (this.isNull()) {
return Quantity.create(anotherQuantity.toObject());
}
return Quantity.create(
{
amount: this.toNumber() + anotherQuantity.toNumber(),
precision: this.precision,
},
this._options
);
}
public decrement(anotherQuantity?: Quantity) {
if (!anotherQuantity) {
return Quantity.create(
{
amount: this.toNumber() - 1,
precision: this.precision,
},
this._options
);
}
if (!this.hasSamePrecision(anotherQuantity)) {
return Result.fail(Error("No se pueden restar cantidades con diferentes precisiones."));
}
if (this.isNull()) {
return Quantity.create(anotherQuantity.toObject());
}
return Quantity.create(
{
amount: this.toNumber() - anotherQuantity.toNumber(),
precision: this.precision,
},
this._options
);
}
}

View File

@ -1,4 +1,6 @@
export interface IListDealers_Response_DTO {
id: string;
name: string;
language: string;
status: string;
}

View File

@ -6,7 +6,8 @@
"author": "Rodax Software <dev@rodax-software.com>",
"license": "ISC",
"scripts": {
"clean": "rm -rf node_modules"
"clean": "rm -rf node_modules",
"test": "jest --verbose"
},
"dependencies": {
"dinero.js": "^1.9.1",

81
shared/tsconfig.json Normal file
View File

@ -0,0 +1,81 @@
{
"compilerOptions": {
/* Basic Options */
"target": "ES2022" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"lib": [
"ES2022",
"dom"
] /* Specify library files to be included in the compilation. */,
"allowJs": false /* Allow javascript files to be compiled. */,
"pretty": true,
// "checkJs": true, /* Report errors in .js files. */
"jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "../dist/" /* Redirect output structure to the directory. */,
//"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
"removeComments": true /* Do not emit comments to output. */,
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"skipLibCheck": false /* Skip type checking of declaration files. */,
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true /* Enable strict null checks. */,
"strictFunctionTypes": true /* Enable strict checking of function types. */,
"strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */,
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": false /* Report errors on unused locals. */,
"noUnusedParameters": false /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
//"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
"paths": {
/* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"@/*": ["./src/*"],
"@shared/*": ["../shared/lib/*"]
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [] /* List of folders to include type definitions from. */,
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"forceConsistentCasingInFileNames": true,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
/* Advanced Options */
"resolveJsonModule": true /* Include modules imported with '.json' extension */,
"suppressImplicitAnyIndexErrors": false
},
"exclude": [
"src/**/__tests__/*",
"src/**/*.mock.*",
"src/**/*.test.*",
"node_modules"
]
}