.
This commit is contained in:
parent
bc554c180e
commit
b5f4c4cdfc
151
modules/core/src/api/application/mappers/patch-collector.ts
Normal file
151
modules/core/src/api/application/mappers/patch-collector.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import { DomainError, ValidationErrorCollection, type ValidationErrorDetail } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
/**
|
||||
* PatchCollector
|
||||
*
|
||||
* Utilidad de apoyo para la construcción de objetos de tipo PATCH en mappers de Application.
|
||||
*
|
||||
* Responsabilidad:
|
||||
* - Acumular propiedades parciales válidas (`patch`)
|
||||
* - Centralizar la validación de entrada (DTO → Value Objects)
|
||||
* - Registrar errores de validación de forma homogénea
|
||||
* - Construir un `Result<T>` final consistente (éxito o `ValidationErrorCollection`)
|
||||
*
|
||||
* Contexto de uso:
|
||||
* - Mappers de tipo `RequestDTO -> *PatchProps`
|
||||
* - Casos de uso de actualización (semántica PATCH)
|
||||
*
|
||||
* Semántica aplicada (PATCH):
|
||||
* - `undefined` → campo omitido → no se modifica
|
||||
* - `null` → eliminación explícita (solo en campos nullable)
|
||||
* - valor definido → validación + transformación a VO → asignación
|
||||
*
|
||||
* Capacidades:
|
||||
* - Helpers para distintos tipos de campo:
|
||||
* - `setBoolean` → campos primitivos no anulables
|
||||
* - `setRequiredVo` → campos obligatorios cuando se envían
|
||||
* - `setNullableVo` → campos opcionales/anulables (usa Maybe/null)
|
||||
* - `setNested` → composición de sub-objetos (address, contact, etc.)
|
||||
*
|
||||
* Reglas de diseño:
|
||||
* - No contiene lógica de negocio
|
||||
* - No accede a repositorios ni infraestructura
|
||||
* - No construye agregados
|
||||
* - No conoce DTOs concretos ni dominio específico
|
||||
* - Es determinista y libre de efectos secundarios externos
|
||||
*
|
||||
* Manejo de errores:
|
||||
* - Los errores se acumulan como `ValidationErrorDetail[]`
|
||||
* - `build()` devuelve:
|
||||
* - `Result.ok(patch)` si no hay errores
|
||||
* - `Result.fail(ValidationErrorCollection)` si existen errores
|
||||
*
|
||||
* Objetivo:
|
||||
* Reducir complejidad accidental en mappers, evitar duplicación de lógica
|
||||
* y garantizar consistencia en validación y reporting de errores.
|
||||
*/
|
||||
export class PatchCollector {
|
||||
private readonly errors: ValidationErrorDetail[] = [];
|
||||
|
||||
public addError(path: string, message: string): void {
|
||||
this.errors.push({ path, message });
|
||||
}
|
||||
|
||||
public hasErrors(): boolean {
|
||||
return this.errors.length > 0;
|
||||
}
|
||||
|
||||
public build<T>(value: T, message: string): Result<T, Error> {
|
||||
if (this.errors.length > 0) {
|
||||
return Result.fail(new ValidationErrorCollection(message, this.errors));
|
||||
}
|
||||
|
||||
return Result.ok(value);
|
||||
}
|
||||
|
||||
public failUnexpected(message: string, cause: unknown): Result<never, Error> {
|
||||
return Result.fail(new DomainError(message, { cause }));
|
||||
}
|
||||
|
||||
public setBoolean(
|
||||
path: string,
|
||||
input: boolean | undefined,
|
||||
setter: (value: boolean) => void
|
||||
): void {
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
setter(input);
|
||||
}
|
||||
|
||||
public setRequiredVo<T>(
|
||||
path: string,
|
||||
input: string | undefined,
|
||||
factory: (value: string) => Result<T, Error>,
|
||||
setter: (value: T) => void,
|
||||
emptyMessage = "Value cannot be empty"
|
||||
): void {
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.trim() === "") {
|
||||
this.addError(path, emptyMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = factory(input);
|
||||
if (result.isFailure) {
|
||||
this.addError(path, result.error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setter(result.data);
|
||||
}
|
||||
|
||||
public setNullableVo<T>(
|
||||
path: string,
|
||||
input: string | null | undefined,
|
||||
factory: (value: string) => Result<T, Error>,
|
||||
setter: (value: ReturnType<typeof this.wrapNullable<T>>) => void
|
||||
): void {
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input === null) {
|
||||
setter(this.wrapNullable<T>(null));
|
||||
return;
|
||||
}
|
||||
|
||||
const result = factory(input);
|
||||
if (result.isFailure) {
|
||||
this.addError(path, result.error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setter(this.wrapNullable(result.data));
|
||||
}
|
||||
|
||||
public setNested<TInput>(
|
||||
_path: string,
|
||||
input: TInput | undefined,
|
||||
mapper: (value: TInput) => void
|
||||
): void {
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapper(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper adaptable al tipo Maybe/nullable real del proyecto.
|
||||
* Aquí lo dejo abstracto para no acoplar el ejemplo a una implementación concreta.
|
||||
*/
|
||||
private wrapNullable<T>(value: T | null): T | null {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
// packages/rdx-ui-or-core/src/forms/apply-validation-error-collection.ts
|
||||
import type { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import type { FieldValues, Path, UseFormReturn } from "react-hook-form";
|
||||
|
||||
export interface ApplyValidationErrorCollectionOptions<TFormValues extends FieldValues> {
|
||||
/**
|
||||
* Permite transformar paths del backend al path real del formulario.
|
||||
* Ej:
|
||||
* - "email_primary" -> "emailPrimary"
|
||||
* - "recipient.name" -> "recipient.name"
|
||||
*/
|
||||
mapPath?: (backendPath: string) => Path<TFormValues> | undefined;
|
||||
|
||||
/**
|
||||
* Nombre del campo raíz donde dejar errores globales/no mapeables.
|
||||
* Opcional. Si no existe, simplemente no se asignan esos errores al form.
|
||||
*/
|
||||
rootFieldName?: Path<TFormValues>;
|
||||
|
||||
/**
|
||||
* Si true, evita sobrescribir el mismo campo varias veces.
|
||||
* Útil si backend manda varios errores sobre el mismo path.
|
||||
*/
|
||||
keepFirstErrorPerField?: boolean;
|
||||
}
|
||||
|
||||
const normalizeBackendPath = (path: string): string => {
|
||||
return path
|
||||
.trim()
|
||||
.replace(/\[(\d+)\]/g, ".$1") // lines[0].name -> lines.0.name
|
||||
.replace(/^\.+|\.+$/g, "") // quita puntos al principio/final
|
||||
.replace(/\.{2,}/g, "."); // colapsa puntos dobles
|
||||
};
|
||||
|
||||
const defaultMapPath = <TFormValues extends FieldValues>(
|
||||
backendPath: string
|
||||
): Path<TFormValues> | undefined => {
|
||||
const normalized = normalizeBackendPath(backendPath);
|
||||
return normalized as Path<TFormValues>;
|
||||
};
|
||||
|
||||
export const applyValidationErrorCollection = <TFormValues extends FieldValues>(
|
||||
form: UseFormReturn<TFormValues>,
|
||||
error: ValidationErrorCollection,
|
||||
options?: ApplyValidationErrorCollectionOptions<TFormValues>
|
||||
) => {
|
||||
const mapPath = options?.mapPath ?? defaultMapPath<TFormValues>;
|
||||
const rootFieldName = options?.rootFieldName;
|
||||
const keepFirstErrorPerField = options?.keepFirstErrorPerField ?? true;
|
||||
|
||||
const assignedFields = new Set<string>();
|
||||
const globalMessages: string[] = [];
|
||||
|
||||
for (const detail of error.details) {
|
||||
const message = detail.message?.trim();
|
||||
const rawPath = detail.path?.trim();
|
||||
|
||||
if (!message) continue;
|
||||
|
||||
if (!rawPath) {
|
||||
globalMessages.push(message);
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldPath = mapPath(rawPath);
|
||||
|
||||
if (!fieldPath) {
|
||||
globalMessages.push(message);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keepFirstErrorPerField && assignedFields.has(fieldPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
form.setError(fieldPath, {
|
||||
type: "server",
|
||||
message,
|
||||
});
|
||||
|
||||
assignedFields.add(fieldPath);
|
||||
}
|
||||
|
||||
if (globalMessages.length > 0 && rootFieldName) {
|
||||
form.setError(rootFieldName, {
|
||||
type: "server",
|
||||
message: globalMessages.join("\n"),
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./apply-validation-error-collection";
|
||||
export * from "./date-helper";
|
||||
export * from "./dto-compare-helper";
|
||||
export * from "./money-dto-helper";
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
export const COUNTRY_OPTIONS = [
|
||||
{ value: "es", label: "España" },
|
||||
{ value: "fr", label: "Francia" },
|
||||
{ value: "de", label: "Alemania" },
|
||||
{ value: "it", label: "Italia" },
|
||||
{ value: "pt", label: "Portugal" },
|
||||
{ value: "us", label: "Estados Unidos" },
|
||||
{ value: "mx", label: "México" },
|
||||
{ value: "ar", label: "Argentina" },
|
||||
{ value: "ES", label: "España" },
|
||||
{ value: "FR", label: "Francia" },
|
||||
{ value: "DE", label: "Alemania" },
|
||||
{ value: "IT", label: "Italia" },
|
||||
{ value: "PT", label: "Portugal" },
|
||||
{ value: "US", label: "Estados Unidos" },
|
||||
{ value: "MX", label: "México" },
|
||||
{ value: "AR", label: "Argentina" },
|
||||
] as const;
|
||||
|
||||
export const LANGUAGE_OPTIONS = [
|
||||
|
||||
@ -33,7 +33,13 @@ export const useCustomerUpdateMutation = () => {
|
||||
|
||||
const result = schema.safeParse(data);
|
||||
if (!result.success) {
|
||||
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
||||
console.log("Error de validación al actualizar cliente:", toValidationErrors(result.error));
|
||||
const errorCollection = new ValidationErrorCollection(
|
||||
"Validation failed",
|
||||
toValidationErrors(result.error)
|
||||
);
|
||||
console.log("Errores de validación convertidos:", errorCollection);
|
||||
throw errorCollection;
|
||||
}
|
||||
|
||||
const dto: UpdateCustomerByIdResult = await updateCustomerById(dataSource, params);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { applyValidationErrorCollection } from "@erp/core";
|
||||
import { useHookForm } from "@erp/core/hooks";
|
||||
import { type ValidationErrorCollection, isValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers";
|
||||
@ -17,8 +18,11 @@ import {
|
||||
CustomerUpdateFormSchema,
|
||||
defaultCustomerUpdateForm,
|
||||
} from "../entities";
|
||||
import { buildCustomerUpdatePatch, buildUpdateCustomerByIdParams } from "../utils";
|
||||
import { focusFirstCustomerUpdateError } from "../utils/focus-first-customer-update-error";
|
||||
import {
|
||||
buildCustomerUpdatePatch,
|
||||
buildUpdateCustomerByIdParams,
|
||||
focusFirstCustomerUpdateFormError,
|
||||
} from "../utils";
|
||||
|
||||
export interface UseCustomerUpdateControllerOptions {
|
||||
onUpdated?(updated: Customer): void;
|
||||
@ -28,6 +32,18 @@ export interface UseCustomerUpdateControllerOptions {
|
||||
errorToasts?: boolean; // mostrar o no toast automáticamente
|
||||
}
|
||||
|
||||
const normalizeSubmitError = (error: unknown): Error | ValidationErrorCollection => {
|
||||
if (isValidationErrorCollection(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return new Error("Unknown error");
|
||||
};
|
||||
|
||||
export const useCustomerUpdateController = (
|
||||
customerId?: string,
|
||||
options?: UseCustomerUpdateControllerOptions
|
||||
@ -91,9 +107,20 @@ export const useCustomerUpdateController = (
|
||||
return;
|
||||
}
|
||||
|
||||
const previousData = customerData;
|
||||
//const previousData = customerData;
|
||||
|
||||
const patchData = buildCustomerUpdatePatch(formData, form.formState.dirtyFields);
|
||||
|
||||
if (Object.keys(patchData).length === 0) {
|
||||
showWarningToast(
|
||||
t("pages.update.no_changes.title", "Sin cambios"),
|
||||
t("pages.update.no_changes.message", "No has realizado ningún cambio.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Parche de actualización construido:", patchData);
|
||||
|
||||
const params = buildUpdateCustomerByIdParams(customerId, patchData);
|
||||
|
||||
console.log("Enviando actualización con params:", params);
|
||||
@ -117,25 +144,49 @@ export const useCustomerUpdateController = (
|
||||
|
||||
options?.onUpdated?.(updated);
|
||||
} catch (error: unknown) {
|
||||
const normalizedError = isValidationErrorCollection(error)
|
||||
? (error as ValidationErrorCollection)
|
||||
: (error as Error);
|
||||
const normalizedError = normalizeSubmitError(error);
|
||||
|
||||
// Revertir form a datos anteriores (si los hay)
|
||||
form.reset(
|
||||
// No revierto el form para que no se pierdan los cambios que
|
||||
// ha hecho el usuario y no han sido guardados.
|
||||
/*form.reset(
|
||||
previousData ? mapCustomerToCustomerUpdateForm(previousData) : defaultCustomerUpdateForm,
|
||||
{ keepDirty: false }
|
||||
);
|
||||
);*/
|
||||
|
||||
if (isValidationErrorCollection(normalizedError)) {
|
||||
applyValidationErrorCollection(form, normalizedError);
|
||||
|
||||
console.log("Errores de validación aplicados al form:", form.formState.errors);
|
||||
|
||||
focusFirstCustomerUpdateFormError(form);
|
||||
|
||||
if (options?.errorToasts !== false) {
|
||||
showWarningToast(
|
||||
t("pages.update.validation_error.title", "Revisa los campos"),
|
||||
t(
|
||||
"pages.update.validation_error.message",
|
||||
"Hay errores de validación. Corrige los campos indicados."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
options?.onError?.(normalizedError, params);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options?.errorToasts !== false) {
|
||||
showErrorToast(t("pages.update.error.title"), normalizedError.message);
|
||||
showErrorToast(
|
||||
t("pages.update.error.title", "No se pudo modificar el cliente"),
|
||||
normalizedError.message ||
|
||||
t("common.errors.unexpected", "Ha ocurrido un error inesperado.")
|
||||
);
|
||||
}
|
||||
|
||||
options?.onError?.(normalizedError, params);
|
||||
}
|
||||
},
|
||||
(errors: FieldErrors<CustomerUpdateForm>) => {
|
||||
focusFirstCustomerUpdateError(errors);
|
||||
focusFirstCustomerUpdateFormError(form);
|
||||
|
||||
showWarningToast(
|
||||
t("forms.validation.title", "Revisa los campos"),
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
import {
|
||||
CountryCodeSchema,
|
||||
CurrencyCodeSchema,
|
||||
LandPhoneSchema,
|
||||
LanguageCodeSchema,
|
||||
MobilePhoneSchema,
|
||||
PostalCodeSchema,
|
||||
TinSchema,
|
||||
URLSchema,
|
||||
} from "@erp/core";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
@ -18,9 +28,10 @@ import { z } from "zod/v4";
|
||||
export const CustomerUpdateFormSchema = z.object({
|
||||
reference: z.string(),
|
||||
isCompany: z.boolean(),
|
||||
|
||||
name: z.string().min(1, "El nombre es obligatorio"),
|
||||
tradeName: z.string(),
|
||||
tin: z.string(),
|
||||
tin: TinSchema,
|
||||
|
||||
defaultTaxes: z.array(z.string()),
|
||||
|
||||
@ -28,22 +39,22 @@ export const CustomerUpdateFormSchema = z.object({
|
||||
street2: z.string(),
|
||||
city: z.string(),
|
||||
province: z.string(),
|
||||
postalCode: z.string(),
|
||||
country: z.string().min(1, "El país es obligatorio"),
|
||||
postalCode: PostalCodeSchema.or(z.literal("")),
|
||||
country: CountryCodeSchema.or(z.literal("")),
|
||||
|
||||
primaryEmail: z.email("Email inválido").or(z.literal("")),
|
||||
secondaryEmail: z.email("Email inválido").or(z.literal("")),
|
||||
|
||||
primaryPhone: z.string(),
|
||||
secondaryPhone: z.string(),
|
||||
primaryMobile: z.string(),
|
||||
secondaryMobile: z.string(),
|
||||
primaryPhone: LandPhoneSchema.or(z.literal("")),
|
||||
secondaryPhone: LandPhoneSchema.or(z.literal("")),
|
||||
primaryMobile: MobilePhoneSchema.or(z.literal("")),
|
||||
secondaryMobile: MobilePhoneSchema.or(z.literal("")),
|
||||
|
||||
fax: z.string(),
|
||||
website: z.url("URL inválida").or(z.literal("")),
|
||||
fax: LandPhoneSchema.or(z.literal("")).or(z.literal("")),
|
||||
website: URLSchema.or(z.literal("")).or(z.literal("")),
|
||||
|
||||
legalRecord: z.string(),
|
||||
legalRecord: z.string().or(z.literal("")),
|
||||
|
||||
languageCode: z.string().min(1, "El idioma es obligatorio"),
|
||||
currencyCode: z.string().min(1, "La moneda es obligatoria"),
|
||||
languageCode: LanguageCodeSchema,
|
||||
currencyCode: CurrencyCodeSchema,
|
||||
});
|
||||
|
||||
@ -33,13 +33,10 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.email_primary.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.email_primary.label")}
|
||||
leftIcon={
|
||||
<AtSignIcon className="h-[18px] w-[18px] text-muted-foreground" strokeWidth={1.5} />
|
||||
}
|
||||
leftIcon={<AtSignIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="primaryEmail"
|
||||
placeholder={t("form_fields.email_primary.placeholder")}
|
||||
readOnly={readOnly}
|
||||
required
|
||||
typePreset="email"
|
||||
/>
|
||||
|
||||
@ -48,12 +45,7 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.mobile_primary.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.mobile_primary.label")}
|
||||
leftIcon={
|
||||
<SmartphoneIcon
|
||||
className="h-[18px] w-[18px] text-muted-foreground"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
leftIcon={<SmartphoneIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="primaryMobile"
|
||||
placeholder={t("form_fields.mobile_primary.placeholder")}
|
||||
readOnly={readOnly}
|
||||
@ -65,9 +57,7 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.phone_primary.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.phone_primary.label")}
|
||||
leftIcon={
|
||||
<PhoneIcon className="h-[18px] w-[18px] text-muted-foreground" strokeWidth={1.5} />
|
||||
}
|
||||
leftIcon={<PhoneIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="primaryPhone"
|
||||
placeholder={t("form_fields.phone_primary.placeholder")}
|
||||
readOnly={readOnly}
|
||||
@ -81,9 +71,7 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.email_secondary.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.email_secondary.label")}
|
||||
leftIcon={
|
||||
<AtSignIcon className="h-[18px] w-[18px] text-muted-foreground" strokeWidth={1.5} />
|
||||
}
|
||||
leftIcon={<AtSignIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="secondaryEmail"
|
||||
placeholder={t("form_fields.email_secondary.placeholder")}
|
||||
readOnly={readOnly}
|
||||
@ -95,12 +83,7 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.mobile_secondary.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.mobile_secondary.label")}
|
||||
leftIcon={
|
||||
<SmartphoneIcon
|
||||
className="h-[18px] w-[18px] text-muted-foreground"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
leftIcon={<SmartphoneIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="secondaryMobile"
|
||||
placeholder={t("form_fields.mobile_secondary.placeholder")}
|
||||
readOnly={readOnly}
|
||||
@ -111,9 +94,7 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.phone_secondary.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.phone_secondary.label")}
|
||||
leftIcon={
|
||||
<PhoneIcon className="h-[18px] w-[18px] text-muted-foreground" strokeWidth={1.5} />
|
||||
}
|
||||
leftIcon={<PhoneIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="secondaryPhone"
|
||||
placeholder={t("form_fields.phone_secondary.placeholder")}
|
||||
readOnly={readOnly}
|
||||
@ -138,12 +119,7 @@ export const CustomerContactEditor = ({
|
||||
description={t("form_fields.website.description")}
|
||||
disabled={disabled}
|
||||
label={t("form_fields.website.label")}
|
||||
leftIcon={
|
||||
<GlobeIcon
|
||||
className="h-[18px] w-[18px] text-muted-foreground"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
leftIcon={<GlobeIcon className="size-4" strokeWidth={1.5} />}
|
||||
name="website"
|
||||
placeholder={t("form_fields.website.placeholder")}
|
||||
readOnly={readOnly}
|
||||
|
||||
@ -25,9 +25,6 @@ export const buildCustomerUpdatePatch = (
|
||||
if (!formHasAnyDirty(dirtyFields)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
console.log("Campos sucios detectados:", dirtyFields);
|
||||
|
||||
const patch: CustomerUpdatePatch = {};
|
||||
|
||||
if (dirtyFields.reference) {
|
||||
|
||||
@ -23,38 +23,77 @@ export const buildUpdateCustomerByIdParams = (
|
||||
id: string,
|
||||
patchData: CustomerUpdatePatch
|
||||
): UpdateCustomerByIdParams => {
|
||||
const data: Record<string, unknown> = {};
|
||||
|
||||
// --- escalares ---
|
||||
if (patchData.reference !== undefined) {
|
||||
data.reference = patchData.reference;
|
||||
}
|
||||
|
||||
if (patchData.isCompany !== undefined) {
|
||||
data.is_company = patchData.isCompany;
|
||||
}
|
||||
|
||||
if (patchData.name !== undefined) {
|
||||
data.name = patchData.name;
|
||||
}
|
||||
|
||||
if (patchData.tradeName !== undefined) {
|
||||
data.trade_name = patchData.tradeName;
|
||||
}
|
||||
|
||||
if (patchData.tin !== undefined) {
|
||||
data.tin = patchData.tin;
|
||||
}
|
||||
|
||||
if (patchData.defaultTaxes !== undefined) {
|
||||
data.default_taxes = patchData.defaultTaxes?.toString();
|
||||
}
|
||||
|
||||
if (patchData.legalRecord !== undefined) {
|
||||
data.legal_record = patchData.legalRecord;
|
||||
}
|
||||
|
||||
if (patchData.languageCode !== undefined) {
|
||||
data.language_code = patchData.languageCode;
|
||||
}
|
||||
|
||||
if (patchData.currencyCode !== undefined) {
|
||||
data.currency_code = patchData.currencyCode;
|
||||
}
|
||||
|
||||
// --- address ---
|
||||
const address: Record<string, unknown> = {};
|
||||
|
||||
if (patchData.street !== undefined) address.street = patchData.street;
|
||||
if (patchData.street2 !== undefined) address.street2 = patchData.street2;
|
||||
if (patchData.city !== undefined) address.city = patchData.city;
|
||||
if (patchData.province !== undefined) address.province = patchData.province;
|
||||
if (patchData.postalCode !== undefined) address.postal_code = patchData.postalCode;
|
||||
if (patchData.country !== undefined) address.country = patchData.country;
|
||||
|
||||
if (Object.keys(address).length > 0) {
|
||||
data.address = address;
|
||||
}
|
||||
|
||||
// --- contact ---
|
||||
const contact: Record<string, unknown> = {};
|
||||
|
||||
if (patchData.primaryEmail !== undefined) contact.email_primary = patchData.primaryEmail;
|
||||
if (patchData.secondaryEmail !== undefined) contact.email_secondary = patchData.secondaryEmail;
|
||||
if (patchData.primaryPhone !== undefined) contact.phone_primary = patchData.primaryPhone;
|
||||
if (patchData.secondaryPhone !== undefined) contact.phone_secondary = patchData.secondaryPhone;
|
||||
if (patchData.primaryMobile !== undefined) contact.mobile_primary = patchData.primaryMobile;
|
||||
if (patchData.secondaryMobile !== undefined) contact.mobile_secondary = patchData.secondaryMobile;
|
||||
if (patchData.fax !== undefined) contact.fax = patchData.fax;
|
||||
if (patchData.website !== undefined) contact.website = patchData.website;
|
||||
|
||||
if (Object.keys(contact).length > 0) {
|
||||
data.contact = contact;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
data: {
|
||||
reference: patchData.reference,
|
||||
is_company: patchData.isCompany,
|
||||
name: patchData.name,
|
||||
trade_name: patchData.tradeName,
|
||||
tin: patchData.tin,
|
||||
|
||||
default_taxes: patchData.defaultTaxes?.toString(),
|
||||
|
||||
address: {
|
||||
street: patchData.street,
|
||||
street2: patchData.street2,
|
||||
city: patchData.city,
|
||||
province: patchData.province,
|
||||
postal_code: patchData.postalCode,
|
||||
country: patchData.country,
|
||||
},
|
||||
contact: {
|
||||
email_primary: patchData.primaryEmail,
|
||||
email_secondary: patchData.secondaryEmail,
|
||||
phone_primary: patchData.primaryPhone,
|
||||
phone_secondary: patchData.secondaryPhone,
|
||||
mobile_primary: patchData.primaryMobile,
|
||||
mobile_secondary: patchData.secondaryMobile,
|
||||
fax: patchData.fax,
|
||||
website: patchData.website,
|
||||
},
|
||||
legal_record: patchData.legalRecord,
|
||||
language_code: patchData.languageCode,
|
||||
currency_code: patchData.currencyCode,
|
||||
},
|
||||
data,
|
||||
} satisfies UpdateCustomerByIdParams;
|
||||
};
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import type { FieldErrors } from "react-hook-form";
|
||||
|
||||
import type { CustomerUpdateForm } from "../entities";
|
||||
|
||||
export const focusFirstCustomerUpdateError = (errors: FieldErrors<CustomerUpdateForm>) => {
|
||||
const firstKey = Object.keys(errors)[0] as keyof CustomerUpdateForm | undefined;
|
||||
|
||||
if (!firstKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelector<HTMLElement>(`[name="${String(firstKey)}"]`)?.focus();
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
|
||||
import type { CustomerUpdateForm } from "../entities";
|
||||
|
||||
export const focusFirstCustomerUpdateFormError = (form: UseFormReturn<CustomerUpdateForm>) => {
|
||||
const errors = form.formState.errors;
|
||||
const firstKey = Object.keys(errors)[0] as keyof CustomerUpdateForm | undefined;
|
||||
|
||||
if (firstKey) {
|
||||
form.setFocus(firstKey);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
@ -1,3 +1,3 @@
|
||||
export * from "./build-customer.update-patch";
|
||||
export * from "./build-update-customer-by-id-params";
|
||||
export * from "./focus-first-customer-update-error";
|
||||
export * from "./focus-first-customer-update-form-error";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user