Clientes y facturas de cliente

This commit is contained in:
David Arranz 2025-09-24 19:30:35 +02:00
parent 327756413d
commit 3846199cbe
101 changed files with 623 additions and 288 deletions

View File

@ -75,7 +75,7 @@
"uuid": "^11.0.5",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
"zod": "^3.25.67"
"zod": "^4.1.11"
},
"engines": {
"node": ">=22"

View File

@ -1,7 +1,7 @@
import { DateTime } from "luxon";
import http from "node:http";
import os from "node:os";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { createApp } from "./app";
import { ENV } from "./config";
import { tryConnectToDatabase } from "./config/database";

View File

@ -1,7 +1,7 @@
import { IModuleServer, ModuleParams } from "@erp/core/api";
import { logger } from "../logger";
import { initModels, registerModels } from "./model-loader";
import { registerService } from "./service-registry";
import { getService, listServices, registerService } from "./service-registry";
const registeredModules: Map<string, IModuleServer> = new Map();
const initializedModules = new Set<string>();
@ -81,7 +81,13 @@ async function loadModule(name: string, params: ModuleParams, stack: string[]) {
// 3) Registrar dependencias que expone (permite async)
const pkgApi = await withPhase(name, "registerDependencies", async () => {
return await Promise.resolve(pkg.registerDependencies?.(params));
return await Promise.resolve(
pkg.registerDependencies?.({
...params,
listServices,
getService,
})
);
});
// 4) Registrar modelos de Sequelize, si existen

View File

@ -1,6 +1,6 @@
import customerInvoicesAPIModule from "@erp/customer-invoices/api";
import customersAPIModule from "@erp/customers/api";
//import verifactuAPIModule from "@erp/verifactu/api";
import verifactuAPIModule from "@erp/verifactu/api";
import { registerModule } from "./lib";
@ -8,5 +8,5 @@ export const registerModules = () => {
//registerModule(authAPIModule);
registerModule(customersAPIModule);
registerModule(customerInvoicesAPIModule);
//registerModule(verifactuAPIModule);
registerModule(verifactuAPIModule);
};

View File

@ -25,6 +25,7 @@
"@repo/rdx-utils": "workspace:*",
"@repo/rdx-ui": "workspace:*",
"@repo/shadcn-ui": "workspace:*",
"@hookform/resolvers": "^5.0.1",
"@tanstack/react-query": "^5.75.4",
"axios": "^1.9.0",
"express": "^4.18.2",
@ -35,6 +36,6 @@
"react-i18next": "^15.5.1",
"react-router-dom": "^6.26.0",
"sequelize": "^6.37.5",
"zod": "^3.25.67"
"zod": "^4.1.11"
}
}

View File

