Repaso de clientes

This commit is contained in:
David Arranz 2026-04-04 19:38:09 +02:00
parent 6f11d0cebd
commit 04184ebf1d
13 changed files with 98 additions and 99 deletions

View File

@ -4,4 +4,4 @@ import {
} from "./get-customer-by-id.response.dto"; } from "./get-customer-by-id.response.dto";
export const CreateCustomerResponseSchema = GetCustomerByIdResponseSchema; export const CreateCustomerResponseSchema = GetCustomerByIdResponseSchema;
export type CustomerCreationResponseDTO = GetCustomerByIdResponseDTO; export type CreateCustomerResponseDTO = GetCustomerByIdResponseDTO;

View File

@ -48,11 +48,11 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle
const submitHandler = form.handleSubmit( const submitHandler = form.handleSubmit(
async (formData) => { async (formData) => {
const inputPayload: CreateCustomerParams = buildCreateCustomerParams(formData); const params: CreateCustomerParams = buildCreateCustomerParams(formData);
try { try {
// Enviamos cambios al servidor // Enviamos cambios al servidor
const created = await mutateAsync(inputPayload); // payload es CustomerCreatePayload const created = await mutateAsync(params); // payload es CustomerCreatePayload
if (options?.successToasts !== false) { if (options?.successToasts !== false) {
showSuccessToast( showSuccessToast(
@ -69,7 +69,7 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle
showErrorToast(t("pages.create.error.title"), normalizedError.message); showErrorToast(t("pages.create.error.title"), normalizedError.message);
} }
options?.onError?.(normalizedError, inputPayload); options?.onError?.(normalizedError, params);
} }
}, },
(errors: FieldErrors<CustomerCreateForm>) => { (errors: FieldErrors<CustomerCreateForm>) => {

View File

@ -21,38 +21,42 @@ import type { CustomerCreateForm } from "../entities";
*/ */
export const buildCreateCustomerParams = (formData: CustomerCreateForm): CreateCustomerParams => { export const buildCreateCustomerParams = (formData: CustomerCreateForm): CreateCustomerParams => {
// La API de creación de cliente requiere un ID único para el nuevo cliente
const id = UniqueID.generateNewID().toString();
return { return {
// Generamos un ID único para el nuevo cliente. id,
// La API de creación de cliente requiere un ID,
id: UniqueID.generateNewID().toString(),
reference: formData.reference, data: {
is_company: formData.isCompany ? "1" : "0", id,
name: formData.name, reference: formData.reference,
trade_name: formData.tradeName, is_company: formData.isCompany ? "1" : "0",
tin: formData.tin, name: formData.name,
default_taxes: formData.defaultTaxes, trade_name: formData.tradeName,
tin: formData.tin,
default_taxes: formData.defaultTaxes,
street: formData.street, street: formData.street,
street2: formData.street2, street2: formData.street2,
city: formData.city, city: formData.city,
province: formData.province, province: formData.province,
postal_code: formData.postalCode, postal_code: formData.postalCode,
country: formData.country, country: formData.country,
email_primary: formData.primaryEmail, email_primary: formData.primaryEmail,
email_secondary: formData.secondaryEmail, email_secondary: formData.secondaryEmail,
phone_primary: formData.primaryPhone, phone_primary: formData.primaryPhone,
phone_secondary: formData.secondaryPhone, phone_secondary: formData.secondaryPhone,
mobile_primary: formData.primaryMobile, mobile_primary: formData.primaryMobile,
mobile_secondary: formData.secondaryMobile, mobile_secondary: formData.secondaryMobile,
fax: formData.fax, fax: formData.fax,
website: formData.website, website: formData.website,
legal_record: formData.legalRecord, legal_record: formData.legalRecord,
language_code: formData.languageCode, language_code: formData.languageCode,
currency_code: formData.currencyCode, currency_code: formData.currencyCode,
},
}; };
}; };

View File

@ -1,4 +1,4 @@
import type { CustomerCreationResponseDTO } from "@erp/customers/common"; import type { CreateCustomerResponseDTO } from "@erp/customers/common";
import type { GetCustomerByIdResult, UpdateCustomerByIdResult } from "../api"; import type { GetCustomerByIdResult, UpdateCustomerByIdResult } from "../api";
import type { Customer } from "../entities"; import type { Customer } from "../entities";
@ -18,7 +18,7 @@ import type { Customer } from "../entities";
export const GetCustomerByIdAdapter = { export const GetCustomerByIdAdapter = {
fromDTO( fromDTO(
dto: GetCustomerByIdResult | CustomerCreationResponseDTO | UpdateCustomerByIdResult, dto: GetCustomerByIdResult | CreateCustomerResponseDTO | UpdateCustomerByIdResult,
context?: unknown context?: unknown
): Customer { ): Customer {
const taxesAdapter = (taxes: string) => const taxesAdapter = (taxes: string) =>

View File

@ -1,6 +1,6 @@
import type { IDataSource } from "@erp/core/client"; import type { IDataSource } from "@erp/core/client";
import type { CreateCustomerRequestDTO, CustomerCreationResponseDTO } from "../../../common"; import type { CreateCustomerRequestDTO, CreateCustomerResponseDTO } from "../../../common";
/** /**
* Crea un nuevo cliente en el sistema utilizando la fuente de datos proporcionada. * Crea un nuevo cliente en el sistema utilizando la fuente de datos proporcionada.
@ -11,12 +11,22 @@ import type { CreateCustomerRequestDTO, CustomerCreationResponseDTO } from "../.
* @throws Error si el ID del cliente no es proporcionado o si la creación falla. * @throws Error si el ID del cliente no es proporcionado o si la creación falla.
*/ */
export type CreateCustomerParams = CreateCustomerRequestDTO; export interface CreateCustomerParams {
id: string;
data: CreateCustomerRequestDTO;
}
export type CreateCustomerResult = CustomerCreationResponseDTO; export type CreateCustomerResult = CreateCustomerResponseDTO;
export function createCustomer(dataSource: IDataSource, params: CreateCustomerParams) { export function createCustomer(dataSource: IDataSource, params: CreateCustomerParams) {
const { id } = params; const { id, data } = params;
if (!id) throw new Error("customerId is required"); if (!id) throw new Error("customerId is required");
return dataSource.createOne<CreateCustomerRequestDTO, CreateCustomerResult>("customers", params);
if (id !== data.id) throw new Error("customerId in params must match id in data");
return dataSource.createOne<CreateCustomerRequestDTO, CreateCustomerResponseDTO>(
"customers",
data
);
} }

View File

@ -5,23 +5,22 @@ import type { IDataSource } from "@erp/core/client";
* *
* @param dataSource - La fuente de datos para interactuar con la API. * @param dataSource - La fuente de datos para interactuar con la API.
* @param params - Los parámetros necesarios para eliminar el cliente. * @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. * @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. * @throws Error si el ID del cliente no es proporcionado o si la eliminación falla.
*/ */
export type DeleteCustomerByIdParams = { export interface DeleteCustomerByIdParams {
id: string; id: string;
}; signal?: AbortSignal;
}
export type DeleteCustomerByIdResult = void; export type DeleteCustomerByIdResult = void;
export function deleteCustomerById( export function deleteCustomerById(
dataSource: IDataSource, dataSource: IDataSource,
params: DeleteCustomerByIdParams, params: DeleteCustomerByIdParams
signal?: AbortSignal
): Promise<DeleteCustomerByIdResult> { ): Promise<DeleteCustomerByIdResult> {
const { id } = params; const { id, signal } = params;
if (!id) throw new Error("customerId is required"); if (!id) throw new Error("customerId is required");
return dataSource.deleteOne<DeleteCustomerByIdResult>("customers", id, { return dataSource.deleteOne<DeleteCustomerByIdResult>("customers", id, {
signal, signal,

View File

@ -7,23 +7,19 @@ import type { GetCustomerByIdResponseDTO } from "../../../common";
* *
* @param dataSource - La fuente de datos para interactuar con la API. * @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 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. * @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. * @throws Error si el ID del cliente no es proporcionado o si la recuperación falla.
*/ */
export type GetCustomerByIdParams = { export interface GetCustomerByIdParams {
id: string; id: string;
}; signal?: AbortSignal;
}
export type GetCustomerByIdResult = GetCustomerByIdResponseDTO; export type GetCustomerByIdResult = GetCustomerByIdResponseDTO;
export function getCustomerById( export function getCustomerById(dataSource: IDataSource, params: GetCustomerByIdParams) {
dataSource: IDataSource, const { id, signal } = params;
params: GetCustomerByIdParams,
signal?: AbortSignal
) {
const { id } = params;
if (!id) throw new Error("customerId is required"); if (!id) throw new Error("customerId is required");
return dataSource.getOne<GetCustomerByIdResult>("customers", id, { signal }); return dataSource.getOne<GetCustomerByIdResponseDTO>("customers", id, { signal });
} }

View File

@ -1,5 +1,5 @@
export * from "./create-customer.api"; export * from "./create-customer.api";
export * from "./delete-customer-by-id.api"; export * from "./delete-customer-by-id.api";
export * from "./get-customer-by-id.api"; export * from "./get-customer-by-id.api";
export * from "./get-list-customers-by-criteria.api"; export * from "./list-customers-by-criteria.api";
export * from "./update-customer-by-id.api"; export * from "./update-customer-by-id.api";

View File

@ -9,24 +9,23 @@ import type { ListCustomersResponseDTO } from "../../../common";
* *
* @param dataSource - La fuente de datos para interactuar con la API. * @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 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. * @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. * @throws Error si la recuperación de la lista de clientes falla.
*/ */
export type ListCustomersByCriteriaParams = { export type ListCustomersByCriteriaParams = {
criteria: CriteriaDTO; criteria?: CriteriaDTO;
signal?: AbortSignal;
}; };
export type ListCustomersResult = ListCustomersResponseDTO; export type ListCustomersResult = ListCustomersResponseDTO;
export function getListCustomersByCriteria( export function getListCustomersByCriteria(
dataSource: IDataSource, dataSource: IDataSource,
params: ListCustomersByCriteriaParams, params: ListCustomersByCriteriaParams
signal?: AbortSignal
): Promise<ListCustomersResult> { ): Promise<ListCustomersResult> {
const { criteria } = params; const { criteria, signal } = params || { criteria: undefined, signal: undefined };
return dataSource.getList<ListCustomersResult>("customers", { return dataSource.getList<ListCustomersResponseDTO>("customers", {
signal, signal,
...criteria, ...criteria,
}); });

View File

@ -25,7 +25,7 @@ export function updateCustomerById(
const { id, data } = params; const { id, data } = params;
if (!id) throw new Error("customerId is required"); if (!id) throw new Error("customerId is required");
return dataSource.updateOne<UpdateCustomerByIdRequestDTO, UpdateCustomerByIdResult>( return dataSource.updateOne<UpdateCustomerByIdRequestDTO, UpdateCustomerByIdResponseDTO>(
"customers", "customers",
id, id,
data data

View File

@ -1,5 +1,5 @@
import { useDataSource } from "@erp/core/hooks"; import { useDataSource } from "@erp/core/hooks";
import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd"; import { ValidationErrorCollection } from "@repo/rdx-ddd";
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
import { CreateCustomerRequestSchema } from "../../../common"; import { CreateCustomerRequestSchema } from "../../../common";
@ -25,15 +25,12 @@ export const useCustomerCreateMutation = () => {
mutationKey: CUSTOMER_CREATE_KEY, mutationKey: CUSTOMER_CREATE_KEY,
mutationFn: async (payload) => { mutationFn: async (payload) => {
const id = UniqueID.generateNewID().toString(); const result = schema.safeParse(payload);
const payloadWithId: CreateCustomerParams = { ...payload, id };
const result = schema.safeParse(payloadWithId);
if (!result.success) { if (!result.success) {
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error)); throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
} }
const dto: CreateCustomerResult = await createCustomer(dataSource, payloadWithId); const dto: CreateCustomerResult = await createCustomer(dataSource, payload);
return GetCustomerByIdAdapter.fromDTO(dto); return GetCustomerByIdAdapter.fromDTO(dto);
}, },

View File

@ -1,11 +1,7 @@
import { useDataSource } from "@erp/core/hooks"; import { useDataSource } from "@erp/core/hooks";
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query"; import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
import { import { type DeleteCustomerByIdParams, deleteCustomerById } from "../api";
type DeleteCustomerByIdParams,
type DeleteCustomerByIdResult,
deleteCustomerById,
} from "../api";
import { import {
type DeleteCustomerCacheContext, type DeleteCustomerCacheContext,
@ -22,33 +18,31 @@ export const useCustomerDeleteMutation = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const dataSource = useDataSource(); const dataSource = useDataSource();
return useMutation< return useMutation<{ id: string }, DefaultError, DeleteCustomerByIdParams, DeleteCustomerContext>(
DeleteCustomerByIdResult, {
DefaultError, mutationKey: CUSTOMER_DELETE_KEY,
DeleteCustomerByIdParams, mutationFn: async ({ id, signal }) => {
DeleteCustomerContext if (!id) {
>({ throw new Error("customerId is required");
mutationKey: CUSTOMER_DELETE_KEY, }
mutationFn: async ({ id }) => {
if (!id) {
throw new Error("customerId is required");
}
await deleteCustomerById(dataSource, { id }, new AbortController().signal); await deleteCustomerById(dataSource, { id, signal });
}, return { id };
onMutate: async ({ id }) => { },
return prepareDeleteCustomerOptimisticUpdate(queryClient, id); onMutate: async ({ id }) => {
}, return prepareDeleteCustomerOptimisticUpdate(queryClient, id);
},
onError: (_error, _variables, context) => { onError: (_error, _variables, context) => {
rollbackDeleteCustomerOptimisticUpdate(queryClient, context); rollbackDeleteCustomerOptimisticUpdate(queryClient, context);
}, },
onSuccess: (_, variables) => { onSuccess: ({ id }) => {
finalizeDeletedCustomerCaches(queryClient, variables.id); finalizeDeletedCustomerCaches(queryClient, id);
}, },
onSettled: async () => { onSettled: async () => {
await invalidateCustomerListQueries(queryClient); await invalidateCustomerListQueries(queryClient);
}, },
}); }
);
}; };

View File

@ -98,8 +98,8 @@ export const useCustomerUpdateController = (
} }
const previousData = customerData; const previousData = customerData;
const patchData = buildCustomerUpdatePatch(formData, dirtyFields);
const patchData = buildCustomerUpdatePatch(formData, dirtyFields);
const params: UpdateCustomerByIdParams = buildUpdateCustomerByIdParams(customerId, patchData); const params: UpdateCustomerByIdParams = buildUpdateCustomerByIdParams(customerId, patchData);
try { try {