Facturas de cliente
This commit is contained in:
parent
198137d426
commit
5584b6039b
@ -1,6 +1,7 @@
|
||||
import { Toaster, TooltipProvider } from "@repo/shadcn-ui/components";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
import { i18n } from "@/locales";
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"@repo/shadcn-ui": "workspace:*",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@tanstack/react-query": "^5.75.4",
|
||||
"ag-grid-community": "^33.3.0",
|
||||
"axios": "^1.9.0",
|
||||
"express": "^4.18.2",
|
||||
"http-status": "^2.1.0",
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
import { MultiSelectField, MultiSelectFieldProps } from "@repo/rdx-ui/components";
|
||||
import * as React from "react";
|
||||
import type { FieldValues } from "react-hook-form";
|
||||
import { TaxesList } from "../../constants";
|
||||
|
||||
/**
|
||||
* Igual que MultiSelect pero con `options` preconfiguradas a TaxesList.
|
||||
* Puedes sobreescribir `options` si lo necesitas, se mergean (TaxesList primero).
|
||||
*/
|
||||
export type TaxesMultiSelectFieldProps<T extends FieldValues> = Omit<
|
||||
MultiSelectFieldProps<T>,
|
||||
"options"
|
||||
>;
|
||||
|
||||
const TaxesMultiSelectFieldInner = React.forwardRef(
|
||||
<T extends FieldValues>(
|
||||
props: TaxesMultiSelectFieldProps<T>,
|
||||
ref: React.Ref<HTMLButtonElement>
|
||||
) => {
|
||||
return (
|
||||
<MultiSelectField ref={ref} {...(props as MultiSelectFieldProps<T>)} options={TaxesList} />
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
TaxesMultiSelectFieldInner.displayName = "TaxesMultiSelectField";
|
||||
|
||||
export const TaxesMultiSelectField = TaxesMultiSelectFieldInner as <T extends FieldValues>(
|
||||
p: TaxesMultiSelectFieldProps<T> & { ref?: React.Ref<HTMLButtonElement> }
|
||||
) => React.JSX.Element;
|
||||
@ -1,2 +1,6 @@
|
||||
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
|
||||
export * from "./form";
|
||||
export * from "./taxes-multi-select";
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.74.11",
|
||||
"ag-grid-community": "^33.3.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"express": "^4.18.2",
|
||||
"handlebars": "^4.7.8",
|
||||
@ -46,7 +47,6 @@
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
"@repo/shadcn-ui": "workspace:*",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"ag-grid-community": "^33.3.0",
|
||||
"ag-grid-react": "^33.3.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"libphonenumber-js": "^1.12.7",
|
||||
|
||||
@ -13,7 +13,6 @@ export const CreateCustomerInvoiceItemRequestSchema = z.object({
|
||||
|
||||
export const CreateCustomerInvoiceRequestSchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
series: z.string().default(""),
|
||||
|
||||
@ -1,9 +1,24 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const UpdateCustomerInvoiceByIdParamsRequestSchema = z.object({
|
||||
customer_id: z.string(),
|
||||
invoice_id: z.string(),
|
||||
});
|
||||
|
||||
export const UpdateCustomerByIdRequestSchema = z.object({});
|
||||
export const UpdateCustomerInvoiceByIdRequestSchema = z.object({
|
||||
invoice_number: z.string(),
|
||||
series: z.string().default(""),
|
||||
|
||||
export type UpdateCustomerByIdRequestDTO = z.infer<typeof UpdateCustomerByIdRequestSchema>;
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string().default(""),
|
||||
|
||||
customer_id: z.uuid(),
|
||||
|
||||
notes: z.string().default(""),
|
||||
|
||||
language_code: z.string().toLowerCase().default("es"),
|
||||
currency_code: z.string().toUpperCase().default("EUR"),
|
||||
});
|
||||
|
||||
export type UpdateCustomerInvoiceByIdRequestDTO = Partial<
|
||||
z.infer<typeof UpdateCustomerInvoiceByIdRequestSchema>
|
||||
>;
|
||||
|
||||
@ -18,6 +18,11 @@
|
||||
"series": "Serie",
|
||||
"status": "Status",
|
||||
"invoice_date": "Date",
|
||||
"recipient_tin": "Customer TIN",
|
||||
"recipient_name": "Customer name",
|
||||
"recipient_city": "Customer city",
|
||||
"recipient_province": "Customer province",
|
||||
"recipient_postal_code": "Customer postal code",
|
||||
"total_amount": "Total price"
|
||||
}
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@ import type {
|
||||
SizeColumnsToFitProvidedWidthStrategy,
|
||||
ValueFormatterParams,
|
||||
} from "ag-grid-community";
|
||||
import { AllCommunityModule, ColDef, GridOptions, ModuleRegistry } from "ag-grid-community";
|
||||
import { ColDef, GridOptions } from "ag-grid-community";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { MoneyDTO } from "@erp/core";
|
||||
@ -19,8 +19,6 @@ import { useCustomerInvoicesQuery } from "../hooks";
|
||||
import { useTranslation } from "../i18n";
|
||||
import { CustomerInvoiceStatusBadge } from "./customer-invoice-status-badge";
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
|
||||
// Create new GridExample component
|
||||
export const CustomerInvoicesListGrid = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -58,12 +56,14 @@ export const CustomerInvoicesListGrid = () => {
|
||||
return formatDate(params.value);
|
||||
},
|
||||
},
|
||||
{ field: "recipient.tin" },
|
||||
{ field: "recipient.name" },
|
||||
|
||||
{ field: "recipient.city" },
|
||||
{ field: "recipient.province" },
|
||||
{ field: "recipient.postal_code" },
|
||||
{ field: "recipient.tin", headerName: t("pages.list.grid_columns.recipient_tin") },
|
||||
{ field: "recipient.name", headerName: t("pages.list.grid_columns.recipient_name") },
|
||||
{ field: "recipient.city", headerName: t("pages.list.grid_columns.recipient_city") },
|
||||
{ field: "recipient.province", headerName: t("pages.list.grid_columns.recipient_province") },
|
||||
{
|
||||
field: "recipient.postal_code",
|
||||
headerName: t("pages.list.grid_columns.recipient_postal_code"),
|
||||
},
|
||||
{
|
||||
field: "taxable_amount",
|
||||
headerName: t("pages.list.grid_columns.taxable_amount"),
|
||||
|
||||
@ -1,20 +1,43 @@
|
||||
import { useDataSource, useQueryKey } from "@erp/core/hooks";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { CreateCustomerInvoiceRequestDTO } from "../../common/dto";
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { CreateCustomerInvoiceRequestSchema } from "../../common";
|
||||
import { CustomerInvoiceData, CustomerInvoiceFormData } from "../schemas";
|
||||
|
||||
type CreateCustomerInvoicePayload = {
|
||||
data: CustomerInvoiceFormData;
|
||||
};
|
||||
|
||||
export const useCreateCustomerInvoiceMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
const keys = useQueryKey();
|
||||
const schema = CreateCustomerInvoiceRequestSchema;
|
||||
|
||||
return useMutation<
|
||||
CreateCustomerInvoiceRequestDTO,
|
||||
Error,
|
||||
Partial<CreateCustomerInvoiceRequestDTO>
|
||||
>({
|
||||
mutationFn: (data) => {
|
||||
console.log(data);
|
||||
return dataSource.createOne("customer-invoices", data);
|
||||
return useMutation<CustomerInvoiceData, DefaultError, CreateCustomerInvoicePayload>({
|
||||
mutationKey: ["customer-invoice:create"],
|
||||
|
||||
mutationFn: async (payload) => {
|
||||
const { data } = payload;
|
||||
const invoiceId = UniqueID.generateNewID();
|
||||
|
||||
const newInvoiceData = {
|
||||
...data,
|
||||
id: invoiceId.toString(),
|
||||
};
|
||||
|
||||
const result = schema.safeParse(newInvoiceData);
|
||||
if (!result.success) {
|
||||
// Construye errores detallados
|
||||
const validationErrors = result.error.issues.map((err) => ({
|
||||
field: err.path.join("."),
|
||||
message: err.message,
|
||||
}));
|
||||
|
||||
throw new ValidationErrorCollection("Validation failed", validationErrors);
|
||||
}
|
||||
|
||||
const created = await dataSource.createOne("customer-invoices", newInvoiceData);
|
||||
return created as CustomerInvoiceData;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["customer-invoices"] });
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
UpdateCustomerInvoiceByIdRequestDTO,
|
||||
UpdateCustomerInvoiceByIdRequestSchema,
|
||||
} from "../../common";
|
||||
import { CustomerInvoiceFormData } from "../schemas";
|
||||
import { CUSTOMER_INVOICE_QUERY_KEY } from "./use-customer-invoice-query";
|
||||
|
||||
export const CUSTOMER_INVOICES_LIST_KEY = ["customer-invoices"] as const;
|
||||
|
||||
type UpdateCustomerInvoiceContext = {};
|
||||
|
||||
type UpdateCustomerInvoicePayload = {
|
||||
id: string;
|
||||
data: Partial<CustomerInvoiceFormData>;
|
||||
};
|
||||
|
||||
export function useUpdateCustomerInvoice() {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
const schema = UpdateCustomerInvoiceByIdRequestSchema;
|
||||
|
||||
return useMutation<
|
||||
CustomerInvoiceFormData,
|
||||
Error,
|
||||
UpdateCustomerInvoicePayload,
|
||||
UpdateCustomerInvoiceContext
|
||||
>({
|
||||
mutationKey: ["customer-invoice:update"], //, customerId],
|
||||
|
||||
mutationFn: async (payload) => {
|
||||
const { id: invoiceId, data } = payload;
|
||||
if (!invoiceId) {
|
||||
throw new Error("customerInvoiceId is required");
|
||||
}
|
||||
|
||||
const result = schema.safeParse(data);
|
||||
if (!result.success) {
|
||||
// Construye errores detallados
|
||||
const validationErrors = result.error.issues.map((err) => ({
|
||||
field: err.path.join("."),
|
||||
message: err.message,
|
||||
}));
|
||||
|
||||
throw new ValidationErrorCollection("Validation failed", validationErrors);
|
||||
}
|
||||
|
||||
const updated = await dataSource.updateOne("customer-invoices", invoiceId, data);
|
||||
return updated as CustomerInvoiceFormData;
|
||||
},
|
||||
onSuccess: (updated: CustomerInvoiceFormData, variables) => {
|
||||
const { id: invoiceId } = variables;
|
||||
|
||||
// Refresca inmediatamente el detalle
|
||||
queryClient.setQueryData<UpdateCustomerInvoiceByIdRequestDTO>(
|
||||
CUSTOMER_INVOICE_QUERY_KEY(invoiceId),
|
||||
updated
|
||||
);
|
||||
|
||||
// Otra opción es invalidar el detalle para forzar refetch:
|
||||
// queryClient.invalidateQueries({ queryKey: CUSTOMER_QUERY_KEY(customerId) });
|
||||
|
||||
// Invalida el listado para refrescar desde servidor
|
||||
queryClient.invalidateQueries({ queryKey: CUSTOMER_INVOICES_LIST_KEY });
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -13,7 +13,7 @@ export const CustomerInvoicesList = () => {
|
||||
<>
|
||||
<AppBreadcrumb />
|
||||
<AppContent>
|
||||
<div className='flex items-center justify-between space-y-2'>
|
||||
<div className='flex items-center justify-between space-y-6'>
|
||||
<div>
|
||||
<h2 className='text-2xl font-bold tracking-tight'>{t("pages.list.title")}</h2>
|
||||
<p className='text-muted-foreground'>{t("pages.list.description")}</p>
|
||||
@ -28,7 +28,7 @@ export const CustomerInvoicesList = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col w-full h-full py-4'>
|
||||
<div className='flex flex-col w-full h-full py-3'>
|
||||
<CustomerInvoicesListGrid />
|
||||
</div>
|
||||
</AppContent>
|
||||
|
||||
@ -6,4 +6,10 @@ export const CustomerInvoiceFormSchema = z.object({
|
||||
series: z.string().optional(),
|
||||
});
|
||||
|
||||
export const CustomerInvoiceFormData = z.infer<typeof CustomerInvoiceFormSchema>;
|
||||
export type CustomerInvoiceFormData = z.infer<typeof CustomerInvoiceFormSchema>;
|
||||
|
||||
export const defaultCustomerFormData: CustomerInvoiceFormData = {
|
||||
invoice_number: "",
|
||||
status: "draft",
|
||||
series: "",
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.74.11",
|
||||
"ag-grid-community": "^33.3.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"express": "^4.18.2",
|
||||
"i18next": "^25.1.1",
|
||||
@ -38,7 +39,6 @@
|
||||
"@repo/rdx-ui": "workspace:*",
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
"@repo/shadcn-ui": "workspace:*",
|
||||
"ag-grid-community": "^33.3.0",
|
||||
"ag-grid-react": "^33.3.0",
|
||||
"lucide-react": "^0.503.0",
|
||||
"react": "^19.1.0",
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { AG_GRID_LOCALE_ES } from "@ag-grid-community/locale";
|
||||
import type { ValueFormatterParams } from "ag-grid-community";
|
||||
import {
|
||||
AllCommunityModule,
|
||||
ColDef,
|
||||
GridOptions,
|
||||
ModuleRegistry,
|
||||
SizeColumnsToContentStrategy,
|
||||
SizeColumnsToFitGridStrategy,
|
||||
SizeColumnsToFitProvidedWidthStrategy,
|
||||
} from "ag-grid-community";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { ErrorOverlay } from "@repo/rdx-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
@ -19,8 +18,6 @@ import { useCustomersQuery } from "../hooks";
|
||||
import { useTranslation } from "../i18n";
|
||||
import { CustomerStatusBadge } from "./customer-status-badge";
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
|
||||
// Create new GridExample component
|
||||
export const CustomersListGrid = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -124,6 +121,19 @@ export const CustomersListGrid = () => {
|
||||
[autoSizeStrategy, colDefs]
|
||||
);
|
||||
|
||||
if (isLoadError) {
|
||||
return (
|
||||
<>
|
||||
<ErrorOverlay
|
||||
errorMessage={
|
||||
(loadError as Error)?.message ??
|
||||
t("pages.update.loadErrorMsg", "Inténtalo de nuevo más tarde.")
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Container: Defines the grid's theme & dimensions.
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -2,7 +2,7 @@ import { useDataSource, useQueryKey } from "@erp/core/hooks";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { CustomersListData } from "../schemas";
|
||||
|
||||
// Obtener todas las facturas
|
||||
// Obtener todos los clientes
|
||||
export const useCustomersQuery = (params?: any) => {
|
||||
const dataSource = useDataSource();
|
||||
const keys = useQueryKey();
|
||||
|
||||
@ -21,11 +21,11 @@ export const CustomersList = () => {
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button onClick={() => navigate("/customers/create")} className='cursor-pointer'>
|
||||
<PlusIcon className='w-4 h-4 mr-2' />
|
||||
{t("pages.list.title")}
|
||||
{t("pages.create.title")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col w-full h-full py-4'>
|
||||
<div className='flex flex-col w-full h-full py-3'>
|
||||
<CustomersListGrid />
|
||||
</div>
|
||||
<Outlet />
|
||||
|
||||
@ -31,5 +31,5 @@
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"packageManager": "pnpm@10.15.0"
|
||||
"packageManager": "pnpm@10.17.1"
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ importers:
|
||||
version: 29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3))
|
||||
ts-jest:
|
||||
specifier: ^29.2.5
|
||||
version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3)
|
||||
version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3)
|
||||
tsconfig-paths:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@ -395,6 +395,9 @@ importers:
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.75.4
|
||||
version: 5.81.2(react@19.1.0)
|
||||
ag-grid-community:
|
||||
specifier: ^33.3.0
|
||||
version: 33.3.2
|
||||
axios:
|
||||
specifier: ^1.9.0
|
||||
version: 1.10.0
|
||||
@ -572,8 +575,6 @@ importers:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
modules/customer-payments: {}
|
||||
|
||||
modules/customers:
|
||||
dependencies:
|
||||
'@ag-grid-community/locale':
|
||||
@ -705,40 +706,6 @@ importers:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.23
|
||||
|
||||
modules/document-numbering:
|
||||
dependencies:
|
||||
'@erp/auth':
|
||||
specifier: workspace:*
|
||||
version: link:../auth
|
||||
'@erp/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@repo/rdx-criteria':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-criteria
|
||||
'@repo/rdx-ddd':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-ddd
|
||||
'@repo/rdx-logger':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-logger
|
||||
'@repo/rdx-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-utils
|
||||
express:
|
||||
specifier: ^4.18.2
|
||||
version: 4.21.2
|
||||
sequelize:
|
||||
specifier: ^6.37.5
|
||||
version: 6.37.7(mysql2@3.14.1)
|
||||
zod:
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11
|
||||
devDependencies:
|
||||
'@types/express':
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.23
|
||||
|
||||
modules/verifactu:
|
||||
dependencies:
|
||||
'@erp/auth':
|
||||
@ -12871,7 +12838,7 @@ snapshots:
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3):
|
||||
ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3):
|
||||
dependencies:
|
||||
bs-logger: 0.2.6
|
||||
ejs: 3.1.10
|
||||
@ -12889,6 +12856,7 @@ snapshots:
|
||||
'@jest/transform': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
babel-jest: 29.7.0(@babel/core@7.27.4)
|
||||
esbuild: 0.25.5
|
||||
jest-util: 29.7.0
|
||||
|
||||
ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user