@ -1,7 +1,7 @@
import { TaxCatalogProvider } from "@erp/core";
import { Percentage, ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
const DEFAULT_SCALE = 2;
const DEFAULT_MIN_VALUE = 0;

View File

@ -1,5 +1,5 @@
import { RequestHandler } from "express";
import { ZodSchema } from "zod/v4";
import { z } from "zod/v4";
import { InternalApiError, ValidationApiError } from "../errors";
import { ExpressController } from "../express-controller";
@ -35,7 +35,7 @@ export type ValidateRequestWithSchemaOptions = {
};
export const validateRequest = <T extends "body" | "query" | "params">(
schema: ZodSchema,
schema: z.ZodObject<any>,
source: T = "body" as T,
options: ValidateRequestWithSchemaOptions = { sanitize: true }
): RequestHandler => {

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
import { AmountBaseSchema } from "./base.schemas";
/**

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
/**
* Cadena con valor numérico:

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
/**
Esquema del DTO para Criteria.fromPrimitives(...)

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
import { MetadataSchema } from "./metadata.dto";
/**

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const MetadataSchema = z
.object({

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
import { NumericStringSchema } from "./base.schemas";
/**

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
import { NumericStringSchema } from "./base.schemas";
/**

View File

@ -1,4 +1,5 @@
export * from "./use-datasource";
export * from "./use-hook-form";
export * from "./use-pagination";
export * from "./use-query-key";
export * from "./use-toggle";

View File

@ -0,0 +1 @@
export * from "./use-hook-form";

View File

@ -0,0 +1,44 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { FieldValues, UseFormProps, UseFormReturn, useForm } from "react-hook-form";
import * as z4 from "zod/v4/core";
type UseHookFormProps<T extends FieldValues = FieldValues> = UseFormProps<T> & {
resolverSchema: z4.$ZodType<T, any>;
initialValues: UseFormProps<T>["defaultValues"];
onDirtyChange?: (isDirty: boolean) => void;
};
export function useHookForm<T extends FieldValues = FieldValues>({
resolverSchema,
initialValues,
disabled,
onDirtyChange,
...rest
}: UseHookFormProps<T>): UseFormReturn<T> {
const form = useForm<T>({
...rest,
resolver: zodResolver(resolverSchema),
defaultValues: initialValues,
disabled,
});
const {
formState: { isDirty },
} = form;
useEffect(() => {
onDirtyChange?.(isDirty);
}, [isDirty, onDirtyChange]);
useEffect(() => {
const applyReset = async () => {
const values = typeof initialValues === "function" ? await initialValues() : initialValues;
form.reset(values);
};
applyReset();
}, [initialValues, form]);
return form;
}

View File

@ -20,7 +20,7 @@
"react-hook-form": "^7.58.1",
"react-i18next": "^15.5.1",
"sequelize": "^6.37.5",
"zod": "^3.25.67"
"zod": "^4.1.11"
},
"devDependencies": {
"@hookform/devtools": "^4.4.0",

View File

@ -1,7 +1,7 @@
import { DomainValidationError } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
interface CustomerInvoiceItemDescriptionProps {
value: string;

View File

@ -1,7 +1,7 @@
import { DomainValidationError } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
interface ICustomerInvoiceNumberProps {
value: string;

View File

@ -1,7 +1,7 @@
import { DomainValidationError } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
interface ICustomerInvoiceSerieProps {
value: string;

View File

@ -1,5 +1,8 @@
import { IModuleServer, ModuleParams } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Transaction } from "sequelize";
import { customerInvoicesRouter, models } from "./infrastructure";
import { buildCustomerInvoiceDependencies } from "./infrastructure/dependencies";
export const customerInvoicesAPIModule: IModuleServer = {
name: "customer-invoices",
@ -12,16 +15,33 @@ export const customerInvoicesAPIModule: IModuleServer = {
customerInvoicesRouter(params);
logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
},
async registerDependencies(params) {
const { database, logger } = params;
const { logger, listServices } = params; /* = ModuleParams & {
getService: (name: string) => any;
};*/
logger.info("🚀 CustomerInvoices module dependencies registered", {
label: this.name,
});
logger.info(listServices());
//getService()
const deps = buildCustomerInvoiceDependencies(params);
return {
models,
services: {
getCustomerInvoice: () => {},
/*...*/
getInvoiceByIdInCompany: (
companyId: UniqueID,
invoiceId: UniqueID,
transaction?: Transaction
) => {
const { service } = deps;
return service.getInvoiceByIdInCompany(companyId, invoiceId, transaction);
},
},
};
},

View File

@ -45,10 +45,12 @@ export type CustomerInvoiceDeps = {
//delete: () => DeleteCustomerInvoiceUseCase;
report: () => ReportCustomerInvoiceUseCase;
};
getService: (name: string) => any;
listServices: () => string[];
};
export function buildCustomerInvoiceDependencies(params: ModuleParams): CustomerInvoiceDeps {
const { database } = params;
const { database, listServices, getService } = params;
const transactionManager = new SequelizeTransactionManager(database);
const catalogs = { taxes: spainTaxCatalogProvider };
@ -157,5 +159,7 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer
report: () =>
new ReportCustomerInvoiceUseCase(service, transactionManager, presenterRegistry),
},
listServices,
getService,
};
}

View File

