From 2043bbb78b513f9ddfbffa6310ee7b28e7f60c32 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 29 Sep 2025 20:22:59 +0200 Subject: [PATCH] Facturas de cliente --- .../src/web/components/form}/form-debug.tsx | 0 modules/core/src/web/components/form/index.ts | 1 + .../customer-invoice-items.full.presenter.ts | 2 +- .../domain/customer-invoice.full.presenter.ts | 2 +- ...get-customer-invoice-by-id.response.dto.ts | 2 +- .../src/common/locales/en.json | 53 ++- .../src/common/locales/es.json | 183 +++++++- .../customer-invoice-editor-skeleton.tsx | 35 ++ .../customer-invoice-status-badge.tsx | 2 +- .../editor/customer-invoice-edit-form.tsx | 63 +++ .../src/web/components/editor/index.ts | 1 + .../editor/invoice-basic-info-fields.tsx | 90 ++++ .../editor/invoice-items-editor.tsx | 123 +++++ .../components/editor/invoice-tax-summary.tsx | 108 +++++ .../web/components/editor/invoice-totals.tsx | 114 +++++ .../components/editor/items/blocks-view.tsx | 147 ++++++ .../src/web/components/editor/items/index.ts | 2 + .../components/editor/items/table-view.tsx | 141 ++++++ .../web/components/editor/items/types.d.ts | 5 + .../src/web/components/index.tsx | 5 + .../src/web/components/page-header.tsx | 41 ++ .../src/web/customer-invoice-routes.tsx | 4 + .../customer-invoices/src/web/hooks/index.ts | 1 + ... => create-customer-invoice-edit-form.tsx} | 6 +- .../src/web/pages/create/create.tsx | 4 +- .../customer-invoices/src/web/pages/index.ts | 1 + .../pages/update/customer-invoices-update.tsx | 243 +++++----- .../src/web/pages/update/index.ts | 1 + .../schemas/customer-invoices.api.schema.ts | 12 +- .../schemas/customer-invoices.form.schema.ts | 107 ++++- .../repositories/customer.repository.ts | 2 +- ...selector.tsx => client-selector-modal.tsx} | 2 +- .../customer-modal-selector.tsx | 436 ++++++++++++++++++ .../customer-modal-selector/index.ts | 1 + .../customer-additional-config-fields.tsx | 2 +- .../editor/customer-address-fields.tsx | 2 +- .../editor/customer-basic-info-fields.tsx | 2 +- .../editor/customer-contact-fields.tsx | 17 +- .../components/editor/customer-edit-form.tsx | 4 +- .../{ => editor}/customer-editor-skeleton.tsx | 2 +- .../src/web/components/editor/index.ts | 1 + modules/customers/src/web/components/index.ts | 5 +- .../src/web/schemas/customer.api.schema.ts | 2 +- modules/customers/tsconfig.json | 6 +- packages/rdx-criteria/readme.md | 38 ++ .../src/criteria-to-sequelize-converter.ts | 6 +- .../components/form/DatePickerInputField.tsx | 6 +- .../rdx-ui/src/components/form/fieldset.tsx | 15 +- .../src/components/layout/app-breadcrumb.tsx | 2 +- .../src/components/layout/app-content.tsx | 14 +- .../src/components/layout/app-header.tsx | 14 + .../rdx-ui/src/components/layout/index.tsx | 1 + 52 files changed, 1910 insertions(+), 169 deletions(-) rename modules/{customers/src/web/components => core/src/web/components/form}/form-debug.tsx (100%) create mode 100644 modules/customer-invoices/src/web/components/customer-invoice-editor-skeleton.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/customer-invoice-edit-form.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/index.ts create mode 100644 modules/customer-invoices/src/web/components/editor/invoice-basic-info-fields.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/invoice-items-editor.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/invoice-tax-summary.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/invoice-totals.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/items/blocks-view.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/items/index.ts create mode 100644 modules/customer-invoices/src/web/components/editor/items/table-view.tsx create mode 100644 modules/customer-invoices/src/web/components/editor/items/types.d.ts create mode 100644 modules/customer-invoices/src/web/components/page-header.tsx rename modules/customer-invoices/src/web/pages/create/{customer-invoice-edit-form.tsx => create-customer-invoice-edit-form.tsx} (99%) create mode 100644 modules/customer-invoices/src/web/pages/update/index.ts rename modules/customers/src/web/components/{client-selector.tsx => client-selector-modal.tsx} (99%) create mode 100644 modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx create mode 100644 modules/customers/src/web/components/customer-modal-selector/index.ts rename modules/customers/src/web/components/{ => editor}/customer-editor-skeleton.tsx (96%) create mode 100644 packages/rdx-criteria/readme.md create mode 100644 packages/rdx-ui/src/components/layout/app-header.tsx diff --git a/modules/customers/src/web/components/form-debug.tsx b/modules/core/src/web/components/form/form-debug.tsx similarity index 100% rename from modules/customers/src/web/components/form-debug.tsx rename to modules/core/src/web/components/form/form-debug.tsx diff --git a/modules/core/src/web/components/form/index.ts b/modules/core/src/web/components/form/index.ts index 960ce369..df2aa4f4 100644 --- a/modules/core/src/web/components/form/index.ts +++ b/modules/core/src/web/components/form/index.ts @@ -1 +1,2 @@ +export * from "./form-debug.tsx"; export * from "./taxes-multi-select-field.tsx"; diff --git a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts index fe9be325..63799652 100644 --- a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts +++ b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice-items.full.presenter.ts @@ -41,7 +41,7 @@ export class CustomerInvoiceItemsFullPresenter extends Presenter { discount_amount: allAmounts.discountAmount.toObjectString(), taxable_amount: allAmounts.taxableAmount.toObjectString(), - taxes: invoiceItem.taxes.getCodesToString(), + tax_codes: invoiceItem.taxes.getCodesToString().split(","), taxes_amount: allAmounts.taxesAmount.toObjectString(), total_amount: allAmounts.totalAmount.toObjectString(), diff --git a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts index 0a9de682..00f3645d 100644 --- a/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts +++ b/modules/customer-invoices/src/api/application/presenters/domain/customer-invoice.full.presenter.ts @@ -39,7 +39,7 @@ export class CustomerInvoiceFullPresenter extends Presenter< id: invoice.id.toString(), company_id: invoice.companyId.toString(), - invoice_number: invoice.invoiceNumber.toString(), + invoice_number: toEmptyString(invoice.invoiceNumber, (value) => value.toString()), status: invoice.status.toPrimitive(), series: toEmptyString(invoice.series, (value) => value.toString()), diff --git a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts index 6e64f939..ba49a7e0 100644 --- a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts +++ b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts @@ -61,7 +61,7 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({ quantity: QuantitySchema, unit_amount: MoneySchema, - taxes: z.string(), + tax_codes: z.array(z.string()), subtotal_amount: MoneySchema, discount_percentage: PercentageSchema, diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index eb666280..ff685673 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -7,6 +7,15 @@ "insert_row_below": "Insert row below", "remove_row": "Remove" }, + "catalog": { + "status": { + "draft": "Draft", + "emitted": "Emitted", + "sent": "Sent", + "received": "Received", + "rejected": "Rejected" + } + }, "pages": { "title": "Customer invoices", "description": "Manage your customer invoices", @@ -44,28 +53,52 @@ "description": "View the details of the selected customer invoice" } }, - "status": { - "draft": "Draft", - "emitted": "Emitted", - "sent": "Sent", - "received": "Received", - "rejected": "Rejected" + "form_groups": { + "customer": { + "title": "Customer", + "description": "Select the customer for this invoice" + }, + "items": { + "title": "Invoice details", + "description": "" + }, + "basic_into": { + "title": "Invoice information", + "description": "" + }, + "totals": { + "title": "Invoice totals", + "description": "" + }, + "tax_resume": { + "title": "Resumen de impuestos", + "description": "" + }, + "preferences": { + "title": "Preferences", + "description": "Additional invoice settings" + } }, "form_fields": { + "status": { + "label": "Estado", + "placeholder": "", + "description": "" + }, "invoice_number": { "label": "Invoice number", "placeholder": "", "description": "" }, "invoice_date": { - "label": "Date", + "label": "Invoice date", "placeholder": "Select a date", - "description": "Invoice issue date" + "description": "Invoice date" }, - "invoice_series": { + "series": { "label": "Serie", "placeholder": "", - "description": "" + "description": "Invoice serie" }, "operation_date": { "label": "Operation date", diff --git a/modules/customer-invoices/src/common/locales/es.json b/modules/customer-invoices/src/common/locales/es.json index 96afbba8..b5654815 100644 --- a/modules/customer-invoices/src/common/locales/es.json +++ b/modules/customer-invoices/src/common/locales/es.json @@ -1,11 +1,186 @@ { - "common": {}, + "common": { + "append_empty_row": "Añadir fila", + "append_empty_row_tooltip": "Añadir una fila vacía", + "duplicate_row": "Duplicar", + "insert_row_above": "Insertar fila arriba", + "insert_row_below": "Insertar fila abajo", + "remove_row": "Eliminar" + }, + "catalog": { + "status": { + "draft": "Borrador", + "emitted": "Emitida", + "sent": "Enviada", + "received": "Recibida", + "rejected": "Rechazada" + } + }, + "pages": { + "title": "Facturas de clientes", + "description": "Gestiona tus facturas de clientes", + "list": { + "title": "Listado de facturas de clientes", + "description": "Lista todas las facturas de clientes", + "grid_columns": { + "invoice_number": "Nº factura", + "series": "Serie", + "status": "Estado", + "invoice_date": "Fecha", + "recipient_tin": "NIF cliente", + "recipient_name": "Nombre cliente", + "recipient_city": "Ciudad cliente", + "recipient_province": "Provincia cliente", + "recipient_postal_code": "Código postal cliente", + "total_amount": "Precio total" + } + }, + "create": { + "title": "Nueva factura de cliente", + "description": "Crear una nueva factura de cliente", + "back_to_list": "Volver al listado" + }, + "edit": { + "title": "Editar factura de cliente", + "description": "Editar la factura de cliente seleccionada" + }, + "delete": { + "title": "Eliminar factura de cliente", + "description": "Eliminar la factura de cliente seleccionada" + }, + "view": { + "title": "Ver factura de cliente", + "description": "Ver los detalles de la factura de cliente seleccionada" + } + }, + + "form_groups": { + "customer": { + "title": "Cliente", + "description": "Selecciona el cliente para esta factura" + }, + "items": { + "title": "Detalles de la factura", + "description": "" + }, + "basic_into": { + "title": "Información de la factura", + "description": "" + }, + "totals": { + "title": "Totales de la factura", + "description": "" + }, + "preferences": { + "title": "Preferencias", + "description": "Configuraciones adicionales de la factura" + } + }, + "form_fields": { + "invoice_number": { + "label": "Número de factura", + "placeholder": "", + "description": "" + }, + "invoice_date": { + "label": "Fecha", + "placeholder": "Selecciona una fecha", + "description": "Fecha de emisión de la factura" + }, + "series": { + "label": "Serie", + "placeholder": "", + "description": "" + }, + "operation_date": { + "label": "Fecha de operación", + "placeholder": "Selecciona una fecha", + "description": "Fecha de la operación de la factura" + }, + "description": { + "label": "Descripción", + "placeholder": "Descripción de la factura", + "description": "Descripción general de la factura" + }, + "subtotal_price": { + "label": "Subtotal", + "placeholder": "", + "desc": "Subtotal de la factura" + }, + "discount": { + "label": "Descuento (%)", + "placeholder": "", + "desc": "Porcentaje de descuento" + }, + "discount_price": { + "label": "Importe del descuento", + "placeholder": "", + "desc": "Importe del descuento porcentual" + }, + "total_price": { + "label": "Precio total", + "placeholder": "", + "desc": "Precio total de la factura" + }, + "notes": { + "label": "Notas", + "placeholder": "Notas adicionales sobre la factura", + "description": "Notas adicionales que se pueden incluir en la factura" + }, + "items": { + "quantity": { + "label": "Cantidad", + "placeholder": "", + "description": "" + }, + "description": { + "label": "Descripción", + "placeholder": "", + "description": "" + }, + "unit_price": { + "label": "Precio unitario", + "placeholder": "", + "description": "Precio unitario del producto" + }, + "subtotal_price": { + "label": "Subtotal", + "placeholder": "", + "description": "" + }, + "discount": { + "label": "Dto (%)", + "placeholder": "", + "description": "Porcentaje de descuento" + }, + "discount_price": { + "label": "Importe del descuento", + "placeholder": "", + "desc": "Importe del descuento porcentual" + }, + "taxes": { + "label": "Impuestos", + "placeholder": "", + "desc": "Impuestos" + }, + "taxes_price": { + "label": "Importe impuestos", + "placeholder": "", + "desc": "Importe porcentual de los impuestos" + }, + "total_price": { + "label": "Precio total", + "placeholder": "", + "description": "Precio total con descuento porcentual" + } + } + }, "components": { "customer_invoice_taxes_multi_select": { "label": "Impuestos", - "placeholder": "Seleccionar impuestos", - "description": "Seleccionar los impuestos a aplicar a los artículos de la factura", - "invalid_tax_selection": "Selección de impuestos no válida. Por favor, seleccione un impuesto válido." + "placeholder": "Selecciona impuestos", + "description": "Selecciona los impuestos a aplicar a los artículos de la factura", + "invalid_tax_selection": "Selección de impuestos no válida. Por favor, selecciona un impuesto válido." } } } diff --git a/modules/customer-invoices/src/web/components/customer-invoice-editor-skeleton.tsx b/modules/customer-invoices/src/web/components/customer-invoice-editor-skeleton.tsx new file mode 100644 index 00000000..d8d60f88 --- /dev/null +++ b/modules/customer-invoices/src/web/components/customer-invoice-editor-skeleton.tsx @@ -0,0 +1,35 @@ +// components/CustomerSkeleton.tsx +import { AppBreadcrumb, AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; +import { useTranslation } from "../i18n"; + +export const CustomerInvoiceEditorSkeleton = () => { + const { t } = useTranslation(); + return ( + <> + + +
+ diff --git a/packages/rdx-ui/src/components/form/fieldset.tsx b/packages/rdx-ui/src/components/form/fieldset.tsx index 1566b02c..f17b79b3 100644 --- a/packages/rdx-ui/src/components/form/fieldset.tsx +++ b/packages/rdx-ui/src/components/form/fieldset.tsx @@ -5,7 +5,7 @@ export const Fieldset = ({ className, children, ...props }: React.ComponentProps
*+[data-slot=control]]:mt-6 bg-gray-50/50 rounded-xl p-6", + " *:data-[slot=text]:mt-1 [&>*+[data-slot=control]]:mt-6 bg-card rounded-xl p-6", className )} {...props} @@ -23,7 +23,7 @@ export const FieldGroup = ({ className, children, ...props }: React.ComponentPro export const Field = ({ className, children, ...props }: React.ComponentProps<"div">) => (
[data-slot=label]+[data-slot=control]]:mt-3 [&>[data-slot=label]+[data-slot=description]]:mt-1 [&>[data-slot=description]+[data-slot=control]]:mt-3 [&>[data-slot=control]+[data-slot=description]]:mt-3 [&>[data-slot=control]+[data-slot=error]]:mt-3 *:data-[slot=label]:font-medium", + "[&>[data-slot=label]+[data-slot=control]]:mt-3 [&>[data-slot=label]+[data-slot=description]]:mt-1 [&>[data-slot=description]+[data-slot=control]]:mt-3 [&>[data-slot=control]+[data-slot=description]]:mt-3 [&>[data-slot=control]+[data-slot=error]]:mt-3 *:data-[slot=label]:font-medium bg-transparent", className )} {...props} @@ -35,7 +35,10 @@ export const Field = ({ className, children, ...props }: React.ComponentProps<"d export const Legend = ({ className, children, ...props }: React.ComponentProps<"div">) => (
{children} @@ -43,7 +46,11 @@ export const Legend = ({ className, children, ...props }: React.ComponentProps<" ); export const Description = ({ className, children, ...props }: React.ComponentProps<"p">) => ( -

+

{children}

); diff --git a/packages/rdx-ui/src/components/layout/app-breadcrumb.tsx b/packages/rdx-ui/src/components/layout/app-breadcrumb.tsx index 349c48f2..01e62e7f 100644 --- a/packages/rdx-ui/src/components/layout/app-breadcrumb.tsx +++ b/packages/rdx-ui/src/components/layout/app-breadcrumb.tsx @@ -12,7 +12,7 @@ import { export const AppBreadcrumb = () => { return (
-
+
diff --git a/packages/rdx-ui/src/components/layout/app-content.tsx b/packages/rdx-ui/src/components/layout/app-content.tsx index c7e15ed8..bdede8e3 100644 --- a/packages/rdx-ui/src/components/layout/app-content.tsx +++ b/packages/rdx-ui/src/components/layout/app-content.tsx @@ -1,9 +1,19 @@ import { cn } from "@repo/shadcn-ui/lib/utils"; import { PropsWithChildren } from "react"; -export const AppContent = ({ className, children }: PropsWithChildren<{ className?: string }>) => { +export const AppContent = ({ + className, + children, + ...props +}: PropsWithChildren<{ className?: string }>) => { return ( -
+
{children}
); diff --git a/packages/rdx-ui/src/components/layout/app-header.tsx b/packages/rdx-ui/src/components/layout/app-header.tsx new file mode 100644 index 00000000..ae04843e --- /dev/null +++ b/packages/rdx-ui/src/components/layout/app-header.tsx @@ -0,0 +1,14 @@ +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { PropsWithChildren } from "react"; + +export const AppHeader = ({ + className, + children, + ...props +}: PropsWithChildren<{ className?: string }>) => { + return ( +
+ {children} +
+ ); +}; diff --git a/packages/rdx-ui/src/components/layout/index.tsx b/packages/rdx-ui/src/components/layout/index.tsx index 437da7db..6af15182 100644 --- a/packages/rdx-ui/src/components/layout/index.tsx +++ b/packages/rdx-ui/src/components/layout/index.tsx @@ -1,3 +1,4 @@ export * from "./app-breadcrumb.tsx"; export * from "./app-content.tsx"; +export * from "./app-header.tsx"; export * from "./app-layout.tsx";