.
This commit is contained in:
parent
d4665a6de6
commit
740f5fd238
@ -273,7 +273,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
|||||||
description: "aaaa",
|
description: "aaaa",
|
||||||
unit_price: {
|
unit_price: {
|
||||||
amount: "10000",
|
amount: "10000",
|
||||||
precision: 4,
|
scale: 4,
|
||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
},
|
},
|
||||||
discount: {
|
discount: {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { t } from "i18next";
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useFieldArray, useFormContext } from "react-hook-form";
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||||
import { useDetailColumns } from "../../hooks";
|
import { useDetailColumns } from "../../hooks";
|
||||||
import { CatalogPickerDataTable } from "../CatalogPickerDataTable";
|
|
||||||
import { SortableDataTable } from "../SortableDataTable";
|
import { SortableDataTable } from "../SortableDataTable";
|
||||||
|
|
||||||
export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) => {
|
export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData }) => {
|
||||||
@ -42,12 +41,13 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
header: () => (
|
header: () => (
|
||||||
<div className='text-right'>{t("quotes.form_fields.items.quantity.label")}</div>
|
<div className='text-right'>{t("quotes.form_fields.items.quantity.label")}</div>
|
||||||
),
|
),
|
||||||
size: 5,
|
size: 8,
|
||||||
cell: ({ row: { index } }) => {
|
cell: ({ row: { index } }) => {
|
||||||
return (
|
return (
|
||||||
<FormQuantityField
|
<FormQuantityField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
precision={Quantity.DEFAULT_PRECISION}
|
scale={0}
|
||||||
|
className='text-right'
|
||||||
{...register(`items.${index}.quantity`)}
|
{...register(`items.${index}.quantity`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -73,14 +73,14 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<FormCurrencyField
|
<FormCurrencyField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
currency={currency}
|
currency={currency}
|
||||||
precision={4}
|
scale={4}
|
||||||
className='text-right'
|
className='text-right'
|
||||||
{...register(`items.${index}.unit_price`)}
|
{...register(`items.${index}.unit_price`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
/*{
|
||||||
id: "subtotal_price" as const,
|
id: "subtotal_price" as const,
|
||||||
accessorKey: "subtotal_price",
|
accessorKey: "subtotal_price",
|
||||||
header: () => (
|
header: () => (
|
||||||
@ -91,34 +91,33 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<FormCurrencyField
|
<FormCurrencyField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
currency={currency}
|
currency={currency}
|
||||||
precision={4}
|
scale={4}
|
||||||
|
disabled
|
||||||
className='text-right'
|
className='text-right'
|
||||||
{...register(`items.${index}.subtotal_price`)}
|
{...register(`items.${index}.subtotal_price`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},*/
|
||||||
{
|
{
|
||||||
id: "discount" as const,
|
id: "discount" as const,
|
||||||
accessorKey: "discount",
|
accessorKey: "discount",
|
||||||
size: 5,
|
size: 8,
|
||||||
header: () => (
|
header: () => (
|
||||||
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
|
<div className='text-right'>{t("quotes.form_fields.items.discount.label")}</div>
|
||||||
),
|
),
|
||||||
cell: ({ row: { index }, column: { id } }) => {
|
cell: ({ row: { index } }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<FormPercentageField
|
||||||
<FormPercentageField
|
variant='outline'
|
||||||
variant='outline'
|
scale={2}
|
||||||
precision={0}
|
className='text-right'
|
||||||
className='text-right'
|
{...register(`items.${index}.discount`)}
|
||||||
{...register(`items.${index}.discount`)}
|
/>
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
/*{
|
||||||
id: "total_price" as const,
|
id: "total_price" as const,
|
||||||
accessorKey: "total_price",
|
accessorKey: "total_price",
|
||||||
header: () => (
|
header: () => (
|
||||||
@ -129,13 +128,14 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<FormCurrencyField
|
<FormCurrencyField
|
||||||
variant='outline'
|
variant='outline'
|
||||||
currency={currency}
|
currency={currency}
|
||||||
precision={4}
|
scale={4}
|
||||||
|
disabled
|
||||||
className='text-right'
|
className='text-right'
|
||||||
{...register(`items.${index}.total_price`)}
|
{...register(`items.${index}.total_price`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},*/
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
enableDragHandleColumn: true,
|
enableDragHandleColumn: true,
|
||||||
@ -177,8 +177,8 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
fieldActions.append({
|
fieldActions.append({
|
||||||
...newArticle,
|
...newArticle,
|
||||||
quantity: {
|
quantity: {
|
||||||
amount: 12,
|
amount: 100,
|
||||||
precision: Quantity.DEFAULT_PRECISION,
|
scale: Quantity.DEFAULT_SCALE,
|
||||||
},
|
},
|
||||||
unit_price: newArticle.retail_price,
|
unit_price: newArticle.retail_price,
|
||||||
});
|
});
|
||||||
@ -216,7 +216,7 @@ export const QuoteDetailsCardEditor = ({ currency }: { currency: CurrencyData })
|
|||||||
<ResizableHandle withHandle className='mx-3' />
|
<ResizableHandle withHandle className='mx-3' />
|
||||||
<ResizablePanel defaultSize={defaultLayout[1]} minSize={10}>
|
<ResizablePanel defaultSize={defaultLayout[1]} minSize={10}>
|
||||||
<DataTableProvider syncWithLocation={false}>
|
<DataTableProvider syncWithLocation={false}>
|
||||||
<CatalogPickerDataTable onClick={handleInsertArticle} />
|
{/*<CatalogPickerDataTable onClick={handleInsertArticle} />*/}
|
||||||
</DataTableProvider>
|
</DataTableProvider>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { CurrencyData, IUpdateQuote_Request_DTO, MoneyValue } from "@shared/cont
|
|||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { SubmitHandler, useForm } from "react-hook-form";
|
import { SubmitHandler, useForm } from "react-hook-form";
|
||||||
import useFormPersist from "react-hook-form-persist";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
||||||
@ -57,42 +56,46 @@ export const QuoteEdit = () => {
|
|||||||
validity: "",
|
validity: "",
|
||||||
subtotal_price: {
|
subtotal_price: {
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
precision: 2,
|
scale: 2,
|
||||||
currency_code: data?.currency_code,
|
currency_code: data?.currency_code,
|
||||||
},
|
},
|
||||||
discount: {
|
discount: {
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
precision: 0,
|
scale: 0,
|
||||||
},
|
},
|
||||||
total_price: {
|
total_price: {
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
precision: 2,
|
scale: 2,
|
||||||
currency_code: data?.currency_code,
|
currency_code: data?.currency_code,
|
||||||
},
|
},
|
||||||
items: [
|
/*items: [
|
||||||
{
|
{
|
||||||
|
quantity: {
|
||||||
|
amount: undefined,
|
||||||
|
scale: 2,
|
||||||
|
},
|
||||||
subtotal_price: {
|
subtotal_price: {
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
precision: 4,
|
scale: 4,
|
||||||
currency_code: data?.currency_code,
|
currency_code: data?.currency_code,
|
||||||
},
|
},
|
||||||
discount: {
|
discount: {
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
precision: 0,
|
scale: 0,
|
||||||
},
|
},
|
||||||
total_price: {
|
total_price: {
|
||||||
amount: undefined,
|
amount: undefined,
|
||||||
precision: 4,
|
scale: 4,
|
||||||
currency_code: data?.currency_code,
|
currency_code: data?.currency_code,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],*/
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { watch, getValues, setValue, formState } = form;
|
const { watch, getValues, setValue, formState } = form;
|
||||||
|
|
||||||
const { clear } = useFormPersist(
|
/*const { clear } = useFormPersist(
|
||||||
"quote-edit",
|
"quote-edit",
|
||||||
{
|
{
|
||||||
watch,
|
watch,
|
||||||
@ -102,7 +105,7 @@ export const QuoteEdit = () => {
|
|||||||
storage: window.localStorage, // default window.sessionStorage
|
storage: window.localStorage, // default window.sessionStorage
|
||||||
//exclude: ['foo']
|
//exclude: ['foo']
|
||||||
}
|
}
|
||||||
);
|
);*/
|
||||||
|
|
||||||
const { isSubmitting } = formState;
|
const { isSubmitting } = formState;
|
||||||
|
|
||||||
@ -116,7 +119,7 @@ export const QuoteEdit = () => {
|
|||||||
//onSettled: () => {},
|
//onSettled: () => {},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast("Guardado!");
|
toast("Guardado!");
|
||||||
clear();
|
//clear();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -139,18 +142,21 @@ export const QuoteEdit = () => {
|
|||||||
let quoteSubtotal = MoneyValue.create().object;
|
let quoteSubtotal = MoneyValue.create().object;
|
||||||
|
|
||||||
// Recálculo líneas
|
// Recálculo líneas
|
||||||
items.map((item, index) => {
|
items &&
|
||||||
const itemTotals = calculateItemTotals(item);
|
items.map((item, index) => {
|
||||||
|
const itemTotals = calculateItemTotals(item);
|
||||||
|
|
||||||
if (itemTotals === null) {
|
console.log(itemTotals?.quantity.toObject());
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
if (itemTotals === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
quoteSubtotal = quoteSubtotal.add(itemTotals.totalPrice);
|
||||||
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
|
||||||
});
|
setValue(`items.${index}.subtotal_price`, itemTotals.subtotalPrice.toObject());
|
||||||
|
setValue(`items.${index}.total_price`, itemTotals.totalPrice.toObject());
|
||||||
|
});
|
||||||
|
|
||||||
// Recálculo completo
|
// Recálculo completo
|
||||||
setValue("subtotal_price", quoteSubtotal.toObject());
|
setValue("subtotal_price", quoteSubtotal.toObject());
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@
|
|||||||
import { CurrencyData, MoneyValue } from "@shared/contexts";
|
import { CurrencyData, MoneyValue } from "@shared/contexts";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import * as React from "react";
|
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 { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
import { FormErrorMessage } from "./FormErrorMessage";
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
@ -38,7 +38,7 @@ export type FormCurrencyFieldProps<
|
|||||||
UseControllerProps<TFieldValues, TName> &
|
UseControllerProps<TFieldValues, TName> &
|
||||||
VariantProps<typeof formCurrencyFieldVariants> & {
|
VariantProps<typeof formCurrencyFieldVariants> & {
|
||||||
currency: CurrencyData;
|
currency: CurrencyData;
|
||||||
precision: number;
|
scale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrencyFieldProps>(
|
export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrencyFieldProps>(
|
||||||
@ -54,7 +54,7 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
rules,
|
rules,
|
||||||
readOnly,
|
readOnly,
|
||||||
precision,
|
scale,
|
||||||
currency,
|
currency,
|
||||||
variant,
|
variant,
|
||||||
} = props;
|
} = props;
|
||||||
@ -68,22 +68,17 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
}
|
}
|
||||||
|
|
||||||
const moneyOrError = MoneyValue.create(value);
|
const moneyOrError = MoneyValue.create(value);
|
||||||
|
|
||||||
if (moneyOrError.isFailure) {
|
if (moneyOrError.isFailure) {
|
||||||
throw moneyOrError.error;
|
throw moneyOrError.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return moneyOrError.object
|
return moneyOrError.object.toString();
|
||||||
.convertPrecision(precision ?? value.precision)
|
|
||||||
.toUnit()
|
|
||||||
.toString();
|
|
||||||
},
|
},
|
||||||
|
output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
|
||||||
|
const { value: amount } = values ?? { value: null };
|
||||||
|
|
||||||
output: (value: string | undefined) => {
|
const moneyOrError = MoneyValue.createFromFormattedValue(amount, currency.code);
|
||||||
const moneyOrError = MoneyValue.create({
|
|
||||||
amount: value?.replace(",", "") ?? null,
|
|
||||||
precision,
|
|
||||||
currencyCode: currency.code,
|
|
||||||
});
|
|
||||||
if (moneyOrError.isFailure) {
|
if (moneyOrError.isFailure) {
|
||||||
throw moneyOrError.error;
|
throw moneyOrError.error;
|
||||||
}
|
}
|
||||||
@ -118,11 +113,17 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
|||||||
groupSeparator='.'
|
groupSeparator='.'
|
||||||
decimalSeparator=','
|
decimalSeparator=','
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
//fixedDecimalLength={precision} <- no activar para que sea más cómodo escribir las cantidades
|
//allowDecimals={scale !== 0}
|
||||||
decimalsLimit={precision}
|
decimalsLimit={scale}
|
||||||
decimalScale={precision}
|
decimalScale={scale}
|
||||||
|
//fixedDecimalLength={scale} <- no activar para que sea más cómodo escribir las cantidades
|
||||||
|
step={1}
|
||||||
|
// { ...field }
|
||||||
value={transform.input(field.value)}
|
value={transform.input(field.value)}
|
||||||
onValueChange={(e) => field.onChange(transform.output(e))}
|
//onChange={() => {}}
|
||||||
|
onValueChange={(value, name, values) =>
|
||||||
|
field.onChange(transform.output(value, name, values))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
|||||||
@ -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<FormTextFieldProps, "type"> & {
|
|
||||||
defaultValue?: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FormMoneyField = forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement> & 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<number>(MoneyValue.DEFAULT_PRECISION);
|
|
||||||
const [currencyCode, setCurrencyCode] = useState<string>(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<HTMLInputElement>) => {
|
|
||||||
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 (
|
|
||||||
<Controller
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
control={control}
|
|
||||||
name={name}
|
|
||||||
rules={{ required }}
|
|
||||||
disabled={disabled}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
render={({ field, fieldState, formState }) => {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
{...field}
|
|
||||||
placeholder='number'
|
|
||||||
onChange={(e) => field.onChange(transform.output(e))}
|
|
||||||
value={transform.input(field.value)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn(className, "space-y-3")}>
|
|
||||||
{label && <FormLabel label={label} hint={hint} />}
|
|
||||||
<div className={cn(button ? "flex" : null)}>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
leadIcon ? "relative flex items-stretch flex-grow focus-within:z-10" : ""
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{leadIcon && (
|
|
||||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
|
||||||
{createElement(
|
|
||||||
leadIcon,
|
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormControl
|
|
||||||
className={cn("block", leadIcon ? "pl-10" : "", trailIcon ? "pr-10" : "")}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder={placeholder}
|
|
||||||
className={cn(
|
|
||||||
fieldState.error ? "border-destructive focus-visible:ring-destructive" : ""
|
|
||||||
)}
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => field.onChange(transform.output(e))}
|
|
||||||
value={transform.input(field.value)}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
{trailIcon && (
|
|
||||||
<div className='absolute inset-y-0 right-0 flex items-center pl-3 pointer-events-none'>
|
|
||||||
{createElement(
|
|
||||||
trailIcon,
|
|
||||||
{
|
|
||||||
className: "h-5 w-5 text-muted-foreground",
|
|
||||||
"aria-hidden": true,
|
|
||||||
},
|
|
||||||
null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{button && <>{createElement(button)}</>}
|
|
||||||
</div>
|
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -4,7 +4,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
||||||
import { Percentage } from "@shared/contexts";
|
import { Percentage } from "@shared/contexts";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
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 { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
import { FormErrorMessage } from "./FormErrorMessage";
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
@ -39,7 +39,7 @@ export type FormPercentageFieldProps<
|
|||||||
FormInputWithIconProps &
|
FormInputWithIconProps &
|
||||||
UseControllerProps<TFieldValues, TName> &
|
UseControllerProps<TFieldValues, TName> &
|
||||||
VariantProps<typeof formPercentageFieldVariants> & {
|
VariantProps<typeof formPercentageFieldVariants> & {
|
||||||
precision: number;
|
scale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FormPercentageField = React.forwardRef<
|
export const FormPercentageField = React.forwardRef<
|
||||||
@ -57,7 +57,7 @@ export const FormPercentageField = React.forwardRef<
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
rules,
|
rules,
|
||||||
readOnly,
|
readOnly,
|
||||||
precision,
|
scale,
|
||||||
variant,
|
variant,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -74,18 +74,14 @@ export const FormPercentageField = React.forwardRef<
|
|||||||
throw percentageOrError.error;
|
throw percentageOrError.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return percentageOrError.object.toString();
|
||||||
percentageOrError.object
|
|
||||||
.toNumber()
|
|
||||||
//.toPrecision(precision ?? value.precision)
|
|
||||||
.toString()
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
output: (value: string | undefined) => {
|
output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
|
||||||
const percentageOrError = Percentage.create({
|
console.log(values);
|
||||||
amount: value?.replace(",", "") ?? null,
|
const { value: amount } = values ?? { value: null };
|
||||||
precision,
|
|
||||||
});
|
const percentageOrError = Percentage.createFromFormattedValue(amount);
|
||||||
|
|
||||||
if (percentageOrError.isFailure) {
|
if (percentageOrError.isFailure) {
|
||||||
throw percentageOrError.error;
|
throw percentageOrError.error;
|
||||||
}
|
}
|
||||||
@ -122,10 +118,16 @@ export const FormPercentageField = React.forwardRef<
|
|||||||
groupSeparator='.'
|
groupSeparator='.'
|
||||||
decimalSeparator=','
|
decimalSeparator=','
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
decimalsLimit={precision}
|
allowDecimals={scale !== 0}
|
||||||
decimalScale={precision}
|
decimalsLimit={scale}
|
||||||
|
decimalScale={scale}
|
||||||
|
step={1}
|
||||||
|
//{...field}
|
||||||
value={transform.input(field.value)}
|
value={transform.input(field.value)}
|
||||||
onValueChange={(e) => field.onChange(transform.output(e))}
|
//onChange={() => {}}
|
||||||
|
onValueChange={(value, name, values) =>
|
||||||
|
field.onChange(transform.output(value, name, values))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
import { FormControl, FormDescription, FormField, FormItem, InputProps } from "@/ui";
|
||||||
import { Quantity } from "@shared/contexts";
|
import { Quantity } from "@shared/contexts";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
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 { FieldPath, FieldValues, UseControllerProps, useFormContext } from "react-hook-form";
|
||||||
import { FormErrorMessage } from "./FormErrorMessage";
|
import { FormErrorMessage } from "./FormErrorMessage";
|
||||||
import { FormLabel, FormLabelProps } from "./FormLabel";
|
import { FormLabel, FormLabelProps } from "./FormLabel";
|
||||||
@ -38,7 +38,7 @@ export type FormQuantityFieldProps<
|
|||||||
FormInputWithIconProps &
|
FormInputWithIconProps &
|
||||||
UseControllerProps<TFieldValues, TName> &
|
UseControllerProps<TFieldValues, TName> &
|
||||||
VariantProps<typeof formQuantityFieldVariants> & {
|
VariantProps<typeof formQuantityFieldVariants> & {
|
||||||
precision: number;
|
scale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantityFieldProps>(
|
export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantityFieldProps>(
|
||||||
@ -54,7 +54,7 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
rules,
|
rules,
|
||||||
readOnly,
|
readOnly,
|
||||||
precision,
|
scale,
|
||||||
variant,
|
variant,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -71,18 +71,12 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
|
|||||||
throw quantityOrError.error;
|
throw quantityOrError.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return quantityOrError.object.toString();
|
||||||
quantityOrError.object
|
|
||||||
.toNumber()
|
|
||||||
//.toPrecision(precision ?? value.precision)
|
|
||||||
.toString()
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
output: (value: string | undefined) => {
|
output: (value: string | undefined, name?: string, values?: CurrencyInputOnChangeValues) => {
|
||||||
const quantityOrError = Quantity.create({
|
const { value: amount } = values ?? { value: null };
|
||||||
amount: value?.replace(",", "") ?? null,
|
|
||||||
precision,
|
const quantityOrError = Quantity.createFromFormattedValue(amount);
|
||||||
});
|
|
||||||
if (quantityOrError.isFailure) {
|
if (quantityOrError.isFailure) {
|
||||||
throw quantityOrError.error;
|
throw quantityOrError.error;
|
||||||
}
|
}
|
||||||
@ -107,7 +101,7 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<CurrencyInput
|
<CurrencyInput
|
||||||
name={field.name}
|
name={field.name}
|
||||||
//ref={field.ref} <-- no activar que hace cosas raras
|
//ref={field.ref} //<-- no activar que hace cosas raras
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
@ -115,10 +109,16 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
|
|||||||
groupSeparator='.'
|
groupSeparator='.'
|
||||||
decimalSeparator=','
|
decimalSeparator=','
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
decimalsLimit={precision}
|
allowDecimals={scale !== 0}
|
||||||
decimalScale={precision}
|
decimalsLimit={scale}
|
||||||
|
decimalScale={scale}
|
||||||
|
step={1}
|
||||||
|
//{...field}
|
||||||
value={transform.input(field.value)}
|
value={transform.input(field.value)}
|
||||||
onValueChange={(e) => field.onChange(transform.output(e))}
|
//onChange={() => {}}
|
||||||
|
onValueChange={(value, name, values) =>
|
||||||
|
field.onChange(transform.output(value, name, values))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{description && <FormDescription>{description}</FormDescription>}
|
{description && <FormDescription>{description}</FormDescription>}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ export * from "./FormDatePickerField";
|
|||||||
export * from "./FormErrorMessage";
|
export * from "./FormErrorMessage";
|
||||||
export * from "./FormGroup";
|
export * from "./FormGroup";
|
||||||
export * from "./FormLabel";
|
export * from "./FormLabel";
|
||||||
export * from "./FormMoneyField";
|
|
||||||
export * from "./FormPercentageField";
|
export * from "./FormPercentageField";
|
||||||
export * from "./FormQuantityField";
|
export * from "./FormQuantityField";
|
||||||
export * from "./FormTextAreaField";
|
export * from "./FormTextAreaField";
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
|
// jest.config.js
|
||||||
|
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
globals: {
|
preset: "ts-jest",
|
||||||
"ts-jest": {
|
|
||||||
tsconfig: "tsconfig.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
moduleFileExtensions: ["ts", "js"],
|
|
||||||
transform: {
|
|
||||||
"^.+\\.(ts|tsx)$": "ts-jest",
|
|
||||||
},
|
|
||||||
testMatch: ["**/*.test.(ts|js)"],
|
|
||||||
testEnvironment: "node",
|
testEnvironment: "node",
|
||||||
|
moduleNameMapper: {
|
||||||
|
"^@shared/(.*)$": "<rootDir>/../shared/lib/$1",
|
||||||
|
"^@/(.*)$": "<rootDir>/src/$1",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "eslint --ignore-path .gitignore . --ext .ts",
|
"lint": "eslint --ignore-path .gitignore . --ext .ts",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"test": "jest --verbose",
|
"test": "jest --config=./jest.config.ts --rootDir=./src/ --verbose",
|
||||||
"clean": "rm -rf node_modules"
|
"clean": "rm -rf node_modules"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"@types/express-session": "^1.18.0",
|
"@types/express-session": "^1.18.0",
|
||||||
"@types/glob": "^8.1.0",
|
"@types/glob": "^8.1.0",
|
||||||
"@types/http-status": "^1.1.2",
|
"@types/http-status": "^1.1.2",
|
||||||
"@types/jest": "^29.5.6",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
"@types/module-alias": "^2.0.1",
|
"@types/module-alias": "^2.0.1",
|
||||||
@ -49,7 +49,7 @@
|
|||||||
"module-alias": "^2.2.3",
|
"module-alias": "^2.2.3",
|
||||||
"prettier": "3.0.1",
|
"prettier": "3.0.1",
|
||||||
"supertest": "^6.2.2",
|
"supertest": "^6.2.2",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.2.2",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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<ISequelizeAdapter>;
|
||||||
|
let mockRepositoryManager: jest.Mocked<IRepositoryManager>;
|
||||||
|
let mockQuoteRepository: jest.Mocked<IQuoteRepository>;
|
||||||
|
let mockTransaction: { complete: jest.Mock };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockTransaction = { complete: jest.fn() };
|
||||||
|
|
||||||
|
mockAdapter = {
|
||||||
|
startTransaction: jest.fn().mockReturnValue(mockTransaction),
|
||||||
|
} as unknown as jest.Mocked<ISequelizeAdapter>;
|
||||||
|
|
||||||
|
mockQuoteRepository = {
|
||||||
|
getById: jest.fn(),
|
||||||
|
} as unknown as jest.Mocked<IQuoteRepository>;
|
||||||
|
|
||||||
|
mockRepositoryManager = {
|
||||||
|
getRepository: jest.fn().mockReturnValue(() => mockQuoteRepository),
|
||||||
|
} as unknown as jest.Mocked<IRepositoryManager>;
|
||||||
|
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -37,9 +37,6 @@ export class GetQuoteUseCase
|
|||||||
async execute(request: IGetQuoteUseCaseRequest): Promise<GetQuoteResponseOrError> {
|
async execute(request: IGetQuoteUseCaseRequest): Promise<GetQuoteResponseOrError> {
|
||||||
const { id } = request;
|
const { id } = request;
|
||||||
|
|
||||||
// Validación de datos
|
|
||||||
// No hay en este caso
|
|
||||||
|
|
||||||
return await this.findQuote(id);
|
return await this.findQuote(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["jest"],
|
"types": ["jest"],
|
||||||
"baseUrl": "./src",
|
//"baseUrl": "./src",
|
||||||
|
"moduleResolution": "node",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@shared/*": ["../shared/lib/*"]
|
"@shared/*": ["../shared/lib/*"]
|
||||||
|
|||||||
@ -25,10 +25,11 @@ describe("MoneyValue Value Object", () => {
|
|||||||
it("Should create a valid money value from string and format it", () => {
|
it("Should create a valid money value from string and format it", () => {
|
||||||
const validMoneyValueFromString = MoneyValue.create({
|
const validMoneyValueFromString = MoneyValue.create({
|
||||||
amount: "5075",
|
amount: "5075",
|
||||||
|
scale: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(validMoneyValueFromString.isSuccess).toBe(true);
|
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.
|
// 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.getAmount()).toBe(100);
|
||||||
expect(moneyValue.getCurrency().code).toBe("EUR");
|
expect(moneyValue.getCurrency().code).toBe("EUR");
|
||||||
expect(moneyValue.getPrecision()).toBe(3);
|
expect(moneyValue.getScale()).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create MoneyValue from string and currency", () => {
|
it("should create MoneyValue from string and currency", () => {
|
||||||
@ -67,7 +68,7 @@ describe("MoneyValue Value Object", () => {
|
|||||||
|
|
||||||
expect(moneyValue.getAmount()).toBe(12345);
|
expect(moneyValue.getAmount()).toBe(12345);
|
||||||
expect(moneyValue.getCurrency().code).toBe("USD");
|
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", () => {
|
it("should fail to create MoneyValue with invalid amount", () => {
|
||||||
@ -86,12 +87,7 @@ describe("MoneyValue Value Object", () => {
|
|||||||
}).object;
|
}).object;
|
||||||
const result = moneyValue.toString();
|
const result = moneyValue.toString();
|
||||||
|
|
||||||
expect(result).toBe("7525");
|
expect(result).toBe("75.25");
|
||||||
});
|
|
||||||
|
|
||||||
// Prueba la verificación de valor nulo.
|
|
||||||
it("Should check if value is null", () => {
|
|
||||||
expect(() => MoneyValue.create(null)).toThrowError();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prueba la verificación de valor cero.
|
// Prueba la verificación de valor cero.
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export const defaultMoneyValueOptions: IMoneyValueOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface MoneyValueObject {
|
export interface MoneyValueObject {
|
||||||
amount: number;
|
amount: number | null;
|
||||||
scale: number;
|
scale: number;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ export interface IMoneyValueProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultMoneyValueProps = {
|
const defaultMoneyValueProps = {
|
||||||
amount: 0,
|
amount: null,
|
||||||
currencyCode: CurrencyData.DEFAULT_CURRENCY_CODE,
|
currencyCode: CurrencyData.DEFAULT_CURRENCY_CODE,
|
||||||
scale: 2,
|
scale: 2,
|
||||||
};
|
};
|
||||||
@ -141,6 +141,8 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
|||||||
scale = defaultMoneyValueProps.scale,
|
scale = defaultMoneyValueProps.scale,
|
||||||
} = props || {};
|
} = props || {};
|
||||||
|
|
||||||
|
console.log(props, { amount, currencyCode, scale });
|
||||||
|
|
||||||
const validationResult = MoneyValue.validate(amount, options);
|
const validationResult = MoneyValue.validate(amount, options);
|
||||||
|
|
||||||
if (validationResult.isFailure) {
|
if (validationResult.isFailure) {
|
||||||
@ -159,6 +161,71 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
|||||||
return Result.ok<MoneyValue>(new this(prop, isNull(_amount), options));
|
return Result.ok<MoneyValue>(new this(prop, isNull(_amount), options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static createFromFormattedValue(
|
||||||
|
value: NullOr<number | string>,
|
||||||
|
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<number | string>): NullOr<number> {
|
private static sanitize(amount: NullOr<number | string>): NullOr<number> {
|
||||||
let _amount: NullOr<number> = null;
|
let _amount: NullOr<number> = null;
|
||||||
|
|
||||||
@ -191,6 +258,16 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
|||||||
.object;
|
.object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static _toString(value: NullOr<number>, 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) {
|
constructor(value: Dinero, isNull: boolean, options: IMoneyValueOptions) {
|
||||||
super(value);
|
super(value);
|
||||||
this._isNull = Object.freeze(isNull);
|
this._isNull = Object.freeze(isNull);
|
||||||
@ -202,7 +279,7 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public toString(): string {
|
public toString(): string {
|
||||||
return this._isNull ? "" : String(this.props?.getAmount());
|
return MoneyValue._toString(this.isNull() ? null : this.getAmount(), this.getScale());
|
||||||
}
|
}
|
||||||
|
|
||||||
public toJSON() {
|
public toJSON() {
|
||||||
@ -320,7 +397,7 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
|||||||
public toObject(): MoneyValueObject {
|
public toObject(): MoneyValueObject {
|
||||||
const obj = this.props.toObject();
|
const obj = this.props.toObject();
|
||||||
return {
|
return {
|
||||||
amount: obj.amount,
|
amount: this._isNull ? null : obj.amount,
|
||||||
scale: obj.precision,
|
scale: obj.precision,
|
||||||
currency_code: String(obj.currency),
|
currency_code: String(obj.currency),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -47,22 +47,33 @@ export class Percentage extends NullableValueObject<IPercentage> {
|
|||||||
scale: NullOr<number>,
|
scale: NullOr<number>,
|
||||||
options: IPercentageOptions
|
options: IPercentageOptions
|
||||||
) {
|
) {
|
||||||
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(
|
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED;
|
||||||
defaultPercentageProps.amount
|
|
||||||
|
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()
|
const ruleScale = Joi.number()
|
||||||
.min(Percentage.MIN_SCALE)
|
.min(Percentage.MIN_SCALE)
|
||||||
.max(Percentage.MAX_SCALE)
|
.max(Percentage.MAX_SCALE)
|
||||||
.label(options.label ? options.label : "percentage");
|
.label(options.label ? options.label : "scale");
|
||||||
|
|
||||||
const validationResults = new ResultCollection([
|
const validationResults = new ResultCollection([
|
||||||
RuleValidator.validate<NullOr<number>>(
|
RuleValidator.validate<NullOr<number>>(
|
||||||
Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER),
|
Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString),
|
||||||
value
|
value
|
||||||
),
|
),
|
||||||
RuleValidator.validate<NullOr<number>>(
|
RuleValidator.validate<NullOr<number>>(
|
||||||
Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER, ruleScale),
|
Joi.alternatives(
|
||||||
|
RuleValidator.RULE_IS_TYPE_NUMBER.label(options.label ? options.label : "scale"),
|
||||||
|
ruleScale
|
||||||
|
),
|
||||||
scale
|
scale
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@ -181,18 +192,18 @@ export class Percentage extends NullableValueObject<IPercentage> {
|
|||||||
return _value;
|
return _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _toNumber(value: NullOr<number>, scale: number): number {
|
private static _toString(value: NullOr<number>, scale: number): string {
|
||||||
if (isNull(value)) {
|
if (value === null) {
|
||||||
return 0;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const factor = Math.pow(10, scale);
|
const factor = Math.pow(10, scale);
|
||||||
const amount = Number(value) / factor;
|
const amount = Number(value) / factor;
|
||||||
return Number(amount.toFixed(scale));
|
return amount.toFixed(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _isWithinRange(value: NullOr<number>, scale: number): boolean {
|
private static _isWithinRange(value: NullOr<number>, 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;
|
return _value >= Percentage.MIN_VALUE && _value <= Percentage.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,12 +237,12 @@ export class Percentage extends NullableValueObject<IPercentage> {
|
|||||||
return this._isNull;
|
return this._isNull;
|
||||||
};
|
};
|
||||||
|
|
||||||
public toNumber(): number {
|
public toString(): string {
|
||||||
return Percentage._toNumber(this.amount, this.scale);
|
return Percentage._toString(this.amount, this.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public toString(): string {
|
public toNumber(): number {
|
||||||
return this.isNull() ? "" : String(this.toNumber());
|
return this.isNull() ? 0 : Number(this.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public toPrimitive(): NullOr<number> {
|
public toPrimitive(): NullOr<number> {
|
||||||
|
|||||||
@ -47,27 +47,28 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
const ruleEmpty = RuleValidator.RULE_ALLOW_EMPTY;
|
const ruleEmpty = RuleValidator.RULE_ALLOW_EMPTY;
|
||||||
|
|
||||||
const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
|
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(
|
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
|
||||||
options.label ? options.label : "quantity"
|
options.label ? options.label : "amount"
|
||||||
);
|
);
|
||||||
|
|
||||||
const ruleScale = Joi.number()
|
const ruleScale = Joi.number()
|
||||||
.min(Quantity.MIN_SCALE)
|
.min(Quantity.MIN_SCALE)
|
||||||
.max(Quantity.MAX_SCALE)
|
.max(Quantity.MAX_SCALE)
|
||||||
.label(options.label ? options.label : "quantity");
|
.label(options.label ? options.label : "scale");
|
||||||
|
|
||||||
const rules = Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString);
|
|
||||||
|
|
||||||
const validationResults = new ResultCollection([
|
const validationResults = new ResultCollection([
|
||||||
RuleValidator.validate<NullOr<number>>(
|
RuleValidator.validate<NullOr<number>>(
|
||||||
Joi.alternatives(ruleNull, ruleNumber, ruleString),
|
Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString),
|
||||||
value
|
value
|
||||||
),
|
),
|
||||||
RuleValidator.validate<NullOr<number>>(
|
RuleValidator.validate<NullOr<number>>(
|
||||||
Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER, ruleScale),
|
Joi.alternatives(
|
||||||
|
RuleValidator.RULE_IS_TYPE_NUMBER.label(options.label ? options.label : "scale"),
|
||||||
|
ruleScale
|
||||||
|
),
|
||||||
scale
|
scale
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@ -76,15 +77,6 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
return validationResults.getFirstFaultyResult();
|
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();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user