This commit is contained in:
David Arranz 2025-09-21 21:46:51 +02:00
parent 580beaba4c
commit b3c14b061b
18 changed files with 128 additions and 77 deletions

View File

@ -1,6 +1,6 @@
{
"common": {
"required": "required"
"required": ""
},
"components": {
"taxes_multi_select": {

View File

@ -1,6 +1,6 @@
{
"common": {
"required": "requerido"
"required": ""
},
"components": {
"taxes_multi_select": {

View File

@ -47,9 +47,15 @@ export function TaxesMultiSelectField<TFormValues extends FieldValues>({
render={({ field }) => (
<FormItem className={cn("space-y-0", className)}>
{label && (
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
<div className='mb-1 flex justify-between gap-2'>
<div className='flex items-center gap-2'>
<FormLabel htmlFor={name} className='m-0'>
{label}
</FormLabel>
{required && (
<span className='text-xs text-destructive'>{t("common.required")}</span>
)}
</div>
</div>
)}
<FormControl>

View File

@ -39,7 +39,7 @@ export class CustomerFullPresenter extends Presenter<Customer, GetCustomerByIdRe
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
default_taxes: customer.defaultTaxes.getAll().join(", "),
default_taxes: customer.defaultTaxes.getAll().map((tax) => tax.toString()),
status: customer.isActive ? "active" : "inactive",
language_code: customer.languageCode.toPrimitive(),

View File

@ -1,4 +1,11 @@
import { DomainError, ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd";
import {
DomainError,
ValidationErrorCollection,
ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd";
import { UpdateCustomerByIdRequestDTO } from "../../../../common";
import { CustomerPatchProps } from "../../../domain";
import {
City,
@ -34,7 +41,7 @@ import { Collection, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-ut
*
*/
export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerRequestDTO) {
export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerByIdRequestDTO) {
try {
const errors: ValidationErrorDetail[] = [];
const customerPatchProps: CustomerPatchProps = {};
@ -83,18 +90,50 @@ export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerRequestDTO)
);
});
toPatchField(dto.email).ifSet((email) => {
customerPatchProps.email = extractOrPushError(
maybeFromNullableVO(email, (value) => EmailAddress.create(value)),
"email",
toPatchField(dto.email_primary).ifSet((email_primary) => {
customerPatchProps.emailPrimary = extractOrPushError(
maybeFromNullableVO(email_primary, (value) => EmailAddress.create(value)),
"email_primary",
errors
);
});
toPatchField(dto.phone).ifSet((phone) => {
customerPatchProps.phone = extractOrPushError(
maybeFromNullableVO(phone, (value) => PhoneNumber.create(value)),
"phone",
toPatchField(dto.email_secondary).ifSet((email_secondary) => {
customerPatchProps.emailSecondary = extractOrPushError(
maybeFromNullableVO(email_secondary, (value) => EmailAddress.create(value)),
"email_secondary",
errors
);
});
toPatchField(dto.mobile_primary).ifSet((mobile_primary) => {
customerPatchProps.mobilePrimary = extractOrPushError(
maybeFromNullableVO(mobile_primary, (value) => PhoneNumber.create(value)),
"mobile_primary",
errors
);
});
toPatchField(dto.mobile_secondary).ifSet((mobile_secondary) => {
customerPatchProps.mobilePrimary = extractOrPushError(
maybeFromNullableVO(mobile_secondary, (value) => PhoneNumber.create(value)),
"mobile_secondary",
errors
);
});
toPatchField(dto.phone_primary).ifSet((phone_primary) => {
customerPatchProps.phonePrimary = extractOrPushError(
maybeFromNullableVO(phone_primary, (value) => PhoneNumber.create(value)),
"phone_primary",
errors
);
});
toPatchField(dto.phone_secondary).ifSet((phone_secondary) => {
customerPatchProps.phoneSecondary = extractOrPushError(
maybeFromNullableVO(phone_secondary, (value) => PhoneNumber.create(value)),
"phone_secondary",
errors
);
});
@ -158,7 +197,7 @@ export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerRequestDTO)
return;
}
defaultTaxes!.split(",").forEach((taxCode, index) => {
defaultTaxes!.forEach((taxCode, index) => {
const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
if (tax && customerPatchProps.defaultTaxes) {
customerPatchProps.defaultTaxes.add(tax);
@ -173,7 +212,9 @@ export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerRequestDTO)
}
if (errors.length > 0) {
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
return Result.fail(
new ValidationErrorCollection("Customer props mapping failed (update)", errors)
);
}
return Result.ok(customerPatchProps);
@ -183,7 +224,7 @@ export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerRequestDTO)
}
function mapDTOToUpdatePostalAddressPatchProps(
dto: UpdateCustomerRequestDTO,
dto: UpdateCustomerByIdRequestDTO,
errors: ValidationErrorDetail[]
): PostalAddressPatchProps | undefined {
const postalAddressPatchProps: PostalAddressPatchProps = {};

View File

@ -197,7 +197,9 @@ export class CustomerDomainMapper
// Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
return Result.fail(
new ValidationErrorCollection("Customer props mapping failed (CustomerMapper)", errors)
);
}
const customerProps: CustomerProps = {

View File

@ -29,7 +29,7 @@ export const CreateCustomerRequestSchema = z.object({
legal_record: z.string().default(""),
default_taxes: z.string().default(""),
default_taxes: z.array(z.string()).default([]),
status: z.string().toLowerCase().default("active"),
language_code: z.string().toLowerCase().default("es"),
currency_code: z.string().toUpperCase().default("EUR"),

View File

@ -11,6 +11,7 @@ export const UpdateCustomerByIdRequestSchema = z.object({
name: z.string().optional(),
trade_name: z.string().optional(),
tin: z.string().optional(),
default_taxes: z.array(z.string()).optional(), // completo (sustituye), o null => vaciar
street: z.string().optional(),
street2: z.string().optional(),
@ -31,7 +32,6 @@ export const UpdateCustomerByIdRequestSchema = z.object({
legal_record: z.string().optional(),
default_taxes: z.string().optional(), // completo (sustituye), o null => vaciar
language_code: z.string().optional(),
currency_code: z.string().optional(),
});

View File

@ -25,7 +25,7 @@ export const CreateCustomerResponseSchema = z.object({
legal_record: z.string(),
default_taxes: z.string(),
default_taxes: z.array(z.string()),
status: z.string(),
language_code: z.string(),
currency_code: z.string(),

View File

@ -30,7 +30,7 @@ export const GetCustomerByIdResponseSchema = z.object({
legal_record: z.string(),
default_taxes: z.string(),
default_taxes: z.array(z.string()),
status: z.string(),
language_code: z.string(),
currency_code: z.string(),

View File

@ -25,7 +25,7 @@ export const ListCustomersResponseSchema = createListViewResponseSchema(
website: z.string(),
//legal_record: z.string(),
//default_taxes: z.string(),
//default_taxes: z.array(z.string()),
language_code: z.string(),
currency_code: z.string(),

View File

@ -30,7 +30,7 @@ export const UpdateCustomerByIdResponseSchema = z.object({
legal_record: z.string(),
default_taxes: z.string(),
default_taxes: z.array(z.string()),
language_code: z.string(),
currency_code: z.string(),

View File

@ -79,7 +79,7 @@ export const CustomersListGrid = () => {
size='icon'
className='size-8'
onClick={() => {
navigate(`${data.id}/edit`);
navigate(`${data.id}/edit`, { relative: "path" });
}}
>
<ChevronRightIcon />

View File

@ -1,5 +1,4 @@
import { TaxesMultiSelectField } from "@erp/core/components";
import { SelectField, TextAreaField } from "@repo/rdx-ui/components";
import { SelectField } from "@repo/rdx-ui/components";
import {
Card,
CardContent,
@ -23,41 +22,29 @@ export const CustomerAdditionalConfigFields = ({
<CardTitle>{t("form_groups.preferences.title")}</CardTitle>
<CardDescription>{t("form_groups.preferences.description")}</CardDescription>
</CardHeader>
<CardContent className='grid grid-cols-1 gap-y-8 gap-x-6 @xl:grid-cols-2'>
<TaxesMultiSelectField
control={control}
name='default_taxes'
required
label={t("form_fields.default_taxes.label")}
placeholder={t("form_fields.default_taxes.placeholder")}
description={t("form_fields.default_taxes.description")}
/>
<SelectField
control={control}
name='language_code'
required
label={t("form_fields.language_code.label")}
placeholder={t("form_fields.language_code.placeholder")}
description={t("form_fields.language_code.description")}
items={[...LANGUAGE_OPTIONS]}
/>
<SelectField
control={control}
name='currency_code'
required
label={t("form_fields.currency_code.label")}
placeholder={t("form_fields.currency_code.placeholder")}
description={t("form_fields.currency_code.description")}
items={[...CURRENCY_OPTIONS]}
/>
<TextAreaField
control={control}
name='legal_record'
required
label={t("form_fields.legal_record.label")}
placeholder={t("form_fields.legal_record.placeholder")}
description={t("form_fields.legal_record.description")}
/>
<CardContent>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<SelectField
className='lg:col-span-2'
control={control}
name='language_code'
required
label={t("form_fields.language_code.label")}
placeholder={t("form_fields.language_code.placeholder")}
description={t("form_fields.language_code.description")}
items={[...LANGUAGE_OPTIONS]}
/>
<SelectField
className='lg:col-span-2'
control={control}
name='currency_code'
required
label={t("form_fields.currency_code.label")}
placeholder={t("form_fields.currency_code.placeholder")}
description={t("form_fields.currency_code.description")}
items={[...CURRENCY_OPTIONS]}
/>
</div>
</CardContent>
</Card>
);

View File

@ -55,7 +55,7 @@ export const CustomerAddressFields = ({ control }: { control: Control<CustomerUp
description={t("form_fields.postal_code.description")}
/>
</div>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-0 '>
<TextField
className='lg:col-span-2'
control={control}

View File

@ -1,5 +1,5 @@
import { TaxesMultiSelectField } from "@erp/core/components";
import { TextField } from "@repo/rdx-ui/components";
import { TextAreaField, TextField } from "@repo/rdx-ui/components";
import {
Card,
CardContent,
@ -27,8 +27,8 @@ export const CustomerBasicInfoFields = ({ control }: { control: Control<Customer
<CardTitle>Identificación</CardTitle>
</CardHeader>
<CardContent>
<div className='grid grid-cols-1 gap-6 md:grid-cols-4'>
<div className='sm:col-span-full'>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<div className='lg:col-span-full'>
<FormField
control={control}
name='is_company'
@ -64,8 +64,9 @@ export const CustomerBasicInfoFields = ({ control }: { control: Control<Customer
)}
/>
</div>
<div className='sm:col-span-2'>
</div>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-12 '>
<div className='lg:col-span-2'>
<TextField
control={control}
name='name'
@ -76,7 +77,7 @@ export const CustomerBasicInfoFields = ({ control }: { control: Control<Customer
/>
</div>
<div className='sm:col-span-2'>
<div className='lg:col-span-2'>
<TextField
control={control}
name='trade_name'
@ -86,7 +87,7 @@ export const CustomerBasicInfoFields = ({ control }: { control: Control<Customer
/>
</div>
<div className='sm:col-span-2'>
<div className='lg:col-span-2'>
<TaxesMultiSelectField
control={control}
name='default_taxes'
@ -96,7 +97,7 @@ export const CustomerBasicInfoFields = ({ control }: { control: Control<Customer
description={t("form_fields.default_taxes.description")}
/>
</div>
<div className='col-auto'>
<div className='lg:col-span-2'>
<TextField
control={control}
name='reference'
@ -105,6 +106,14 @@ export const CustomerBasicInfoFields = ({ control }: { control: Control<Customer
description={t("form_fields.reference.description")}
/>
</div>
<TextAreaField
className='lg:col-span-full'
control={control}
name='legal_record'
label={t("form_fields.legal_record.label")}
placeholder={t("form_fields.legal_record.placeholder")}
description={t("form_fields.legal_record.description")}
/>
</div>
</CardContent>
</Card>

View File

@ -65,7 +65,7 @@ export const CustomerEditForm = ({ defaultValues, onSubmit, isPending }: Custome
return (
<Form {...form}>
<FormDebug form={form} />
<form onSubmit={form.handleSubmit(handleSubmit, handleError)}>
<form id='customer-edit-form' onSubmit={form.handleSubmit(handleSubmit, handleError)}>
<div className='flex gap-6'>
<div className='w-full xl:w-2/3 space-y-12'>
<CustomerBasicInfoFields control={form.control} />

View File

@ -46,9 +46,15 @@ export function TextAreaField<TFormValues extends FieldValues>({
render={({ field }) => (
<FormItem className={cn("space-y-0", className)}>
{label && (
<div className='flex justify-between items-center'>
<FormLabel className='m-0'>{label}</FormLabel>
{required && <span className='text-xs text-destructive'>{t("common.required")}</span>}
<div className='mb-1 flex justify-between gap-2'>
<div className='flex items-center gap-2'>
<FormLabel htmlFor={name} className='m-0'>
{label}
</FormLabel>
{required && (
<span className='text-xs text-destructive'>{t("common.required")}</span>
)}
</div>
</div>
)}
<FormControl>