diff --git a/client/src/app/quotes/components/SortableDataTable.tsx b/client/src/app/quotes/components/SortableDataTable.tsx
index 0f33ed9..bd02583 100644
--- a/client/src/app/quotes/components/SortableDataTable.tsx
+++ b/client/src/app/quotes/components/SortableDataTable.tsx
@@ -273,7 +273,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
description: "aaaa",
unit_price: {
amount: "10000",
- precision: 4,
+ scale: 4,
currency: "EUR",
},
discount: {
diff --git a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx
index 68f484f..1434d46 100644
--- a/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx
+++ b/client/src/app/quotes/components/editors/QuoteDetailsCardEditor.tsx
@@ -12,7 +12,6 @@ import { t } from "i18next";
import { useCallback, useState } from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useDetailColumns } from "../../hooks";
-import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
import { SortableDataTable } from "../SortableDataTable";
export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) => {
@@ -42,12 +41,13 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
header: () => (
{t("quotes.form_fields.items.quantity.label")}
),
- size: 5,
+ size: 8,
cell: ({ row: { index } }) => {
return (
);
@@ -73,14 +73,14 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
);
},
},
- {
+ /*{
id: "subtotal_price" as const,
accessorKey: "subtotal_price",
header: () => (
@@ -91,34 +91,33 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
);
},
- },
+ },*/
{
id: "discount" as const,
accessorKey: "discount",
- size: 5,
+ size: 8,
header: () => (
{t("quotes.form_fields.items.discount.label")}
),
- cell: ({ row: { index }, column: { id } }) => {
+ cell: ({ row: { index } }) => {
return (
- <>
-
- >
+
);
},
},
- {
+ /*{
id: "total_price" as const,
accessorKey: "total_price",
header: () => (
@@ -129,13 +128,14 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
);
},
- },
+ },*/
],
{
enableDragHandleColumn: true,
@@ -177,8 +177,8 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
fieldActions.append({
...newArticle,
quantity: {
- amount: 12,
- precision: Quantity.DEFAULT_PRECISION,
+ amount: 100,
+ scale: Quantity.DEFAULT_SCALE,
},
unit_price: newArticle.retail_price,
});
@@ -216,7 +216,7 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
-
+ {/**/}
diff --git a/client/src/app/quotes/edit.tsx b/client/src/app/quotes/edit.tsx
index f867c1b..759301b 100644
--- a/client/src/app/quotes/edit.tsx
+++ b/client/src/app/quotes/edit.tsx
@@ -12,7 +12,6 @@ import { CurrencyData, IUpdateQuote_Request_DTO, MoneyValue } from "@shared/cont
import { t } from "i18next";
import { useEffect, useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";
-import useFormPersist from "react-hook-form-persist";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
@@ -57,42 +56,46 @@ export const QuoteEdit = () => {
validity: "",
subtotal_price: {
amount: undefined,
- precision: 2,
+ scale: 2,
currency_code: data?.currency_code,
},
discount: {
amount: undefined,
- precision: 0,
+ scale: 0,
},
total_price: {
amount: undefined,
- precision: 2,
+ scale: 2,
currency_code: data?.currency_code,
},
- items: [
+ /*items: [
{
+ quantity: {
+ amount: undefined,
+ scale: 2,
+ },
subtotal_price: {
amount: undefined,
- precision: 4,
+ scale: 4,
currency_code: data?.currency_code,
},
discount: {
amount: undefined,
- precision: 0,
+ scale: 0,
},
total_price: {
amount: undefined,
- precision: 4,
+ scale: 4,
currency_code: data?.currency_code,
},
},
- ],
+ ],*/
},
});
const { watch, getValues, setValue, formState } = form;
- const { clear } = useFormPersist(
+ /*const { clear } = useFormPersist(
"quote-edit",
{
watch,
@@ -102,7 +105,7 @@ export const QuoteEdit = () => {
storage: window.localStorage, // default window.sessionStorage
//exclude: ['foo']
}
- );
+ );*/
const { isSubmitting } = formState;
@@ -116,7 +119,7 @@ export const QuoteEdit = () => {
//onSettled: () => {},
onSuccess: () => {
toast("Guardado!");
- clear();
+ //clear();
},
});
};
@@ -139,18 +142,21 @@ export const QuoteEdit = () => {
let quoteSubtotal = MoneyValue.create().object;
// Recálculo líneas
- items.map((item, index) => {
- const itemTotals = calculateItemTotals(item);
+ items &&
+ items.map((item, index) => {
+ const itemTotals = calculateItemTotals(item);
- if (itemTotals === null) {
- return;
- }
+ console.log(itemTotals?.quantity.toObject());
- quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
+ if (itemTotals === null) {
+ return;
+ }
- setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
- setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
- });
+ quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
+
+ setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
+ setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
+ });
// Recálculo completo
setValue("subtotal_price", quoteSubtotal.toObject());
diff --git a/client/src/components/Forms/FormCurrencyField.tsx b/client/src/components/Forms/FormCurrencyField.tsx
index 0908afc..826f237 100644
--- a/client/src/components/Forms/FormCurrencyField.tsx
+++ b/client/src/components/Forms/FormCurrencyField.tsx
@@ -3,7 +3,7 @@ import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@
import { CurrencyData, MoneyValue } from "@shared/contexts";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
-import CurrencyInput from "react-currency-input-field";
+import CurrencyInput, { CurrencyInputOnChangeValues } from "react-currency-input-field";
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
import { FormErrorMessage } from "./FormErrorMessage";
import { FormLabel, FormLabelProps } from "./FormLabel";
@@ -38,7 +38,7 @@ export type FormCurrencyFieldProps<
UseControllerProps &
VariantProps & {
currency: CurrencyData;
- precision: number;
+ scale: number;
};
export const FormCurrencyField = React.forwardRef(
@@ -54,7 +54,7 @@ export const FormCurrencyField = React.forwardRef {
+ const { value: amount } = values ?? { value: null };
- output: (value: string | undefined) => {
- const moneyOrError = MoneyValue.create({
- amount: value?.replace(",", "") ?? null,
- precision,
- currencyCode: currency.code,
- });
+ const moneyOrError = MoneyValue.createFromFormattedValue(amount, currency.code);
if (moneyOrError.isFailure) {
throw moneyOrError.error;
}
@@ -118,11 +113,17 @@ export const FormCurrencyField = React.forwardRef field.onChange(transform.output(e))}
+ //onChange={() => {}}
+ onValueChange={(value, name, values) =>
+ field.onChange(transform.output(value, name, values))
+ }
/>
{description && {description}}
diff --git a/client/src/components/Forms/FormMoneyField.tsx b/client/src/components/Forms/FormMoneyField.tsx
deleted file mode 100644
index 30ac6cb..0000000
--- a/client/src/components/Forms/FormMoneyField.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { cn } from "@/lib/utils";
-import { FormControl, FormDescription, FormMessage, Input } from "@/ui";
-
-import { IMoney } from "@/lib/types";
-import { MoneyValue } from "@shared/contexts";
-import { createElement, forwardRef, useState } from "react";
-import { Controller, useFormContext } from "react-hook-form";
-import { FormLabel } from "./FormLabel";
-import { FormTextFieldProps } from "./FormTextField";
-
-type FormMoneyFieldProps = Omit & {
- defaultValue?: any;
-};
-
-export const FormMoneyField = forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & FormMoneyFieldProps
->((props, ref) => {
- const {
- label,
- placeholder,
- hint,
- description,
- required,
- className,
- leadIcon,
- trailIcon,
- button,
- disabled,
- name,
- defaultValue,
- } = props;
-
- //const error = Boolean(errors && errors[name]);
-
- const { control } = useFormContext();
-
- const [precision, setPrecision] = useState(MoneyValue.DEFAULT_PRECISION);
- const [currencyCode, setCurrencyCode] = useState(MoneyValue.DEFAULT_CURRENCY_CODE);
-
- const transform = {
- input: (value: IMoney) => {
- const moneyOrError = MoneyValue.create(value);
- if (moneyOrError.isFailure) {
- throw moneyOrError.error;
- }
-
- const moneyValue = moneyOrError.object;
-
- setPrecision(moneyValue.getPrecision());
- setCurrencyCode(moneyValue.getCurrency().code);
-
- return moneyValue.toFormat();
- },
- output: (event: React.ChangeEvent) => {
- const output = parseFloat(event.target.value);
-
- const moneyOrError = MoneyValue.create({
- amount: output * Math.pow(10, precision),
- precision,
- currencyCode,
- });
-
- if (moneyOrError.isFailure) {
- throw moneyOrError.error;
- }
-
- return moneyOrError.object.toObject();
- },
- };
-
- return (
- {
- return (
- field.onChange(transform.output(e))}
- value={transform.input(field.value)}
- />
- );
-
- return (
-
- {label &&
}
-
-
- {leadIcon && (
-
- {createElement(
- leadIcon,
- {
- className: "h-5 w-5 text-muted-foreground",
- "aria-hidden": true,
- },
- null
- )}
-
- )}
-
-
- field.onChange(transform.output(e))}
- value={transform.input(field.value)}
- />
-
- {trailIcon && (
-
- {createElement(
- trailIcon,
- {
- className: "h-5 w-5 text-muted-foreground",
- "aria-hidden": true,
- },
- null
- )}
-
- )}
-
- {button && <>{createElement(button)}>}
-
- {description &&
{description}}
-
-
- );
- }}
- />
- );
-});
diff --git a/client/src/components/Forms/FormPercentageField.tsx b/client/src/components/Forms/FormPercentageField.tsx
index d4ea2f6..e10e6f0 100644
--- a/client/src/components/Forms/FormPercentageField.tsx
+++ b/client/src/components/Forms/FormPercentageField.tsx
@@ -4,7 +4,7 @@ import { cn } from "@/lib/utils";
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
import { Percentage } from "@shared/contexts";
import { cva, type VariantProps } from "class-variance-authority";
-import CurrencyInput from "react-currency-input-field";
+import CurrencyInput, { CurrencyInputOnChangeValues } from "react-currency-input-field";
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
import { FormErrorMessage } from "./FormErrorMessage";
import { FormLabel, FormLabelProps } from "./FormLabel";
@@ -39,7 +39,7 @@ export type FormPercentageFieldProps<
FormInputWithIconProps &
UseControllerProps &
VariantProps & {
- precision: number;
+ scale: number;
};
export const FormPercentageField = React.forwardRef<
@@ -57,7 +57,7 @@ export const FormPercentageField = React.forwardRef<
defaultValue,
rules,
readOnly,
- precision,
+ scale,
variant,
} = props;
@@ -74,18 +74,14 @@ export const FormPercentageField = React.forwardRef<
throw percentageOrError.error;
}
- return (
- percentageOrError.object
- .toNumber()
- //.toPrecision(precision ?? value.precision)
- .toString()
- );
+ return percentageOrError.object.toString();
},
- output: (value: string | undefined) => {
- const percentageOrError = Percentage.create({
- amount: value?.replace(",", "") ?? null,
- precision,
- });
+ output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
+ console.log(values);
+ const { value: amount } = values ?? { value: null };
+
+ const percentageOrError = Percentage.createFromFormattedValue(amount);
+
if (percentageOrError.isFailure) {
throw percentageOrError.error;
}
@@ -122,10 +118,16 @@ export const FormPercentageField = React.forwardRef<
groupSeparator='.'
decimalSeparator=','
placeholder={placeholder}
- decimalsLimit={precision}
- decimalScale={precision}
+ allowDecimals={scale !== 0}
+ decimalsLimit={scale}
+ decimalScale={scale}
+ step={1}
+ //{...field}
value={transform.input(field.value)}
- onValueChange={(e) => field.onChange(transform.output(e))}
+ //onChange={() => {}}
+ onValueChange={(value, name, values) =>
+ field.onChange(transform.output(value, name, values))
+ }
/>
{description && {description}}
diff --git a/client/src/components/Forms/FormQuantityField.tsx b/client/src/components/Forms/FormQuantityField.tsx
index f44e139..cfcad85 100644
--- a/client/src/components/Forms/FormQuantityField.tsx
+++ b/client/src/components/Forms/FormQuantityField.tsx
@@ -4,7 +4,7 @@ import { cn } from "@/lib/utils";
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
import { Quantity } from "@shared/contexts";
import { cva, type VariantProps } from "class-variance-authority";
-import CurrencyInput from "react-currency-input-field";
+import CurrencyInput, { CurrencyInputOnChangeValues } from "react-currency-input-field";
import { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
import { FormErrorMessage } from "./FormErrorMessage";
import { FormLabel, FormLabelProps } from "./FormLabel";
@@ -38,7 +38,7 @@ export type FormQuantityFieldProps<
FormInputWithIconProps &
UseControllerProps &
VariantProps & {
- precision: number;
+ scale: number;
};
export const FormQuantityField = React.forwardRef(
@@ -54,7 +54,7 @@ export const FormQuantityField = React.forwardRef {
- const quantityOrError = Quantity.create({
- amount: value?.replace(",", "") ?? null,
- precision,
- });
+ output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
+ const { value: amount } = values ?? { value: null };
+
+ const quantityOrError = Quantity.createFromFormattedValue(amount);
if (quantityOrError.isFailure) {
throw quantityOrError.error;
}
@@ -107,7 +101,7 @@ export const FormQuantityField = React.forwardRef
field.onChange(transform.output(e))}
+ //onChange={() => {}}
+ onValueChange={(value, name, values) =>
+ field.onChange(transform.output(value, name, values))
+ }
/>
{description && {description}}
diff --git a/client/src/components/Forms/index.ts b/client/src/components/Forms/index.ts
index 615f2cc..467f9b9 100644
--- a/client/src/components/Forms/index.ts
+++ b/client/src/components/Forms/index.ts
@@ -3,7 +3,6 @@ export * from "./FormDatePickerField";
export * from "./FormErrorMessage";
export * from "./FormGroup";
export * from "./FormLabel";
-export * from "./FormMoneyField";
export * from "./FormPercentageField";
export * from "./FormQuantityField";
export * from "./FormTextAreaField";
diff --git a/server/jest.config.js b/server/jest.config.js
index 6b7b9ef..940a000 100644
--- a/server/jest.config.js
+++ b/server/jest.config.js
@@ -1,13 +1,11 @@
+// jest.config.js
+/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+
module.exports = {
- globals: {
- "ts-jest": {
- tsconfig: "tsconfig.json",
- },
- },
- moduleFileExtensions: ["ts", "js"],
- transform: {
- "^.+\\.(ts|tsx)$": "ts-jest",
- },
- testMatch: ["**/*.test.(ts|js)"],
+ preset: "ts-jest",
testEnvironment: "node",
+ moduleNameMapper: {
+ "^@shared/(.*)$": "/../shared/lib/$1",
+ "^@/(.*)$": "/src/$1",
+ },
};
diff --git a/server/package.json b/server/package.json
index 31950b6..93dcda5 100644
--- a/server/package.json
+++ b/server/package.json
@@ -11,7 +11,7 @@
"build": "tsc",
"lint": "eslint --ignore-path .gitignore . --ext .ts",
"lint:fix": "npm run lint -- --fix",
- "test": "jest --verbose",
+ "test": "jest --config=./jest.config.ts --rootDir=./src/ --verbose",
"clean": "rm -rf node_modules"
},
"devDependencies": {
@@ -22,7 +22,7 @@
"@types/express-session": "^1.18.0",
"@types/glob": "^8.1.0",
"@types/http-status": "^1.1.2",
- "@types/jest": "^29.5.6",
+ "@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^9.0.6",
"@types/luxon": "^3.3.1",
"@types/module-alias": "^2.0.1",
@@ -49,7 +49,7 @@
"module-alias": "^2.2.3",
"prettier": "3.0.1",
"supertest": "^6.2.2",
- "ts-jest": "^29.1.1",
+ "ts-jest": "^29.2.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.2.2"
},
diff --git a/server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts b/server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts
new file mode 100644
index 0000000..f4fbb69
--- /dev/null
+++ b/server/src/contexts/sales/application/Quote/GetQuote.useCase.test.ts
@@ -0,0 +1,81 @@
+import { IRepositoryManager } from "@/contexts/common/domain";
+import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
+import { UniqueID } from "@shared/contexts";
+import { IQuoteRepository } from "../../domain";
+
+import { Quote } from "../../domain/entities/Quotes/Quote";
+import { ISalesContext } from "../../infrastructure";
+import { GetQuoteUseCase } from "./GetQuote.useCase";
+
+describe("GetQuoteUseCase", () => {
+ let getQuoteUseCase: GetQuoteUseCase;
+ let mockAdapter: jest.Mocked;
+ let mockRepositoryManager: jest.Mocked;
+ let mockQuoteRepository: jest.Mocked;
+ let mockTransaction: { complete: jest.Mock };
+
+ beforeEach(() => {
+ mockTransaction = { complete: jest.fn() };
+
+ mockAdapter = {
+ startTransaction: jest.fn().mockReturnValue(mockTransaction),
+ } as unknown as jest.Mocked;
+
+ mockQuoteRepository = {
+ getById: jest.fn(),
+ } as unknown as jest.Mocked;
+
+ mockRepositoryManager = {
+ getRepository: jest.fn().mockReturnValue(() => mockQuoteRepository),
+ } as unknown as jest.Mocked;
+
+ const context: ISalesContext = {
+ adapter: mockAdapter,
+ repositoryManager: mockRepositoryManager,
+ dealer: undefined,
+ };
+
+ getQuoteUseCase = new GetQuoteUseCase(context);
+ });
+
+ it("should return a quote when found", async () => {
+ const id = new UniqueID();
+ const quote = new Quote({ id, content: "Test Quote" });
+
+ mockQuoteRepository.getById.mockResolvedValueOnce(quote);
+ mockTransaction.complete.mockImplementationOnce(async (fn: any) => await fn({}));
+
+ const request = { id };
+ const response = await getQuoteUseCase.execute(request);
+
+ expect(response.isSuccess).toBe(true);
+ expect(response.getValue()).toEqual(quote);
+ });
+
+ it("should return a NOT_FOUND_ERROR when quote is not found", async () => {
+ const id = new UniqueID();
+
+ mockQuoteRepository.getById.mockResolvedValueOnce(null);
+ mockTransaction.complete.mockImplementationOnce(async (fn: any) => await fn({}));
+
+ const request = { id };
+ const response = await getQuoteUseCase.execute(request);
+
+ expect(response.isFailure).toBe(true);
+ expect(response.errorValue().message).toBe("Quote not found");
+ });
+
+ it("should return a REPOSITORY_ERROR on database error", async () => {
+ const id = new UniqueID();
+ const error = new Error("Database error");
+
+ mockQuoteRepository.getById.mockRejectedValueOnce(error);
+ mockTransaction.complete.mockImplementationOnce(async (fn: any) => await fn({}));
+
+ const request = { id };
+ const response = await getQuoteUseCase.execute(request);
+
+ expect(response.isFailure).toBe(true);
+ expect(response.errorValue().message).toBe("Query error");
+ });
+});
diff --git a/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts b/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts
index aa27e5c..fa6197a 100644
--- a/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts
+++ b/server/src/contexts/sales/application/Quote/GetQuote.useCase.ts
@@ -37,9 +37,6 @@ export class GetQuoteUseCase
async execute(request: IGetQuoteUseCaseRequest): Promise {
const { id } = request;
- // Validación de datos
- // No hay en este caso
-
return await this.findQuote(id);
}
diff --git a/server/tsconfig.eslint.json b/server/tsconfig.eslint.json
index bc6937c..7afb9a0 100644
--- a/server/tsconfig.eslint.json
+++ b/server/tsconfig.eslint.json
@@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest"],
- "baseUrl": "./src",
+ //"baseUrl": "./src",
+ "moduleResolution": "node",
"paths": {
"@/*": ["./src/*"],
"@shared/*": ["../shared/lib/*"]
diff --git a/shared/lib/contexts/common/domain/entities/MoneyValue.test.ts b/shared/lib/contexts/common/domain/entities/MoneyValue.test.ts
index 791f4a1..11f5929 100644
--- a/shared/lib/contexts/common/domain/entities/MoneyValue.test.ts
+++ b/shared/lib/contexts/common/domain/entities/MoneyValue.test.ts
@@ -25,10 +25,11 @@ describe("MoneyValue Value Object", () => {
it("Should create a valid money value from string and format it", () => {
const validMoneyValueFromString = MoneyValue.create({
amount: "5075",
+ scale: 2,
});
expect(validMoneyValueFromString.isSuccess).toBe(true);
- expect(validMoneyValueFromString.object.toString()).toEqual("50,75 €");
+ expect(validMoneyValueFromString.object.toString()).toEqual("50.75");
});
// Prueba la creación de un valor monetario con una cadena no válida.
@@ -52,7 +53,7 @@ describe("MoneyValue Value Object", () => {
expect(moneyValue.getAmount()).toBe(100);
expect(moneyValue.getCurrency().code).toBe("EUR");
- expect(moneyValue.getPrecision()).toBe(3);
+ expect(moneyValue.getScale()).toBe(3);
});
it("should create MoneyValue from string and currency", () => {
@@ -67,7 +68,7 @@ describe("MoneyValue Value Object", () => {
expect(moneyValue.getAmount()).toBe(12345);
expect(moneyValue.getCurrency().code).toBe("USD");
- expect(moneyValue.getPrecision()).toBe(2);
+ expect(moneyValue.getScale()).toBe(2);
});
it("should fail to create MoneyValue with invalid amount", () => {
@@ -86,12 +87,7 @@ describe("MoneyValue Value Object", () => {
}).object;
const result = moneyValue.toString();
- expect(result).toBe("7525");
- });
-
- // Prueba la verificación de valor nulo.
- it("Should check if value is null", () => {
- expect(() => MoneyValue.create(null)).toThrowError();
+ expect(result).toBe("75.25");
});
// Prueba la verificación de valor cero.
diff --git a/shared/lib/contexts/common/domain/entities/MoneyValue.ts b/shared/lib/contexts/common/domain/entities/MoneyValue.ts
index 4da095b..1a378be 100644
--- a/shared/lib/contexts/common/domain/entities/MoneyValue.ts
+++ b/shared/lib/contexts/common/domain/entities/MoneyValue.ts
@@ -20,7 +20,7 @@ export const defaultMoneyValueOptions: IMoneyValueOptions = {
};
export interface MoneyValueObject {
- amount: number;
+ amount: number | null;
scale: number;
currency_code: string;
}
@@ -43,7 +43,7 @@ export interface IMoneyValueProps {
}
const defaultMoneyValueProps = {
- amount: 0,
+ amount: null,
currencyCode: CurrencyData.DEFAULT_CURRENCY_CODE,
scale: 2,
};
@@ -141,6 +141,8 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
scale = defaultMoneyValueProps.scale,
} = props || {};
+ console.log(props, { amount, currencyCode, scale });
+
const validationResult = MoneyValue.validate(amount, options);
if (validationResult.isFailure) {
@@ -159,6 +161,71 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
return Result.ok(new this(prop, isNull(_amount), options));
}
+ public static createFromFormattedValue(
+ value: NullOr,
+ currencyCode: string,
+ _options: IMoneyValueOptions = {
+ locale: defaultMoneyValueOptions.locale,
+ }
+ ) {
+ if (value === null || value === "") {
+ return MoneyValue.create({
+ amount: null,
+ scale: MoneyValue.DEFAULT_SCALE,
+ currencyCode,
+ });
+ }
+
+ const valueStr = String(value);
+ const [integerPart, decimalPart] = valueStr.split(",");
+
+ let _amount = integerPart;
+ let _scale = 2;
+
+ if (decimalPart === undefined) {
+ // 99
+ _scale = 0;
+ } else {
+ if (decimalPart === "") {
+ // 99,
+ _amount = integerPart + decimalPart.padEnd(1, "0");
+ _scale = 1;
+ }
+ if (decimalPart.length === 1) {
+ // 99,1
+ _amount = integerPart + decimalPart.padEnd(1, "0");
+ _scale = 1;
+ } else {
+ if (decimalPart.length === 2) {
+ // 99,12
+ _amount = integerPart + decimalPart.padEnd(2, "0");
+ _scale = 2;
+ } else {
+ if (decimalPart.length === 3) {
+ // 99,123
+ _amount = integerPart + decimalPart.padEnd(3, "0");
+ _scale = 3;
+ } else {
+ if (decimalPart.length === 4) {
+ // 99,1235
+ _amount = integerPart + decimalPart.padEnd(4, "0");
+ _scale = 4;
+ }
+ }
+ }
+ }
+ }
+
+ return MoneyValue.create(
+ {
+ amount: _amount,
+ scale: _scale,
+ currencyCode,
+ },
+ _options
+ );
+ }
+
private static sanitize(amount: NullOr): NullOr {
let _amount: NullOr = null;
@@ -191,6 +258,16 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
.object;
}
+ private static _toString(value: NullOr, scale: number): string {
+ if (value === null) {
+ return "";
+ }
+
+ const factor = Math.pow(10, scale);
+ const amount = Number(value) / factor;
+ return amount.toFixed(scale);
+ }
+
constructor(value: Dinero, isNull: boolean, options: IMoneyValueOptions) {
super(value);
this._isNull = Object.freeze(isNull);
@@ -202,7 +279,7 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
};
public toString(): string {
- return this._isNull ? "" : String(this.props?.getAmount());
+ return MoneyValue._toString(this.isNull() ? null : this.getAmount(), this.getScale());
}
public toJSON() {
@@ -320,7 +397,7 @@ export class MoneyValue extends ValueObject implements IMoneyValue {
public toObject(): MoneyValueObject {
const obj = this.props.toObject();
return {
- amount: obj.amount,
+ amount: this._isNull ? null : obj.amount,
scale: obj.precision,
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 cc88f50..80d4dd1 100644
--- a/shared/lib/contexts/common/domain/entities/Percentage.ts
+++ b/shared/lib/contexts/common/domain/entities/Percentage.ts
@@ -47,22 +47,33 @@ export class Percentage extends NullableValueObject {
scale: NullOr,
options: IPercentageOptions
) {
- const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(
- defaultPercentageProps.amount
+ const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED;
+
+ const ruleEmpty = RuleValidator.RULE_ALLOW_EMPTY;
+
+ const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
+ options.label ? options.label : "amount"
+ );
+
+ const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
+ options.label ? options.label : "amount"
);
const ruleScale = Joi.number()
.min(Percentage.MIN_SCALE)
.max(Percentage.MAX_SCALE)
- .label(options.label ? options.label : "percentage");
+ .label(options.label ? options.label : "scale");
const validationResults = new ResultCollection([
RuleValidator.validate>(
- Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER),
+ Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString),
value
),
RuleValidator.validate>(
- Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER, ruleScale),
+ Joi.alternatives(
+ RuleValidator.RULE_IS_TYPE_NUMBER.label(options.label ? options.label : "scale"),
+ ruleScale
+ ),
scale
),
]);
@@ -181,18 +192,18 @@ export class Percentage extends NullableValueObject {
return _value;
}
- private static _toNumber(value: NullOr, scale: number): number {
- if (isNull(value)) {
- return 0;
+ private static _toString(value: NullOr, scale: number): string {
+ if (value === null) {
+ return "";
}
const factor = Math.pow(10, scale);
const amount = Number(value) / factor;
- return Number(amount.toFixed(scale));
+ return amount.toFixed(scale);
}
private static _isWithinRange(value: NullOr, scale: number): boolean {
- const _value = Percentage._toNumber(value, scale);
+ const _value = Number(Percentage._toString(value, scale));
return _value >= Percentage.MIN_VALUE && _value <= Percentage.MAX_VALUE;
}
@@ -226,12 +237,12 @@ export class Percentage extends NullableValueObject {
return this._isNull;
};
- public toNumber(): number {
- return Percentage._toNumber(this.amount, this.scale);
+ public toString(): string {
+ return Percentage._toString(this.amount, this.scale);
}
- public toString(): string {
- return this.isNull() ? "" : String(this.toNumber());
+ public toNumber(): number {
+ return this.isNull() ? 0 : Number(this.toString());
}
public toPrimitive(): NullOr {
diff --git a/shared/lib/contexts/common/domain/entities/Quantity.ts b/shared/lib/contexts/common/domain/entities/Quantity.ts
index 1ef3552..de6cdd5 100644
--- a/shared/lib/contexts/common/domain/entities/Quantity.ts
+++ b/shared/lib/contexts/common/domain/entities/Quantity.ts
@@ -47,27 +47,28 @@ export class Quantity extends NullableValueObject {
const ruleEmpty = RuleValidator.RULE_ALLOW_EMPTY;
const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
- options.label ? options.label : "quantity"
+ options.label ? options.label : "amount"
);
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
- options.label ? options.label : "quantity"
+ options.label ? options.label : "amount"
);
const ruleScale = Joi.number()
.min(Quantity.MIN_SCALE)
.max(Quantity.MAX_SCALE)
- .label(options.label ? options.label : "quantity");
-
- const rules = Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString);
+ .label(options.label ? options.label : "scale");
const validationResults = new ResultCollection([
RuleValidator.validate>(
- Joi.alternatives(ruleNull, ruleNumber, ruleString),
+ Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString),
value
),
RuleValidator.validate>(
- Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER, ruleScale),
+ Joi.alternatives(
+ RuleValidator.RULE_IS_TYPE_NUMBER.label(options.label ? options.label : "scale"),
+ ruleScale
+ ),
scale
),
]);
@@ -76,15 +77,6 @@ export class Quantity extends NullableValueObject {
return validationResults.getFirstFaultyResult();
}
- // Convert the value to a number if it's a string
- let numericValue = typeof value === "string" ? parseInt(value, 10) : Number(value);
-
- // Check if scale is null, and set to default if so
- let numericScale = scale === null ? Quantity.DEFAULT_SCALE : Number(scale);
-
- // Calculate the adjusted value
- const adjustedValue = numericValue / Math.pow(10, numericScale);
-
return Result.ok();
}