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", "uuid": "^11.0.5",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"zod": "^3.25.67" "zod": "^4.1.11"
}, },
"engines": { "engines": {
"node": ">=22" "node": ">=22"

View File

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

View File

@ -1,7 +1,7 @@
import { IModuleServer, ModuleParams } from "@erp/core/api"; import { IModuleServer, ModuleParams } from "@erp/core/api";
import { logger } from "../logger"; import { logger } from "../logger";
import { initModels, registerModels } from "./model-loader"; 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 registeredModules: Map<string, IModuleServer> = new Map();
const initializedModules = new Set<string>(); 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) // 3) Registrar dependencias que expone (permite async)
const pkgApi = await withPhase(name, "registerDependencies", 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 // 4) Registrar modelos de Sequelize, si existen

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4"; import { z } from "zod/v4";
import { AmountBaseSchema } from "./base.schemas"; 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: * 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(...) 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"; 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 export const MetadataSchema = z
.object({ .object({

View File

@ -1,4 +1,4 @@
import * as z from "zod/v4"; import { z } from "zod/v4";
import { NumericStringSchema } from "./base.schemas"; 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"; import { NumericStringSchema } from "./base.schemas";
/** /**

View File

@ -1,4 +1,5 @@
export * from "./use-datasource"; export * from "./use-datasource";
export * from "./use-hook-form";
export * from "./use-pagination"; export * from "./use-pagination";
export * from "./use-query-key"; export * from "./use-query-key";
export * from "./use-toggle"; 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-hook-form": "^7.58.1",
"react-i18next": "^15.5.1", "react-i18next": "^15.5.1",
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"zod": "^3.25.67" "zod": "^4.1.11"
}, },
"devDependencies": { "devDependencies": {
"@hookform/devtools": "^4.4.0", "@hookform/devtools": "^4.4.0",

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
import { IModuleServer, ModuleParams } from "@erp/core/api"; import { IModuleServer, ModuleParams } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Transaction } from "sequelize";
import { customerInvoicesRouter, models } from "./infrastructure"; import { customerInvoicesRouter, models } from "./infrastructure";
import { buildCustomerInvoiceDependencies } from "./infrastructure/dependencies";
export const customerInvoicesAPIModule: IModuleServer = { export const customerInvoicesAPIModule: IModuleServer = {
name: "customer-invoices", name: "customer-invoices",
@ -12,16 +15,33 @@ export const customerInvoicesAPIModule: IModuleServer = {
customerInvoicesRouter(params); customerInvoicesRouter(params);
logger.info("🚀 CustomerInvoices module initialized", { label: this.name }); logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
}, },
async registerDependencies(params) { async registerDependencies(params) {
const { database, logger } = params; const { logger, listServices } = params; /* = ModuleParams & {
getService: (name: string) => any;
};*/
logger.info("🚀 CustomerInvoices module dependencies registered", { logger.info("🚀 CustomerInvoices module dependencies registered", {
label: this.name, label: this.name,
}); });
logger.info(listServices());
//getService()
const deps = buildCustomerInvoiceDependencies(params);
return { return {
models, models,
services: { 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; //delete: () => DeleteCustomerInvoiceUseCase;
report: () => ReportCustomerInvoiceUseCase; report: () => ReportCustomerInvoiceUseCase;
}; };
getService: (name: string) => any;
listServices: () => string[];
}; };
export function buildCustomerInvoiceDependencies(params: ModuleParams): CustomerInvoiceDeps { export function buildCustomerInvoiceDependencies(params: ModuleParams): CustomerInvoiceDeps {
const { database } = params; const { database, listServices, getService } = params;
const transactionManager = new SequelizeTransactionManager(database); const transactionManager = new SequelizeTransactionManager(database);
const catalogs = { taxes: spainTaxCatalogProvider }; const catalogs = { taxes: spainTaxCatalogProvider };
@ -157,5 +159,7 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer
report: () => report: () =>
new ReportCustomerInvoiceUseCase(service, transactionManager, presenterRegistry), new ReportCustomerInvoiceUseCase(service, transactionManager, presenterRegistry),
}, },
listServices,
getService,
}; };
} }

View File

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

View File

@ -1,5 +1,5 @@
import { CriteriaSchema } from "@erp/core"; import { CriteriaSchema } from "@erp/core";
import * as z from "zod/v4"; import { z } from "zod/v4";
export const CustomerInvoiceListRequestSchema = CriteriaSchema; export const CustomerInvoiceListRequestSchema = CriteriaSchema;
export type CustomerInvoiceListRequestDTO = z.infer<typeof CustomerInvoiceListRequestSchema>; 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: * 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({ export const GetCustomerInvoiceByIdRequestSchema = z.object({
invoice_id: z.string(), 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({ export const ReportCustomerInvoiceByIdRequestSchema = z.object({
invoice_id: z.string(), 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({ export const UpdateCustomerInvoiceByIdParamsRequestSchema = z.object({
customer_id: z.string(), customer_id: z.string(),

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
export * from "./use-create-customer-invoice-mutation"; export * from "./use-create-customer-invoice-mutation";
export * from "./use-customer-invoice-query";
export * from "./use-customer-invoices-context"; export * from "./use-customer-invoices-context";
export * from "./use-customer-invoices-query"; export * from "./use-customer-invoices-query";
export * from "./use-detail-columns"; 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"; import { CreateCustomerInvoiceRequestSchema } from "../../../common/dto";
export const CustomerInvoiceItemDataFormSchema = CreateCustomerInvoiceRequestSchema.extend({ 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 CustomerCreateSchema = CreateCustomerRequestSchema;
export const CustomerUpdateSchema = UpdateCustomerByIdRequestSchema; export const CustomerUpdateSchema = UpdateCustomerByIdRequestSchema;*/
export const CustomerSchema = GetCustomerByIdResponseSchema.omit({ export const CustomerSchema = GetCustomerInvoiceByIdResponseSchema.omit({
metadata: true, metadata: true,
}); });
export type CustomerData = z.infer<typeof CustomerSchema>;*/ export type CustomerInvoiceData = z.infer<typeof CustomerSchema>;
export type CustomerInvoicesListData = ListCustomerInvoicesResponseDTO; 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-hook-form": "^7.58.1",
"react-i18next": "^15.5.1", "react-i18next": "^15.5.1",
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"zod": "^3.25.67" "zod": "^4.1.11"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.21", "@types/express": "^4.17.21",

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api"; 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 { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { import {

View File

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

View File

@ -1,5 +1,5 @@
import { CriteriaSchema } from "@erp/core"; import { CriteriaSchema } from "@erp/core";
import * as z from "zod/v4"; import { z } from "zod/v4";
export const CustomerListRequestSchema = CriteriaSchema; export const CustomerListRequestSchema = CriteriaSchema;
export type CustomerListRequestDTO = z.infer<typeof CustomerListRequestSchema>; 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: * 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: * 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({ export const UpdateCustomerByIdParamsRequestSchema = z.object({
customer_id: z.string(), customer_id: z.string(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ type CreateCustomerPayload = {
data: CustomerFormData; data: CustomerFormData;
}; };
export function useCreateCustomerMutation() { export function useCreateCustomer() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const dataSource = useDataSource(); const dataSource = useDataSource();
const schema = CreateCustomerRequestSchema; 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 dataSource = useDataSource();
const enabled = (options?.enabled ?? true) && !!customerId; const enabled = (options?.enabled ?? true) && !!customerId;
const queryResult = useQuery<CustomerData, DefaultError>({ return useQuery<CustomerData, DefaultError>({
queryKey: CUSTOMER_QUERY_KEY(customerId ?? "unknown"), queryKey: CUSTOMER_QUERY_KEY(customerId ?? "unknown"),
queryFn: async (context) => { queryFn: async (context) => {
const { signal } = context; const { signal } = context;
if (!customerId) { if (!customerId) {
if (!customerId) throw new Error("customerId is required"); if (!customerId) throw new Error("customerId is required");
} }
const customer = await dataSource.getOne<CustomerData>("customers", customerId, { return await dataSource.getOne<CustomerData>("customers", customerId, {
signal, signal,
}); });
return customer;
}, },
enabled, enabled,
}); });
return queryResult;
} }
/* /*

View File

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

View File

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

View File

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

View File

@ -3,17 +3,23 @@
"version": "0.0.1", "version": "0.0.1",
"main": "src/index.ts", "main": "src/index.ts",
"types": "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": { "peerDependencies": {
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"express": "^4.18.2" "express": "^4.18.2",
"zod": "^4.1.11"
}, },
"devDependencies": { "@types/express": "^4.17.21" }, "devDependencies": { "@types/express": "^4.17.21" },
"dependencies": { "dependencies": {
"@erp/auth": "workspace:*", "@erp/auth": "workspace:*",
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@repo/rdx-ddd": "workspace:*", "@repo/rdx-ddd": "workspace:*",
"@repo/rdx-criteria": "workspace:*",
"@repo/rdx-utils": "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) { public async execute(params: SendInvoiceUseCaseInput) {
const { invoice_id } = params; const { invoice_id } = params;
console.log("CASO DE USO -----ESTO ES UNA PRUEBA>>>>>>", invoice_id);
const idOrError = UniqueID.create(invoice_id); const idOrError = UniqueID.create(invoice_id);
if (idOrError.isFailure) { if (idOrError.isFailure) {
@ -25,14 +25,41 @@ export class SendInvoiceUseCase {
} }
const invoiceId = idOrError.data; const invoiceId = idOrError.data;
console.log("CASO DE USO -----VALIDADO>>>>>>", invoiceId);
return this.transactionManager.complete(async (transaction: Transaction) => { return this.transactionManager.complete(async (transaction: Transaction) => {
try { 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) { if (invoiceOrError.isFailure) {
return Result.fail(invoiceOrError.error); return Result.fail(invoiceOrError.error);
} }
const invoice = invoiceOrError.data; const invoice2 = invoiceOrError.data;
return Result.ok({}); return Result.ok({});
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);

View File

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

View File

@ -42,29 +42,6 @@ export class VerifactuRecordService {
// return Result.fail(invoiceResult.error); // 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>>>>>>"); console.log("ESTO ES UNA PRUEBA>>>>>>");
return Result.ok(invoice); return Result.ok(invoice);

View File

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api"; 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 { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { SendCustomerInvoiceByIdRequestSchema } from "../../../common/dto";
import { buildVerifactuDependencies } from "../dependencies"; import { buildVerifactuDependencies } from "../dependencies";
import { SendInvoiceVerifactuController } from "./controllers"; import { SendInvoiceVerifactuController } from "./controllers";
@ -14,7 +15,6 @@ export const verifactuRouter = (params: ModuleParams) => {
logger: ILogger; logger: ILogger;
}; };
console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>>>>>>>>>>>>>>>>>>>");
const deps = buildVerifactuDependencies(params); const deps = buildVerifactuDependencies(params);
const router: Router = Router({ mergeParams: true }); const router: Router = Router({ mergeParams: true });
@ -40,11 +40,17 @@ export const verifactuRouter = (params: ModuleParams) => {
router.get( router.get(
"/:invoice_id/sendVerifactu", "/:invoice_id/sendVerifactu",
//checkTabContext, //checkTabContext,
validateRequest(SendCustomerInvoiceByIdRequestSchema, "params"), validateRequest(SendInvoiceByIdRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => { (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); const controller = new SendInvoiceVerifactuController(useCase);
return controller.execute(req, res, next); 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 { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import { VerifactuRecordEstado } from "@erp/customer-invoices/api/domain/aggregates/value-objects";
import { import {
UniqueID, UniqueID,
ValidationErrorCollection, 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", "dinero.js": "^1.9.1",
"libphonenumber-js": "^1.11.20", "libphonenumber-js": "^1.11.20",
"shallow-equal-object": "^1.1.1", "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 { Result } from "@repo/rdx-utils";
import * as z from "zod/v4"; import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers"; import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object"; import { ValueObject } from "./value-object";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@
"i18next": "^25.1.1", "i18next": "^25.1.1",
"react-hook-form": "^7.58.1", "react-hook-form": "^7.58.1",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"zod": "^3.25.67" "zod": "^4.1.11"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
@ -59,6 +59,6 @@
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"sonner": "^2.0.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 * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"; import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod/v4"; import { z } from "zod/v4";
import { Badge } from "@repo/shadcn-ui/components/badge"; import { Badge } from "@repo/shadcn-ui/components/badge";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";

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