From 6adb538aa43a6d2cd70a800c297f76ad285ddc5a Mon Sep 17 00:00:00 2001 From: david Date: Sat, 2 May 2026 22:51:05 +0200 Subject: [PATCH] . --- .../use-issued-invoices-grid-columns.tsx | 2 +- .../blocks/proformas-grid/proformas-grid.bak | 73 +++++ .../blocks/proformas-grid/proformas-grid.tsx | 37 +-- .../use-proforma-grid-columns.tsx | 302 ++++++++++++++++++ .../pages/update/proforma-update-comp.tsx | 113 ------- .../pages/update/proforma-update-form.tsx | 51 --- .../web/proformas/update/adapters/index.ts | 5 +- ...forma-form-to-commercial-document-lines.ts | 15 + ...-to-proforma-items-update-form.adapter.ts} | 14 +- ...roforma-to-proforma-update-form.adapter.ts | 88 +++++ ...-proforma-to-selected-customer.adapter.ts} | 17 +- ...roforma-to-proforma-update-form.adapter.ts | 35 -- .../web/proformas/update/controllers/index.ts | 2 + .../use-update-proforma-controller.ts | 19 ++ .../use-update-proforma-items-controller.ts | 67 ++-- .../use-update-proforma-tax-controller.ts | 99 ++++++ .../use-update-proforma-totals-controller.ts | 36 +++ .../commercial-document-calculation.entity.ts | 33 ++ .../web/proformas/update/entities/index.ts | 2 + .../proforma-item-update-form.entity.ts | 3 + .../proforma-item-update-form.schema.ts | 11 +- .../entities/proforma-update-form.entity.ts | 9 + .../entities/proforma-update-form.schema.ts | 30 +- .../entities/proforma-update-totals.entity.ts | 27 ++ .../web/proformas/update/ui/blocks/index.ts | 4 +- .../update/ui/blocks/items-editor/index.ts | 1 - .../blocks/items-editor/item-row-editor.tsx | 58 ---- .../ui/blocks/items-editor/items-editor.tsx | 73 ----- .../ui/blocks/proforma-basic-info-fields.tsx | 74 ----- .../ui/blocks/proforma-form-field-shell.tsx | 55 ---- .../ui/blocks/proforma-header-fields-card.tsx | 185 ----------- .../update/ui/blocks/proforma-line-editor.tsx | 23 ++ .../ui/blocks/proforma-totals-summary.tsx | 202 ++++++++++++ .../selected-recipient-summary.tsx | 281 ++++++---------- .../editors/proforma-update-editor-form.tsx | 47 ++- .../editors/proforma-update-header-editor.tsx | 4 + .../editors/proforma-update-items-editor.tsx | 4 + .../proforma-update-recipient-editor.tsx | 85 +++-- .../ui/editors/proforma-update-tax-editor.tsx | 192 +++++++++++ .../update/ui/pages/proforma-update-page.tsx | 4 + .../update/utils/calculate-proforma-totals.ts | 13 + ...culate-commercial-document-line-amounts.ts | 28 ++ ...culate-commercial-document-lines-totals.ts | 33 ++ ...ulate-commercial-document-tax-breakdown.ts | 56 ++++ .../calculate-commercial-document-totals.ts | 49 +++ .../calculate-proportional-discount.ts | 11 + .../update/utils/calculations/index.ts | 1 + .../utils/calculations/money-calculation.ts | 15 + .../src/web/proformas/update/utils/index.ts | 2 +- .../{types.ts => data-table-meta.ts} | 0 .../datatable/data-table-pagination.tsx | 150 --------- .../data-table-pagesize-select.tsx | 43 +++ .../data-table-pagination-button.tsx | 38 +++ .../data-table-pagination-controls.tsx | 78 +++++ .../data-table-pagination-summary.tsx | 40 +++ .../data-table-pagination.tsx | 85 +++++ .../datatable/data-table-pagination/index.ts | 1 + .../src/components/datatable/data-table.tsx | 253 ++++++--------- .../src/components/form/form-section-card.tsx | 61 ++-- packages/rdx-utils/src/helpers/index.ts | 1 + .../src/helpers/percentage-helper.ts | 10 + 61 files changed, 2040 insertions(+), 1310 deletions(-) create mode 100644 modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.bak delete mode 100644 modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx delete mode 100644 modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts rename modules/customer-invoices/src/web/proformas/update/adapters/{proforma-items-to-proforma-items-update-form.adapter.ts => map-proforma-items-to-proforma-items-update-form.adapter.ts} (59%) create mode 100644 modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts rename modules/customer-invoices/src/web/proformas/update/adapters/{proforma-to-selected-customer.adapter.ts => map-proforma-to-selected-customer.adapter.ts} (57%) delete mode 100644 modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-proforma-update-form.adapter.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-totals-controller.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts delete mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/index.ts delete mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/item-row-editor.tsx delete mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/items-editor.tsx delete mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-basic-info-fields.tsx delete mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-form-field-shell.tsx delete mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-header-fields-card.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/ui/blocks/proforma-totals-summary.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-tax-editor.tsx create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculate-proforma-totals.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/calculate-commercial-document-line-amounts.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/calculate-commercial-document-lines-totals.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/calculate-commercial-document-tax-breakdown.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/calculate-commercial-document-totals.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/calculate-proportional-discount.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/index.ts create mode 100644 modules/customer-invoices/src/web/proformas/update/utils/calculations/money-calculation.ts rename packages/rdx-ui/src/components/datatable/{types.ts => data-table-meta.ts} (100%) delete mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination.tsx create mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination/data-table-pagesize-select.tsx create mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination/data-table-pagination-button.tsx create mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination/data-table-pagination-controls.tsx create mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination/data-table-pagination-summary.tsx create mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination/data-table-pagination.tsx create mode 100644 packages/rdx-ui/src/components/datatable/data-table-pagination/index.ts create mode 100644 packages/rdx-utils/src/helpers/percentage-helper.ts diff --git a/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx b/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx index 2d61f346..07314e51 100644 --- a/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx +++ b/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx @@ -1,6 +1,6 @@ -import { DateHelper } from "@erp/rdx-utils"; import { ReactQRCode } from "@lglab/react-qr-code"; import { DataTableColumnHeader } from "@repo/rdx-ui/components"; +import { DateHelper } from "@repo/rdx-utils"; import { Button, ButtonGroup, diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.bak b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.bak new file mode 100644 index 00000000..0ea74b29 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.bak @@ -0,0 +1,73 @@ +import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components"; +import type { ColumnDef } from "@tanstack/react-table"; + +import { useTranslation } from "../../../../../i18n"; +import type { ProformaList, ProformaListRow } from "../../../../shared"; + +interface ProformasGridProps { + data?: ProformaList; + loading: boolean; + fetching?: boolean; + + columns: ColumnDef[]; + + pageIndex: number; + pageSize: number; + onPageChange: (pageIndex: number) => void; + onPageSizeChange: (size: number) => void; + + onRowClick?: (proformaId: string) => void; +} + +export const ProformasGrid = ({ + data, + loading, + fetching, + columns, + pageIndex, + pageSize, + onPageChange, + onPageSizeChange, + onRowClick, +}: ProformasGridProps) => { + const { t } = useTranslation(); + const { items, totalItems } = data || { items: [], totalItems: 0 }; + + if (loading) { + return ( + + ); + } + + return ( +
+ {/* + * ─── CAPA DE FADE ────────────────────────────────────────────────────── + * Div absolutamente posicionado sobre el área scrollable. + * - pointer-events: none → no bloquea interacciones. + * - linear-gradient → de transparente a bg-card (blanco/oscuro). + * - z-10 → queda por DEBAJO de la columna sticky (z-20). + * - Solo visible cuando aún hay contenido a la derecha. + * ──────────────────────────────────────────────────────────────────────── + */} + onRowClick?.(row.id)} + pageIndex={pageIndex} + pageSize={pageSize} + totalItems={totalItems} + /> +
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx index 0ea74b29..8108430d 100644 --- a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/proformas-grid.tsx @@ -45,29 +45,18 @@ export const ProformasGrid = ({ } return ( -
- {/* - * ─── CAPA DE FADE ────────────────────────────────────────────────────── - * Div absolutamente posicionado sobre el área scrollable. - * - pointer-events: none → no bloquea interacciones. - * - linear-gradient → de transparente a bg-card (blanco/oscuro). - * - z-10 → queda por DEBAJO de la columna sticky (z-20). - * - Solo visible cuando aún hay contenido a la derecha. - * ──────────────────────────────────────────────────────────────────────── - */} - onRowClick?.(row.id)} - pageIndex={pageIndex} - pageSize={pageSize} - totalItems={totalItems} - /> -
+ onRowClick?.(row.id)} + pageIndex={pageIndex} + pageSize={pageSize} + totalItems={totalItems} + /> ); }; diff --git a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx index 580a129b..3b849d12 100644 --- a/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx +++ b/modules/customer-invoices/src/web/proformas/list/ui/blocks/proformas-grid/use-proforma-grid-columns.tsx @@ -1,6 +1,7 @@ import { DateHelper } from "@repo/rdx-utils"; import { Button, + Checkbox, Tooltip, TooltipContent, TooltipProvider, @@ -34,6 +35,7 @@ type GridActionHandlers = { onChangeStatusClick?: (proforma: ProformaListRow, nextStatus: ProformaStatus) => void; onLinkedInvoiceClick?: (proforma: ProformaListRow) => void; getNextStatus?: (proforma: ProformaListRow) => ProformaStatus | null; + canIssue?: (proforma: ProformaListRow) => boolean; canEdit?: (proforma: ProformaListRow) => boolean; canDelete?: (proforma: ProformaListRow) => boolean; @@ -44,6 +46,306 @@ export function useProformasGridColumns( ): ColumnDef[] { const { t } = useTranslation(); + return React.useMemo[]>( + () => [ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "series", + header: "Serie", + enableHiding: true, + }, + { + accessorKey: "invoiceNumber", + header: ({ column }) => { + return ( + + ); + }, + enableHiding: false, + meta: { + cellClassName: "font-medium", + }, + }, + { + accessorKey: "status", + header: "Estado", + enableHiding: true, + cell: ({ row }) => { + const proforma = row.original; + + const isIssued = proforma.status === "issued"; + const invoiceId = proforma.linkedInvoiceId; + + return ( +
+ + {/* Enlace discreto a factura real */} + {isIssued && ( + + + + + + Ver factura {invoiceId} + + + } + /> + Ver factura {invoiceId} + + + )} +
+ ); + }, + }, + + // Cliente + { + accessorKey: "recipientName", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const proforma = row.original; + return ( +
+ {proforma.recipient.name} + {proforma.recipient.tin} +
+ ); + }, + meta: { + cellClassName: "max-w-128", + }, + }, + { + accessorKey: "invoiceDate", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => DateHelper.format(row.original.invoiceDate), + }, + { + accessorKey: "reference", + header: "Referencia", + enableHiding: true, + cell: ({ row }) =>
{row.original.reference}
, + meta: { + cellClassName: "hidden lg:table-cell max-w-16", + headerClassName: "hidden lg:table-cell", + }, + }, + + { + accessorKey: "subtotalAmountFmt", + header: () => "Subtotal", + meta: { + cellClassName: "hidden 2xl:table-cell text-right tabular-nums font-medium", + headerClassName: "hidden 2xl:table-cell text-right", + }, + }, + { + accessorKey: "totalDiscountAmountFmt", + header: "Dtos", + meta: { + cellClassName: "hidden 2xl:table-cell text-right tabular-nums font-medium", + headerClassName: "hidden 2xl:table-cell text-right", + }, + }, + { + accessorKey: "taxableAmountFmt", + header: "Base Imp.", + meta: { + cellClassName: "hidden 2xl:table-cell text-right tabular-nums font-medium", + headerClassName: "hidden 2xl:table-cell text-right", + }, + }, + { + accessorKey: "taxesAmountFmt", + header: "Impuestos", + meta: { + cellClassName: "hidden 2xl:table-cell text-right tabular-nums font-medium", + headerClassName: "hidden 2xl:table-cell text-right", + }, + }, + { + accessorKey: "totalAmountFmt", + header: "Total", + meta: { + cellClassName: "hidden xl:table-cell text-right tabular-nums font-semibold", + headerClassName: "hidden xl:table-cell text-right", + }, + }, + + { + id: "actions", + meta: { + isActionsColumn: true, + headerClassName: "text-right", + }, + header: "Acciones", + enableSorting: false, + cell: ({ row }) => { + const proforma = row.original; + const isIssued = proforma.status === PROFORMA_STATUS.ISSUED; + const isApproved = proforma.status === PROFORMA_STATUS.APPROVED; + const availableTransitions = + PROFORMA_STATUS_TRANSITIONS[proforma.status as ProformaStatus] ?? []; + + return ( +
+ {!isIssued && actionHandlers.onEditClick && ( + + + actionHandlers.onEditClick?.(proforma)} + size="icon" + variant="ghost" + > + + Editar + + } + /> + Editar + + + )} + + {/* Cambiar estado */} + {!isIssued && availableTransitions.length && actionHandlers.onChangeStatusClick && ( + + + + actionHandlers.onChangeStatusClick?.(proforma, availableTransitions[0]) + } + size="icon" + variant="ghost" + > + + Cambiar estado + + } + /> + + Cambiar a {t(`catalog.proformas.status.${availableTransitions[0]}.label`)} + + + + )} + + {/* Emitir factura: solo si approved */} + {!isIssued && isApproved && actionHandlers.onIssueClick && ( + + + actionHandlers.onIssueClick?.(proforma)} + size="icon" + variant="ghost" + > + + + } + /> + Emitir a factura + + + )} + + {/* Eliminar */} + {!isIssued && actionHandlers.onDeleteClick && ( + + + { + e.preventDefault(); + actionHandlers.onDeleteClick?.(proforma); + }} + size="icon" + variant="ghost" + > + + + } + /> + Eliminar + + + )} +
+ ); + }, + }, + ], + [t, actionHandlers] + ); + return React.useMemo[]>( () => [ /*{ diff --git a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx deleted file mode 100644 index bf452968..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { PageHeader } from "@erp/core/components"; -import { UnsavedChangesProvider, UpdateCommitButtonGroup, useHookForm } from "@erp/core/hooks"; -import { AppContent, AppHeader } from "@repo/rdx-ui/components"; -import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers"; -import { useId, useMemo } from "react"; -import { type FieldErrors, FormProvider } from "react-hook-form"; -import { useNavigate } from "react-router-dom"; - -import { useTranslation } from "../../../i18n"; -import { ProformaDtoAdapter } from "../../adapters"; -import { useUpdateProforma } from "../../shared/hooks/use-proforma-update-mutation"; -import { - type Proforma, - type ProformaFormData, - ProformaFormSchema, - defaultProformaFormData, -} from "../../types"; - -import { useProformaContext } from "./context"; -import { ProformaUpdateForm } from "./proforma-update-form"; - -export type ProformaUpdateCompProps = { - proforma: Proforma; -}; - -export const ProformaUpdateComp = ({ proforma: proformaData }: ProformaUpdateCompProps) => { - const { t } = useTranslation(); - const navigate = useNavigate(); - const formId = useId(); - - const context = useProformaContext(); - const { proforma_id } = context; - - const isPending = !proformaData; - - const { - mutate, - isPending: isUpdating, - isError: isUpdateError, - error: updateError, - } = useUpdateProforma(); - - const initialValues = useMemo(() => { - return proformaData - ? ProformaDtoAdapter.fromDto(proformaData, context) - : defaultProformaFormData; - }, [proformaData, context]); - - const form = useHookForm({ - resolverSchema: ProformaFormSchema, - initialValues, - disabled: !proformaData || isUpdating, - }); - - const handleSubmit = (formData: ProformaFormData) => { - const dto = ProformaDtoAdapter.toDto(formData, context); - mutate( - { id: proforma_id, data: dto as Partial }, - { - onSuccess: () => - showSuccessToast(t("pages.update.success.title"), t("pages.update.success.message")), - onError: (e) => showErrorToast(t("pages.update.errorTitle"), e.message), - } - ); - }; - - const handleReset = () => - form.reset((proformaData as unknown as ProformaFormData) ?? defaultProformaFormData); - - const handleBack = () => { - navigate(-1); - }; - - const handleError = (errors: FieldErrors) => { - console.error("Errores en el formulario:", errors); - // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario - }; - - return ( - - - - } - title={`${t("pages.proformas.edit.title")} #${proformaData.invoice_number}`} - /> - - - - - - - - - ); -}; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx deleted file mode 100644 index f59c01ad..00000000 --- a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-form.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { FormDebug } from "@erp/core/components"; -import { cn } from "@repo/shadcn-ui/lib/utils"; -import { type FieldErrors, useFormContext } from "react-hook-form"; - -import type { ProformaFormData } from "../../types"; - -import { ProformaBasicInfoFields, ProformaItems, ProformaRecipient, ProformaTotals } from "./ui"; - -interface ProformaUpdateFormProps { - formId: string; - onSubmit: (data: ProformaFormData) => void; - onError: (errors: FieldErrors) => void; - className?: string; -} - -export const ProformaUpdateForm = ({ - formId, - onSubmit, - onError, - className, -}: ProformaUpdateFormProps) => { - const form = useFormContext(); - - return ( -
) => { - event.stopPropagation(); - form.handleSubmit(onSubmit, onError)(event); - }} - > - - -
-
- - -
- -
- -
-
- -
-
-
- - ); -}; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/index.ts b/modules/customer-invoices/src/web/proformas/update/adapters/index.ts index b0e1ab13..5e0db6ac 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/index.ts @@ -1,2 +1,3 @@ -export * from "./proforma-to-proforma-update-form.adapter"; -export * from "./proforma-to-selected-customer.adapter"; +export * from "./map-proforma-form-to-commercial-document-lines"; +export * from "./map-proforma-to-proforma-update-form.adapter"; +export * from "./map-proforma-to-selected-customer.adapter"; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts new file mode 100644 index 00000000..a4498d25 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-form-to-commercial-document-lines.ts @@ -0,0 +1,15 @@ +import type { CommercialDocumentLineInput, ProformaUpdateForm } from "../entities"; + +export const mapProformaFormToCommercialDocumentLines = ( + form: ProformaUpdateForm +): CommercialDocumentLineInput[] => { + return form.items + .filter((item) => item.isValued) + .map((item) => ({ + quantity: item.quantity, + unitAmount: item.unitAmount, + itemDiscountPercentage: item.itemDiscountPercentage, + taxPercentage: item.taxPercentage, + equivalenceSurchargePercentage: item.equivalenceSurchargePercentage, + })); +}; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/proforma-items-to-proforma-items-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts similarity index 59% rename from modules/customer-invoices/src/web/proformas/update/adapters/proforma-items-to-proforma-items-update-form.adapter.ts rename to modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts index 35810851..4414c673 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/proforma-items-to-proforma-items-update-form.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-items-to-proforma-items-update-form.adapter.ts @@ -15,9 +15,15 @@ export const mapProformaItemsToProformaItemsUpdateForm = ( id: item.id, position: item.position, isValued: item.isValued, - description: item.description, - quantity: item.quantity, - unitAmount: item.unitAmount, - itemDiscountPercentage: item.itemDiscountPercentage, + + description: item.description ?? null, + + quantity: item.quantity ?? null, + unitAmount: item.unitAmount ?? null, + + itemDiscountPercentage: item.itemDiscountPercentage ?? null, + + taxPercentage: item.ivaPercentage ?? null, + equivalenceSurchargePercentage: item.recPercentage ?? null, }; }; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts new file mode 100644 index 00000000..e47a859e --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-proforma-update-form.adapter.ts @@ -0,0 +1,88 @@ +import type { Proforma, ProformaItem } from "../../shared"; +import type { ProformaTaxMode, ProformaUpdateForm } from "../entities"; + +import { mapProformaItemsToProformaItemsUpdateForm } from "./map-proforma-items-to-proforma-items-update-form.adapter"; + +/** + * Mapea una proforma a un formulario de actualización de proforma. + */ +export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => { + const taxMode = inferProformaTaxMode(proforma.items); + + const defaultTaxSummary = proforma.taxes[0]; + const firstTaxableItem = getFirstTaxableItem(proforma.items); + + const defaultTaxPercentage = + defaultTaxSummary?.ivaPercentage ?? firstTaxableItem?.ivaPercentage ?? null; + + const defaultEquivalenceSurchargePercentage = + defaultTaxSummary?.recPercentage ?? firstTaxableItem?.recPercentage ?? null; + + const defaultRetentionPercentage = + defaultTaxSummary?.retentionPercentage ?? firstTaxableItem?.retentionPercentage ?? null; + + return { + series: proforma.series ?? "", + + invoiceDate: proforma.invoiceDate ?? "", + operationDate: proforma.operationDate ?? "", + + customerId: proforma.customerId ?? "", + + description: proforma.description ?? "", + reference: proforma.reference ?? "", + notes: proforma.notes ?? "", + + languageCode: proforma.languageCode ?? "es", + currencyCode: proforma.currencyCode ?? "EUR", + + globalDiscountPercentage: proforma.globalDiscountPercentage ?? 0, + + taxMode, + defaultTaxPercentage, + hasEquivalenceSurcharge: hasPositivePercentage(defaultEquivalenceSurchargePercentage), + hasRetention: hasPositivePercentage(defaultRetentionPercentage), + retentionPercentage: defaultRetentionPercentage, + + paymentMethod: proforma.paymentMethod ?? "", + + items: proforma.items.map(mapProformaItemsToProformaItemsUpdateForm), + }; +}; + +const getFirstTaxableItem = (items: ProformaItem[]): ProformaItem | undefined => { + return items.find((item) => item.isValued) ?? items[0]; +}; + +const inferProformaTaxMode = (items: ProformaItem[]): ProformaTaxMode => { + const comparableItems = items.filter((item) => item.isValued); + + const sourceItems = comparableItems.length > 0 ? comparableItems : items; + + const ivaPercentages = uniqueNumbers(sourceItems.map((item) => item.ivaPercentage)); + const recPercentages = uniqueNumbers(sourceItems.map((item) => item.recPercentage)); + const retentionPercentages = uniqueNumbers(sourceItems.map((item) => item.retentionPercentage)); + + const hasSingleTaxSetup = + ivaPercentages.length <= 1 && recPercentages.length <= 1 && retentionPercentages.length <= 1; + + return hasSingleTaxSetup ? "single" : "perLine"; +}; + +const uniqueNumbers = (values: Array): number[] => { + return Array.from( + new Set( + values + .filter((value): value is number => value !== null && value !== undefined) + .map((value) => normalizePercentage(value)) + ) + ); +}; + +const normalizePercentage = (value: number): number => { + return Math.round(value * 10000) / 10000; +}; + +const hasPositivePercentage = (value: number | null | undefined): boolean => { + return value !== null && value !== undefined && value > 0; +}; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-selected-customer.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-selected-customer.adapter.ts similarity index 57% rename from modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-selected-customer.adapter.ts rename to modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-selected-customer.adapter.ts index 99d4fae4..71e5415e 100644 --- a/modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-selected-customer.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/update/adapters/map-proforma-to-selected-customer.adapter.ts @@ -17,11 +17,24 @@ export const mapProformaToSelectedCustomer = ( } return { + status: "active", + id: proforma.customerId, name: proforma.recipient.name ?? "", + tradeName: "", tin: proforma.recipient.tin ?? "", - languageCode: proforma.languageCode ?? "", - currencyCode: proforma.currencyCode ?? "", + street: proforma.recipient.street ?? "", + street2: proforma.recipient.street2 ?? "", + city: proforma.recipient.city ?? "", + province: proforma.recipient.province ?? "", + postalCode: proforma.recipient.postalCode ?? "", + country: proforma.recipient.country ?? "", + + primaryEmail: "", + primaryMobile: "", + primaryPhone: "", + + isCompany: true, }; }; diff --git a/modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-proforma-update-form.adapter.ts b/modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-proforma-update-form.adapter.ts deleted file mode 100644 index 727fb584..00000000 --- a/modules/customer-invoices/src/web/proformas/update/adapters/proforma-to-proforma-update-form.adapter.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Proforma } from "../../shared"; -import type { ProformaUpdateForm } from "../entities"; - -import { mapProformaItemsToProformaItemsUpdateForm } from "./proforma-items-to-proforma-items-update-form.adapter"; - -/** - * Mapea una proforma a un formulario de actualización de proforma. - * - * @param proforma - * @returns - */ - -export const mapProformaToProformaUpdateForm = (proforma: Proforma): ProformaUpdateForm => { - return { - series: proforma.series ?? "", - - invoiceDate: proforma.invoiceDate ?? "", - operationDate: proforma.operationDate ?? "", - - customerId: proforma.customerId ?? "", - - description: proforma.description ?? "", - reference: proforma.reference ?? "", - notes: proforma.notes ?? "", - - languageCode: proforma.languageCode ?? "es", - currencyCode: proforma.currencyCode ?? "EUR", - - globalDiscountPercentage: proforma.globalDiscountPercentage ?? 0, - - paymentMethod: proforma.paymentMethod ?? "", - - items: proforma.items.map(mapProformaItemsToProformaItemsUpdateForm), - }; -}; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/index.ts b/modules/customer-invoices/src/web/proformas/update/controllers/index.ts index 2fcdad69..f4331ac6 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/index.ts @@ -1,3 +1,5 @@ export * from "./use-update-proforma-controller"; export * from "./use-update-proforma-items-controller"; export * from "./use-update-proforma-page-controller"; +export * from "./use-update-proforma-tax-controller"; +export * from "./use-update-proforma-totals-controller"; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts index 2a633078..3eee858c 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts @@ -25,6 +25,8 @@ import { } from "../utils"; import { useUpdateProformaItemsController } from "./use-update-proforma-items-controller"; +import { useUpdateProformaTaxController } from "./use-update-proforma-tax-controller"; +import { useUpdateProformaTotalsController } from "./use-update-proforma-totals-controller"; export interface UseUpdateProformaControllerOptions { onUpdated?(updated: Proforma): void; @@ -238,16 +240,33 @@ export const useUpdateProformaController = ( submitHandler(event); }; + const currencyCode = form.watch("currencyCode"); + const languageCode = form.watch("languageCode"); + + const taxCtrl = useUpdateProformaTaxController({ + form, + }); + const itemsCtrl = useUpdateProformaItemsController({ form, }); + const totalsCtrl = useUpdateProformaTotalsController({ + form, + }); + return { // form formId, form, itemsCtrl, + taxCtrl, + totalsCtrl, + + // + currencyCode, + languageCode, // handlers del form onSubmit, diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts index 45a99e2d..62c0d116 100644 --- a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-items-controller.ts @@ -1,4 +1,3 @@ -import { NumberHelper } from "@repo/rdx-utils"; import * as React from "react"; import { type FieldArrayWithId, @@ -10,6 +9,8 @@ import { import type { ProformaItemUpdateForm, ProformaUpdateForm } from "../entities"; import { buildProformaItemUpdateDefault } from "../utils"; +import { calculateCommercialDocumentLineAmounts } from "../utils/calculations/calculate-commercial-document-line-amounts"; +import { calculateCommercialDocumentLinesTotals } from "../utils/calculations/calculate-commercial-document-lines-totals"; export interface ProformaItemAmounts { subtotal: number; @@ -66,51 +67,6 @@ const normalizeItemPositions = (items: ProformaItemUpdateForm[]): ProformaItemUp })); }; -const calculateItemAmounts = ( - item?: Pick -): ProformaItemAmounts => { - if (!item) { - return { - subtotal: 0, - itemDiscountAmount: 0, - total: 0, - }; - } - - const quantity = NumberHelper.toSafeNumber(item.quantity); - const unitAmount = NumberHelper.toSafeNumber(item.unitAmount); - const itemDiscountPercentage = NumberHelper.toSafeNumber(item.itemDiscountPercentage); - - const subtotal = roundCurrency(quantity * unitAmount); - const itemDiscountAmount = roundCurrency(subtotal * (itemDiscountPercentage / 100)); - const total = roundCurrency(subtotal - itemDiscountAmount); - - return { - subtotal, - itemDiscountAmount, - total, - }; -}; - -const calculateItemsTotals = (items: ProformaItemUpdateForm[]): ProformaItemsTotals => { - return items.reduce( - (acc, item) => { - const amounts = calculateItemAmounts(item); - - return { - subtotal: roundCurrency(acc.subtotal + amounts.subtotal), - discountAmount: roundCurrency(acc.discountAmount + amounts.itemDiscountAmount), - total: roundCurrency(acc.total + amounts.total), - }; - }, - { - subtotal: 0, - discountAmount: 0, - total: 0, - } - ); -}; - export const useUpdateProformaItemsController = ({ form, }: UseUpdateProformaItemsControllerParams): UseUpdateProformaItemsControllerResult => { @@ -223,11 +179,16 @@ export const useUpdateProformaItemsController = ({ const getItemAmounts = React.useCallback( (index: number): ProformaItemAmounts => { - return calculateItemAmounts(items[index]); + const amounts = calculateCommercialDocumentLineAmounts(items[index]); + + return { + subtotal: amounts.grossAmount, + itemDiscountAmount: amounts.itemDiscountAmount, + total: amounts.taxableBaseBeforeGlobalDiscount, + }; }, [items] ); - const insertItemAt = React.useCallback( (index: number) => { const currentItems = getValues("items") ?? []; @@ -259,7 +220,15 @@ export const useUpdateProformaItemsController = ({ [insertItemAt] ); - const totals = React.useMemo(() => calculateItemsTotals(items), [items]); + const totals = React.useMemo(() => { + const lineTotals = calculateCommercialDocumentLinesTotals(items); + + return { + subtotal: lineTotals.subtotalBeforeDiscounts, + discountAmount: lineTotals.lineDiscountTotal, + total: lineTotals.taxableBaseBeforeGlobalDiscount, + }; + }, [items]); const itemErrors = React.useMemo(() => { if (!Array.isArray(errors.items)) { diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts new file mode 100644 index 00000000..aba8a672 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-tax-controller.ts @@ -0,0 +1,99 @@ +import { useCallback, useEffect } from "react"; +import { type UseFormReturn, useWatch } from "react-hook-form"; + +import type { ProformaTaxMode, ProformaUpdateForm } from "../entities"; + +interface UseUpdateProformaTaxControllerParams { + form: UseFormReturn; +} + +export interface UseUpdateProformaTaxControllerResult { + taxMode: ProformaTaxMode; + + defaultTaxPercentage: number | null; + hasEquivalenceSurcharge: boolean; + hasRetention: boolean; + retentionPercentage: number | null; + + usesSingleTax: boolean; + usesPerLineTax: boolean; + + enablePerLineTaxes: () => void; + disablePerLineTaxes: () => void; +} + +const getEquivalenceSurchargePercentage = ( + taxPercentage: number | null | undefined +): number | null => { + if (taxPercentage === 21) return 5.2; + if (taxPercentage === 10) return 1.4; + if (taxPercentage === 4) return 0.5; + + return null; +}; + +export const useUpdateProformaTaxController = ({ + form, +}: UseUpdateProformaTaxControllerParams): UseUpdateProformaTaxControllerResult => { + const { control, getValues, setValue } = form; + + const taxMode = useWatch({ control, name: "taxMode" }); + const hasEquivalenceSurcharge = useWatch({ control, name: "hasEquivalenceSurcharge" }) ?? false; + const hasRetention = useWatch({ control, name: "hasRetention" }) ?? false; + const retentionPercentage = useWatch({ control, name: "retentionPercentage" }) ?? null; + const defaultTaxPercentage = useWatch({ control, name: "defaultTaxPercentage" }) ?? null; + + useEffect(() => { + if (taxMode !== "single") return; + + const currentItems = getValues("items") ?? []; + const equivalenceSurchargePercentage = hasEquivalenceSurcharge + ? getEquivalenceSurchargePercentage(defaultTaxPercentage) + : null; + + currentItems.forEach((_, index) => { + setValue(`items.${index}.taxPercentage`, defaultTaxPercentage ?? null, { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); + + setValue(`items.${index}.equivalenceSurchargePercentage`, equivalenceSurchargePercentage, { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); + }); + }, [defaultTaxPercentage, getValues, hasEquivalenceSurcharge, setValue, taxMode]); + + const enablePerLineTaxes = useCallback(() => { + setValue("taxMode", "perLine", { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); + }, [setValue]); + + const disablePerLineTaxes = useCallback(() => { + setValue("taxMode", "single", { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); + }, [setValue]); + + return { + taxMode, + + defaultTaxPercentage, + hasEquivalenceSurcharge, + hasRetention, + retentionPercentage, + + usesSingleTax: taxMode === "single", + usesPerLineTax: taxMode === "perLine", + + enablePerLineTaxes, + disablePerLineTaxes, + }; +}; diff --git a/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-totals-controller.ts b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-totals-controller.ts new file mode 100644 index 00000000..cd4bb416 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-totals-controller.ts @@ -0,0 +1,36 @@ +import { useMemo } from "react"; +import { type UseFormReturn, useWatch } from "react-hook-form"; + +import type { ProformaTotals, ProformaUpdateForm } from "../entities"; +import { calculateProformaTotals } from "../utils"; + +interface UseUpdateProformaTotalsControllerParams { + form: UseFormReturn; +} + +export interface UseUpdateProformaTotalsControllerResult { + totals: ProformaTotals; +} + +export const useUpdateProformaTotalsController = ({ + form, +}: UseUpdateProformaTotalsControllerParams): UseUpdateProformaTotalsControllerResult => { + const { control, getValues } = form; + + const items = useWatch({ control, name: "items" }); + const globalDiscountPercentage = useWatch({ control, name: "globalDiscountPercentage" }); + const hasRetention = useWatch({ control, name: "hasRetention" }); + const retentionPercentage = useWatch({ control, name: "retentionPercentage" }); + + const totals = useMemo(() => { + return calculateProformaTotals({ + ...getValues(), + items, + globalDiscountPercentage, + hasRetention, + retentionPercentage, + }); + }, [items, globalDiscountPercentage, hasRetention, retentionPercentage]); + + return { totals }; +}; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts new file mode 100644 index 00000000..8c8e903c --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/entities/commercial-document-calculation.entity.ts @@ -0,0 +1,33 @@ +export interface CommercialDocumentLineInput { + quantity: number | null; + unitAmount: number | null; + itemDiscountPercentage: number | null; + taxPercentage: number | null; +} + +export interface CommercialDocumentLineAmounts { + grossAmount: number; + itemDiscountAmount: number; + taxableBaseBeforeGlobalDiscount: number; +} + +export interface CommercialDocumentTaxBreakdownLine { + taxPercentage: number; + taxableBase: number; + taxAmount: number; +} + +export interface CommercialDocumentTotals { + subtotalBeforeDiscounts: number; + + lineDiscountTotal: number; + globalDiscountPercentage: number; + globalDiscountAmount: number; + + taxableBase: number; + + taxBreakdown: CommercialDocumentTaxBreakdownLine[]; + taxTotal: number; + + total: number; +} diff --git a/modules/customer-invoices/src/web/proformas/update/entities/index.ts b/modules/customer-invoices/src/web/proformas/update/entities/index.ts index e14ff55c..be0cf0af 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/index.ts @@ -1,6 +1,8 @@ +export * from "./commercial-document-calculation.entity"; export * from "./proforma-item-update-form.entity"; export * from "./proforma-item-update-form.schema"; export * from "./proforma-item-update-patch.entity"; export * from "./proforma-update-form.entity"; export * from "./proforma-update-form.schema"; export * from "./proforma-update-patch.entity"; +export * from "./proforma-update-totals.entity"; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts index cc213a30..e67f38f4 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.entity.ts @@ -9,4 +9,7 @@ export interface ProformaItemUpdateForm { unitAmount: number | null; itemDiscountPercentage: number | null; + + taxPercentage: number | null; + equivalenceSurchargePercentage: number | null; } diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts index d50b47c5..20e9a82a 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-item-update-form.schema.ts @@ -15,16 +15,19 @@ import { z } from "zod/v4"; */ export const ProformaItemUpdateFormSchema = z.object({ - id: z.uuid(), - position: z.number().int().nonnegative(), + id: z.string(), + position: z.number(), isValued: z.boolean(), description: z.string().nullable(), quantity: z.number().nullable(), - unitAmount: z.number().nonnegative().nullable(), + unitAmount: z.number().nullable(), - itemDiscountPercentage: z.number().min(0).max(100).nullable(), + itemDiscountPercentage: z.number().nullable(), + + taxPercentage: z.number().nullable(), + equivalenceSurchargePercentage: z.number().nullable(), }); export type ProformaItemUpdateFormSchemaType = z.infer; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts index f6380476..aa6fd196 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.entity.ts @@ -15,6 +15,8 @@ import type { ProformaItemUpdateForm } from "./proforma-item-update-form.entity"; +export type ProformaTaxMode = "single" | "perLine"; + export interface ProformaUpdateForm { series: string; @@ -32,6 +34,13 @@ export interface ProformaUpdateForm { globalDiscountPercentage: number; + taxMode: ProformaTaxMode; + defaultTaxPercentage: number | null; + + hasEquivalenceSurcharge: boolean; + hasRetention: boolean; + retentionPercentage: number | null; + paymentMethod: string; items: ProformaItemUpdateForm[]; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts index 2b72aea2..f535b467 100644 --- a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-form.schema.ts @@ -1,4 +1,3 @@ -import { CurrencyCodeSchema, LanguageCodeSchema } from "@erp/core"; import { z } from "zod/v4"; import { ProformaItemUpdateFormSchema } from "./proforma-item-update-form.schema"; @@ -18,25 +17,32 @@ import { ProformaItemUpdateFormSchema } from "./proforma-item-update-form.schema */ export const ProformaUpdateFormSchema = z.object({ - series: z.string().or(z.literal("")), + series: z.string(), - invoiceDate: z.string(), - operationDate: z.string().or(z.literal("")), + invoiceDate: z.string().min(1), + operationDate: z.string(), - customerId: z.string(), + customerId: z.string().min(1), description: z.string(), - reference: z.string().or(z.literal("")), - notes: z.string().or(z.literal("")), + reference: z.string(), + notes: z.string(), - languageCode: LanguageCodeSchema, - currencyCode: CurrencyCodeSchema, + languageCode: z.string().min(1), + currencyCode: z.string().min(1), - globalDiscountPercentage: z.number(), + globalDiscountPercentage: z.number().min(0).max(100), - paymentMethod: z.string().or(z.literal("")), + taxMode: z.enum(["single", "perLine"]), + defaultTaxPercentage: z.number().nullable(), - items: z.array(ProformaItemUpdateFormSchema), + hasEquivalenceSurcharge: z.boolean(), + hasRetention: z.boolean(), + retentionPercentage: z.number().nullable(), + + paymentMethod: z.string(), + + items: z.array(ProformaItemUpdateFormSchema).min(1), }); export type ProformaUpdateFormSchemaType = z.infer; diff --git a/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts new file mode 100644 index 00000000..885a0b46 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/entities/proforma-update-totals.entity.ts @@ -0,0 +1,27 @@ +export interface ProformaTaxBreakdownLine { + taxPercentage: number; + taxableBase: number; + taxAmount: number; + equivalenceSurchargePercentage: number | null; + equivalenceSurchargeAmount: number; +} + +export interface ProformaTotals { + subtotalBeforeDiscounts: number; + + lineDiscountTotal: number; + globalDiscountPercentage: number; + globalDiscountAmount: number; + + taxableBase: number; + + taxBreakdown: ProformaTaxBreakdownLine[]; + taxTotal: number; + + equivalenceSurchargeTotal: number; + + retentionPercentage: number | null; + retentionAmount: number; + + total: number; +} diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts index 5ac359fd..7caf7820 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/index.ts @@ -1,5 +1,3 @@ -export * from "./proforma-basic-info-fields"; -export * from "./proforma-form-field-shell"; -export * from "./proforma-header-fields-card"; export * from "./proforma-line-editor"; +export * from "./proforma-totals-summary"; export * from "./selected-recipient"; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/index.ts b/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/index.ts deleted file mode 100644 index 538df18b..00000000 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./items-editor"; diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/item-row-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/item-row-editor.tsx deleted file mode 100644 index d07c37ff..00000000 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/items-editor/item-row-editor.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Button, Input, Label, Textarea } from "@repo/shadcn-ui/components"; -import { useFormContext } from "react-hook-form"; - -import type { ProformaFormData, ProformaItemFormData } from "../../../../../types"; - -export function ItemRowEditor({ - row, - index, - onClose, -}: { - row: ProformaItemFormData; - index: number; - onClose: () => void; -}) { - // Editor simple reutilizando el mismo RHF - const { register } = useFormContext(); - return ( -
-

Edit line #{index + 1}

-
- -