From 01ca0bd5dcdbf2576c7d78c6e2b6c6b3f86343a6 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 23 Sep 2025 13:48:19 +0200 Subject: [PATCH] Clientes --- .../customer-invoices-list-grid.tsx | 4 +- .../request/create-customer.request.dto.ts | 6 +-- .../web/components/customers-list-grid.tsx | 10 +++-- .../editor/customer-basic-info-fields.tsx | 5 ++- .../components/editor/customer-edit-form.tsx | 4 +- .../web/hooks/use-create-customer-mutation.ts | 27 +++++++++--- .../src/web/pages/create/customer-create.tsx | 42 +++++++++--------- .../src/web/schemas/customer.form.schema.ts | 43 ++++++++++++++----- 8 files changed, 94 insertions(+), 47 deletions(-) diff --git a/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx b/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx index c3b80f01..13669e01 100644 --- a/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx +++ b/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx @@ -134,8 +134,8 @@ export const CustomerInvoicesListGrid = () => { resizable: true, }, pagination: true, - paginationPageSize: 10, - paginationPageSizeSelector: [10, 20, 30, 50], + paginationPageSize: 15, + paginationPageSizeSelector: [10, 15, 20, 30, 50], localeText: AG_GRID_LOCALE_ES, }), [autoSizeStrategy, colDefs] diff --git a/modules/customers/src/common/dto/request/create-customer.request.dto.ts b/modules/customers/src/common/dto/request/create-customer.request.dto.ts index 09e9f6e6..64a610fd 100644 --- a/modules/customers/src/common/dto/request/create-customer.request.dto.ts +++ b/modules/customers/src/common/dto/request/create-customer.request.dto.ts @@ -4,18 +4,18 @@ export const CreateCustomerRequestSchema = z.object({ id: z.string().nonempty(), reference: z.string().optional(), - is_company: z.string().toLowerCase().default("false"), + is_company: z.string().toLowerCase().default("true"), name: z.string(), trade_name: z.string().optional(), tin: z.string().optional(), - default_taxes: z.array(z.string()).default([]).optional(), + default_taxes: z.array(z.string()).default([]), street: z.string().optional(), street2: z.string().optional(), city: z.string().optional(), province: z.string().optional(), postal_code: z.string().optional(), - country: z.string().default("es").optional(), + country: z.string().toLowerCase().default("es").optional(), email_primary: z.string().optional(), email_secondary: z.string().optional(), diff --git a/modules/customers/src/web/components/customers-list-grid.tsx b/modules/customers/src/web/components/customers-list-grid.tsx index 0c493983..aa4534f2 100644 --- a/modules/customers/src/web/components/customers-list-grid.tsx +++ b/modules/customers/src/web/components/customers-list-grid.tsx @@ -31,7 +31,11 @@ export const CustomersListGrid = () => { isLoading: isLoadingCustomers, isError: isLoadError, error: loadError, - } = useCustomersQuery(); + } = useCustomersQuery({ + pagination: { + pageSize: 999, + }, + }); // Column Definitions: Defines & controls grid columns. const [colDefs] = useState([ @@ -113,8 +117,8 @@ export const CustomersListGrid = () => { resizable: true, }, pagination: true, - paginationPageSize: 10, - paginationPageSizeSelector: [10, 20, 30, 50], + paginationPageSize: 15, + paginationPageSizeSelector: [10, 15, 20, 30, 50], localeText: AG_GRID_LOCALE_ES, }), [autoSizeStrategy, colDefs] diff --git a/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx b/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx index ebf11161..00375264 100644 --- a/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx @@ -22,6 +22,7 @@ export const CustomerBasicInfoFields = () => { const { control } = useFormContext(); const isCompany = useWatch({ + control, name: "is_company", defaultValue: "true", }); @@ -53,7 +54,9 @@ export const CustomerBasicInfoFields = () => { {t("form_fields.customer_type.label")} { + field.onChange(value === "false" ? "false" : "true"); + }} defaultValue={field.value ? "true" : "false"} className='flex items-center gap-6' > diff --git a/modules/customers/src/web/components/editor/customer-edit-form.tsx b/modules/customers/src/web/components/editor/customer-edit-form.tsx index 594585b1..ff59335f 100644 --- a/modules/customers/src/web/components/editor/customer-edit-form.tsx +++ b/modules/customers/src/web/components/editor/customer-edit-form.tsx @@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { FieldErrors, FormProvider, useForm } from "react-hook-form"; import { useEffect } from "react"; -import { CustomerFormData, CustomerFormSchema } from "../../schemas"; +import { CreateCustomerFormSchema, CustomerFormData } from "../../schemas"; import { FormDebug } from "../form-debug"; import { CustomerAdditionalConfigFields } from "./customer-additional-config-fields"; import { CustomerAddressFields } from "./customer-address-fields"; @@ -27,7 +27,7 @@ export function CustomerEditForm({ onDirtyChange, }: CustomerFormProps) { const form = useForm({ - resolver: zodResolver(CustomerFormSchema), + resolver: zodResolver(CreateCustomerFormSchema), defaultValues: initialValues, disabled, }); diff --git a/modules/customers/src/web/hooks/use-create-customer-mutation.ts b/modules/customers/src/web/hooks/use-create-customer-mutation.ts index 5dd2364a..6bab5c33 100644 --- a/modules/customers/src/web/hooks/use-create-customer-mutation.ts +++ b/modules/customers/src/web/hooks/use-create-customer-mutation.ts @@ -1,6 +1,7 @@ import { useDataSource } from "@erp/core/hooks"; -import { UniqueID } from "@repo/rdx-ddd"; +import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { CreateCustomerRequestSchema, CustomerCreationResponseDTO } from "../../common"; import { CustomerFormData } from "../schemas"; import { CUSTOMERS_LIST_KEY } from "./use-update-customer-mutation"; @@ -11,19 +12,35 @@ type CreateCustomerPayload = { export function useCreateCustomerMutation() { const queryClient = useQueryClient(); const dataSource = useDataSource(); + const schema = CreateCustomerRequestSchema; - return useMutation({ + return useMutation({ mutationKey: ["customer:create"], mutationFn: async (payload) => { const { data } = payload; const customerId = UniqueID.generateNewID(); - const created = await dataSource.createOne("customers", { + const newCustomerData = { ...data, id: customerId.toString(), - }); - return created as CustomerFormData; + }; + + const result = schema.safeParse(newCustomerData); + if (!result.success) { + // Construye errores detallados + const validationErrors = result.error.issues.map((err) => ({ + field: err.path.join("."), + message: err.message, + })); + + console.debug(validationErrors); + + throw new ValidationErrorCollection("Validation failed", validationErrors); + } + + const created = await dataSource.createOne("customers", newCustomerData); + return created as CustomerCreationResponseDTO; }, onSuccess: () => { diff --git a/modules/customers/src/web/pages/create/customer-create.tsx b/modules/customers/src/web/pages/create/customer-create.tsx index 1cdfdb84..fa6577f9 100644 --- a/modules/customers/src/web/pages/create/customer-create.tsx +++ b/modules/customers/src/web/pages/create/customer-create.tsx @@ -17,35 +17,35 @@ export const CustomerCreate = () => { // 2) Estado de creación (mutación) const { - mutateAsync, + mutate, isPending: isCreating, isError: isCreateError, error: createError, } = useCreateCustomerMutation(); // 3) Submit con navegación condicionada por éxito - const handleSubmit = async (formData: CustomerFormData) => { - console.log("formData => ", formData); + const handleSubmit = (formData: CustomerFormData) => { + mutate( + { data: formData }, + { + onSuccess(data) { + setIsDirty(false); + showSuccessToast(t("pages.create.successTitle"), t("pages.create.successMsg")); - /*const changedData: Record = {}; - - Object.keys(dirtyFields).forEach((field) => { - const value = String(currentValues[field as keyof CustomerFormData]); - changedData[field] = value; - });*/ - - try { - const result = await mutateAsync({ data: formData }); - console.log(result); - - if (result) { - showSuccessToast(t("pages.create.successTitle"), t("pages.create.successMsg")); - navigate("/customers/list"); + // El timeout es para que a React le dé tiempo a procesar + // el cambio de estado de isDirty / setIsDirty. + setTimeout(() => { + navigate("/customers/list", { + state: { customerId: data.id, isNew: true }, + replace: true, + }); + }, 0); + }, + onError(error) { + showErrorToast(t("pages.create.errorTitle"), error.message); + }, } - } catch (e) { - showErrorToast(t("pages.create.errorTitle"), (e as Error).message); - } finally { - } + ); }; const handleError = (errors: FieldErrors) => { diff --git a/modules/customers/src/web/schemas/customer.form.schema.ts b/modules/customers/src/web/schemas/customer.form.schema.ts index 233e27dc..1a597249 100644 --- a/modules/customers/src/web/schemas/customer.form.schema.ts +++ b/modules/customers/src/web/schemas/customer.form.schema.ts @@ -1,20 +1,30 @@ import * as z from "zod/v4"; -export const CustomerFormSchema = z.object({ +export const CreateCustomerFormSchema = z.object({ reference: z.string().optional(), - is_company: z.string().optional(), - name: z.string().nonempty(), + is_company: z.enum(["true", "false"]), + name: z + .string({ + error: "El nombre es obligatorio", + }) + .min(1, "El nombre no puede estar vacío"), trade_name: z.string().optional(), tin: z.string().optional(), - default_taxes: z.array(z.string()).optional(), // completo (sustituye), o null => vaciar + default_taxes: z.array(z.string()).default([]), street: z.string().optional(), street2: z.string().optional(), city: z.string().optional(), province: z.string().optional(), postal_code: z.string().optional(), - country: z.string().optional(), + country: z + .string({ + error: "El país es obligatorio", + }) + .min(1, "El país no puede estar vacío") + .toLowerCase() // asegura minúsculas + .default("es"), email_primary: z.string().optional(), email_secondary: z.string().optional(), @@ -28,20 +38,33 @@ export const CustomerFormSchema = z.object({ legal_record: z.string().optional(), - language_code: z.string().nonempty(), - currency_code: z.string().nonempty(), + language_code: z + .string({ + error: "El idioma es obligatorio", + }) + .min(1, "Debe indicar un idioma") + .toUpperCase() // asegura mayúsculas + .default("es"), + + currency_code: z + .string({ + error: "La moneda es obligatoria", + }) + .min(1, "La moneda no puede estar vacía") + .toUpperCase() // asegura mayúsculas + .default("EUR"), }); -export type CustomerFormData = z.infer; +export type CustomerFormData = z.infer; export const defaultCustomerFormData: CustomerFormData = { reference: "", - is_company: "false", + is_company: "true", name: "", trade_name: "", tin: "", - default_taxes: [], + default_taxes: ["iva_21"], street: "", street2: "",