@ -1,5 +1,5 @@
import { NumericStringSchema, PercentageSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CreateCustomerInvoiceItemRequestSchema = z.object({
id: z.uuid(),

View File

@ -1,5 +1,5 @@
import { CriteriaSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CustomerInvoiceListRequestSchema = CriteriaSchema;
export type CustomerInvoiceListRequestDTO = z.infer<typeof CustomerInvoiceListRequestSchema>;

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
/**
* Este DTO es utilizado por el endpoint:

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const GetCustomerInvoiceByIdRequestSchema = z.object({
invoice_id: z.string(),

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const ReportCustomerInvoiceByIdRequestSchema = z.object({
invoice_id: z.string(),

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const UpdateCustomerInvoiceByIdParamsRequestSchema = z.object({
customer_id: z.string(),

View File

@ -5,7 +5,7 @@ import {
PercentageSchema,
QuantitySchema,
} from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CreateCustomerInvoiceResponseSchema = z.object({
id: z.uuid(),

View File

@ -1,5 +1,5 @@
import { MetadataSchema, MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const GetCustomerInvoiceByIdResponseSchema = z.object({
id: z.uuid(),

View File

@ -1,5 +1,5 @@
import { MetadataSchema, MoneySchema, createListViewResponseSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const ListCustomerInvoicesResponseSchema = createListViewResponseSchema(
z.object({

View File

@ -1,5 +1,5 @@
import { AmountSchema, MetadataSchema, PercentageSchema, QuantitySchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const UpdateCustomerInvoiceByIdResponseSchema = z.object({
id: z.uuid(),

View File

@ -1,4 +1,5 @@
export * from "./use-create-customer-invoice-mutation";
export * from "./use-customer-invoice-query";
export * from "./use-customer-invoices-context";
export * from "./use-customer-invoices-query";
export * from "./use-detail-columns";

View File

@ -0,0 +1,49 @@
import { useDataSource } from "@erp/core/hooks";
import { DefaultError, type QueryKey, useQuery } from "@tanstack/react-query";
import { CustomerInvoiceData } from "../schemas";
export const CUSTOMER_INVOICE_QUERY_KEY = (id: string): QueryKey =>
["customer_invoice", id] as const;
type CustomerInvoiceQueryOptions = {
enabled?: boolean;
};
export function useCustomerInvoiceQuery(invoiceId?: string, options?: CustomerInvoiceQueryOptions) {
const dataSource = useDataSource();
const enabled = (options?.enabled ?? true) && !!invoiceId;
return useQuery<CustomerInvoiceData, DefaultError>({
queryKey: CUSTOMER_INVOICE_QUERY_KEY(invoiceId ?? "unknown"),
queryFn: async (context) => {
const { signal } = context;
if (!invoiceId) {
if (!invoiceId) throw new Error("invoiceId is required");
}
return await dataSource.getOne<CustomerInvoiceData>("customer-invoices", invoiceId, {
signal,
});
},
enabled,
});
}
/*
export function useQuery<
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey
>
TQueryFnData: the type returned from the queryFn.
TError: the type of Errors to expect from the queryFn.
TData: the type our data property will eventually have.
Only relevant if you use the select option,
because then the data property can be different
from what the queryFn returns.
Otherwise, it will default to whatever the queryFn returns.
TQueryKey: the type of our queryKey, only relevant
if you use the queryKey that is passed to your queryFn.
*/

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
import { CreateCustomerInvoiceRequestSchema } from "../../../common/dto";
export const CustomerInvoiceItemDataFormSchema = CreateCustomerInvoiceRequestSchema.extend({

View File

@ -0,0 +1,152 @@
import { useHookForm, useUrlParamId } from "@erp/core/hooks";
import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
import { Button } from "@repo/shadcn-ui/components";
import { useNavigate } from "react-router-dom";
import { useCustomerInvoiceQuery } from "../../hooks";
import { useTranslation } from "../../i18n";
export const CustomerInvoiceUpdate = () => {
const invoiceId = useUrlParamId();
const { t } = useTranslation();
const navigate = useNavigate();
// 1) Estado de carga del cliente (query)
const {
data: invoiceData,
isLoading: isLoadingInvoice,
isError: isLoadError,
error: loadError,
} = useCustomerInvoiceQuery(invoiceId, { enabled: !!invoiceId });
// 2) Estado de actualización (mutación)
const {
mutate,
isPending: isUpdating,
isError: isUpdateError,
error: updateError,
} = useUpdateCustomerInvoice();
// 3) Form hook
const form = useHookForm<CustomerInvoiceFormData>({
resolverSchema: CustomerInvoiceFormSchema,
initialValues: customerInvoiceData ?? defaultCustomerInvoiceFormData,
disabled: isUpdating,
});
const handleSubmit = (data: any) => {
// Handle form submission logic here
console.log("Form submitted with data:", data);
mutate(data);
// Navigate to the list page after submission
navigate("/customer-invoices/list");
};
if (isError) {
console.error("Error creating customer invoice:", error);
// Optionally, you can show an error message to the user
}
// Render the component
// You can also handle loading state if needed
// For example, you can disable the submit button while the mutation is in progress
// const isLoading = useCreateCustomerInvoiceMutation().isLoading;
// Return the JSX for the component
// You can customize the form and its fields as needed
// For example, you can use a form library like react-hook-form or Formik to handle form state and validation
// Here, we are using a simple form with a submit button
// Note: Make sure to replace the form fields with your actual invoice fields
// and handle validation as needed.
// This is just a basic example to demonstrate the structure of the component.
// If you are using a form library, you can pass the handleSubmit function to the form's onSubmit prop
// and use the form library's methods to handle form state and validation.
// Example of a simple form submission handler
// You can replace this with your actual form handling logic
// const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
// event.preventDefault();
// const formData = new FormData(event.currentTarget);
return (
<>
<AppBreadcrumb />
<AppContent>
<div className='flex items-center justify-between space-y-2'>
<div>
<h2 className='text-2xl font-bold tracking-tight'>{t("pages.create.title")}</h2>
<p className='text-muted-foreground'>{t("pages.create.description")}</p>
</div>
<div className='flex items-center justify-end mb-4'>
<Button className='cursor-pointer' onClick={() => navigate("/customer-invoices/list")}>
{t("pages.create.back_to_list")}
</Button>
</div>
</div>
<div className='flex flex-1 flex-col gap-4 p-4'>
<CustomerInvoiceEditForm onSubmit={handleSubmit} isPending={isPending} />
</div>
</AppContent>
</>
);
};
/*
return (
<>
<div className='flex items-center justify-between space-y-2'>
<div>
<h2 className='text-2xl font-bold tracking-tight'>
{t('customerInvoices.list.title' />
</h2>
<p className='text-muted-foreground'>
{t('CustomerInvoices.list.subtitle' />
</p>
</div>
<div className='flex items-center space-x-2'>
<Button onClick={() => navigate("/CustomerInvoices/add")}>
<PlusIcon className='w-4 h-4 mr-2' />
{t("customerInvoices.create.title")}
</Button>
</div>
</div>
<Tabs value={status} onValueChange={setStatus}>
<div className='flex flex-col items-start justify-between mb-4 sm:flex-row sm:items-center'>
<div className='w-full mb-4 sm:w-auto sm:mb-0'>
<TabsList className='hidden sm:flex'>
{CustomerInvoiceStatuses.map((s) => (
<TabsTrigger key={s.value} value={s.value}>
{s.label}
</TabsTrigger>
))}
</TabsList>
<div className='flex items-center w-full space-x-2 sm:hidden'>
<Label>{t("customerInvoices.list.tabs_title")}</Label>
<Select value={status} onValueChange={setStatus}>
<SelectTrigger>
<SelectValue placeholder='Seleccionar estado' />
</SelectTrigger>
<SelectContent>
{CustomerInvoiceStatuses.map((s) => (
<SelectItem key={s.value} value={s.value}>
{s.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
{CustomerInvoiceStatuses.map((s) => (
<TabsContent key={s.value} value={s.value}>
<CustomerInvoicesGrid />
</TabsContent>
))}
</Tabs>
</>
);
};
*/

View File

@ -1,13 +1,16 @@
//import * as z from "zod/v4";
import { z } from "zod/v4";
import { ListCustomerInvoicesResponseDTO } from "@erp/customer-invoices/common";
import {
GetCustomerInvoiceByIdResponseSchema,
ListCustomerInvoicesResponseDTO,
} from "@erp/customer-invoices/common";
/*export const CustomerCreateSchema = CreateCustomerRequestSchema;
export const CustomerUpdateSchema = UpdateCustomerByIdRequestSchema;
export const CustomerSchema = GetCustomerByIdResponseSchema.omit({
export const CustomerUpdateSchema = UpdateCustomerByIdRequestSchema;*/
export const CustomerSchema = GetCustomerInvoiceByIdResponseSchema.omit({
metadata: true,
});
export type CustomerData = z.infer<typeof CustomerSchema>;*/
export type CustomerInvoiceData = z.infer<typeof CustomerSchema>;
export type CustomerInvoicesListData = ListCustomerInvoicesResponseDTO;

View File

@ -1 +1,9 @@
import { z } from "zod/v4";
export const CustomerInvoiceFormSchema = z.object({
invoice_number: z.string().optional(),
status: z.string().optional(),
series: z.string().optional(),
});
export const CustomerInvoiceFormData = z.infer<typeof CustomerInvoiceFormSchema>;

View File

@ -18,7 +18,7 @@
"react-hook-form": "^7.58.1",
"react-i18next": "^15.5.1",
"sequelize": "^6.37.5",
"zod": "^3.25.67"
"zod": "^4.1.11"
},
"devDependencies": {
"@types/express": "^4.17.21",

View File

@ -1,6 +1,6 @@
import { DomainValidationError, ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
interface ICustomerNumberProps {
value: string;

View File

@ -1,6 +1,6 @@
import { DomainValidationError, ValueObject } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
interface ICustomerSerieProps {
value: string;

View File

@ -1,5 +1,6 @@
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
import { ModuleParams, validateRequest } from "@erp/core/api";
import { ILogger } from "@repo/rdx-logger";
import { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize";
import {

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CreateCustomerRequestSchema = z.object({
id: z.string().nonempty(),

View File

@ -1,5 +1,5 @@
import { CriteriaSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CustomerListRequestSchema = CriteriaSchema;
export type CustomerListRequestDTO = z.infer<typeof CustomerListRequestSchema>;

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
/**
* Este DTO es utilizado por el endpoint:

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
/**
* Este DTO es utilizado por el endpoint:

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const UpdateCustomerByIdParamsRequestSchema = z.object({
customer_id: z.string(),

View File

@ -1,5 +1,5 @@
import { MetadataSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CreateCustomerResponseSchema = z.object({
id: z.uuid(),

View File

@ -1,5 +1,5 @@
import { MetadataSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const GetCustomerByIdResponseSchema = z.object({
id: z.uuid(),

View File

@ -1,5 +1,5 @@
import { MetadataSchema, createListViewResponseSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const ListCustomersResponseSchema = createListViewResponseSchema(
z.object({

View File

@ -1,5 +1,5 @@
import { MetadataSchema } from "@erp/core";
import * as z from "zod/v4";
import { z } from "zod/v4";
export const UpdateCustomerByIdResponseSchema = z.object({
id: z.uuid(),

View File

@ -1,5 +1,4 @@
export * from "./use-create-customer-mutation";
export * from "./use-customer-form";
export * from "./use-customer-query";
export * from "./use-customers-context";
export * from "./use-customers-query";

View File

@ -9,7 +9,7 @@ type CreateCustomerPayload = {
data: CustomerFormData;
};
export function useCreateCustomerMutation() {
export function useCreateCustomer() {
const queryClient = useQueryClient();
const dataSource = useDataSource();
const schema = CreateCustomerRequestSchema;

View File

@ -1,36 +0,0 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { CustomerFormData, CustomerFormSchema } from "../schemas";
type UseCustomerFormProps = {
initialValues: CustomerFormData;
disabled?: boolean;
onDirtyChange?: (isDirty: boolean) => void;
};
export function useCustomerForm({ initialValues, disabled, onDirtyChange }: UseCustomerFormProps) {
const form = useForm({
resolver: zodResolver(CustomerFormSchema),
defaultValues: initialValues,
disabled,
});
const {
formState: { isDirty },
} = form;
// Avisar cuando cambia el dirty state
useEffect(() => {
if (onDirtyChange) {
onDirtyChange(isDirty);
}
}, [isDirty, onDirtyChange]);
// Resetear el form si cambian los valores iniciales
useEffect(() => {
form.reset(initialValues);
}, [initialValues, form]);
return form;
}

View File

@ -12,22 +12,19 @@ export function useCustomerQuery(customerId?: string, options?: CustomerQueryOpt
const dataSource = useDataSource();
const enabled = (options?.enabled ?? true) && !!customerId;
const queryResult = useQuery<CustomerData, DefaultError>({
return useQuery<CustomerData, DefaultError>({
queryKey: CUSTOMER_QUERY_KEY(customerId ?? "unknown"),
queryFn: async (context) => {
const { signal } = context;
if (!customerId) {
if (!customerId) throw new Error("customerId is required");
}
const customer = await dataSource.getOne<CustomerData>("customers", customerId, {
return await dataSource.getOne<CustomerData>("customers", customerId, {
signal,
});
return customer;
},
enabled,
});
return queryResult;
}
/*

View File

@ -14,7 +14,7 @@ type UpdateCustomerPayload = {
data: Partial<CustomerFormData>;
};
export function useUpdateCustomerMutation() {
export function useUpdateCustomer() {
const queryClient = useQueryClient();
const dataSource = useDataSource();
const schema = UpdateCustomerByIdRequestSchema;

View File

@ -1,13 +1,13 @@
import { AppBreadcrumb, AppContent } from "@repo/rdx-ui/components";
import { useNavigate } from "react-router-dom";
import { FormCommitButtonGroup, UnsavedChangesProvider } from "@erp/core/hooks";
import { FormCommitButtonGroup, UnsavedChangesProvider, useHookForm } from "@erp/core/hooks";
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
import { FieldErrors, FormProvider } from "react-hook-form";
import { CustomerEditForm, ErrorAlert } from "../../components";
import { useCreateCustomerMutation, useCustomerForm } from "../../hooks";
import { useCreateCustomer } from "../../hooks";
import { useTranslation } from "../../i18n";
import { CustomerFormData, defaultCustomerFormData } from "../../schemas";
import { CustomerFormData, CustomerFormSchema, defaultCustomerFormData } from "../../schemas";
export const CustomerCreate = () => {
const { t } = useTranslation();
@ -19,11 +19,13 @@ export const CustomerCreate = () => {
isPending: isCreating,
isError: isCreateError,
error: createError,
} = useCreateCustomerMutation();
} = useCreateCustomer();
// 2) Form hook
const form = useCustomerForm({
const form = useHookForm<CustomerFormData>({
resolverSchema: CustomerFormSchema,
initialValues: defaultCustomerFormData,
disabled: isCreating,
});
// 3) Submit con navegación condicionada por éxito

View File

@ -2,7 +2,12 @@ import { AppBreadcrumb, AppContent, BackHistoryButton } from "@repo/rdx-ui/compo
import { useNavigate } from "react-router-dom";
import { formHasAnyDirty, pickFormDirtyValues } from "@erp/core/client";
import { FormCommitButtonGroup, UnsavedChangesProvider, useUrlParamId } from "@erp/core/hooks";
import {
FormCommitButtonGroup,
UnsavedChangesProvider,
useHookForm,
useUrlParamId,
} from "@erp/core/hooks";
import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers";
import { FieldErrors, FormProvider } from "react-hook-form";
import {
@ -11,9 +16,9 @@ import {
ErrorAlert,
NotFoundCard,
} from "../../components";
import { useCustomerForm, useCustomerQuery, useUpdateCustomerMutation } from "../../hooks";
import { useCustomerQuery, useUpdateCustomer } from "../../hooks";
import { useTranslation } from "../../i18n";
import { CustomerFormData, defaultCustomerFormData } from "../../schemas";
import { CustomerFormData, CustomerFormSchema, defaultCustomerFormData } from "../../schemas";
export const CustomerUpdate = () => {
const customerId = useUrlParamId();
@ -34,11 +39,13 @@ export const CustomerUpdate = () => {
isPending: isUpdating,
isError: isUpdateError,
error: updateError,
} = useUpdateCustomerMutation();
} = useUpdateCustomer();
// 3) Form hook
const form = useCustomerForm({
const form = useHookForm<CustomerFormData>({
resolverSchema: CustomerFormSchema,
initialValues: customerData ?? defaultCustomerFormData,
disabled: isUpdating,
});
// 4) Submit con navegación condicionada por éxito

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
import {
CreateCustomerRequestSchema,

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4";
import { z } from "zod/v4";
export const CustomerFormSchema = z.object({
reference: z.string().optional(),

View File

@ -3,17 +3,23 @@
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": { "./api": "./src/api/index.ts" },
"exports": {
"./api": "./src/api/index.ts",
"./common": "./src/common/index.ts"
},
"peerDependencies": {
"sequelize": "^6.37.5",
"express": "^4.18.2"
"express": "^4.18.2",
"zod": "^4.1.11"
},
"devDependencies": { "@types/express": "^4.17.21" },
"dependencies": {
"@erp/auth": "workspace:*",
"@erp/core": "workspace:*",
"@repo/rdx-ddd": "workspace:*",
"@repo/rdx-criteria": "workspace:*",
"@repo/rdx-utils": "workspace:*",
"@repo/rdx-logger": "workspace:*"
"@repo/rdx-logger": "workspace:*",
"@erp/customer-invoices": "workspace:*"
}
}

View File

@ -0,0 +1,2 @@
export * from "./verifactu-record.full.presenter";
//export * from "./verifactu-record.full.representer";

View File

@ -0,0 +1,24 @@
import { Presenter } from "@erp/core/api";
import { GetVerifactuRecordByIdResponseDTO } from "@erp/verifactu-records/common";
import { VerifactuRecord } from "../../../domain";
export class VerifactuRecordFullPresenter extends Presenter<
VerifactuRecord,
GetVerifactuRecordByIdResponseDTO
> {
toOutput(record: VerifactuRecord): GetVerifactuRecordByIdResponseDTO {
return {
id: record.id.toString(),
invoice_id: record.invoiceId.toString(),
estado: record.estado.toPrimitive(),
url: record.url.toPrimitive(),
// qr1: toEmptyString(record.qr1, (value) => value.toString()),
metadata: {
entity: "verifactu-records",
link: "",
},
};
}
}

View File

@ -17,7 +17,7 @@ export class SendInvoiceUseCase {
public async execute(params: SendInvoiceUseCaseInput) {
const { invoice_id } = params;
console.log("CASO DE USO -----ESTO ES UNA PRUEBA>>>>>>", invoice_id);
const idOrError = UniqueID.create(invoice_id);
if (idOrError.isFailure) {
@ -25,14 +25,41 @@ export class SendInvoiceUseCase {
}
const invoiceId = idOrError.data;
console.log("CASO DE USO -----VALIDADO>>>>>>", invoiceId);
return this.transactionManager.complete(async (transaction: Transaction) => {
try {
const invoiceOrError = await this.service.sendInvoiceToVerifactu(invoiceId, transaction);
console.log(
"CASO DE USO -----LLamar servicio de invoices para recuperar el invoice>>>>>>",
invoiceId
);
const invoice = {
serie: "A",
numero: "1",
fecha_expedicion: "12-09-2025",
tipo_factura: "F1",
descripcion: "Venta de bienes",
nif: "A15022510",
nombre: "Empresa de prueba SL",
lineas: [
{
base_imponible: "200",
tipo_impositivo: "21",
cuota_repercutida: "42",
},
{
base_imponible: "100",
tipo_impositivo: "10",
cuota_repercutida: "10",
},
],
importe_total: "352.00",
};
const invoiceOrError = await this.service.sendInvoiceToVerifactu(invoice, transaction);
if (invoiceOrError.isFailure) {
return Result.fail(invoiceOrError.error);
}
const invoice = invoiceOrError.data;
const invoice2 = invoiceOrError.data;
return Result.ok({});
} catch (error: unknown) {
return Result.fail(error as Error);

View File

@ -1,7 +1,7 @@
import { DomainValidationError } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
interface VerifactuRecordUrlProps {
value: string;

View File

@ -42,29 +42,6 @@ export class VerifactuRecordService {
// return Result.fail(invoiceResult.error);
// }
const invoice = {
serie: "A",
numero: "1",
fecha_expedicion: "12-09-2025",
tipo_factura: "F1",
descripcion: "Venta de bienes",
nif: "A15022510",
nombre: "Empresa de prueba SL",
lineas: [
{
base_imponible: "200",
tipo_impositivo: "21",
cuota_repercutida: "42",
},
{
base_imponible: "100",
tipo_impositivo: "10",
cuota_repercutida: "10",
},
],
importe_total: "352.00",
};
console.log("ESTO ES UNA PRUEBA>>>>>>");
return Result.ok(invoice);

View File

@ -4,11 +4,9 @@ import { models, verifactuRouter } from "./infrastructure";
export const verifactuAPIModule: IModuleServer = {
name: "verifactu",
version: "1.0.0",
dependencies: ["customers-invoices"],
dependencies: ["customer-invoices"],
async init(params: ModuleParams) {
// const contacts = getService<ContactsService>("contacts");
console.log("111111111111111111111111111A>>>>>>>>>>>>>>>>>>>");
const { logger } = params;
verifactuRouter(params);
logger.info("🚀 Verifactu module initialized", { label: this.name });

View File

@ -2,116 +2,62 @@
import type { IMapperRegistry, IPresenterRegistry, ModuleParams } from "@erp/core/api";
import { JsonTaxCatalogProvider } from "@erp/core";
import {
InMemoryMapperRegistry,
InMemoryPresenterRegistry,
SequelizeTransactionManager,
} from "@erp/core/api";
import { SendCustomerInvoiceUseCase } from "../application";
import { CustomerInvoiceItemsReportPersenter } from "../application/presenters/queries/customer-invoice-items.report.presenter";
import { CustomerInvoiceService } from "../domain";
import { CustomerInvoiceDomainMapper, CustomerInvoiceListMapper } from "./mappers";
import { CustomerInvoiceRepository } from "./sequelize";
import { SendInvoiceUseCase } from "../application";
import { VerifactuRecordFullPresenter } from "../application/presenters/domain/verifactu-record.full.presenter";
import { VerifactuRecordService } from "../domain";
//import { VerifactuRecordItemsReportPersenter } from "../application/presenters/queries/verifactu-record-items.report.presenter";
import { VerifactuRecordDomainMapper } from "./mappers";
import { VerifactuRecordRepository } from "./sequelize";
export type CustomerInvoiceDeps = {
export type VerifactuRecordDeps = {
transactionManager: SequelizeTransactionManager;
mapperRegistry: IMapperRegistry;
presenterRegistry: IPresenterRegistry;
repo: CustomerInvoiceRepository;
service: CustomerInvoiceService;
catalogs: {
taxes: JsonTaxCatalogProvider;
};
repo: VerifactuRecordRepository;
service: VerifactuRecordService;
build: {
send: () => SendCustomerInvoiceUseCase;
send: () => SendInvoiceUseCase;
};
getService: (name: string) => any;
listServices: () => string[];
};
export function buildVerifactuDependencies(params: ModuleParams): CustomerInvoiceDeps {
const { database } = params;
export function buildVerifactuDependencies(params: ModuleParams): VerifactuRecordDeps {
const { database, listServices, getService } = params;
const transactionManager = new SequelizeTransactionManager(database);
// Mapper Registry
const mapperRegistry = new InMemoryMapperRegistry();
mapperRegistry
.registerDomainMapper(
{ resource: "customer-invoice" },
new CustomerInvoiceDomainMapper({ taxCatalog: catalogs.taxes })
)
.registerQueryMappers([
mapperRegistry.registerDomainMapper(
{ resource: "verifactu-record" },
new VerifactuRecordDomainMapper({})
);
/* .registerQueryMappers([
{
key: { resource: "customer-invoice", query: "LIST" },
mapper: new CustomerInvoiceListMapper(),
key: { resource: "verifactu-record", query: "LIST" },
mapper: new VerifactuRecordListMapper(),
},
]);
*/
// Repository & Services
const repo = new CustomerInvoiceRepository({ mapperRegistry, database });
const service = new CustomerInvoiceService(repo);
const repo = new VerifactuRecordRepository({ mapperRegistry, database });
const service = new VerifactuRecordService(repo);
// Presenter Registry
const presenterRegistry = new InMemoryPresenterRegistry();
presenterRegistry.registerPresenters([
{
key: {
resource: "customer-invoice-items",
resource: "verifactu-record",
projection: "FULL",
},
presenter: new CustomerInvoiceItemsFullPresenter(presenterRegistry),
},
{
key: {
resource: "recipient-invoice",
projection: "FULL",
},
presenter: new RecipientInvoiceFullPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "FULL",
},
presenter: new CustomerInvoiceFullPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "LIST",
},
presenter: new ListCustomerInvoicesPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "JSON",
},
presenter: new CustomerInvoiceReportPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice-items",
projection: "REPORT",
format: "JSON",
},
presenter: new CustomerInvoiceItemsReportPersenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "HTML",
},
presenter: new CustomerInvoiceReportHTMLPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "PDF",
},
presenter: new CustomerInvoiceReportPDFPresenter(presenterRegistry),
presenter: new VerifactuRecordFullPresenter(presenterRegistry),
},
]);
@ -121,21 +67,20 @@ export function buildVerifactuDependencies(params: ModuleParams): CustomerInvoic
mapperRegistry,
presenterRegistry,
service,
catalogs,
build: {
list: () => new ListCustomerInvoicesUseCase(service, transactionManager, presenterRegistry),
get: () => new GetCustomerInvoiceUseCase(service, transactionManager, presenterRegistry),
create: () =>
new CreateCustomerInvoiceUseCase(
send: () => new SendInvoiceUseCase(service, transactionManager, presenterRegistry),
// get: () => new GetVerifactuRecordUseCase(service, transactionManager, presenterRegistry),
/* create: () =>
new CreateVerifactuRecordUseCase(
service,
transactionManager,
presenterRegistry,
catalogs.taxes
),
// update: () => new UpdateCustomerInvoiceUseCase(service, transactionManager),
// delete: () => new DeleteCustomerInvoiceUseCase(service, transactionManager),
report: () =>
new ReportCustomerInvoiceUseCase(service, transactionManager, presenterRegistry),
*/
// update: () => new UpdateVerifactuRecordUseCase(service, transactionManager),
// delete: () => new DeleteVerifactuRecordUseCase(service, transactionManager),
},
listServices,
getService,
};
}

View File

@ -15,8 +15,6 @@ export class SendInvoiceVerifactuController extends ExpressController {
}
const { invoice_id } = this.req.params;
console.log("CONTROLLER -----ESTO ES UNA PRUEBA>>>>>>");
const result = await this.useCase.execute({ invoice_id });
return result.match(

View File

@ -1,8 +1,9 @@
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
import { ModuleParams, validateRequest } from "@erp/core/api";
import { SendInvoiceByIdRequestSchema } from "@erp/verifactu/common";
import { ILogger } from "@repo/rdx-logger";
import { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize";
import { SendCustomerInvoiceByIdRequestSchema } from "../../../common/dto";
import { buildVerifactuDependencies } from "../dependencies";
import { SendInvoiceVerifactuController } from "./controllers";
@ -14,7 +15,6 @@ export const verifactuRouter = (params: ModuleParams) => {
logger: ILogger;
};
console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>>>>>>>>>>>>>>>>>>>");
const deps = buildVerifactuDependencies(params);
const router: Router = Router({ mergeParams: true });
@ -40,11 +40,17 @@ export const verifactuRouter = (params: ModuleParams) => {
router.get(
"/:invoice_id/sendVerifactu",
//checkTabContext,
validateRequest(SendCustomerInvoiceByIdRequestSchema, "params"),
validateRequest(SendInvoiceByIdRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => {
const useCase = deps.build.report();
const { listServices } = deps;
const useCase = deps.build.send();
logger.info(listServices());
const controller = new SendInvoiceVerifactuController(useCase);
return controller.execute(req, res, next);
}
);
app.use(`${baseRoutePath}/verifactu`, router);
};

View File

@ -0,0 +1 @@
export * from "./verifactu-record.mapper";

View File

@ -1,5 +1,4 @@
import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import { VerifactuRecordEstado } from "@erp/customer-invoices/api/domain/aggregates/value-objects";
import {
UniqueID,
ValidationErrorCollection,

View File

@ -0,0 +1,2 @@
export * from "./domain";
//export * from "./queries";

View File

@ -0,0 +1,74 @@
import { MetadataSchema, MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core";
import { z } from "zod/v4";
export const GetVerifactuRecordByIdResponseSchema = z.object({
id: z.uuid(),
company_id: z.uuid(),
invoice_number: z.string(),
status: z.string(),
series: z.string(),
invoice_date: z.string(),
operation_date: z.string(),
notes: z.string(),
language_code: z.string(),
currency_code: z.string(),
customer_id: z.string(),
recipient: z.object({
id: z.string(),
name: z.string(),
tin: z.string(),
street: z.string(),
street2: z.string(),
city: z.string(),
province: z.string(),
postal_code: z.string(),
country: z.string(),
}),
taxes: z.string(),
payment_method: z
.object({
payment_id: z.string(),
payment_description: z.string(),
})
.optional(),
subtotal_amount: MoneySchema,
discount_percentage: PercentageSchema,
discount_amount: MoneySchema,
taxable_amount: MoneySchema,
taxes_amount: MoneySchema,
total_amount: MoneySchema,
items: z.array(
z.object({
id: z.uuid(),
isNonValued: z.string(),
position: z.string(),
description: z.string(),
quantity: QuantitySchema,
unit_amount: MoneySchema,
taxes: z.string(),
subtotal_amount: MoneySchema,
discount_percentage: PercentageSchema,
discount_amount: MoneySchema,
taxable_amount: MoneySchema,
taxes_amount: MoneySchema,
total_amount: MoneySchema,
})
),
metadata: MetadataSchema.optional(),
});
export type GetVerifactuRecordByIdResponseDTO = z.infer<
typeof GetVerifactuRecordByIdResponseSchema
>;

View File

@ -1 +1,2 @@
export * from "./send-customer-invoice-by-id.request.dto";
export * from "./get-verifactu-record-by-id.response.dto";
export * from "./send-invoice-by-id.request.dto";

View File

@ -1,9 +0,0 @@
import * as z from "zod/v4";
export const SendCustomerInvoiceByIdRequestSchema = z.object({
invoice_id: z.string(),
});
export type SendCustomerInvoiceByIdRequestDTO = z.infer<
typeof SendCustomerInvoiceByIdRequestSchema
>;

View File

@ -0,0 +1,7 @@
import { z } from "zod/v4";
export const SendInvoiceByIdRequestSchema = z.object({
invoice_id: z.string(),
});
export type SendInvoiceByIdRequestDTO = z.infer<typeof SendInvoiceByIdRequestSchema>;

View File

@ -20,6 +20,6 @@
"dinero.js": "^1.9.1",
"libphonenumber-js": "^1.11.20",
"shallow-equal-object": "^1.1.1",
"zod": "^3.24.4"
"zod": "^4.1.11"
}
}

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,6 +1,6 @@
import { Result } from "@repo/rdx-utils";
import { isPossiblePhoneNumber, parsePhoneNumberWithError } from "libphonenumber-js";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result, generateUUIDv4 } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -1,5 +1,5 @@
import { Result } from "@repo/rdx-utils";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

View File

@ -20,7 +20,7 @@
"i18next": "^25.1.1",
"react-hook-form": "^7.58.1",
"typescript": "^5.8.3",
"zod": "^3.25.67"
"zod": "^4.1.11"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
@ -59,6 +59,6 @@
"react-router-dom": "^6.26.0",
"recharts": "^2.15.3",
"sonner": "^2.0.3",
"zod": "^3.24.4"
"zod": "^4.1.11"
}
}

View File

@ -51,7 +51,7 @@ import {
import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { toast } from "sonner";
import * as z from "zod/v4";
import { z } from "zod/v4";
import { Badge } from "@repo/shadcn-ui/components/badge";
import { Button } from "@repo/shadcn-ui/components/button";

Some files were not shown because too many files have changed in this diff Show More