From e759da9916ab38ddcf0953b74daa698886e8b7c6 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 4 Apr 2026 18:58:32 +0200 Subject: [PATCH] Repaso a customers --- .../src/web/_archived/hooks/index.ts | 4 - .../hooks/use-create-customer-mutation.ts | 74 ------------ .../web/_archived/hooks/use-customer-query.ts | 55 --------- .../_archived/hooks/use-customers-context.tsx | 12 -- .../hooks/use-delete-customer-mutation.ts | 57 ---------- .../hooks/use-update-customer-mutation.ts | 65 ----------- .../pages/create/customer-create-page.tsx | 80 ------------- .../src/web/_archived/pages/create/index.ts | 1 - .../create/use-customer-create-controller.ts | 107 ------------------ .../src/web/_archived/pages/create/utils.ts | 41 ------- .../use-customer-create.controller.ts | 13 +-- .../entities/customer-create-form.schema.ts | 34 +++--- .../customer-create-payload.entity.ts | 23 ---- .../src/web/create/entities/index.ts | 1 - modules/customers/src/web/create/index.ts | 1 + .../customer-additional-config-fields.tsx | 0 .../editor/customer-address-fields.tsx | 0 .../editor/customer-basic-info-fields.tsx | 0 .../editor/customer-contact-fields.tsx | 0 .../customer-create-editor-form.tsx} | 8 +- .../editor/customer-taxes-multi-select.tsx | 0 .../src/web/create/ui/editor/index.ts | 1 + .../src/web/create/ui/forms/index.ts | 0 modules/customers/src/web/create/ui/index.ts | 1 + .../create/ui/pages/customer-create-page.tsx | 5 +- .../src/web/create/ui/pages/editor/index.ts | 1 - .../utils/build-customer-create-params.ts | 58 ++++++++++ .../utils/build-customer-create-payload.ts | 29 ----- .../customers/src/web/create/utils/index.ts | 2 +- modules/customers/src/web/customer-routes.tsx | 7 +- .../use-list-customers.controller.ts | 4 +- .../customer-to-list-row-patch.adapter.ts | 12 ++ .../adapters/get-customer-by-id.adapter.ts | 17 ++- .../shared/adapters/list-customers.adapter.ts | 29 ++++- .../src/web/shared/api/create-customer.api.ts | 22 ++-- .../shared/api/delete-customer-by-id.api.ts | 29 ++++- .../web/shared/api/get-customer-by-id.api.ts | 26 ++++- .../api/get-list-customers-by-criteria.api.ts | 33 ++++++ modules/customers/src/web/shared/api/index.ts | 2 +- .../src/web/shared/api/list-customers.api.ts | 17 --- .../shared/api/update-customer-by-id.api.ts | 30 ++++- .../entities/customer-list-row.entity.ts | 5 + .../shared/entities/customer-list.entity.ts | 5 + .../web/shared/entities/customer.entity.ts | 6 + .../shared/hooks/customer-cache-strategy.ts | 6 + .../customers/src/web/shared/hooks/index.ts | 2 +- .../web/shared/hooks/to-validation-errors.ts | 7 +- .../hooks/use-customer-create-mutation.ts | 9 +- .../hooks/use-customer-delete-mutation.ts | 24 ++-- .../shared/hooks/use-customer-get-query.ts | 8 +- .../hooks/use-customer-update-mutation.ts | 18 +-- ...s-query.ts => use-customers-list-query.ts} | 14 ++- ...ustomer-to-customer-update-form.adapter.ts | 2 - .../use-customer-update.controller.ts | 20 ++-- .../entities/customer-update-form.entity.ts | 2 +- .../entities/customer-update-form.schema.ts | 34 +++--- .../entities/customer-update-patch.entity.ts | 9 +- .../update/ui/editor/customer-edit-form.tsx | 8 +- .../update/ui/pages/customer-update-page.tsx | 4 +- .../utils/build-customer.update-patch.ts | 20 ++++ ... => build-update-customer-by-id-params.ts} | 27 +++-- .../customers/src/web/update/utils/index.ts | 3 +- 62 files changed, 430 insertions(+), 704 deletions(-) delete mode 100644 modules/customers/src/web/_archived/hooks/index.ts delete mode 100644 modules/customers/src/web/_archived/hooks/use-create-customer-mutation.ts delete mode 100644 modules/customers/src/web/_archived/hooks/use-customer-query.ts delete mode 100644 modules/customers/src/web/_archived/hooks/use-customers-context.tsx delete mode 100644 modules/customers/src/web/_archived/hooks/use-delete-customer-mutation.ts delete mode 100644 modules/customers/src/web/_archived/hooks/use-update-customer-mutation.ts delete mode 100644 modules/customers/src/web/_archived/pages/create/customer-create-page.tsx delete mode 100644 modules/customers/src/web/_archived/pages/create/index.ts delete mode 100644 modules/customers/src/web/_archived/pages/create/use-customer-create-controller.ts delete mode 100644 modules/customers/src/web/_archived/pages/create/utils.ts delete mode 100644 modules/customers/src/web/create/entities/customer-create-payload.entity.ts create mode 100644 modules/customers/src/web/create/index.ts rename modules/customers/src/web/create/ui/{pages => }/editor/customer-additional-config-fields.tsx (100%) rename modules/customers/src/web/create/ui/{pages => }/editor/customer-address-fields.tsx (100%) rename modules/customers/src/web/create/ui/{pages => }/editor/customer-basic-info-fields.tsx (100%) rename modules/customers/src/web/create/ui/{pages => }/editor/customer-contact-fields.tsx (100%) rename modules/customers/src/web/create/ui/{pages/editor/customer-create-form.tsx => editor/customer-create-editor-form.tsx} (82%) rename modules/customers/src/web/create/ui/{pages => }/editor/customer-taxes-multi-select.tsx (100%) delete mode 100644 modules/customers/src/web/create/ui/forms/index.ts delete mode 100644 modules/customers/src/web/create/ui/pages/editor/index.ts create mode 100644 modules/customers/src/web/create/utils/build-customer-create-params.ts delete mode 100644 modules/customers/src/web/create/utils/build-customer-create-payload.ts create mode 100644 modules/customers/src/web/shared/api/get-list-customers-by-criteria.api.ts delete mode 100644 modules/customers/src/web/shared/api/list-customers.api.ts rename modules/customers/src/web/shared/hooks/{use-list-customers-query.ts => use-customers-list-query.ts} (73%) create mode 100644 modules/customers/src/web/update/utils/build-customer.update-patch.ts rename modules/customers/src/web/update/utils/{build-customer-update-patch.ts => build-update-customer-by-id-params.ts} (55%) diff --git a/modules/customers/src/web/_archived/hooks/index.ts b/modules/customers/src/web/_archived/hooks/index.ts deleted file mode 100644 index 65f6a1d0..00000000 --- a/modules/customers/src/web/_archived/hooks/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -//export * from "./use-create-customer-mutation"; -//export * from "./use-customer-query"; -//export * from "./use-customers-context"; -//export * from "./use-update-customer-mutation"; diff --git a/modules/customers/src/web/_archived/hooks/use-create-customer-mutation.ts b/modules/customers/src/web/_archived/hooks/use-create-customer-mutation.ts deleted file mode 100644 index f149553b..00000000 --- a/modules/customers/src/web/_archived/hooks/use-create-customer-mutation.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useDataSource } from "@erp/core/hooks"; -import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd"; -import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; - -import { CreateCustomerRequestSchema } from "../../../common"; -import { toValidationErrors } from "../../shared/hooks/to-validation-errors"; -import type { Customer, CustomerFormData } from "../schemas"; - -import { CUSTOMERS_LIST_KEY, invalidateCustomerListCache } from "./use-customer-list-query"; -import { setCustomerDetailCache } from "./use-customer-query"; - -export const CUSTOMER_CREATE_KEY = ["customers", "create"] as const; - -type CreateCustomerPayload = { - data: CustomerFormData; -}; - -export function useCreateCustomer() { - const queryClient = useQueryClient(); - const dataSource = useDataSource(); - const schema = CreateCustomerRequestSchema; - - return useMutation({ - mutationKey: CUSTOMER_CREATE_KEY, - - mutationFn: async ({ data }, context) => { - const id = UniqueID.generateNewID().toString(); - const payload = { ...data, id }; - - const result = schema.safeParse(payload); - - if (!result.success) { - throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error)); - } - - const created = await dataSource.createOne("customers", payload); - return created as Customer; - }, - - onSuccess: (created: Customer, variables) => { - const { id: customerId } = created; - - // Invalida el listado para refrescar desde servidor - invalidateCustomerListCache(queryClient); - - // Sincroniza detalle - setCustomerDetailCache(queryClient, customerId, created); - }, - - onSettled: () => { - // Refresca todos los listados - invalidateCustomerListCache(queryClient); - }, - - onMutate: async ({ data }, context) => { - // Cancelar queries del listado para evitar overwrite - await queryClient.cancelQueries({ queryKey: [CUSTOMERS_LIST_KEY] }); - - const optimisticId = UniqueID.generateNewID().toString(); - const optimisticCustomer: Customer = { ...data, id: optimisticId } as Customer; - - // Snapshot previo - const previous = queryClient.getQueryData([CUSTOMERS_LIST_KEY]); - - // Optimista: prepend - queryClient.setQueryData([CUSTOMERS_LIST_KEY], (old) => [ - optimisticCustomer, - ...(old ?? []), - ]); - - return { previous, optimisticId }; - }, - }); -} diff --git a/modules/customers/src/web/_archived/hooks/use-customer-query.ts b/modules/customers/src/web/_archived/hooks/use-customer-query.ts deleted file mode 100644 index 8c482fb2..00000000 --- a/modules/customers/src/web/_archived/hooks/use-customer-query.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { useDataSource } from "@erp/core/hooks"; -import { - type DefaultError, - type QueryClient, - type QueryKey, - useQuery, -} from "@tanstack/react-query"; - -import type { Customer } from "../schemas"; - -export const CUSTOMER_DETAIL_SCOPE = "customers:detail" as const; - -export const getCustomerQueryKey = (id: string) => - [CUSTOMER_DETAIL_SCOPE, { id }] satisfies QueryKey; - -type CustomerQueryOptions = { - enabled?: boolean; - staleTime?: number; - select?: (data: Customer) => TSelected; - placeholderData?: Customer; -}; - -export function useCustomerQuery( - customerId?: string, - options?: CustomerQueryOptions -) { - const dataSource = useDataSource(); - const enabled = (options?.enabled ?? true) && Boolean(customerId); - - return useQuery({ - queryKey: getCustomerQueryKey(customerId ?? "unknown"), - queryFn: async (context) => { - const { signal } = context; - if (!customerId) { - if (!customerId) throw new Error("customerId is required"); - } - return await dataSource.getOne("customers", customerId, { signal }); - }, - enabled, - staleTime: options?.staleTime ?? 60_000, // 1 min por defecto - select: options?.select, - placeholderData: options?.placeholderData, - }); -} - -export function invalidateCustomerDetailCache(qc: QueryClient, id: string) { - return qc.invalidateQueries({ - queryKey: getCustomerQueryKey(id ?? "unknown"), - exact: Boolean(id), - }); -} - -export function setCustomerDetailCache(qc: QueryClient, id: string, data: unknown) { - qc.setQueryData(getCustomerQueryKey(id), data); -} diff --git a/modules/customers/src/web/_archived/hooks/use-customers-context.tsx b/modules/customers/src/web/_archived/hooks/use-customers-context.tsx deleted file mode 100644 index edc59120..00000000 --- a/modules/customers/src/web/_archived/hooks/use-customers-context.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from "react"; - -import { CustomersContext, type CustomersContextType } from "../context"; - -export const useCustomersContext = (): CustomersContextType => { - const context = useContext(CustomersContext); - if (!context) { - throw new Error("useCustomers must be used within a CustomersProvider"); - } - - return context; -}; diff --git a/modules/customers/src/web/_archived/hooks/use-delete-customer-mutation.ts b/modules/customers/src/web/_archived/hooks/use-delete-customer-mutation.ts deleted file mode 100644 index 2707729e..00000000 --- a/modules/customers/src/web/_archived/hooks/use-delete-customer-mutation.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useDataSource } from "@erp/core/hooks"; -import { - type DefaultError, - type QueryKey, - useMutation, - useQueryClient, -} from "@tanstack/react-query"; - -import type { CustomersPage } from "../schemas"; - -import { cancelCustomerListQueries, deleteCustomerIntoListCaches } from "./use-customer-list-query"; -import { invalidateCustomerDetailCache } from "./use-customer-query"; - -export const CUSTOMER_DELETE_KEY = ["customers", "delete"] as const; - -type DeleteCustomerPayload = { - id: string; -}; - -export function useDeleteCustomer() { - const queryClient = useQueryClient(); - const dataSource = useDataSource(); - - return useMutation<{ id: string }, DefaultError, DeleteCustomerPayload>({ - mutationKey: CUSTOMER_DELETE_KEY, - - mutationFn: async ({ id: customerId }) => { - if (!customerId) { - throw new Error("customerId is required"); - } - - await dataSource.deleteOne("customers", customerId); - return { id: customerId }; - }, - - onMutate: async ({ id }) => { - await cancelCustomerListQueries(queryClient); - return deleteCustomerIntoListCaches(queryClient, id); - }, - - onError: (_e, _v, ctx) => { - if (!ctx) return; - const { snapshots } = ctx as ReturnType; - for (const snap of snapshots as Array<{ key: QueryKey; page?: CustomersPage }>) { - if (snap.page) queryClient.setQueryData(snap.key, snap.page); - } - }, - - onSuccess: ({ id }) => { - invalidateCustomerDetailCache(queryClient, id); - }, - - onSettled: (data) => { - deleteCustomerIntoListCaches(queryClient, data?.id ?? "unknown"); - }, - }); -} diff --git a/modules/customers/src/web/_archived/hooks/use-update-customer-mutation.ts b/modules/customers/src/web/_archived/hooks/use-update-customer-mutation.ts deleted file mode 100644 index 08050c71..00000000 --- a/modules/customers/src/web/_archived/hooks/use-update-customer-mutation.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { useDataSource } from "@erp/core/hooks"; -import { ValidationErrorCollection } from "@repo/rdx-ddd"; -import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; - -import { UpdateCustomerByIdRequestSchema } from "../../../common"; -import { toValidationErrors } from "../../shared/hooks/to-validation-errors"; -import type { Customer, CustomerFormData } from "../schemas"; - -import { - invalidateCustomerListCache, - upsertCustomerIntoListCaches, -} from "./use-customer-list-query"; -import { setCustomerDetailCache } from "./use-customer-query"; - -export const CUSTOMER_UPDATE_KEY = ["customers", "update"] as const; - -type UpdateCustomerContext = {}; - -type UpdateCustomerPayload = { - id: string; - data: Partial; -}; - -export function useUpdateCustomer() { - const queryClient = useQueryClient(); - const dataSource = useDataSource(); - const schema = UpdateCustomerByIdRequestSchema; - - return useMutation({ - mutationKey: CUSTOMER_UPDATE_KEY, - - mutationFn: async (payload) => { - const { id: customerId, data } = payload; - if (!customerId) { - throw new Error("customerId is required"); - } - - const result = schema.safeParse(data); - if (!result.success) { - throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error)); - } - - const updated = await dataSource.updateOne("customers", customerId, data); - return updated as Customer; - }, - - onSuccess: (updated: Customer, variables) => { - const { id: customerId } = updated; - - // Invalida el listado para refrescar desde servidor - invalidateCustomerListCache(queryClient); - - // Actualiza detalle - setCustomerDetailCache(queryClient, customerId, updated); - - // Actualiza todas las páginas donde aparezca - upsertCustomerIntoListCaches(queryClient, { ...updated }); - }, - - onSettled: () => { - // Refresca todos los listados - invalidateCustomerListCache(queryClient); - }, - }); -} diff --git a/modules/customers/src/web/_archived/pages/create/customer-create-page.tsx b/modules/customers/src/web/_archived/pages/create/customer-create-page.tsx deleted file mode 100644 index 558398b7..00000000 --- a/modules/customers/src/web/_archived/pages/create/customer-create-page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { PageHeader } from "@erp/core/components"; -import { UnsavedChangesProvider, UpdateCommitButtonGroup } from "@erp/core/hooks"; -import { AppContent, AppHeader } from "@repo/rdx-ui/components"; -import { useNavigate } from "react-router-dom"; - -import { useTranslation } from "../../../i18n"; -import { CustomerEditForm, ErrorAlert } from "../../components"; - -import { useCustomerCreateController } from "./use-customer-create-controller"; - -export const CustomerCreatePage = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); - - const { - form, - formId, - onSubmit, - resetForm, - - isCreating, - isCreateError, - createError, - - FormProvider, - } = useCustomerCreateController({ - onCreated: (created) => - navigate("/customers/list", { - state: { customerId: created.id, isNew: true }, - replace: true, - }), - }); - - return ( - - - - } - title={t("pages.create.title")} - /> - - - {/* Alerta de error de actualización (si ha fallado el último intento) */} - {isCreateError && ( - - )} - - - - - - - ); -}; diff --git a/modules/customers/src/web/_archived/pages/create/index.ts b/modules/customers/src/web/_archived/pages/create/index.ts deleted file mode 100644 index 375deba4..00000000 --- a/modules/customers/src/web/_archived/pages/create/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./customer-create-page"; diff --git a/modules/customers/src/web/_archived/pages/create/use-customer-create-controller.ts b/modules/customers/src/web/_archived/pages/create/use-customer-create-controller.ts deleted file mode 100644 index ac53ac39..00000000 --- a/modules/customers/src/web/_archived/pages/create/use-customer-create-controller.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { useHookForm } from "@erp/core/hooks"; -import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers"; -import { useId } from "react"; -import { FormProvider } from "react-hook-form"; - -import { useTranslation } from "../../../i18n"; -import { useCreateCustomer } from "../../hooks"; -import { - type Customer, - type CustomerFormData, - CustomerFormSchema, - defaultCustomerFormData, -} from "../../schemas"; - -export interface UseCustomerCreateControllerOptions { - onCreated?(created: Customer): void; - successToasts?: boolean; // mostrar o no toast automáticcamente - - onError?(error: Error, data: CustomerFormData): void; - errorToasts?: boolean; // mostrar o no toast automáticcamente -} - -export const useCustomerCreateController = (options?: UseCustomerCreateControllerOptions) => { - const { t } = useTranslation(); - const formId = useId(); // id único por instancia - - // 1) Estado de creación (mutación) - const { - mutateAsync, - isPending: isCreating, - isError: isCreateError, - error: createError, - } = useCreateCustomer(); - - // 2) Form hook - const form = useHookForm({ - resolverSchema: CustomerFormSchema, - initialValues: defaultCustomerFormData, - disabled: isCreating, - }); - - /** Handlers */ - - const resetForm = () => form.reset(defaultCustomerFormData); - - // Versión sincronizada - const submitHandler = form.handleSubmit(async (formData) => { - try { - // Enviamos cambios al servidor - const created = await mutateAsync({ data: formData }); - - // Ha ido bien -> actualizamos form con datos reales - // keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render. - form.reset(created, { keepDirty: false }); - - console.log("form.formState =>", form.formState); - - if (options?.successToasts !== false) { - showSuccessToast( - t("pages.create.success.title", "Cliente creado"), - t("pages.create.success.message", "Se ha creado correctamente.") - ); - } - options?.onCreated?.(created); - } catch (err: unknown) { - console.log("No se pudo crear el cliente."); - - // 1) Enfoca el primer error - /*const firstKey = Object.keys(errors)[0] as keyof CustomerFormData | undefined; - if (firstKey) { - const el = document.querySelector(`[name="${String(firstKey)}"]`); - el?.focus(); - }*/ - - const error = err as Error; - - if (options?.errorToasts !== false) { - showErrorToast(t("pages.update.error.title"), error.message); - } - options?.onError?.(error, formData); - } - }); - - // Evento onSubmit ya preparado para el
- const onSubmit = (event: React.FormEvent) => { - event.stopPropagation(); // <-- evita que el submit se propage por los padre en el árbol DOM - submitHandler(event); - }; - - return { - // form - form, - formId, - - // handlers del form - onSubmit, - resetForm, - - // mutation - isCreating, - isCreateError, - createError, - - // Por comodidad - FormProvider, - }; -}; diff --git a/modules/customers/src/web/_archived/pages/create/utils.ts b/modules/customers/src/web/_archived/pages/create/utils.ts deleted file mode 100644 index ce8469c8..00000000 --- a/modules/customers/src/web/_archived/pages/create/utils.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { InvoiceItem } from "@/types/invoice"; - -export function calculateItemTotal(quantity: number, unitPrice: number, discount = 0): number { - const subtotal = quantity * unitPrice; - const discountAmount = (subtotal * discount) / 100; - return subtotal - discountAmount; -} - -export function calculateInvoiceTotals(items: InvoiceItem[], taxRate = 21) { - const subtotal = items.reduce((sum, item) => { - return ( - sum + (item.quantity.amount * item.unit_price.amount) / Math.pow(10, item.unit_price.scale) - ); - }, 0); - - const totalDiscount = items.reduce((sum, item) => { - const itemSubtotal = - (item.quantity.amount * item.unit_price.amount) / Math.pow(10, item.unit_price.scale); - return sum + (itemSubtotal * item.discount.amount) / Math.pow(10, item.discount.scale) / 100; - }, 0); - - const beforeTax = subtotal - totalDiscount; - const taxAmount = (beforeTax * taxRate) / 100; - const total = beforeTax + taxAmount; - - return { - subtotal: Math.round(subtotal * 100), - totalDiscount: Math.round(totalDiscount * 100), - beforeTax: Math.round(beforeTax * 100), - taxAmount: Math.round(taxAmount * 100), - total: Math.round(total * 100), - }; -} - -export function formatCurrency(amount: number, scale = 2, currency = "EUR"): string { - const value = amount / Math.pow(10, scale); - return new Intl.NumberFormat("es-ES", { - style: "currency", - currency: currency, - }).format(value); -} diff --git a/modules/customers/src/web/create/controllers/use-customer-create.controller.ts b/modules/customers/src/web/create/controllers/use-customer-create.controller.ts index fcae174d..77aa00d7 100644 --- a/modules/customers/src/web/create/controllers/use-customer-create.controller.ts +++ b/modules/customers/src/web/create/controllers/use-customer-create.controller.ts @@ -4,21 +4,20 @@ import { useId } from "react"; import type { FieldErrors } from "react-hook-form"; import { useTranslation } from "../../i18n"; -import type { Customer } from "../../shared"; +import type { CreateCustomerParams, Customer } from "../../shared"; import { useCustomerCreateMutation } from "../../shared/hooks/use-customer-create-mutation"; import { type CustomerCreateForm, CustomerCreateFormSchema, - type CustomerCreatePayload, defaultCustomerCreateForm, } from "../entities"; -import { buildCustomerCreatePayload } from "../utils"; +import { buildCreateCustomerParams } from "../utils"; export interface UseCustomerCreateControllerOptions { onCreated?(created: Customer): void; successToasts?: boolean; // mostrar o no toast automáticcamente - onError?(error: Error, payloadData: CustomerCreatePayload): void; + onError?(error: Error, inputPayload: CreateCustomerParams): void; errorToasts?: boolean; // mostrar o no toast automáticcamente } @@ -49,11 +48,11 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle const submitHandler = form.handleSubmit( async (formData) => { - const payloadData: CustomerCreatePayload = buildCustomerCreatePayload(formData); + const inputPayload: CreateCustomerParams = buildCreateCustomerParams(formData); try { // Enviamos cambios al servidor - const created = await mutateAsync(payloadData); + const created = await mutateAsync(inputPayload); // payload es CustomerCreatePayload if (options?.successToasts !== false) { showSuccessToast( @@ -70,7 +69,7 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle showErrorToast(t("pages.create.error.title"), normalizedError.message); } - options?.onError?.(normalizedError, payloadData); + options?.onError?.(normalizedError, inputPayload); } }, (errors: FieldErrors) => { diff --git a/modules/customers/src/web/create/entities/customer-create-form.schema.ts b/modules/customers/src/web/create/entities/customer-create-form.schema.ts index 63fc7991..c21cdcb2 100644 --- a/modules/customers/src/web/create/entities/customer-create-form.schema.ts +++ b/modules/customers/src/web/create/entities/customer-create-form.schema.ts @@ -15,33 +15,33 @@ import { z } from "zod/v4"; */ export const CustomerCreateFormSchema = z.object({ - reference: z.string().optional().or(z.literal("")), + reference: z.string(), isCompany: z.boolean(), name: z.string().min(1, "El nombre es obligatorio"), - tradeName: z.string().optional().or(z.literal("")), + tradeName: z.string(), tin: z.string(), defaultTaxes: z.array(z.string()), - street: z.string().optional().or(z.literal("")), - street2: z.string().optional().or(z.literal("")), - city: z.string().optional().or(z.literal("")), - province: z.string().optional().or(z.literal("")), - postalCode: z.string().optional().or(z.literal("")), - country: z.string().min(1, "El país es obligatorio").optional().or(z.literal("")), + street: z.string(), + street2: z.string(), + city: z.string(), + province: z.string(), + postalCode: z.string(), + country: z.string().min(1, "El país es obligatorio"), - primaryEmail: z.email("Email inválido").optional().or(z.literal("")), - secondaryEmail: z.email("Email inválido").optional().or(z.literal("")), + primaryEmail: z.email("Email inválido"), + secondaryEmail: z.email("Email inválido"), - primaryPhone: z.string().optional().or(z.literal("")), - secondaryPhone: z.string().optional().or(z.literal("")), - primaryMobile: z.string().optional().or(z.literal("")), - secondaryMobile: z.string().optional().or(z.literal("")), + primaryPhone: z.string(), + secondaryPhone: z.string(), + primaryMobile: z.string(), + secondaryMobile: z.string(), - fax: z.string().optional().or(z.literal("")), - website: z.url("URL inválida").optional().or(z.literal("")), + fax: z.string(), + website: z.url("URL inválida"), - legalRecord: z.string().optional().or(z.literal("")), + legalRecord: z.string(), languageCode: z.string().min(1, "El idioma es obligatorio"), currencyCode: z.string().min(1, "La moneda es obligatoria"), diff --git a/modules/customers/src/web/create/entities/customer-create-payload.entity.ts b/modules/customers/src/web/create/entities/customer-create-payload.entity.ts deleted file mode 100644 index a731fbb8..00000000 --- a/modules/customers/src/web/create/entities/customer-create-payload.entity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { CustomerCreateForm } from "./customer-create-form.entity"; - -/** - * CustomerCreatePayload es un tipo que representa un objeto con las mismas - * propiedades que CustomerCreateForm, pero todas ellas son opcionales. - * - * Esto es útil para representar los datos que se van a enviar a la API para crear un cliente, - * ya que en una creación parcial (POST) no es necesario enviar todos los campos, - * sino solo aquellos que se quieren modificar. - * - * Reglas: - * - debe ser un Partial de CustomerCreateForm - * - no debe tener campos adicionales ni transformaciones - * - debe ser un shape orientado a la API, no a la UI ni al dominio - * - sin shape DTO, solo tipos simples y directos - */ - -export type CustomerCreatePayload = Partial & { - // Aquí se añaden los campos que la API requiera para la creación de un cliente y que no estén en el formulario. - // Por ejemplo: - // - id: string; - id: string; -}; diff --git a/modules/customers/src/web/create/entities/index.ts b/modules/customers/src/web/create/entities/index.ts index e208b9ee..c9663e04 100644 --- a/modules/customers/src/web/create/entities/index.ts +++ b/modules/customers/src/web/create/entities/index.ts @@ -1,4 +1,3 @@ export * from "./customer-create-form.entity"; export * from "./customer-create-form.schema"; export * from "./customer-create-form-default"; -export * from "./customer-create-payload.entity"; diff --git a/modules/customers/src/web/create/index.ts b/modules/customers/src/web/create/index.ts new file mode 100644 index 00000000..4aedf593 --- /dev/null +++ b/modules/customers/src/web/create/index.ts @@ -0,0 +1 @@ +export * from "./ui"; diff --git a/modules/customers/src/web/create/ui/pages/editor/customer-additional-config-fields.tsx b/modules/customers/src/web/create/ui/editor/customer-additional-config-fields.tsx similarity index 100% rename from modules/customers/src/web/create/ui/pages/editor/customer-additional-config-fields.tsx rename to modules/customers/src/web/create/ui/editor/customer-additional-config-fields.tsx diff --git a/modules/customers/src/web/create/ui/pages/editor/customer-address-fields.tsx b/modules/customers/src/web/create/ui/editor/customer-address-fields.tsx similarity index 100% rename from modules/customers/src/web/create/ui/pages/editor/customer-address-fields.tsx rename to modules/customers/src/web/create/ui/editor/customer-address-fields.tsx diff --git a/modules/customers/src/web/create/ui/pages/editor/customer-basic-info-fields.tsx b/modules/customers/src/web/create/ui/editor/customer-basic-info-fields.tsx similarity index 100% rename from modules/customers/src/web/create/ui/pages/editor/customer-basic-info-fields.tsx rename to modules/customers/src/web/create/ui/editor/customer-basic-info-fields.tsx diff --git a/modules/customers/src/web/create/ui/pages/editor/customer-contact-fields.tsx b/modules/customers/src/web/create/ui/editor/customer-contact-fields.tsx similarity index 100% rename from modules/customers/src/web/create/ui/pages/editor/customer-contact-fields.tsx rename to modules/customers/src/web/create/ui/editor/customer-contact-fields.tsx diff --git a/modules/customers/src/web/create/ui/pages/editor/customer-create-form.tsx b/modules/customers/src/web/create/ui/editor/customer-create-editor-form.tsx similarity index 82% rename from modules/customers/src/web/create/ui/pages/editor/customer-create-form.tsx rename to modules/customers/src/web/create/ui/editor/customer-create-editor-form.tsx index 0d7072d5..119466f5 100644 --- a/modules/customers/src/web/create/ui/pages/editor/customer-create-form.tsx +++ b/modules/customers/src/web/create/ui/editor/customer-create-editor-form.tsx @@ -5,13 +5,17 @@ import { CustomerAddressFields } from "./customer-address-fields"; import { CustomerBasicInfoFields } from "./customer-basic-info-fields"; import { CustomerContactFields } from "./customer-contact-fields"; -type CustomerFormProps = { +type CustomerCreateEditorFormProps = { formId: string; onSubmit: (event: React.FormEvent) => void; className?: string; }; -export const CustomerCreateForm = ({ formId, onSubmit, className }: CustomerFormProps) => { +export const CustomerCreateEditorForm = ({ + formId, + onSubmit, + className, +}: CustomerCreateEditorFormProps) => { return (
diff --git a/modules/customers/src/web/create/ui/pages/editor/customer-taxes-multi-select.tsx b/modules/customers/src/web/create/ui/editor/customer-taxes-multi-select.tsx similarity index 100% rename from modules/customers/src/web/create/ui/pages/editor/customer-taxes-multi-select.tsx rename to modules/customers/src/web/create/ui/editor/customer-taxes-multi-select.tsx diff --git a/modules/customers/src/web/create/ui/editor/index.ts b/modules/customers/src/web/create/ui/editor/index.ts index e69de29b..f2dcfda3 100644 --- a/modules/customers/src/web/create/ui/editor/index.ts +++ b/modules/customers/src/web/create/ui/editor/index.ts @@ -0,0 +1 @@ +export * from "./customer-create-editor-form"; diff --git a/modules/customers/src/web/create/ui/forms/index.ts b/modules/customers/src/web/create/ui/forms/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/customers/src/web/create/ui/index.ts b/modules/customers/src/web/create/ui/index.ts index e69de29b..c4e34b27 100644 --- a/modules/customers/src/web/create/ui/index.ts +++ b/modules/customers/src/web/create/ui/index.ts @@ -0,0 +1 @@ +export * from "./pages"; diff --git a/modules/customers/src/web/create/ui/pages/customer-create-page.tsx b/modules/customers/src/web/create/ui/pages/customer-create-page.tsx index d3948a3f..ab79e300 100644 --- a/modules/customers/src/web/create/ui/pages/customer-create-page.tsx +++ b/modules/customers/src/web/create/ui/pages/customer-create-page.tsx @@ -5,8 +5,7 @@ import { FormProvider } from "react-hook-form"; import { useTranslation } from "../../../i18n"; import { useCustomerCreatePageController } from "../../controllers"; - -import { CustomerCreateForm } from "./editor"; +import { CustomerCreateEditorForm } from "../editor"; export const CustomerCreatePage = () => { const { t } = useTranslation(); @@ -42,7 +41,7 @@ export const CustomerCreatePage = () => { )} - { + return { + // Generamos un ID único para el nuevo cliente. + // La API de creación de cliente requiere un ID, + id: UniqueID.generateNewID().toString(), + + reference: formData.reference, + is_company: formData.isCompany ? "1" : "0", + name: formData.name, + trade_name: formData.tradeName, + tin: formData.tin, + default_taxes: formData.defaultTaxes, + + street: formData.street, + street2: formData.street2, + city: formData.city, + province: formData.province, + postal_code: formData.postalCode, + country: formData.country, + + email_primary: formData.primaryEmail, + email_secondary: formData.secondaryEmail, + phone_primary: formData.primaryPhone, + phone_secondary: formData.secondaryPhone, + mobile_primary: formData.primaryMobile, + mobile_secondary: formData.secondaryMobile, + + fax: formData.fax, + website: formData.website, + + legal_record: formData.legalRecord, + + language_code: formData.languageCode, + currency_code: formData.currencyCode, + }; +}; diff --git a/modules/customers/src/web/create/utils/build-customer-create-payload.ts b/modules/customers/src/web/create/utils/build-customer-create-payload.ts deleted file mode 100644 index 3eaa4c04..00000000 --- a/modules/customers/src/web/create/utils/build-customer-create-payload.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { UniqueID } from "@repo/rdx-ddd"; - -import type { CustomerCreateForm, CustomerCreatePayload } from "../entities"; - -/** - * Construye un payload de creación de cliente a partir de los datos - * del formulario y los campos sucios. - * - * Reglas: - * - el payload debe ser un objeto con solo las propiedades que han cambiado (campos sucios). - * - no debe incluir campos que no han cambiado. - * - el shape del payload debe coincidir con el de CustomerCreatePayload, - * es decir, orientado a la API. - * - no debe tener transformaciones ni campos adicionales, solo los que vienen del - * formulario y están sucios. - * - * @param formData: Los datos del formulario de creación de cliente. - * @returns Un objeto que se puede enviar a la API para crear un cliente, - * con todos los campos necesarios. - */ - -export const buildCustomerCreatePayload = (formData: CustomerCreateForm): CustomerCreatePayload => { - return { - ...formData, - - // El backend exige que el cliente envíe un id en la creación. - id: UniqueID.generateNewID().toString(), - }; -}; diff --git a/modules/customers/src/web/create/utils/index.ts b/modules/customers/src/web/create/utils/index.ts index 695f9369..1d175179 100644 --- a/modules/customers/src/web/create/utils/index.ts +++ b/modules/customers/src/web/create/utils/index.ts @@ -1 +1 @@ -export * from "./build-customer-create-payload"; +export * from "./build-customer-create-params"; diff --git a/modules/customers/src/web/customer-routes.tsx b/modules/customers/src/web/customer-routes.tsx index 804ade5c..de7b618e 100644 --- a/modules/customers/src/web/customer-routes.tsx +++ b/modules/customers/src/web/customer-routes.tsx @@ -9,7 +9,9 @@ const CustomerLayout = lazy(() => const CustomersList = lazy(() => import("./list").then((m) => ({ default: m.ListCustomersPage }))); const CustomerView = lazy(() => import("./view").then((m) => ({ default: m.CustomerViewPage }))); -const CustomerAdd = lazy(() => import("./create").then((m) => ({ default: m.CustomerCreatePage }))); +const CustomerCreate = lazy(() => + import("./create").then((m) => ({ default: m.CustomerCreatePage })) +); const CustomerUpdate = lazy(() => import("./update").then((m) => ({ default: m.CustomerUpdatePage })) ); @@ -29,9 +31,10 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => { //{ path: "create", element: }, { path: ":id", element: }, { path: ":id/edit", element: }, + { path: "create", element: }, // - /*{ path: "create", element: }, + /* { path: ":id", element: }, { path: ":id/edit", element: }, { path: ":id/delete", element: }, diff --git a/modules/customers/src/web/list/controllers/use-list-customers.controller.ts b/modules/customers/src/web/list/controllers/use-list-customers.controller.ts index 44ac027a..cd8e5db5 100644 --- a/modules/customers/src/web/list/controllers/use-list-customers.controller.ts +++ b/modules/customers/src/web/list/controllers/use-list-customers.controller.ts @@ -2,7 +2,7 @@ import type { CriteriaDTO } from "@erp/core"; import { useDebounce } from "@repo/rdx-ui/components"; import { useMemo, useState } from "react"; -import { useListCustomersQuery } from "../../shared"; +import { useCustomerListQuery } from "../../shared"; export const useListCustomersController = () => { const [pageIndex, setPageIndex] = useState(0); @@ -21,7 +21,7 @@ export const useListCustomersController = () => { }; }, [pageSize, pageIndex, debouncedQ]); - const query = useListCustomersQuery({ criteria }); + const query = useCustomerListQuery({ criteria }); const setSearchValue = (value: string) => { const nextValue = value.trim().replace(/\s+/g, " "); diff --git a/modules/customers/src/web/shared/adapters/customer-to-list-row-patch.adapter.ts b/modules/customers/src/web/shared/adapters/customer-to-list-row-patch.adapter.ts index a7902ac4..7aa2f56e 100644 --- a/modules/customers/src/web/shared/adapters/customer-to-list-row-patch.adapter.ts +++ b/modules/customers/src/web/shared/adapters/customer-to-list-row-patch.adapter.ts @@ -1,6 +1,18 @@ import type { Customer } from "../entities/customer.entity"; import type { CustomerListRow } from "../entities/customer-list-row.entity"; +/** + * Adaptador para transformar un objeto Customer a un objeto CustomerListRowPatch, + * que es una versión parcial de CustomerListRow. + * Utilizado por el gestor de caché para actualizar algunos campos del objeto CustomerListRow. + * Reglas de adaptación: + * - los campos se asignan directamente + * - los campos son opcionales excepto el id. + * + * @param customer - objeto Customer a adaptar. + * @returns {CustomerListRowPatch} Objeto adaptado a CustomerListRowPatch. + */ + export type CustomerListRowPatch = Pick & Partial; export const CustomerToListRowPatchAdapter = { diff --git a/modules/customers/src/web/shared/adapters/get-customer-by-id.adapter.ts b/modules/customers/src/web/shared/adapters/get-customer-by-id.adapter.ts index cc78a475..246f3f15 100644 --- a/modules/customers/src/web/shared/adapters/get-customer-by-id.adapter.ts +++ b/modules/customers/src/web/shared/adapters/get-customer-by-id.adapter.ts @@ -1,11 +1,24 @@ import type { CustomerCreationResponseDTO } from "@erp/customers/common"; -import type { CustomerGetOutput, CustomerUpdateOutput } from "../api"; +import type { GetCustomerByIdResult, UpdateCustomerByIdResult } from "../api"; import type { Customer } from "../entities"; +/** + * Adaptador para transformar los datos de la API de GetCustomerByIdResult, CustomerCreationResponseDTO o UpdateCustomerByIdResult + * a la entidad Customer utilizada en la aplicación. + * Reglas de adaptación: + * - id, company_id, reference se asignan directamente. + * - is_company se convierte a booleano (true si es "1", false si es "0"). + * - default_taxes se divide por ";" y se filtran los valores vacíos o "#". + * + * @params dto - datos del cliente desde la API. + * @param context - Contexto adicional opcional para la adaptación. + * @returns {Customer} Objeto adaptado a Customer. + */ + export const GetCustomerByIdAdapter = { fromDTO( - dto: CustomerGetOutput | CustomerCreationResponseDTO | CustomerUpdateOutput, + dto: GetCustomerByIdResult | CustomerCreationResponseDTO | UpdateCustomerByIdResult, context?: unknown ): Customer { const taxesAdapter = (taxes: string) => diff --git a/modules/customers/src/web/shared/adapters/list-customers.adapter.ts b/modules/customers/src/web/shared/adapters/list-customers.adapter.ts index e0cd8dd8..4ecf0eed 100644 --- a/modules/customers/src/web/shared/adapters/list-customers.adapter.ts +++ b/modules/customers/src/web/shared/adapters/list-customers.adapter.ts @@ -1,8 +1,20 @@ -import type { CustomerListOutput } from "../api"; +import type { ListCustomersResult } from "../api"; import type { CustomerList, CustomerListRow } from "../entities"; +/** + * Adaptador para transformar los datos de la API de ListCustomersResult + * a la entidad CustomerList utilizada en la aplicación. + * Reglas de adaptación: + * - page, per_page, total_pages, total_items se asignan directamente. + * - items se transforma utilizando ListCustomersRowAdapter para cada elemento. + * + * @param pageDto - lista de clientes desde la API. + * @param context - Contexto adicional opcional para la adaptación. + * @returns {CustomerList} Objeto adaptado a CustomerList. + */ + export const ListCustomersAdapter = { - fromDTO(pageDto: CustomerListOutput, context?: unknown): CustomerList { + fromDTO(pageDto: ListCustomersResult, context?: unknown): CustomerList { return { page: pageDto.page, per_page: pageDto.per_page, @@ -13,7 +25,18 @@ export const ListCustomersAdapter = { }, }; -type CustomerListItemOutput = CustomerListOutput["items"][number]; +/** + * Adaptador para transformar los items de la API de ListCustomersResult a la entidad CustomerListRow. + * Reglas de adaptación: + * - id, company_id, status, reference se asignan directamente. + * - is_company se convierte a booleano (true si es "1", false si es "0"). + * + * @param rowDto - item de cliente desde la API. + * @param context - Contexto adicional opcional para la adaptación. + * @returns {CustomerListRow} Objeto adaptado a CustomerListRow. + */ + +type CustomerListItemOutput = ListCustomersResult["items"][number]; const ListCustomersRowAdapter = { fromDTO(rowDto: CustomerListItemOutput, context?: unknown): CustomerListRow { diff --git a/modules/customers/src/web/shared/api/create-customer.api.ts b/modules/customers/src/web/shared/api/create-customer.api.ts index 91aa7c1f..2ed71617 100644 --- a/modules/customers/src/web/shared/api/create-customer.api.ts +++ b/modules/customers/src/web/shared/api/create-customer.api.ts @@ -2,13 +2,21 @@ import type { IDataSource } from "@erp/core/client"; import type { CreateCustomerRequestDTO, CustomerCreationResponseDTO } from "../../../common"; -export type CustomerCreateInput = CreateCustomerRequestDTO; -export type CustomerCreateOutput = CustomerCreationResponseDTO; +/** + * Crea un nuevo cliente en el sistema utilizando la fuente de datos proporcionada. + * + * @param dataSource - La fuente de datos para interactuar con la API. + * @param params - Los parámetros necesarios para crear el cliente. + * @returns Una promesa que resuelve con los detalles del cliente creado. + * @throws Error si el ID del cliente no es proporcionado o si la creación falla. + */ -export function createCustomer(dataSource: IDataSource, id: string, data: CustomerCreateInput) { +export type CreateCustomerParams = CreateCustomerRequestDTO; + +export type CreateCustomerResult = CustomerCreationResponseDTO; + +export function createCustomer(dataSource: IDataSource, params: CreateCustomerParams) { + const { id } = params; if (!id) throw new Error("customerId is required"); - return dataSource.createOne("customers", { - ...data, - id, - }); + return dataSource.createOne("customers", params); } diff --git a/modules/customers/src/web/shared/api/delete-customer-by-id.api.ts b/modules/customers/src/web/shared/api/delete-customer-by-id.api.ts index 4327c3dc..d3acdb5c 100644 --- a/modules/customers/src/web/shared/api/delete-customer-by-id.api.ts +++ b/modules/customers/src/web/shared/api/delete-customer-by-id.api.ts @@ -1,8 +1,29 @@ import type { IDataSource } from "@erp/core/client"; -import type { DeleteCustomerByIdRequestDTO } from "../../../common"; -export type CustomerDeleteInput = DeleteCustomerByIdRequestDTO; +/** + * Elimina un cliente existente en el sistema utilizando la fuente de datos proporcionada. + * + * @param dataSource - La fuente de datos para interactuar con la API. + * @param params - Los parámetros necesarios para eliminar el cliente. + * @param signal - Un AbortSignal para cancelar la solicitud si es necesario. + * @returns Una promesa que se resuelve cuando el cliente ha sido eliminado exitosamente. + * @throws Error si el ID del cliente no es proporcionado o si la eliminación falla. + */ -export function deleteCustomerById(dataSource: IDataSource, id: string, signal: AbortSignal) { - return dataSource.deleteOne("customers", id, { signal }); +export type DeleteCustomerByIdParams = { + id: string; +}; + +export type DeleteCustomerByIdResult = void; + +export function deleteCustomerById( + dataSource: IDataSource, + params: DeleteCustomerByIdParams, + signal?: AbortSignal +): Promise { + const { id } = params; + if (!id) throw new Error("customerId is required"); + return dataSource.deleteOne("customers", id, { + signal, + }); } diff --git a/modules/customers/src/web/shared/api/get-customer-by-id.api.ts b/modules/customers/src/web/shared/api/get-customer-by-id.api.ts index 8371b9a1..612fec98 100644 --- a/modules/customers/src/web/shared/api/get-customer-by-id.api.ts +++ b/modules/customers/src/web/shared/api/get-customer-by-id.api.ts @@ -2,8 +2,28 @@ import type { IDataSource } from "@erp/core/client"; import type { GetCustomerByIdResponseDTO } from "../../../common"; -export type CustomerGetOutput = GetCustomerByIdResponseDTO; +/** + * Recupera los detalles de un cliente específico utilizando su ID a través de la fuente de datos proporcionada. + * + * @param dataSource - La fuente de datos para interactuar con la API. + * @param params - Los parámetros necesarios para obtener el cliente, incluyendo su ID. + * @param signal - Un AbortSignal para cancelar la solicitud si es necesario. + * @returns Una promesa que resuelve con los detalles del cliente solicitado. + * @throws Error si el ID del cliente no es proporcionado o si la recuperación falla. + */ -export function getCustomerById(dataSource: IDataSource, id: string, signal: AbortSignal) { - return dataSource.getOne("customers", id, { signal }); +export type GetCustomerByIdParams = { + id: string; +}; + +export type GetCustomerByIdResult = GetCustomerByIdResponseDTO; + +export function getCustomerById( + dataSource: IDataSource, + params: GetCustomerByIdParams, + signal?: AbortSignal +) { + const { id } = params; + if (!id) throw new Error("customerId is required"); + return dataSource.getOne("customers", id, { signal }); } diff --git a/modules/customers/src/web/shared/api/get-list-customers-by-criteria.api.ts b/modules/customers/src/web/shared/api/get-list-customers-by-criteria.api.ts new file mode 100644 index 00000000..b27ad19b --- /dev/null +++ b/modules/customers/src/web/shared/api/get-list-customers-by-criteria.api.ts @@ -0,0 +1,33 @@ +import type { CriteriaDTO } from "@erp/core"; +import type { IDataSource } from "@erp/core/client"; + +import type { ListCustomersResponseDTO } from "../../../common"; + +/** + * Recupera una lista de clientes del sistema utilizando la + * fuente de datos proporcionada y los criterios de búsqueda especificados. + * + * @param dataSource - La fuente de datos para interactuar con la API. + * @param params - Los parámetros necesarios para listar los clientes, incluyendo los criterios de búsqueda. + * @param signal - Un AbortSignal para cancelar la solicitud si es necesario. + * @returns Una promesa que resuelve con una lista de clientes que cumplen con los criterios especificados. + * @throws Error si la recuperación de la lista de clientes falla. + */ + +export type ListCustomersByCriteriaParams = { + criteria: CriteriaDTO; +}; + +export type ListCustomersResult = ListCustomersResponseDTO; + +export function getListCustomersByCriteria( + dataSource: IDataSource, + params: ListCustomersByCriteriaParams, + signal?: AbortSignal +): Promise { + const { criteria } = params; + return dataSource.getList("customers", { + signal, + ...criteria, + }); +} diff --git a/modules/customers/src/web/shared/api/index.ts b/modules/customers/src/web/shared/api/index.ts index 8febd039..9ba88218 100644 --- a/modules/customers/src/web/shared/api/index.ts +++ b/modules/customers/src/web/shared/api/index.ts @@ -1,5 +1,5 @@ export * from "./create-customer.api"; export * from "./delete-customer-by-id.api"; export * from "./get-customer-by-id.api"; -export * from "./list-customers.api"; +export * from "./get-list-customers-by-criteria.api"; export * from "./update-customer-by-id.api"; diff --git a/modules/customers/src/web/shared/api/list-customers.api.ts b/modules/customers/src/web/shared/api/list-customers.api.ts deleted file mode 100644 index fe245aeb..00000000 --- a/modules/customers/src/web/shared/api/list-customers.api.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { CriteriaDTO } from "@erp/core"; -import type { IDataSource } from "@erp/core/client"; - -import type { ListCustomersResponseDTO } from "../../../common"; - -export type CustomerListOutput = ListCustomersResponseDTO; - -export function getListCustomers( - dataSource: IDataSource, - criteria: CriteriaDTO, - signal: AbortSignal -) { - return dataSource.getList("customers", { - signal, - ...criteria, - }); -} diff --git a/modules/customers/src/web/shared/api/update-customer-by-id.api.ts b/modules/customers/src/web/shared/api/update-customer-by-id.api.ts index 884ff440..81cfb50e 100644 --- a/modules/customers/src/web/shared/api/update-customer-by-id.api.ts +++ b/modules/customers/src/web/shared/api/update-customer-by-id.api.ts @@ -2,10 +2,32 @@ import type { IDataSource } from "@erp/core/client"; import type { UpdateCustomerByIdRequestDTO, UpdateCustomerByIdResponseDTO } from "../../../common"; -export type CustomerUpdateInput = UpdateCustomerByIdRequestDTO; -export type CustomerUpdateOutput = UpdateCustomerByIdResponseDTO; +/** + * Actualiza un cliente existente en el sistema utilizando la fuente de datos proporcionada. + * + * @param dataSource - La fuente de datos para interactuar con la API. + * @param params - Los parámetros necesarios para actualizar el cliente. + * @returns Una promesa que resuelve con los detalles del cliente actualizado. + * @throws Error si el ID del cliente no es proporcionado o si la actualización falla. + */ + +export type UpdateCustomerByIdParams = { + id: string; + data: UpdateCustomerByIdRequestDTO; +}; + +export type UpdateCustomerByIdResult = UpdateCustomerByIdResponseDTO; + +export function updateCustomerById( + dataSource: IDataSource, + params: UpdateCustomerByIdParams +): Promise { + const { id, data } = params; -export function updateCustomerById(dataSource: IDataSource, id: string, data: CustomerUpdateInput) { if (!id) throw new Error("customerId is required"); - return dataSource.updateOne("customers", id, data); + return dataSource.updateOne( + "customers", + id, + data + ); } diff --git a/modules/customers/src/web/shared/entities/customer-list-row.entity.ts b/modules/customers/src/web/shared/entities/customer-list-row.entity.ts index adc25731..b5f29688 100644 --- a/modules/customers/src/web/shared/entities/customer-list-row.entity.ts +++ b/modules/customers/src/web/shared/entities/customer-list-row.entity.ts @@ -1,3 +1,8 @@ +/** + * Interface que representa una fila en la lista de clientes, adaptada desde la respuesta de la API. + * Contiene solo ciertos campos básicos del cliente. + * + */ export interface CustomerListRow { id: string; companyId: string; diff --git a/modules/customers/src/web/shared/entities/customer-list.entity.ts b/modules/customers/src/web/shared/entities/customer-list.entity.ts index 526d9c90..154756c4 100644 --- a/modules/customers/src/web/shared/entities/customer-list.entity.ts +++ b/modules/customers/src/web/shared/entities/customer-list.entity.ts @@ -1,5 +1,10 @@ import type { CustomerListRow } from "./customer-list-row.entity"; +/** + * Interface que representa la respuesta paginada de una lista de clientes, + * adaptada desde la respuesta de la API. + */ + export interface CustomerList { items: CustomerListRow[]; total_pages: number; diff --git a/modules/customers/src/web/shared/entities/customer.entity.ts b/modules/customers/src/web/shared/entities/customer.entity.ts index 84b7479c..818b9038 100644 --- a/modules/customers/src/web/shared/entities/customer.entity.ts +++ b/modules/customers/src/web/shared/entities/customer.entity.ts @@ -1,3 +1,9 @@ +/** + * Interface que representa un cliente en el sistema, + * adaptada desde la respuesta de la API. + * Contiene todos los campos detallados del cliente. + */ + export interface Customer { id: string; companyId: string; diff --git a/modules/customers/src/web/shared/hooks/customer-cache-strategy.ts b/modules/customers/src/web/shared/hooks/customer-cache-strategy.ts index b91ca85d..f23f7ac5 100644 --- a/modules/customers/src/web/shared/hooks/customer-cache-strategy.ts +++ b/modules/customers/src/web/shared/hooks/customer-cache-strategy.ts @@ -5,6 +5,12 @@ import type { Customer, CustomerList, CustomerListRow } from "../entities"; import { CUSTOMER_QUERY_KEY, LIST_CUSTOMERS_QUERY_KEY_PREFIX } from "./keys"; +/** + * Estrategias de caché para las consultas relacionadas con clientes. + * Incluye funciones para cancelar, invalidar, actualizar y eliminar cachés de clientes, + * así como para manejar actualizaciones optimistas al eliminar clientes. + */ + export interface CustomerListCacheSnapshot { key: QueryKey; page?: CustomerList; diff --git a/modules/customers/src/web/shared/hooks/index.ts b/modules/customers/src/web/shared/hooks/index.ts index 6c7aa19c..f74171cd 100644 --- a/modules/customers/src/web/shared/hooks/index.ts +++ b/modules/customers/src/web/shared/hooks/index.ts @@ -2,4 +2,4 @@ export * from "./use-customer-create-mutation"; export * from "./use-customer-delete-mutation"; export * from "./use-customer-get-query"; export * from "./use-customer-update-mutation"; -export * from "./use-list-customers-query"; +export * from "./use-customers-list-query"; diff --git a/modules/customers/src/web/shared/hooks/to-validation-errors.ts b/modules/customers/src/web/shared/hooks/to-validation-errors.ts index 4f04945e..55b279a6 100644 --- a/modules/customers/src/web/shared/hooks/to-validation-errors.ts +++ b/modules/customers/src/web/shared/hooks/to-validation-errors.ts @@ -1,6 +1,11 @@ import type { ZodError } from "zod"; -// Helpers de validación a errores de dominio +/** + * Convierte un error de validación de Zod en una colección de errores de validación personalizada. + * + * @param error + * @returns array de objetos con el campo y el mensaje de error correspondiente. + */ export function toValidationErrors(error: ZodError) { return error.issues.map((err) => ({ diff --git a/modules/customers/src/web/shared/hooks/use-customer-create-mutation.ts b/modules/customers/src/web/shared/hooks/use-customer-create-mutation.ts index 4904ea30..ca66f05b 100644 --- a/modules/customers/src/web/shared/hooks/use-customer-create-mutation.ts +++ b/modules/customers/src/web/shared/hooks/use-customer-create-mutation.ts @@ -4,7 +4,7 @@ import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react- import { CreateCustomerRequestSchema } from "../../../common"; import { GetCustomerByIdAdapter } from "../adapters"; -import { type CustomerCreateInput, createCustomer } from "../api"; +import { type CreateCustomerParams, type CreateCustomerResult, createCustomer } from "../api"; import type { Customer } from "../entities"; import { @@ -21,18 +21,19 @@ export const useCustomerCreateMutation = () => { const dataSource = useDataSource(); const schema = CreateCustomerRequestSchema; - return useMutation({ + return useMutation({ mutationKey: CUSTOMER_CREATE_KEY, mutationFn: async (payload) => { const id = UniqueID.generateNewID().toString(); + const payloadWithId: CreateCustomerParams = { ...payload, id }; - const result = schema.safeParse(payload); + const result = schema.safeParse(payloadWithId); if (!result.success) { throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error)); } - const dto = await createCustomer(dataSource, id, payload); + const dto: CreateCustomerResult = await createCustomer(dataSource, payloadWithId); return GetCustomerByIdAdapter.fromDTO(dto); }, diff --git a/modules/customers/src/web/shared/hooks/use-customer-delete-mutation.ts b/modules/customers/src/web/shared/hooks/use-customer-delete-mutation.ts index d9b3fbaf..3295d6b9 100644 --- a/modules/customers/src/web/shared/hooks/use-customer-delete-mutation.ts +++ b/modules/customers/src/web/shared/hooks/use-customer-delete-mutation.ts @@ -1,7 +1,11 @@ import { useDataSource } from "@erp/core/hooks"; import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; -import { deleteCustomerById } from "../api"; +import { + type DeleteCustomerByIdParams, + type DeleteCustomerByIdResult, + deleteCustomerById, +} from "../api"; import { type DeleteCustomerCacheContext, @@ -12,25 +16,25 @@ import { } from "./customer-cache-strategy"; import { CUSTOMER_DELETE_KEY } from "./keys"; -export interface DeleteCustomerPayload { - id: string; -} - interface DeleteCustomerContext extends DeleteCustomerCacheContext {} export const useCustomerDeleteMutation = () => { const queryClient = useQueryClient(); const dataSource = useDataSource(); - return useMutation<{ id: string }, DefaultError, DeleteCustomerPayload, DeleteCustomerContext>({ + return useMutation< + DeleteCustomerByIdResult, + DefaultError, + DeleteCustomerByIdParams, + DeleteCustomerContext + >({ mutationKey: CUSTOMER_DELETE_KEY, mutationFn: async ({ id }) => { if (!id) { throw new Error("customerId is required"); } - await deleteCustomerById(dataSource, id, new AbortController().signal); - return { id }; + await deleteCustomerById(dataSource, { id }, new AbortController().signal); }, onMutate: async ({ id }) => { return prepareDeleteCustomerOptimisticUpdate(queryClient, id); @@ -40,8 +44,8 @@ export const useCustomerDeleteMutation = () => { rollbackDeleteCustomerOptimisticUpdate(queryClient, context); }, - onSuccess: ({ id }) => { - finalizeDeletedCustomerCaches(queryClient, id); + onSuccess: (_, variables) => { + finalizeDeletedCustomerCaches(queryClient, variables.id); }, onSettled: async () => { await invalidateCustomerListQueries(queryClient); diff --git a/modules/customers/src/web/shared/hooks/use-customer-get-query.ts b/modules/customers/src/web/shared/hooks/use-customer-get-query.ts index d9c46c9c..62135045 100644 --- a/modules/customers/src/web/shared/hooks/use-customer-get-query.ts +++ b/modules/customers/src/web/shared/hooks/use-customer-get-query.ts @@ -2,7 +2,7 @@ import { useDataSource } from "@erp/core/hooks"; import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query"; import { GetCustomerByIdAdapter } from "../adapters"; -import { getCustomerById } from "../api"; +import { type GetCustomerByIdResult, getCustomerById } from "../api"; import type { Customer } from "../entities"; import { CUSTOMER_QUERY_KEY } from "./keys"; @@ -22,7 +22,11 @@ export const useCustomerGetQuery = ( return useQuery({ queryKey: CUSTOMER_QUERY_KEY(customerId), queryFn: async ({ signal }) => { - const dto = await getCustomerById(dataSource, String(customerId), signal); + const dto: GetCustomerByIdResult = await getCustomerById( + dataSource, + { id: customerId! }, + signal + ); return GetCustomerByIdAdapter.fromDTO(dto); }, enabled, diff --git a/modules/customers/src/web/shared/hooks/use-customer-update-mutation.ts b/modules/customers/src/web/shared/hooks/use-customer-update-mutation.ts index 1a895b88..faaa48a0 100644 --- a/modules/customers/src/web/shared/hooks/use-customer-update-mutation.ts +++ b/modules/customers/src/web/shared/hooks/use-customer-update-mutation.ts @@ -4,7 +4,11 @@ import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react- import { UpdateCustomerByIdRequestSchema } from "../../../common"; import { GetCustomerByIdAdapter } from "../adapters"; -import { type CustomerUpdateInput, updateCustomerById } from "../api"; +import { + type UpdateCustomerByIdParams, + type UpdateCustomerByIdResult, + updateCustomerById, +} from "../api"; import type { Customer } from "../entities"; import { @@ -16,17 +20,12 @@ import { toValidationErrors } from "./to-validation-errors"; type UpdateCustomerContext = {}; -type UpdateCustomerPayload = { - id: string; - data: CustomerUpdateInput; -}; - export const useCustomerUpdateMutation = () => { const queryClient = useQueryClient(); const dataSource = useDataSource(); const schema = UpdateCustomerByIdRequestSchema; - return useMutation({ + return useMutation({ mutationKey: CUSTOMER_UPDATE_KEY, mutationFn: async (payload) => { @@ -40,7 +39,10 @@ export const useCustomerUpdateMutation = () => { throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error)); } - const dto = await updateCustomerById(dataSource, customerId, data); + const dto: UpdateCustomerByIdResult = await updateCustomerById( + dataSource, + payload as UpdateCustomerByIdParams + ); return GetCustomerByIdAdapter.fromDTO(dto); }, diff --git a/modules/customers/src/web/shared/hooks/use-list-customers-query.ts b/modules/customers/src/web/shared/hooks/use-customers-list-query.ts similarity index 73% rename from modules/customers/src/web/shared/hooks/use-list-customers-query.ts rename to modules/customers/src/web/shared/hooks/use-customers-list-query.ts index af9403f4..deadd271 100644 --- a/modules/customers/src/web/shared/hooks/use-list-customers-query.ts +++ b/modules/customers/src/web/shared/hooks/use-customers-list-query.ts @@ -3,18 +3,18 @@ import { useDataSource } from "@erp/core/hooks"; import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query"; import { ListCustomersAdapter } from "../adapters"; -import { getListCustomers } from "../api"; +import { type ListCustomersResult, getListCustomersByCriteria } from "../api"; import type { CustomerList } from "../entities"; import { LIST_CUSTOMERS_QUERY_KEY } from "./keys"; -type ListCustomersQueryOptions = { +type CustomerListQueryOptions = { enabled?: boolean; criteria?: Partial; }; -export const useListCustomersQuery = ( - options?: ListCustomersQueryOptions +export const useCustomerListQuery = ( + options?: CustomerListQueryOptions ): UseQueryResult => { const dataSource = useDataSource(); const enabled = options?.enabled ?? true; @@ -23,7 +23,11 @@ export const useListCustomersQuery = ( return useQuery({ queryKey: LIST_CUSTOMERS_QUERY_KEY(criteria), queryFn: async ({ signal }) => { - const dto = await getListCustomers(dataSource, criteria as CriteriaDTO, signal); + const dto: ListCustomersResult = await getListCustomersByCriteria( + dataSource, + { criteria }, + signal + ); return ListCustomersAdapter.fromDTO(dto); }, enabled, diff --git a/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts b/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts index 8cfc2300..b7d5efa0 100644 --- a/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts +++ b/modules/customers/src/web/update/adapters/customer-to-customer-update-form.adapter.ts @@ -3,8 +3,6 @@ import type { CustomerUpdateForm } from "../entities"; /** * Mapea un cliente a un formulario de actualización de cliente. - * Es decir, adapta el shape de datos del dominio al shape de datos - * que necesita la UI para mostrar el formulario de actualización. * * @param customer * @returns diff --git a/modules/customers/src/web/update/controllers/use-customer-update.controller.ts b/modules/customers/src/web/update/controllers/use-customer-update.controller.ts index 6314de9a..14f148db 100644 --- a/modules/customers/src/web/update/controllers/use-customer-update.controller.ts +++ b/modules/customers/src/web/update/controllers/use-customer-update.controller.ts @@ -5,21 +5,25 @@ import { useEffect, useId, useMemo } from "react"; import type { FieldErrors } from "react-hook-form"; import { useTranslation } from "../../i18n"; -import { type Customer, useCustomerGetQuery, useCustomerUpdateMutation } from "../../shared"; +import { + type Customer, + type UpdateCustomerByIdParams, + useCustomerGetQuery, + useCustomerUpdateMutation, +} from "../../shared"; import { mapCustomerToCustomerUpdateForm } from "../adapters"; import { type CustomerUpdateForm, CustomerUpdateFormSchema, - type CustomerUpdatePatch, defaultCustomerUpdateForm, } from "../entities"; -import { buildCustomerUpdatePatch } from "../utils"; +import { buildCustomerUpdatePatch, buildUpdateCustomerByIdParams } from "../utils"; export interface UseCustomerUpdateControllerOptions { onUpdated?(updated: Customer): void; successToasts?: boolean; // mostrar o no toast automáticamente - onError?(error: Error, patchData: CustomerUpdatePatch): void; + onError?(error: Error, params: UpdateCustomerByIdParams): void; errorToasts?: boolean; // mostrar o no toast automáticamente } @@ -93,12 +97,14 @@ export const useCustomerUpdateController = ( return; } - const patchData: CustomerUpdatePatch = buildCustomerUpdatePatch(formData, dirtyFields); const previousData = customerData; + const patchData = buildCustomerUpdatePatch(formData, dirtyFields); + + const params: UpdateCustomerByIdParams = buildUpdateCustomerByIdParams(customerId, patchData); try { // Enviamos cambios al servidor - const updated = await mutateAsync({ id: customerId, data: patchData }); + const updated = await mutateAsync(params); // Ha ido bien -> actualizamos form con datos reales // keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render. @@ -127,7 +133,7 @@ export const useCustomerUpdateController = ( showErrorToast(t("pages.update.error.title"), normalizedError.message); } - options?.onError?.(normalizedError, patchData); + options?.onError?.(normalizedError, params); } }, (errors: FieldErrors) => { diff --git a/modules/customers/src/web/update/entities/customer-update-form.entity.ts b/modules/customers/src/web/update/entities/customer-update-form.entity.ts index fc8d6236..f232dd73 100644 --- a/modules/customers/src/web/update/entities/customer-update-form.entity.ts +++ b/modules/customers/src/web/update/entities/customer-update-form.entity.ts @@ -2,7 +2,7 @@ * CustomerUpdateForm representa el shape de datos del formulario de actualización de cliente. * Es decir, los campos que se muestran en el formulario y que el usuario puede editar. * - * Este shape es específico para la UI y no tiene por qué coincidir + * Es específico de la UI y no tiene por qué coincidir * con el shape del dominio ni con el de la API. * * Debe cumplir las siguientes reglas: diff --git a/modules/customers/src/web/update/entities/customer-update-form.schema.ts b/modules/customers/src/web/update/entities/customer-update-form.schema.ts index 1b4ac7f2..c6591ca5 100644 --- a/modules/customers/src/web/update/entities/customer-update-form.schema.ts +++ b/modules/customers/src/web/update/entities/customer-update-form.schema.ts @@ -15,33 +15,33 @@ import { z } from "zod/v4"; */ export const CustomerUpdateFormSchema = z.object({ - reference: z.string().optional().or(z.literal("")), + reference: z.string(), isCompany: z.boolean(), name: z.string().min(1, "El nombre es obligatorio"), - tradeName: z.string().optional().or(z.literal("")), + tradeName: z.string(), tin: z.string(), defaultTaxes: z.array(z.string()), - street: z.string().optional().or(z.literal("")), - street2: z.string().optional().or(z.literal("")), - city: z.string().optional().or(z.literal("")), - province: z.string().optional().or(z.literal("")), - postalCode: z.string().optional().or(z.literal("")), - country: z.string().min(1, "El país es obligatorio").optional().or(z.literal("")), + street: z.string(), + street2: z.string(), + city: z.string(), + province: z.string(), + postalCode: z.string(), + country: z.string().min(1, "El país es obligatorio"), - primaryEmail: z.email("Email inválido").optional().or(z.literal("")), - secondaryEmail: z.email("Email inválido").optional().or(z.literal("")), + primaryEmail: z.email("Email inválido"), + secondaryEmail: z.email("Email inválido"), - primaryPhone: z.string().optional().or(z.literal("")), - secondaryPhone: z.string().optional().or(z.literal("")), - primaryMobile: z.string().optional().or(z.literal("")), - secondaryMobile: z.string().optional().or(z.literal("")), + primaryPhone: z.string(), + secondaryPhone: z.string(), + primaryMobile: z.string(), + secondaryMobile: z.string(), - fax: z.string().optional().or(z.literal("")), - website: z.url("URL inválida").optional().or(z.literal("")), + fax: z.string(), + website: z.url("URL inválida"), - legalRecord: z.string().optional().or(z.literal("")), + legalRecord: z.string(), languageCode: z.string().min(1, "El idioma es obligatorio"), currencyCode: z.string().min(1, "La moneda es obligatoria"), diff --git a/modules/customers/src/web/update/entities/customer-update-patch.entity.ts b/modules/customers/src/web/update/entities/customer-update-patch.entity.ts index 46a5670d..529586ed 100644 --- a/modules/customers/src/web/update/entities/customer-update-patch.entity.ts +++ b/modules/customers/src/web/update/entities/customer-update-patch.entity.ts @@ -1,12 +1,11 @@ import type { CustomerUpdateForm } from "./customer-update-form.entity"; /** - * CustomerUpdatePatch es un tipo que representa un objeto con las mismas - * propiedades que CustomerUpdateForm, pero todas ellas son opcionales. + * CustomerUpdatePatch representa los cambios que se van a aplicar a un cliente. + * Se representa con las mismas propiedades que CustomerUpdateForm, + * pero todas ellas son opcionales. * - * Esto es útil para representar los datos que se van a enviar a la API para actualizar un cliente, - * ya que en una actualización parcial (PATCH) no es necesario enviar todos los campos, - * sino solo aquellos que se quieren modificar. + * A la API solo hay que enviar los campos que han cambiado. * * Reglas: * - debe ser un Partial de CustomerUpdateForm diff --git a/modules/customers/src/web/update/ui/editor/customer-edit-form.tsx b/modules/customers/src/web/update/ui/editor/customer-edit-form.tsx index 816529ed..b8b854bf 100644 --- a/modules/customers/src/web/update/ui/editor/customer-edit-form.tsx +++ b/modules/customers/src/web/update/ui/editor/customer-edit-form.tsx @@ -5,13 +5,17 @@ import { CustomerAddressFields } from "./customer-address-fields"; import { CustomerBasicInfoFields } from "./customer-basic-info-fields"; import { CustomerContactFields } from "./customer-contact-fields"; -type CustomerFormProps = { +type CustomerUpdateEditorFormProps = { formId: string; onSubmit: (event: React.FormEvent) => void; className?: string; }; -export const CustomerEditForm = ({ formId, onSubmit, className }: CustomerFormProps) => { +export const CustomerUpdateEditorForm = ({ + formId, + onSubmit, + className, +}: CustomerUpdateEditorFormProps) => { return (
diff --git a/modules/customers/src/web/update/ui/pages/customer-update-page.tsx b/modules/customers/src/web/update/ui/pages/customer-update-page.tsx index e500f219..93f0a1fd 100644 --- a/modules/customers/src/web/update/ui/pages/customer-update-page.tsx +++ b/modules/customers/src/web/update/ui/pages/customer-update-page.tsx @@ -6,7 +6,7 @@ import { FormProvider } from "react-hook-form"; import { useTranslation } from "../../../i18n"; import { useCustomerUpdatePageController } from "../../controllers"; import { CustomerEditorSkeleton } from "../components"; -import { CustomerEditForm } from "../editor"; +import { CustomerUpdateEditorForm } from "../editor"; export const CustomerUpdatePage = () => { const { t } = useTranslation(); @@ -105,7 +105,7 @@ export const CustomerUpdatePage = () => { )} - +): CustomerUpdatePatch => { + return pickFormDirtyValues(formData, dirtyFields) as CustomerUpdatePatch; +}; diff --git a/modules/customers/src/web/update/utils/build-customer-update-patch.ts b/modules/customers/src/web/update/utils/build-update-customer-by-id-params.ts similarity index 55% rename from modules/customers/src/web/update/utils/build-customer-update-patch.ts rename to modules/customers/src/web/update/utils/build-update-customer-by-id-params.ts index f64a12f7..01069972 100644 --- a/modules/customers/src/web/update/utils/build-customer-update-patch.ts +++ b/modules/customers/src/web/update/utils/build-update-customer-by-id-params.ts @@ -1,7 +1,5 @@ -import { pickFormDirtyValues } from "@erp/core/client"; -import type { FieldNamesMarkedBoolean } from "react-hook-form"; - -import type { CustomerUpdateForm, CustomerUpdatePatch } from "../entities"; +import type { UpdateCustomerByIdParams } from "../../shared"; +import type { CustomerUpdatePatch } from "../entities"; /** * Construye un parche de actualización de cliente a partir de los datos @@ -15,15 +13,22 @@ import type { CustomerUpdateForm, CustomerUpdatePatch } from "../entities"; * - no debe tener transformaciones ni campos adicionales, solo los que vienen del * formulario y están sucios. * - * @param formData - * @param dirtyFields + * @param id - El ID del cliente que se va a actualizar. + * @param patchData - Los datos del parche de actualización. * @returns Un objeto que se puede enviar a la API para actualizar un cliente, * con solo los campos que han cambiado. */ -export const buildCustomerUpdatePatch = ( - formData: CustomerUpdateForm, - dirtyFields: FieldNamesMarkedBoolean -): CustomerUpdatePatch => { - return pickFormDirtyValues(formData, dirtyFields) as CustomerUpdatePatch; +export const buildUpdateCustomerByIdParams = ( + id: string, + patchData: CustomerUpdatePatch +): UpdateCustomerByIdParams => { + if (!id) { + throw new Error("customerId is required"); + } + + return { + id, + data: patchData, + } as UpdateCustomerByIdParams; }; diff --git a/modules/customers/src/web/update/utils/index.ts b/modules/customers/src/web/update/utils/index.ts index 5ecc5358..ec596089 100644 --- a/modules/customers/src/web/update/utils/index.ts +++ b/modules/customers/src/web/update/utils/index.ts @@ -1 +1,2 @@ -export * from "./build-customer-update-patch"; +export * from "./build-customer.update-patch"; +export * from "./build-update-customer-by-id-params";