Clientes y facturas de cliente

This commit is contained in:
David Arranz 2025-09-16 19:41:27 +02:00
parent c285c2d897
commit f72273b069
7 changed files with 93 additions and 61 deletions

View File

@ -3,13 +3,13 @@ import { CustomerInvoiceListDTO } from "@erp/customer-invoices/api/infrastructur
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { toEmptyString } from "@repo/rdx-ddd"; import { toEmptyString } from "@repo/rdx-ddd";
import { ArrayElement, Collection } from "@repo/rdx-utils"; import { ArrayElement, Collection } from "@repo/rdx-utils";
import { CustomerInvoiceListResponseDTO } from "../../../../common/dto"; import { ListCustomerInvoicesResponseDTO } from "../../../../common/dto";
export class ListCustomerInvoicesPresenter extends Presenter { export class ListCustomerInvoicesPresenter extends Presenter {
protected _mapInvoice(invoice: CustomerInvoiceListDTO) { protected _mapInvoice(invoice: CustomerInvoiceListDTO) {
const recipientDTO = invoice.recipient.toObjectString(); const recipientDTO = invoice.recipient.toObjectString();
const invoiceDTO: ArrayElement<CustomerInvoiceListResponseDTO["items"]> = { const invoiceDTO: ArrayElement<ListCustomerInvoicesResponseDTO["items"]> = {
id: invoice.id.toString(), id: invoice.id.toString(),
company_id: invoice.companyId.toString(), company_id: invoice.companyId.toString(),
customer_id: invoice.customerId.toString(), customer_id: invoice.customerId.toString(),
@ -48,7 +48,7 @@ export class ListCustomerInvoicesPresenter extends Presenter {
toOutput(params: { toOutput(params: {
customerInvoices: Collection<CustomerInvoiceListDTO>; customerInvoices: Collection<CustomerInvoiceListDTO>;
criteria: Criteria; criteria: Criteria;
}): CustomerInvoiceListResponseDTO { }): ListCustomerInvoicesResponseDTO {
const { customerInvoices, criteria } = params; const { customerInvoices, criteria } = params;
const invoices = customerInvoices.map((invoice) => this._mapInvoice(invoice)); const invoices = customerInvoices.map((invoice) => this._mapInvoice(invoice));

View File

@ -3,7 +3,7 @@ import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { CustomerInvoiceListResponseDTO } from "../../../common/dto"; import { ListCustomerInvoicesResponseDTO } from "../../../common/dto";
import { CustomerInvoiceService } from "../../domain"; import { CustomerInvoiceService } from "../../domain";
import { ListCustomerInvoicesPresenter } from "../presenters"; import { ListCustomerInvoicesPresenter } from "../presenters";
@ -21,7 +21,7 @@ export class ListCustomerInvoicesUseCase {
public execute( public execute(
params: ListCustomerInvoicesUseCaseInput params: ListCustomerInvoicesUseCaseInput
): Promise<Result<CustomerInvoiceListResponseDTO, Error>> { ): Promise<Result<ListCustomerInvoicesResponseDTO, Error>> {
const { criteria, companyId } = params; const { criteria, companyId } = params;
const presenter = this.presenterRegistry.getPresenter({ const presenter = this.presenterRegistry.getPresenter({
resource: "customer-invoice", resource: "customer-invoice",

View File

@ -101,17 +101,17 @@
<aside class="flex items-start mb-4 w-full"> <aside class="flex items-start mb-4 w-full">
<!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda --> <!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda -->
<div class="flex flex-col items-start text-left"> <div class="w-[70%] flex flex-col items-start text-left">
<img src="https://rodax-software.com/images/logo1.jpg" alt="Logo Rodax" class="block h-14 w-auto mb-1" /> <img src="https://rodax-software.com/images/logo1.jpg" alt="Logo Rodax" class="block h-14 w-auto mb-1" />
<div class="flex w-full gap-30"> <div class="flex w-full gap-9">
<div class="w-[30%] bg-amber-400 p-3 text-sm leading-tight"> <div class="p-1 ">
<p><span class="font-semibold">Factura nº:</span> {{reference}}</p> <p><span>Factura nº:</span>xxxxxxxx</p>
<p><span class="font-semibold">Fecha:</span> {{date}}</p> <p><span>Fecha:</span>12/12/2024</p>
</div> </div>
<div class="w-[70%] bg-amber-700 p-3 text-sm leading-tight text-white"> <div class="p-1">
<h2 class="font-semibold uppercase mb-1">{{customer.name}}</h2> <h2 class="font-semibold uppercase mb-1">{{customer.name}}</h2>
<p>AAAA</p> <p>AAAA</p>
<p>BBBBBB</p> <p>BBBBBBsdfsfsdf sfsdf sf sdfs fsdfsd fsdf sdfsd fds </p>
<p>CCCCC</p> <p>CCCCC</p>
<p>DDDDD</p> <p>DDDDD</p>
</div> </div>

View File

@ -1,7 +1,7 @@
import { MetadataSchema, MoneySchema, createListViewResponseSchema } from "@erp/core"; import { MetadataSchema, MoneySchema, createListViewResponseSchema } from "@erp/core";
import * as z from "zod/v4"; import * as z from "zod/v4";
export const ListCustomerInvoiceResponseSchema = createListViewResponseSchema( export const ListCustomerInvoicesResponseSchema = createListViewResponseSchema(
z.object({ z.object({
id: z.uuid(), id: z.uuid(),
company_id: z.uuid(), company_id: z.uuid(),
@ -40,4 +40,4 @@ export const ListCustomerInvoiceResponseSchema = createListViewResponseSchema(
}) })
); );
export type CustomerInvoiceListResponseDTO = z.infer<typeof ListCustomerInvoiceResponseSchema>; export type ListCustomerInvoicesResponseDTO = z.infer<typeof ListCustomerInvoicesResponseSchema>;

View File

@ -1,25 +1,37 @@
import { useState } from "react";
import { AG_GRID_LOCALE_ES } from "@ag-grid-community/locale"; import { AG_GRID_LOCALE_ES } from "@ag-grid-community/locale";
// Grid import type {
import type { ColDef, GridOptions, ValueFormatterParams } from "ag-grid-community"; SizeColumnsToContentStrategy,
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community"; SizeColumnsToFitGridStrategy,
SizeColumnsToFitProvidedWidthStrategy,
ModuleRegistry.registerModules([AllCommunityModule]); ValueFormatterParams,
} from "ag-grid-community";
import { AllCommunityModule, ColDef, GridOptions, ModuleRegistry } from "ag-grid-community";
import { useMemo, useState } from "react";
import { MoneyDTO } from "@erp/core"; import { MoneyDTO } from "@erp/core";
import { formatDate, formatMoney } from "@erp/core/client"; import { formatDate, formatMoney } from "@erp/core/client";
// Core CSS import { Button } from "@repo/shadcn-ui/components";
import { AgGridReact } from "ag-grid-react"; import { AgGridReact } from "ag-grid-react";
import { Link } from "react-router-dom"; import { ChevronRightIcon } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useCustomerInvoicesQuery } from "../hooks"; import { useCustomerInvoicesQuery } from "../hooks";
import { useTranslation } from "../i18n"; import { useTranslation } from "../i18n";
import { CustomerInvoiceStatusBadge } from "./customer-invoice-status-badge"; import { CustomerInvoiceStatusBadge } from "./customer-invoice-status-badge";
ModuleRegistry.registerModules([AllCommunityModule]);
// Create new GridExample component // Create new GridExample component
export const CustomerInvoicesListGrid = () => { export const CustomerInvoicesListGrid = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, isLoading, isPending, isError, error } = useCustomerInvoicesQuery({
const navigate = useNavigate();
const {
data: customersData,
isLoading: isLoadingCustomerInvoices,
isError: isLoadError,
error: loadError,
} = useCustomerInvoicesQuery({
pagination: { pagination: {
pageSize: 999, pageSize: 999,
}, },
@ -78,31 +90,56 @@ export const CustomerInvoicesListGrid = () => {
}, },
}, },
{ {
field: "id", colId: "actions",
headerName: t("pages.list.grid_columns.total_amount"), headerName: t("pages.list.grid_columns.actions", "Actions"),
cellRenderer: (params: ValueFormatterParams) => { cellRenderer: (params: ValueFormatterParams) => {
return <Link to={params.value}>Hola</Link>; const { data } = params;
return (
<Button
variant='secondary'
size='icon'
className='size-8'
onClick={() => {
navigate(`${data.id}/edit`);
}}
>
<ChevronRightIcon />
</Button>
);
}, },
}, },
]); ]);
const gridOptions: GridOptions = { const autoSizeStrategy = useMemo<
rowModelType: "clientSide", | SizeColumnsToFitGridStrategy
columnDefs: colDefs, | SizeColumnsToFitProvidedWidthStrategy
defaultColDef: { | SizeColumnsToContentStrategy
editable: false, >(() => {
flex: 1, return {
minWidth: 100, type: "fitGridWidth",
filter: true, defaultMinWidth: 100,
sortable: true, columnLimits: [{ colId: "actions", minWidth: 75, maxWidth: 75 }],
resizable: true, };
}, }, []);
pagination: true,
paginationPageSize: 15, const gridOptions: GridOptions = useMemo(
paginationPageSizeSelector: [10, 15, 20, 30, 50], () => ({
localeText: AG_GRID_LOCALE_ES, columnDefs: colDefs,
rowSelection: { mode: "multiRow" }, autoSizeStrategy: autoSizeStrategy,
}; defaultColDef: {
editable: false,
flex: 1,
filter: false,
sortable: false,
resizable: true,
},
pagination: true,
paginationPageSize: 10,
paginationPageSizeSelector: [10, 20, 30, 50],
localeText: AG_GRID_LOCALE_ES,
}),
[autoSizeStrategy, colDefs]
);
// Container: Defines the grid's theme & dimensions. // Container: Defines the grid's theme & dimensions.
return ( return (
@ -113,7 +150,11 @@ export const CustomerInvoicesListGrid = () => {
width: "100%", width: "100%",
}} }}
> >
<AgGridReact rowData={data?.items ?? []} loading={isLoading || isPending} {...gridOptions} /> <AgGridReact
rowData={customersData?.items ?? []}
loading={isLoadingCustomerInvoices}
{...gridOptions}
/>
</div> </div>
); );
}; };

View File

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

View File

@ -110,15 +110,6 @@ export const CustomerEditForm = ({ formId, data, onSubmit, isPending }: Customer
)} )}
/> />
<TextField
control={form.control}
name='tin'
required
label={t("form_fields.tin.label")}
placeholder={t("form_fields.tin.placeholder")}
description={t("form_fields.tin.description")}
/>
<TextField <TextField
control={form.control} control={form.control}
name='name' name='name'