Facturas de cliente

This commit is contained in:
David Arranz 2025-07-02 10:57:09 +02:00
parent b4c330458d
commit 81fffc4a0e
17 changed files with 130 additions and 45 deletions

View File

@ -4,6 +4,7 @@ import dotenv from "dotenv";
import express, { Application } from "express";
import helmet from "helmet";
import responseTime from "response-time";
import { ENV } from "./config";
import { logger } from "./lib/logger";
dotenv.config();
@ -25,7 +26,7 @@ export function createApp(): Application {
// enable CORS - Cross Origin Resource Sharing
app.use(
cors({
origin: process.env.FRONTEND_URL || "http://localhost:5173",
origin: ENV.NODE_ENV === "development" ? "*" : process.env.FRONTEND_URL,
methods: "GET,POST,PUT,DELETE,OPTIONS",
credentials: true,

View File

@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
"dev": "vite --host --clearScreen false",
"build": "tsc && vite build",
"preview": "vite preview",
"clean": "rm -rf dist && rm -rf node_modules && rm -rf .turbo",
@ -19,6 +19,7 @@
"@tailwindcss/postcss": "^4.1.5",
"@tailwindcss/vite": "^4.1.6",
"@tanstack/react-query-devtools": "^5.74.11",
"@types/dinero.js": "^1.9.4",
"@types/node": "^22.15.12",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
@ -37,6 +38,7 @@
"@repo/shadcn-ui": "workspace:*",
"@tanstack/react-query": "^5.74.11",
"axios": "^1.9.0",
"dinero.js": "^1.9.1",
"i18next": "^25.0.2",
"i18next-browser-languagedetector": "^8.1.0",
"react": "^19.1.0",

View File

@ -9,11 +9,14 @@ import { i18n } from "@/locales";
import { AuthProvider, createAuthService } from "@erp/auth/client";
import { DataSourceProvider, createAxiosDataSource, createAxiosInstance } from "@erp/core/client";
import DineroFactory from "dinero.js";
import "./app.css";
import { clearAccessToken, getAccessToken, setAccessToken } from "./lib";
import { AppRoutes } from "./routes";
export const App = () => {
DineroFactory.globalLocale = "es-ES";
const queryClient = new QueryClient({
defaultOptions: {
queries: {

View File

@ -6,10 +6,14 @@
"./api": "./src/api/index.ts",
"./client": "./src/web/index.ts"
},
"peerDependencies": {
"dinero.js": "^1.9.1"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@testing-library/react-hooks": "^8.0.1",
"@types/axios": "^0.14.4",
"@types/dinero.js": "^1.9.4",
"@types/express": "^4.17.21",
"@types/jest": "29.5.14",
"@types/react": "^19.1.2",

View File

@ -0,0 +1 @@
export * from "./module-metadata.d";

View File

@ -0,0 +1,3 @@
export const formatDate = (value: string) => {
return new Date(value).toLocaleDateString();
};

View File

@ -0,0 +1,2 @@
export * from "./date-func";
export * from "./money-funcs";

View File

@ -0,0 +1,12 @@
import DineroFactory, { Currency } from "dinero.js";
import { MoneyDTO } from "../../../common";
export const formatMoney = (value: MoneyDTO) => {
const money = DineroFactory({
amount: Number(value.amount),
currency: value.currency_code as Currency,
precision: Number(value.scale),
});
return money.toFormat();
};

View File

@ -1,2 +1,3 @@
export * from "./data-source";
export * from "./helpers";
export * from "./modules";

View File

@ -8,8 +8,12 @@
"./api": "./src/api/index.ts",
"./client": "./src/web/manifest.ts"
},
"peerDependencies": {
"dinero.js": "^1.9.1"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/dinero.js": "^1.9.4",
"@types/express": "^4.17.21",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
@ -17,6 +21,7 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@ag-grid-community/locale": "34.0.0",
"@erp/core": "workspace:*",
"@hookform/resolvers": "^5.0.1",
"@repo/rdx-criteria": "workspace:*",

View File

@ -10,17 +10,19 @@ export enum INVOICE_STATUS {
DRAFT = "draft",
EMITTED = "emitted",
SENT = "sent",
RECEIVED = "received",
REJECTED = "rejected",
}
export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusProps> {
private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "rejected"];
private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "received", "rejected"];
private static readonly FIELD = "invoiceStatus";
private static readonly ERROR_CODE = "INVALID_INVOICE_STATUS";
private static readonly TRANSITIONS: Record<string, string[]> = {
draft: [INVOICE_STATUS.EMITTED],
emitted: [INVOICE_STATUS.SENT, INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT],
sent: [INVOICE_STATUS.REJECTED],
sent: [INVOICE_STATUS.RECEIVED, INVOICE_STATUS.REJECTED],
received: [],
rejected: [],
};
@ -43,7 +45,9 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
? CustomerInvoiceStatus.createSent()
: value === "emitted"
? CustomerInvoiceStatus.createSent()
: CustomerInvoiceStatus.createDraft()
: value === ""
? CustomerInvoiceStatus.createReceived()
: CustomerInvoiceStatus.createDraft()
);
}
@ -59,6 +63,10 @@ export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusPro
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.SENT });
}
public static createReceived(): CustomerInvoiceStatus {
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.RECEIVED });
}
public static createRejected(): CustomerInvoiceStatus {
return new CustomerInvoiceStatus({ value: INVOICE_STATUS.REJECTED });
}

View File

@ -17,6 +17,7 @@ export const ListCustomerInvoicesResultSchema = createListViewSchema(
scale: z.number(),
currency_code: z.string(),
}),
total_price: z.object({
amount: z.number(),
scale: z.number(),

View File

@ -1,12 +1,14 @@
// React Grid Logic
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
// Theme
import type { ColDef, ValueFormatterParams } from "ag-grid-community";
import { AG_GRID_LOCALE_ES } from "@ag-grid-community/locale";
// Grid
import type { ColDef, GridOptions, ValueFormatterParams } from "ag-grid-community";
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
ModuleRegistry.registerModules([AllCommunityModule]);
import { MoneyDTO } from "@erp/core";
import { formatDate, formatMoney } from "@erp/core/client";
// Core CSS
import { AgGridReact } from "ag-grid-react";
import { useCustomerInvoicesQuery } from "../hooks";
@ -54,38 +56,62 @@ export const CustomerInvoicesGrid = () => {
// Column Definitions: Defines & controls grid columns.
const [colDefs] = useState<ColDef[]>([
{ field: "invoice_number" },
{ field: "invoice_series" },
{ field: "invoice_number", headerName: "Num. factura" },
{ field: "invoice_series", headerName: "Serie" },
{
field: "status",
field: "invoice_status",
filter: true,
headerName: "Estado",
},
{ field: "issue_date" },
{ field: "operation_date" },
{
field: "subtotal",
field: "issue_date",
headerName: "Fecha fact.",
valueFormatter: (params: ValueFormatterParams) => {
return "0 €";
//return `£${params.value.toLocaleString()}`;
return formatDate(params.value);
},
},
{
field: "total",
field: "subtotal_price",
valueFormatter: (params: ValueFormatterParams) => {
return "0 €";
//return `£${params.value.toLocaleString()}`;
const rawValue: MoneyDTO = params.value;
return formatMoney(rawValue);
},
},
{
field: "total_price",
valueFormatter: (params: ValueFormatterParams) => {
const rawValue: MoneyDTO = params.value;
return formatMoney(rawValue);
},
},
]);
// Apply settings across all columns
const defaultColDef = useMemo<ColDef>(() => {
return {
filter: true,
const gridOptions: GridOptions = {
columnDefs: colDefs,
defaultColDef: {
editable: true,
flex: 1,
minWidth: 100,
filter: false,
sortable: false,
resizable: false,
};
}, []);
resizable: true,
},
sideBar: true,
statusBar: {
statusPanels: [
{ statusPanel: "agTotalAndFilteredRowCountComponent", align: "left" },
{ statusPanel: "agAggregationComponent" },
],
},
rowGroupPanelShow: "always",
pagination: true,
paginationPageSize: 10,
paginationPageSizeSelector: [10, 20, 30, 50],
enableCharts: true,
localeText: AG_GRID_LOCALE_ES,
rowSelection: { mode: "multiRow" },
};
// Container: Defines the grid's theme & dimensions.
return (
@ -96,13 +122,7 @@ export const CustomerInvoicesGrid = () => {
width: "100%",
}}
>
<AgGridReact
rowData={data?.items ?? []}
loading={isLoading || isPending}
columnDefs={colDefs}
defaultColDef={defaultColDef}
pagination={true}
/>
<AgGridReact rowData={data?.items ?? []} loading={isLoading || isPending} {...gridOptions} />
</div>
);
};

View File

@ -1,25 +1,21 @@
import { IListResponseDTO } from "@erp/core";
import { useDataSource, useQueryKey } from "@erp/core/client";
import { useQuery } from "@tanstack/react-query";
import { IListCustomerInvoicesResponseDTO } from "../../common/dto";
import { ListCustomerInvoicesResultDTO } from "../../common/dto";
// Obtener todas las facturas
export const useCustomerInvoicesQuery = (params: any) => {
const dataSource = useDataSource();
const keys = useQueryKey();
return useQuery({
return useQuery<ListCustomerInvoicesResultDTO>({
queryKey: keys().data().resource("customer-invoices").action("list").params(params).get(),
queryFn: (context) => {
console.log(dataSource.getBaseUrl());
const { signal } = context;
return dataSource.getList<IListResponseDTO<IListCustomerInvoicesResponseDTO>>(
"customer-invoices",
{
signal,
...params,
}
);
return dataSource.getList<ListCustomerInvoicesResultDTO>("customer-invoices", {
signal,
...params,
});
},
});
};

View File

@ -3,6 +3,6 @@
"compilerOptions": {
"composite": true
},
"include": ["src"],
"include": ["src", "../../modules/core/src/web/lib/helpers/money-funcs.ts"],
"exclude": ["src/**/__tests__/*"]
}

View File

@ -213,6 +213,9 @@ importers:
axios:
specifier: ^1.9.0
version: 1.10.0
dinero.js:
specifier: ^1.9.1
version: 1.9.1
i18next:
specifier: ^25.0.2
version: 25.2.1(typescript@5.8.3)
@ -274,6 +277,9 @@ importers:
'@tanstack/react-query-devtools':
specifier: ^5.74.11
version: 5.81.2(@tanstack/react-query@5.81.2(react@19.1.0))(react@19.1.0)
'@types/dinero.js':
specifier: ^1.9.4
version: 1.9.4
'@types/node':
specifier: ^22.15.12
version: 22.15.32
@ -365,6 +371,9 @@ importers:
axios:
specifier: ^1.9.0
version: 1.10.0
dinero.js:
specifier: ^1.9.1
version: 1.9.1
http-status:
specifier: ^2.1.0
version: 2.1.0
@ -393,6 +402,9 @@ importers:
'@types/axios':
specifier: ^0.14.4
version: 0.14.4
'@types/dinero.js':
specifier: ^1.9.4
version: 1.9.4
'@types/express':
specifier: ^4.17.21
version: 4.17.23
@ -417,6 +429,9 @@ importers:
modules/customer-invoices:
dependencies:
'@ag-grid-community/locale':
specifier: 34.0.0
version: 34.0.0
'@erp/core':
specifier: workspace:*
version: link:../core
@ -450,6 +465,9 @@ importers:
date-fns:
specifier: ^4.1.0
version: 4.1.0
dinero.js:
specifier: ^1.9.1
version: 1.9.1
express:
specifier: ^4.18.2
version: 4.21.2
@ -487,6 +505,9 @@ importers:
'@biomejs/biome':
specifier: 1.9.4
version: 1.9.4
'@types/dinero.js':
specifier: ^1.9.4
version: 1.9.4
'@types/express':
specifier: ^4.17.21
version: 4.17.23
@ -860,6 +881,9 @@ packages:
'@adobe/css-tools@4.3.3':
resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==}
'@ag-grid-community/locale@34.0.0':
resolution: {integrity: sha512-F3Bs6wpT4CCOgN5wmzhimQz6GK5VUznsl8+SZXv0A7bA5aw8qLuVc/yEIpqNRykQwwt0vytcHhZzVDDMuZSy1g==}
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@ -6245,6 +6269,8 @@ snapshots:
'@adobe/css-tools@4.3.3': {}
'@ag-grid-community/locale@34.0.0': {}
'@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0':