.
This commit is contained in:
parent
a845edb9c7
commit
47802ab932
@ -57,7 +57,7 @@
|
||||
"react-currency-input-field": "^3.8.0",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.51.5",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-hook-form-persist": "^2.1.0",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-resizable-panels": "^2.0.19",
|
||||
|
||||
@ -42,26 +42,27 @@ import { useCallback, useMemo, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { FieldValues, UseFieldArrayReturn } from "react-hook-form";
|
||||
import { AddNewRowButton } from "./AddNewRowButton";
|
||||
import { SortableDataTableToolbar } from "./SortableDataTableToolbar";
|
||||
import { SortableTableRow } from "./SortableTableRow";
|
||||
import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar";
|
||||
import { QuoteItemsSortableTableRow } from "./QuoteItemsSortableTableRow";
|
||||
|
||||
declare module "@tanstack/react-table" {
|
||||
interface TableMeta<TData extends RowData> {
|
||||
insertItem: (rowIndex: number, data: TData) => void;
|
||||
appendItem: (data: TData) => void;
|
||||
insertItem: (rowIndex: number, data?: unknown) => void;
|
||||
appendItem: (data?: unknown) => void;
|
||||
duplicateItems: (rowIndex?: number) => void;
|
||||
deleteItems: (rowIndex?: number | number[]) => void;
|
||||
updateItem: (rowIndex: number, rowData: TData, fieldName: string, value: unknown) => void;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SortableProps {
|
||||
export interface QuoteItemsSortableProps {
|
||||
id: UniqueIdentifier;
|
||||
}
|
||||
|
||||
export type SortableDataTableProps = {
|
||||
export type QuoteItemsSortableDataTableProps = {
|
||||
columns: ColumnDef<unknown, unknown>[];
|
||||
data: Record<"id", string>[];
|
||||
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
||||
actions: Omit<UseFieldArrayReturn<FieldValues, "items">, "fields">;
|
||||
};
|
||||
|
||||
@ -94,34 +95,12 @@ const dropAnimationConfig: DropAnimation = {
|
||||
},
|
||||
};
|
||||
|
||||
/*const defaultColumn: Partial<ColumnDef<unknown>> = {
|
||||
cell: ({ table, row: { index, original }, column, getValue }) => {
|
||||
const initialValue = getValue();
|
||||
|
||||
// We need to keep and update the state of the cell normally
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
// If the initialValue is changed external, sync it up with our state
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<input
|
||||
value={value as string}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onBlur={() => {
|
||||
console.log(column.id, value);
|
||||
table.options.meta?.updateItem(index, original, column.id, value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};*/
|
||||
|
||||
export function SortableDataTable({ columns, data, actions }: SortableDataTableProps) {
|
||||
export function QuoteItemsSortableDataTable({
|
||||
columns,
|
||||
data,
|
||||
defaultValues,
|
||||
actions,
|
||||
}: QuoteItemsSortableDataTableProps) {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [activeId, setActiveId] = useState<UniqueIdentifier | null>();
|
||||
|
||||
@ -154,11 +133,11 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
||||
maxSize: 96, //enforced during column resizing
|
||||
},
|
||||
meta: {
|
||||
insertItem: (rowIndex: number, data: object = {}) => {
|
||||
actions.insert(rowIndex, data, { shouldFocus: true });
|
||||
insertItem: (rowIndex: number, data?: unknown) => {
|
||||
actions.insert(rowIndex, data || defaultValues?.items[0], { shouldFocus: true });
|
||||
},
|
||||
appendItem: (data: object = {}) => {
|
||||
actions.append(data, { shouldFocus: true });
|
||||
appendItem: (data?: unknown) => {
|
||||
actions.append(data || defaultValues?.items[0], { shouldFocus: true });
|
||||
},
|
||||
duplicateItems: (rowIndex?: number) => {
|
||||
if (rowIndex != undefined) {
|
||||
@ -323,7 +302,7 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
||||
onDragCancel={handleDragCancel}
|
||||
collisionDetection={closestCenter}
|
||||
>
|
||||
<SortableDataTableToolbar table={table} />
|
||||
<QuoteItemsSortableDataTableToolbar table={table} />
|
||||
<Table className='table-fixed'>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
@ -346,13 +325,13 @@ export function SortableDataTable({ columns, data, actions }: SortableDataTableP
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{filterItems(table.getRowModel().rows).map((row) => (
|
||||
<SortableTableRow key={row.id} id={row.id}>
|
||||
<QuoteItemsSortableTableRow key={row.id} id={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id} className='px-2 py-1 align-top'>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</SortableTableRow>
|
||||
</QuoteItemsSortableTableRow>
|
||||
))}
|
||||
</SortableContext>
|
||||
</TableBody>
|
||||
@ -27,7 +27,7 @@ import {
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
|
||||
export const SortableDataTableToolbar = ({ table }: { table: Table<unknown> }) => {
|
||||
export const QuoteItemsSortableDataTableToolbar = ({ table }: { table: Table<unknown> }) => {
|
||||
const selectedRowsCount = table.getSelectedRowModel().rows.length;
|
||||
|
||||
if (selectedRowsCount) {
|
||||
@ -4,20 +4,20 @@ import { DraggableSyntheticListeners } from "@dnd-kit/core";
|
||||
import { defaultAnimateLayoutChanges, useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { CSSProperties, PropsWithChildren, createContext, useMemo } from "react";
|
||||
import { SortableProps } from "./SortableDataTable";
|
||||
import { QuoteItemsSortableProps } from "./QuoteItemsSortableDataTable";
|
||||
|
||||
interface Context {
|
||||
attributes: Record<string, any>;
|
||||
listeners: DraggableSyntheticListeners;
|
||||
ref(node: HTMLElement | null): void;
|
||||
}
|
||||
export const SortableTableRowContext = createContext<Context>({
|
||||
export const QuoteItemsSortableTableRowContext = createContext<Context>({
|
||||
attributes: {},
|
||||
listeners: undefined,
|
||||
ref() {},
|
||||
});
|
||||
|
||||
function animateLayoutChanges(args) {
|
||||
function animateLayoutChanges(args: any) {
|
||||
if (args.isSorting || args.wasDragging) {
|
||||
return defaultAnimateLayoutChanges(args);
|
||||
}
|
||||
@ -25,7 +25,10 @@ function animateLayoutChanges(args) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function SortableTableRow({ id, children }: PropsWithChildren<SortableProps>) {
|
||||
export function QuoteItemsSortableTableRow({
|
||||
id,
|
||||
children,
|
||||
}: PropsWithChildren<QuoteItemsSortableProps>) {
|
||||
const {
|
||||
attributes,
|
||||
isDragging,
|
||||
@ -54,7 +57,7 @@ export function SortableTableRow({ id, children }: PropsWithChildren<SortablePro
|
||||
);
|
||||
|
||||
return (
|
||||
<SortableTableRowContext.Provider value={context}>
|
||||
<QuoteItemsSortableTableRowContext.Provider value={context}>
|
||||
<TableRow
|
||||
key={id}
|
||||
id={String(id)}
|
||||
@ -67,6 +70,6 @@ export function SortableTableRow({ id, children }: PropsWithChildren<SortablePro
|
||||
>
|
||||
{children}
|
||||
</TableRow>
|
||||
</SortableTableRowContext.Provider>
|
||||
</QuoteItemsSortableTableRowContext.Provider>
|
||||
);
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
import { Badge, Button, Card, CardContent } from "@/ui";
|
||||
|
||||
import { DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
||||
import { DataTable, DataTableSkeleton, ErrorOverlay, SimpleEmptyState } from "@/components";
|
||||
|
||||
import { DataTable } from "@/components";
|
||||
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||
import { IListQuotes_Response_DTO, MoneyValue, UTCDateValue } from "@shared/contexts";
|
||||
|
||||
@ -12,16 +12,18 @@ import { t } from "i18next";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||
import { useDetailColumns } from "../../hooks";
|
||||
import { SortableDataTable } from "../SortableDataTable";
|
||||
import { QuoteItemsSortableDataTable } from "../QuoteItemsSortableDataTable";
|
||||
|
||||
export const QuoteDetailsCardEditor = ({
|
||||
currency,
|
||||
language,
|
||||
defaultValues,
|
||||
}: {
|
||||
currency: CurrencyData;
|
||||
language: Language;
|
||||
defaultValues: Readonly<{ [x: string]: any }> | undefined;
|
||||
}) => {
|
||||
const { control, register, getValues } = useFormContext();
|
||||
const { control, register } = useFormContext();
|
||||
|
||||
const { fields, ...fieldActions } = useFieldArray({
|
||||
control,
|
||||
@ -186,7 +188,7 @@ export const QuoteDetailsCardEditor = ({
|
||||
);
|
||||
|
||||
const handleInsertArticle = useCallback(
|
||||
(newArticle) => {
|
||||
(newArticle: any) => {
|
||||
fieldActions.append({
|
||||
...newArticle,
|
||||
quantity: {
|
||||
@ -204,7 +206,14 @@ export const QuoteDetailsCardEditor = ({
|
||||
const defaultLayout = [265, 440, 655];
|
||||
const navCollapsedSize = 4;
|
||||
|
||||
return <SortableDataTable actions={fieldActions} columns={columns} data={fields} />;
|
||||
return (
|
||||
<QuoteItemsSortableDataTable
|
||||
actions={fieldActions}
|
||||
columns={columns}
|
||||
data={fields}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ResizablePanelGroup
|
||||
@ -226,7 +235,7 @@ export const QuoteDetailsCardEditor = ({
|
||||
}}
|
||||
className={cn(isCollapsed && "min-w-[50px] transition-all duration-300 ease-in-out")}
|
||||
>
|
||||
<SortableDataTable actions={fieldActions} columns={columns} data={fields} />
|
||||
<QuoteItemsSortableDataTable actions={fieldActions} columns={columns} data={fields} />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle className='mx-3' />
|
||||
<ResizablePanel defaultSize={defaultLayout[1]} minSize={10}>
|
||||
|
||||
@ -6,6 +6,12 @@ import { useFormContext } from "react-hook-form";
|
||||
export const QuoteGeneralCardEditor = () => {
|
||||
const { register, formState } = useFormContext();
|
||||
|
||||
console.log({
|
||||
...register("customer_information", {
|
||||
required: true,
|
||||
}),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='grid gap-6 md:grid-cols-6'>
|
||||
<FormGroup
|
||||
|
||||
@ -48,7 +48,7 @@ export const QuoteCreate = () => {
|
||||
setWarnWhen(false);
|
||||
mutate(formData, {
|
||||
onSuccess: (data) => {
|
||||
navigate(`/quotes/edit/${data.id}`, { relative: "path" });
|
||||
navigate(`/quotes/edit/${data.id}`, { relative: "path", replace: true });
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
|
||||
@ -8,16 +8,20 @@ import {
|
||||
import { calculateItemTotals } from "@/lib/calc";
|
||||
import { useUrlId } from "@/lib/hooks/useUrlId";
|
||||
import { Badge, Button, Form, Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui";
|
||||
import { CurrencyData, IUpdateQuote_Request_DTO, Language, MoneyValue } from "@shared/contexts";
|
||||
import { CurrencyData, IGetQuote_Response_DTO, Language, MoneyValue } from "@shared/contexts";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { QuoteDetailsCardEditor, QuoteGeneralCardEditor } from "./components/editors";
|
||||
import { useQuotes } from "./hooks";
|
||||
|
||||
interface QuoteDataForm extends IUpdateQuote_Request_DTO {}
|
||||
/*type QuoteDataForm = Omit<IGetQuote_Response_DTO, "items"> & {
|
||||
items: IGetQuote_QuoteItem_Response_DTO;
|
||||
};*/
|
||||
|
||||
type QuoteDataForm = IGetQuote_Response_DTO;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const QuoteEdit = () => {
|
||||
@ -42,12 +46,8 @@ export const QuoteEdit = () => {
|
||||
|
||||
const { data, status, error: queryError } = useOne(quoteId);
|
||||
|
||||
const { mutate } = useUpdate(String(quoteId));
|
||||
|
||||
const form = useForm<QuoteDataForm>({
|
||||
mode: "onBlur",
|
||||
values: data,
|
||||
defaultValues: {
|
||||
const defaultValues = useMemo(
|
||||
() => ({
|
||||
date: "",
|
||||
reference: "",
|
||||
customer_information: "",
|
||||
@ -59,7 +59,7 @@ export const QuoteEdit = () => {
|
||||
subtotal_price: {
|
||||
amount: undefined,
|
||||
scale: 2,
|
||||
currency_code: data?.currency_code,
|
||||
currency_code: data?.currency_code ?? quoteCurrency.code,
|
||||
},
|
||||
discount: {
|
||||
amount: undefined,
|
||||
@ -68,32 +68,46 @@ export const QuoteEdit = () => {
|
||||
total_price: {
|
||||
amount: undefined,
|
||||
scale: 2,
|
||||
currency_code: data?.currency_code,
|
||||
currency_code: data?.currency_code ?? quoteCurrency.code,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
description: "",
|
||||
quantity: {
|
||||
amount: undefined,
|
||||
amount: 1,
|
||||
scale: 0,
|
||||
},
|
||||
subtotal_price: {
|
||||
amount: undefined,
|
||||
unit_price: {
|
||||
amount: null,
|
||||
scale: 4,
|
||||
currency_code: data?.currency_code,
|
||||
currency_code: data?.currency_code ?? quoteCurrency.code,
|
||||
},
|
||||
subtotal_price: {
|
||||
amount: null,
|
||||
scale: 4,
|
||||
currency_code: data?.currency_code ?? quoteCurrency.code,
|
||||
},
|
||||
discount: {
|
||||
amount: undefined,
|
||||
amount: null,
|
||||
scale: 2,
|
||||
},
|
||||
total_price: {
|
||||
amount: undefined,
|
||||
amount: null,
|
||||
scale: 4,
|
||||
currency_code: data?.currency_code,
|
||||
currency_code: data?.currency_code ?? quoteCurrency.code,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[data, quoteCurrency]
|
||||
);
|
||||
|
||||
const { mutate } = useUpdate(String(quoteId));
|
||||
|
||||
const form = useForm<QuoteDataForm>({
|
||||
mode: "onBlur",
|
||||
values: data,
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { watch, getValues, setValue, formState } = form;
|
||||
@ -117,6 +131,7 @@ export const QuoteEdit = () => {
|
||||
mutate(data, {
|
||||
onError: (error) => {
|
||||
console.debug(error);
|
||||
toast.error(error.message);
|
||||
//alert(error.message);
|
||||
},
|
||||
//onSettled: () => {},
|
||||
@ -132,9 +147,6 @@ export const QuoteEdit = () => {
|
||||
const { unsubscribe } = watch((_, { name, type }) => {
|
||||
const value = getValues();
|
||||
|
||||
console.log("USEEFFECT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||
console.log(name);
|
||||
|
||||
if (name) {
|
||||
if (name === "currency_code") {
|
||||
setQuoteCurrency(
|
||||
@ -241,7 +253,11 @@ export const QuoteEdit = () => {
|
||||
<QuoteGeneralCardEditor />
|
||||
</TabsContent>
|
||||
<TabsContent value='items'>
|
||||
<QuoteDetailsCardEditor currency={quoteCurrency} language={quoteLanguage} />
|
||||
<QuoteDetailsCardEditor
|
||||
currency={quoteCurrency}
|
||||
language={quoteLanguage}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='history'></TabsContent>
|
||||
|
||||
@ -110,6 +110,9 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
|
||||
)}
|
||||
<FormControl>
|
||||
<CurrencyInput
|
||||
intlConfig={{
|
||||
locale: language.code,
|
||||
}}
|
||||
name={field.name}
|
||||
//ref={field.ref} <-- no activar que hace cosas raras
|
||||
onBlur={field.onBlur}
|
||||
|
||||
@ -48,7 +48,7 @@ import { SortableTableRow } from "./SortableTableRow";
|
||||
declare module "@tanstack/react-table" {
|
||||
interface TableMeta<TData extends RowData> {
|
||||
insertItem: (rowIndex: number, data: TData) => void;
|
||||
appendItem: (data: TData) => void;
|
||||
appendItem: (data?: TData) => void;
|
||||
duplicateItems: (rowIndex?: number) => void;
|
||||
deleteItems: (rowIndex?: number | number[]) => void;
|
||||
updateItem: (rowIndex: number, rowData: TData, fieldName: string, value: unknown) => void;
|
||||
|
||||
@ -11,5 +11,5 @@ export * from "./Layout";
|
||||
export * from "./LoadingIndicator";
|
||||
export * from "./LoadingOverlay";
|
||||
export * from "./ProtectedRoute";
|
||||
export * from "./SorteableDataTable";
|
||||
//export * from "./SorteableDataTable";
|
||||
export * from "./TailwindIndicator";
|
||||
|
||||
@ -1,24 +1,13 @@
|
||||
import { MoneyValue, Percentage, Quantity } from "@shared/contexts";
|
||||
import { IMoney, IPercentage, IQuantity } from "./types";
|
||||
|
||||
export const calculateItemTotals = (item: {
|
||||
quantity?: IQuantity;
|
||||
unit_price?: IMoney;
|
||||
discount?: IPercentage;
|
||||
}): {
|
||||
quantity: Quantity;
|
||||
unitPrice: MoneyValue;
|
||||
subtotalPrice: MoneyValue;
|
||||
discount: Percentage;
|
||||
totalPrice: MoneyValue;
|
||||
} | null => {
|
||||
export const calculateItemTotals = (item: any) => {
|
||||
console.log(item);
|
||||
|
||||
const { quantity: quantity_dto, unit_price: unit_price_dto, discount: discount_dto } = item;
|
||||
|
||||
if (quantity_dto === "" || unit_price_dto === "") {
|
||||
/*if (quantity_dto.amount === null || unit_price_dto.amount === null) {
|
||||
return null;
|
||||
}
|
||||
}*/
|
||||
|
||||
const quantityOrError = Quantity.create(quantity_dto);
|
||||
if (quantityOrError.isFailure) {
|
||||
|
||||
@ -7,33 +7,29 @@ import {
|
||||
import { NullOr } from "@shared/utilities";
|
||||
import Joi from "joi";
|
||||
|
||||
export interface IArticleIdentifierOptions
|
||||
extends INullableValueObjectOptions {}
|
||||
export interface IArticleIdentifierOptions extends INullableValueObjectOptions {}
|
||||
|
||||
export class ArticleIdentifier extends NullableValueObject<number> {
|
||||
protected static validate(
|
||||
value: NullOr<number | string>,
|
||||
options: IArticleIdentifierOptions = {},
|
||||
options: IArticleIdentifierOptions = {}
|
||||
) {
|
||||
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(null);
|
||||
|
||||
const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
|
||||
options.label ? options.label : "ArticleIdentifier",
|
||||
options.label ? options.label : "ArticleIdentifier"
|
||||
);
|
||||
|
||||
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(
|
||||
/^[-]?\d+$/,
|
||||
).label(options.label ? options.label : "ArticleIdentifier");
|
||||
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
|
||||
options.label ? options.label : "ArticleIdentifier"
|
||||
);
|
||||
|
||||
const rules = Joi.alternatives(ruleNull, ruleNumber, ruleString);
|
||||
|
||||
return RuleValidator.validate<NullOr<number>>(rules, value);
|
||||
}
|
||||
|
||||
public static create(
|
||||
value: NullOr<number | string>,
|
||||
options: IArticleIdentifierOptions = {},
|
||||
) {
|
||||
public static create(value: NullOr<number | string>, options: IArticleIdentifierOptions = {}) {
|
||||
const _options = {
|
||||
label: "ArticleIdentifier",
|
||||
...options,
|
||||
@ -64,8 +60,8 @@ export class ArticleIdentifier extends NullableValueObject<number> {
|
||||
return this.isNull() ? "" : String(this.value);
|
||||
}
|
||||
|
||||
public toPrimitive(): number {
|
||||
return this.toNumber();
|
||||
public toPrimitive(): number | null {
|
||||
return this.isNull() ? null : this.toNumber();
|
||||
}
|
||||
|
||||
public increment(amount: number = 1) {
|
||||
|
||||
@ -190,7 +190,10 @@ export class CreateQuoteUseCase
|
||||
QuoteItem.create({
|
||||
articleId: item.article_id,
|
||||
description: Description.create(item.description).object,
|
||||
quantity: Quantity.create(item.quantity).object,
|
||||
quantity: Quantity.create({
|
||||
amount: item.quantity.amount,
|
||||
scale: item.quantity.scale,
|
||||
}).object,
|
||||
unitPrice: UnitPrice.create({
|
||||
amount: item.unit_price?.amount,
|
||||
currencyCode: item.unit_price?.currency_code,
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
UnitPrice,
|
||||
} from "@shared/contexts";
|
||||
|
||||
import { ArticleIdentifier } from "@/contexts/catalog/domain";
|
||||
import { IUpdateQuote_Request_DTO } from "@shared/contexts";
|
||||
import {
|
||||
Dealer,
|
||||
@ -183,28 +184,73 @@ export class UpdateQuoteUseCase
|
||||
return Result.fail(discountOrError.error);
|
||||
}
|
||||
|
||||
const items = new Collection<QuoteItem>(
|
||||
quoteDTO.items?.map(
|
||||
(item) =>
|
||||
QuoteItem.create({
|
||||
articleId: item.article_id,
|
||||
description: Description.create(item.description).object,
|
||||
quantity: Quantity.create({
|
||||
amount: item.quantity.amount,
|
||||
scale: item.quantity.scale,
|
||||
}).object,
|
||||
unitPrice: UnitPrice.create({
|
||||
amount: item.unit_price?.amount,
|
||||
currencyCode: item.unit_price?.currency_code,
|
||||
scale: item.unit_price?.scale,
|
||||
}).object,
|
||||
discount: Percentage.create({
|
||||
amount: item.discount?.amount,
|
||||
scale: item.discount?.scale,
|
||||
}).object,
|
||||
}).object
|
||||
)
|
||||
);
|
||||
let items: Collection<QuoteItem>;
|
||||
|
||||
try {
|
||||
items = new Collection<QuoteItem>(
|
||||
quoteDTO.items?.map((item) => {
|
||||
const articleIdOrError = ArticleIdentifier.create(item.article_id);
|
||||
if (articleIdOrError.isFailure) {
|
||||
throw articleIdOrError.error;
|
||||
}
|
||||
|
||||
const descriptionOrError = Description.create(item.description);
|
||||
if (descriptionOrError.isFailure) {
|
||||
throw descriptionOrError.error;
|
||||
}
|
||||
|
||||
const quantityOrError = Quantity.create({
|
||||
amount: item.quantity.amount,
|
||||
scale: item.quantity.scale,
|
||||
});
|
||||
if (quantityOrError.isFailure) {
|
||||
throw quantityOrError.error;
|
||||
}
|
||||
|
||||
const unitPriceOrError = UnitPrice.create({
|
||||
amount: item.unit_price?.amount,
|
||||
currencyCode: item.unit_price?.currency_code,
|
||||
scale: item.unit_price?.scale,
|
||||
});
|
||||
if (unitPriceOrError.isFailure) {
|
||||
throw unitPriceOrError.error;
|
||||
}
|
||||
|
||||
const percentageOrError = Percentage.create({
|
||||
amount: item.discount?.amount,
|
||||
scale: item.discount?.scale,
|
||||
});
|
||||
if (percentageOrError.isFailure) {
|
||||
throw percentageOrError.error;
|
||||
}
|
||||
|
||||
const quoteItemOrError = QuoteItem.create({
|
||||
articleId: articleIdOrError.object,
|
||||
description: descriptionOrError.object,
|
||||
quantity: quantityOrError.object,
|
||||
unitPrice: unitPriceOrError.object,
|
||||
discount: percentageOrError.object,
|
||||
});
|
||||
|
||||
if (quoteItemOrError.isFailure) {
|
||||
throw quoteItemOrError.error;
|
||||
}
|
||||
|
||||
return quoteItemOrError.object;
|
||||
})
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
//let error = e as Error;
|
||||
/*if (error.name === "ValidationError") {
|
||||
error = e as ValidationError;
|
||||
}
|
||||
|
||||
if (error.name === "DomainError") {
|
||||
error = e as DomainError;
|
||||
}*/
|
||||
|
||||
return Result.fail(e as IDomainError);
|
||||
}
|
||||
|
||||
return Quote.create(
|
||||
{
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ArticleIdentifier } from "@/contexts/catalog/domain";
|
||||
import {
|
||||
Description,
|
||||
Entity,
|
||||
@ -11,7 +12,7 @@ import {
|
||||
} from "@shared/contexts";
|
||||
|
||||
export interface IQuoteItemProps extends IEntityProps {
|
||||
articleId: string | null;
|
||||
articleId: ArticleIdentifier;
|
||||
description: Description; // Descripción del artículo o servicio
|
||||
quantity: Quantity; // Cantidad de unidades
|
||||
unitPrice: MoneyValue; // Precio unitario en la moneda de la factura
|
||||
@ -21,7 +22,7 @@ export interface IQuoteItemProps extends IEntityProps {
|
||||
}
|
||||
|
||||
export interface IQuoteItem {
|
||||
articleId: string | null;
|
||||
articleId: ArticleIdentifier;
|
||||
description: Description;
|
||||
quantity: Quantity;
|
||||
unitPrice: MoneyValue;
|
||||
@ -35,7 +36,7 @@ export class QuoteItem extends Entity<IQuoteItemProps> implements IQuoteItem {
|
||||
return Result.ok(new QuoteItem(props, id));
|
||||
}
|
||||
|
||||
get articleId(): string | null {
|
||||
get articleId(): ArticleIdentifier {
|
||||
return this.props.articleId;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ArticleIdentifier } from "@/contexts/catalog/domain";
|
||||
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
|
||||
import { Description, MoneyValue, Percentage, Quantity, UniqueID } from "@shared/contexts";
|
||||
import { IQuoteItemProps, Quote, QuoteItem } from "../../domain";
|
||||
@ -22,8 +23,10 @@ class QuoteItemMapper
|
||||
const { sourceParent } = params;
|
||||
const id = this.mapsValue(source, "item_id", UniqueID.create);
|
||||
|
||||
const articleId = this.mapsValue(source, "id_article", ArticleIdentifier.create);
|
||||
|
||||
const props: IQuoteItemProps = {
|
||||
articleId: source.id_article === "" ? null : source.id_article,
|
||||
articleId,
|
||||
description: this.mapsValue(source, "description", Description.create),
|
||||
quantity: this.mapsValue(source, "quantity", (quantity) =>
|
||||
Quantity.create({
|
||||
@ -83,7 +86,7 @@ class QuoteItemMapper
|
||||
item_id: source.id.toString(),
|
||||
quote_id: sourceParent.id.toPrimitive(),
|
||||
position: index,
|
||||
id_article: source.articleId,
|
||||
id_article: source.articleId.toPrimitive(),
|
||||
description: source.description.toPrimitive(),
|
||||
quantity: source.quantity.convertScale(2).toPrimitive(),
|
||||
unit_price: source.unitPrice.convertScale(4).toPrimitive(),
|
||||
|
||||
@ -30,7 +30,7 @@ export class QuoteItem_Model extends Model<
|
||||
|
||||
declare quote_id: string;
|
||||
declare item_id: string;
|
||||
declare id_article: CreationOptional<string | null>;
|
||||
declare id_article: CreationOptional<number | null>;
|
||||
declare position: number;
|
||||
declare description: CreationOptional<string | null>;
|
||||
declare quantity: CreationOptional<number | null>;
|
||||
|
||||
@ -2,7 +2,7 @@ import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../domain";
|
||||
|
||||
export interface IMoney_DTO {
|
||||
amount: number;
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
currency_code: string;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export interface IPercentage_DTO {
|
||||
amount: number;
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../domain";
|
||||
|
||||
export interface IQuantity_Request_DTO {
|
||||
amount: number;
|
||||
export interface IQuantity_DTO {
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
@ -21,4 +21,5 @@ export function ensureQuantity_DTOIsValid(quantity: IQuantity_Request_DTO) {
|
||||
return Result.ok(true);
|
||||
}
|
||||
|
||||
export interface IQuantity_Response_DTO extends IQuantity_Request_DTO {}
|
||||
export interface IQuantity_Request_DTO extends IQuantity_DTO {}
|
||||
export interface IQuantity_Response_DTO extends IQuantity_DTO {}
|
||||
|
||||
@ -1,29 +1,23 @@
|
||||
import Joi from "joi";
|
||||
import { UndefinedOr } from "../../../../utilities";
|
||||
import { DomainError, handleDomainError } from "../errors";
|
||||
import { RuleValidator } from "../RuleValidator";
|
||||
import { Result } from "./Result";
|
||||
import {
|
||||
IStringValueObjectOptions,
|
||||
StringValueObject,
|
||||
} from "./StringValueObject";
|
||||
import { IStringValueObjectOptions, StringValueObject } from "./StringValueObject";
|
||||
|
||||
export class Description extends StringValueObject {
|
||||
protected static validate(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions
|
||||
) {
|
||||
protected static validate(value: UndefinedOr<string>, options: IStringValueObjectOptions) {
|
||||
const ruleIsEmpty = Joi.string()
|
||||
.optional()
|
||||
.allow(null)
|
||||
.allow("")
|
||||
.default("")
|
||||
.label(String(options.label));
|
||||
|
||||
return RuleValidator.validate<string>(ruleIsEmpty, value);
|
||||
}
|
||||
|
||||
public static create(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions = {}
|
||||
) {
|
||||
public static create(value: UndefinedOr<string>, options: IStringValueObjectOptions = {}) {
|
||||
const _options = {
|
||||
label: "description",
|
||||
...options,
|
||||
@ -32,7 +26,9 @@ export class Description extends StringValueObject {
|
||||
const validationResult = Description.validate(value, _options);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
return Result.fail(
|
||||
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(new Description(validationResult.object));
|
||||
|
||||
@ -1,27 +1,17 @@
|
||||
import { UndefinedOr } from "../../../../utilities";
|
||||
import { DomainError, handleDomainError } from "../errors";
|
||||
import { RuleValidator } from "../RuleValidator";
|
||||
import { Result } from "./Result";
|
||||
import {
|
||||
IStringValueObjectOptions,
|
||||
StringValueObject,
|
||||
} from "./StringValueObject";
|
||||
import { IStringValueObjectOptions, StringValueObject } from "./StringValueObject";
|
||||
|
||||
export class Measure extends StringValueObject {
|
||||
protected static validate(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions,
|
||||
) {
|
||||
const ruleIsEmpty = RuleValidator.RULE_ALLOW_EMPTY.default("").label(
|
||||
String(options.label),
|
||||
);
|
||||
protected static validate(value: UndefinedOr<string>, options: IStringValueObjectOptions) {
|
||||
const ruleIsEmpty = RuleValidator.RULE_ALLOW_EMPTY.default("").label(String(options.label));
|
||||
|
||||
return RuleValidator.validate<string>(ruleIsEmpty, value);
|
||||
}
|
||||
|
||||
public static create(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions = {},
|
||||
) {
|
||||
public static create(value: UndefinedOr<string>, options: IStringValueObjectOptions = {}) {
|
||||
const _options = {
|
||||
label: "description",
|
||||
...options,
|
||||
@ -30,7 +20,9 @@ export class Measure extends StringValueObject {
|
||||
const validationResult = Measure.validate(value, _options);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
return Result.fail(
|
||||
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok(new Measure(validationResult.object));
|
||||
|
||||
@ -4,6 +4,7 @@ import DineroFactory, { Currency, Dinero } from "dinero.js";
|
||||
import Joi from "joi";
|
||||
import { isNull } from "lodash";
|
||||
import { NullOr, UndefinedOr } from "../../../../utilities";
|
||||
import { DomainError, handleDomainError } from "../errors";
|
||||
import { RuleValidator } from "../RuleValidator";
|
||||
import { CurrencyData } from "./CurrencyData";
|
||||
import { Result } from "./Result";
|
||||
@ -144,7 +145,9 @@ export class MoneyValue extends ValueObject<Dinero> implements IMoneyValue {
|
||||
const validationResult = MoneyValue.validate(amount, options);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
return Result.fail(
|
||||
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, options)
|
||||
);
|
||||
}
|
||||
|
||||
const _amount: NullOr<number> = MoneyValue.sanitize(validationResult.object);
|
||||
|
||||
@ -218,7 +218,7 @@ export class Percentage extends NullableValueObject<IPercentage> {
|
||||
}
|
||||
|
||||
get scale(): number {
|
||||
return this.isNull() ? 0 : Number(this.props?.scale);
|
||||
return Number(this.props?.scale);
|
||||
}
|
||||
|
||||
public getAmount(): NullOr<number> {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import Joi from "joi";
|
||||
import { NullOr } from "../../../../utilities";
|
||||
import { DomainError, handleDomainError } from "../errors";
|
||||
import { RuleValidator } from "../RuleValidator";
|
||||
import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject";
|
||||
import { Result } from "./Result";
|
||||
@ -98,7 +99,9 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
||||
const validationResult = Quantity.validate(amount, scale, _options);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
return Result.fail(
|
||||
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
|
||||
);
|
||||
}
|
||||
|
||||
let _amount: NullOr<number> = Quantity._sanitize(amount);
|
||||
|
||||
@ -22,14 +22,8 @@ export class FieldCriteria extends StringValueObject implements IFieldCriteria {
|
||||
}
|
||||
|
||||
protected static validate(value: UndefinedOr<string>) {
|
||||
if (
|
||||
RuleValidator.validate(RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, value)
|
||||
.isSuccess
|
||||
) {
|
||||
const stringOrError = RuleValidator.validate(
|
||||
RuleValidator.RULE_IS_TYPE_STRING,
|
||||
value
|
||||
);
|
||||
if (RuleValidator.validate(RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, value).isSuccess) {
|
||||
const stringOrError = RuleValidator.validate(RuleValidator.RULE_IS_TYPE_STRING, value);
|
||||
|
||||
if (stringOrError.isFailure) {
|
||||
return stringOrError;
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
import Joi from "joi";
|
||||
import { UndefinedOr } from "../../../../utilities";
|
||||
|
||||
import { DomainError, handleDomainError } from "../errors";
|
||||
import { RuleValidator } from "../RuleValidator";
|
||||
import { Result } from "./Result";
|
||||
import {
|
||||
IStringValueObjectOptions,
|
||||
StringValueObject,
|
||||
} from "./StringValueObject";
|
||||
import { IStringValueObjectOptions, StringValueObject } from "./StringValueObject";
|
||||
|
||||
export class Slug extends StringValueObject {
|
||||
protected static readonly MIN_LENGTH = 2;
|
||||
protected static readonly MAX_LENGTH = 100;
|
||||
|
||||
protected static validate(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions
|
||||
) {
|
||||
protected static validate(value: UndefinedOr<string>, options: IStringValueObjectOptions) {
|
||||
const rule = Joi.string()
|
||||
.allow(null)
|
||||
.allow("")
|
||||
@ -66,10 +61,7 @@ export class Slug extends StringValueObject {
|
||||
return slug ? slug.trim() : "";
|
||||
}
|
||||
|
||||
public static create(
|
||||
value: UndefinedOr<string>,
|
||||
options: IStringValueObjectOptions = {}
|
||||
) {
|
||||
public static create(value: UndefinedOr<string>, options: IStringValueObjectOptions = {}) {
|
||||
const _options = {
|
||||
label: "slug",
|
||||
...options,
|
||||
@ -78,7 +70,9 @@ export class Slug extends StringValueObject {
|
||||
const validationResult = Slug.validate(value, _options);
|
||||
|
||||
if (validationResult.isFailure) {
|
||||
return Result.fail(validationResult.error);
|
||||
return Result.fail(
|
||||
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
|
||||
);
|
||||
}
|
||||
|
||||
const slugValue = Slug.sanitize(validationResult.object);
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
IMoney_Response_DTO,
|
||||
IPercentage_Response_DTO,
|
||||
IQuantity_Response_DTO,
|
||||
} from "../../../../../common";
|
||||
import { IMoney_DTO, IPercentage_DTO, IQuantity_DTO } from "../../../../../common";
|
||||
|
||||
export interface IGetQuote_Response_DTO {
|
||||
id: string;
|
||||
@ -17,9 +13,9 @@ export interface IGetQuote_Response_DTO {
|
||||
notes: string;
|
||||
validity: string;
|
||||
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
subtotal_price: IMoney_DTO;
|
||||
discount: IPercentage_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
items: IGetQuote_QuoteItem_Response_DTO[];
|
||||
|
||||
@ -28,10 +24,10 @@ export interface IGetQuote_Response_DTO {
|
||||
|
||||
export interface IGetQuote_QuoteItem_Response_DTO {
|
||||
article_id: string;
|
||||
quantity: IQuantity_Response_DTO;
|
||||
quantity: IQuantity_DTO;
|
||||
description: string;
|
||||
unit_price: IMoney_Response_DTO;
|
||||
subtotal_price: IMoney_Response_DTO;
|
||||
discount: IPercentage_Response_DTO;
|
||||
total_price: IMoney_Response_DTO;
|
||||
unit_price: IMoney_DTO;
|
||||
subtotal_price: IMoney_DTO;
|
||||
discount: IPercentage_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
}
|
||||
|
||||
@ -50,49 +50,49 @@ export function ensureUpdateQuote_Request_DTOIsValid(quoteDTO: IUpdateQuote_Requ
|
||||
validity: Joi.string().optional().allow(null).allow("").default(""),
|
||||
|
||||
subtotal_price: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
currency_code: Joi.string(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
|
||||
discount: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
|
||||
total_price: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
currency_code: Joi.string(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
|
||||
items: Joi.array().items(
|
||||
Joi.object({
|
||||
article_id: Joi.string().optional().allow(null).allow("").default(""),
|
||||
article_id: Joi.number().optional().allow(null).allow("").default(""),
|
||||
quantity: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
}).unknown(),
|
||||
description: Joi.string(),
|
||||
}).optional(),
|
||||
description: Joi.string().optional().allow(null).allow("").default(""),
|
||||
unit_price: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
currency_code: Joi.string(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
subtotal_price: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
currency_code: Joi.string(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
discount: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
total_price: Joi.object({
|
||||
amount: Joi.number(),
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
currency_code: Joi.string(),
|
||||
}).unknown(),
|
||||
}).optional(),
|
||||
}).unknown(true)
|
||||
),
|
||||
}).unknown(true);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user