diff --git a/biome.json b/biome.json index 18d0763b..6501d228 100644 --- a/biome.json +++ b/biome.json @@ -122,7 +122,7 @@ "noClassAssign": "error", "noCommentText": "error", "noCompareNegZero": "error", - "noConsole": "warn", + "noConsole": "off", "noConstEnum": "error", "noControlCharactersInRegex": "error", "noDoubleEquals": "error", diff --git a/modules/customer-invoices/src/web/adapters/index.ts b/modules/customer-invoices/src/web/adapters/index.ts index 92b523d1..c2a1bd43 100644 --- a/modules/customer-invoices/src/web/adapters/index.ts +++ b/modules/customer-invoices/src/web/adapters/index.ts @@ -1,2 +1,3 @@ -export * from "./invoice-dto.adapter"; +export * from "../proformas/adapters/proforma-dto.adapter"; + export * from "./invoice-resume-dto.adapter"; diff --git a/modules/customer-invoices/src/web/context/index.ts b/modules/customer-invoices/src/web/context/index.ts deleted file mode 100644 index 07d8befe..00000000 --- a/modules/customer-invoices/src/web/context/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./invoice-context"; diff --git a/modules/customer-invoices/src/web/hooks/calcs/index.ts b/modules/customer-invoices/src/web/hooks/calcs/index.ts index 8c2e9b38..d16f97a4 100644 --- a/modules/customer-invoices/src/web/hooks/calcs/index.ts +++ b/modules/customer-invoices/src/web/hooks/calcs/index.ts @@ -1 +1 @@ -export * from "./use-invoice-auto-recalc"; +export * from "./use-proforma-auto-recalc"; diff --git a/modules/customer-invoices/src/web/hooks/calcs/use-invoice-auto-recalc.ts b/modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts similarity index 91% rename from modules/customer-invoices/src/web/hooks/calcs/use-invoice-auto-recalc.ts rename to modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts index 70f19e05..611674da 100644 --- a/modules/customer-invoices/src/web/hooks/calcs/use-invoice-auto-recalc.ts +++ b/modules/customer-invoices/src/web/hooks/calcs/use-proforma-auto-recalc.ts @@ -1,14 +1,16 @@ -import { TaxCatalogProvider } from "@erp/core"; +import type { TaxCatalogProvider } from "@erp/core"; import React from "react"; -import { UseFormReturn, useWatch } from "react-hook-form"; +import { type UseFormReturn, useWatch } from "react-hook-form"; + import { + type InvoiceItemCalcResult, calculateInvoiceHeaderAmounts, calculateInvoiceItemAmounts, - InvoiceItemCalcResult, } from "../../domain"; -import { InvoiceFormData, InvoiceItemFormData } from "../../schemas"; +import type { ProformaFormData } from "../../proformas/schema"; +import type { InvoiceFormData, InvoiceItemFormData } from "../../schemas"; -export type UseInvoiceAutoRecalcParams = { +export type UseProformaAutoRecalcParams = { currency_code: string; taxCatalog: TaxCatalogProvider; debug?: boolean; @@ -20,9 +22,9 @@ export type UseInvoiceAutoRecalcParams = { * Adaptado a formulario con números planos (no DTOs). * Evita renders innecesarios (debounce + useDeferredValue). */ -export function useInvoiceAutoRecalc( - form: UseFormReturn, - { currency_code, taxCatalog, debug = true }: UseInvoiceAutoRecalcParams +export function useProformaAutoRecalc( + form: UseFormReturn, + { currency_code, taxCatalog, debug = true }: UseProformaAutoRecalcParams ) { const { trigger, control } = form; @@ -120,7 +122,7 @@ export function useInvoiceAutoRecalc( setInvoiceTotals(form, totals); if (debug) console.log("📊 Recalc invoice totals", totals.subtotal_amount); - void trigger([ + trigger([ "subtotal_amount", "discount_amount", "taxable_amount", @@ -141,6 +143,7 @@ export function useInvoiceAutoRecalc( form, trigger, debug, + prevDiscount, ]); } @@ -168,8 +171,6 @@ function setInvoiceTotals( const { setValue } = form; const opts = { shouldDirty: true, shouldValidate: false } as const; - console.log(totals); - setValue("subtotal_amount", totals.subtotal_amount, opts); setValue("items_discount_amount", totals.items_discount_amount, opts); setValue("discount_amount", totals.discount_amount, opts); diff --git a/modules/customer-invoices/src/web/hooks/index.ts b/modules/customer-invoices/src/web/hooks/index.ts index d89192ff..73844ba6 100644 --- a/modules/customer-invoices/src/web/hooks/index.ts +++ b/modules/customer-invoices/src/web/hooks/index.ts @@ -4,4 +4,3 @@ export * from "./use-customer-invoices-query"; export * from "./use-invoice-query"; export * from "./use-items-table-navigation"; export * from "./use-pinned-preview-sheet"; -export * from "./use-update-customer-invoice-mutation"; diff --git a/modules/customer-invoices/src/web/pages/update/index.ts b/modules/customer-invoices/src/web/pages/update/index.ts deleted file mode 100644 index de801651..00000000 --- a/modules/customer-invoices/src/web/pages/update/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./invoice-update-page"; diff --git a/modules/customer-invoices/src/web/pages/update/invoice-update-page.tsx b/modules/customer-invoices/src/web/pages/update/invoice-update-page.tsx deleted file mode 100644 index 2f860f4a..00000000 --- a/modules/customer-invoices/src/web/pages/update/invoice-update-page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { SpainTaxCatalogProvider } from "@erp/core"; -import { useUrlParamId } from "@erp/core/hooks"; -import { ErrorAlert } from "@erp/customers/components"; -import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; -import { useMemo } from "react"; - -import { InvoiceProvider } from "../../context"; -import { useInvoiceQuery } from "../../hooks"; -import { useTranslation } from "../../i18n"; -import { CustomerInvoiceEditorSkeleton } from "../../shared/ui/components"; - -import { InvoiceUpdateComp } from "./invoice-update-comp"; - -export const InvoiceUpdatePage = () => { - const invoice_id = useUrlParamId(); - const { t } = useTranslation(); - const taxCatalog = useMemo(() => SpainTaxCatalogProvider(), []); - - const invoiceQuery = useInvoiceQuery(invoice_id, { enabled: !!invoice_id }); - const { data: invoiceData, isLoading, isError, error } = invoiceQuery; - - if (isLoading) { - return ; - } - - if (isError || !invoiceData) { - return ( - - - - - ); - } - - // Monta el contexto aquí, así todo lo que esté dentro puede usar hooks - return ( - - - - ); -}; diff --git a/modules/customer-invoices/src/web/proformas/adapters/index.ts b/modules/customer-invoices/src/web/proformas/adapters/index.ts index e2a4d99c..22d61455 100644 --- a/modules/customer-invoices/src/web/proformas/adapters/index.ts +++ b/modules/customer-invoices/src/web/proformas/adapters/index.ts @@ -1 +1,2 @@ +export * from "./proforma-dto.adapter"; export * from "./proforma-summary-dto.adapter"; diff --git a/modules/customer-invoices/src/web/adapters/invoice-dto.adapter.ts b/modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts similarity index 80% rename from modules/customer-invoices/src/web/adapters/invoice-dto.adapter.ts rename to modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts index dcb0842f..6c7ec348 100644 --- a/modules/customer-invoices/src/web/adapters/invoice-dto.adapter.ts +++ b/modules/customer-invoices/src/web/proformas/adapters/proforma-dto.adapter.ts @@ -1,17 +1,23 @@ -import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core"; +import { + MoneyDTOHelper, + PercentageDTOHelper, + QuantityDTOHelper, + type TaxCatalogProvider, +} from "@erp/core"; -import type { - GetIssuedInvoiceByIdResponseDTO, - UpdateCustomerInvoiceByIdRequestDTO, -} from "../../common"; -import type { InvoiceContextValue } from "../context"; -import type { InvoiceFormData } from "../schemas/invoice.form.schema"; +import type { Proforma, ProformaFormData, UpdateProformaInput } from "../schema"; + +export type ProformaDtoAdapterContext = { + taxCatalog: TaxCatalogProvider; + currency_code: string; + language_code: string; +}; /** * Convierte el DTO completo de API a datos numéricos para el formulario. */ -export const invoiceDtoToFormAdapter = { - fromDto(dto: GetIssuedInvoiceByIdResponseDTO, context: InvoiceContextValue): InvoiceFormData { +export const ProformaDtoAdapter = { + fromDto(dto: Proforma, context: ProformaDtoAdapterContext): ProformaFormData { const { taxCatalog } = context; return { invoice_number: dto.invoice_number, @@ -63,8 +69,8 @@ export const invoiceDtoToFormAdapter = { }; }, - toDto(form: InvoiceFormData, context: InvoiceContextValue): UpdateCustomerInvoiceByIdRequestDTO { - const { currency_code } = context; + toDto(form: ProformaFormData, context: ProformaDtoAdapterContext): UpdateProformaInput { + const { currency_code, language_code } = context; return { series: form.series, @@ -77,8 +83,8 @@ export const invoiceDtoToFormAdapter = { description: form.description, notes: form.notes, - language_code: context.language_code, - currency_code: context.currency_code, + language_code, + currency_code, items: form.items?.map((item) => ({ description: item.description, diff --git a/modules/customer-invoices/src/web/proformas/hooks/index.ts b/modules/customer-invoices/src/web/proformas/hooks/index.ts index 0ea9230d..e48fb5de 100644 --- a/modules/customer-invoices/src/web/proformas/hooks/index.ts +++ b/modules/customer-invoices/src/web/proformas/hooks/index.ts @@ -1,2 +1,4 @@ +export * from "./use-proforma-items-columns"; export * from "./use-proforma-query"; +export * from "./use-proforma-update-mutation"; export * from "./use-proformas-query"; diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/items/use-items-columns.tsx b/modules/customer-invoices/src/web/proformas/hooks/use-proforma-items-columns.tsx similarity index 89% rename from modules/customer-invoices/src/web/shared/ui/components/editor/items/use-items-columns.tsx rename to modules/customer-invoices/src/web/proformas/hooks/use-proforma-items-columns.tsx index 4a622858..56fbb14d 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/items/use-items-columns.tsx +++ b/modules/customer-invoices/src/web/proformas/hooks/use-proforma-items-columns.tsx @@ -5,16 +5,15 @@ import type { ColumnDef } from "@tanstack/react-table"; import * as React from "react"; import { Controller, useFormContext } from "react-hook-form"; -import { useInvoiceContext } from "../../../../../context"; -import { CustomerInvoiceTaxesMultiSelect } from "../../customer-invoice-taxes-multi-select"; +import { ProformaTaxesMultiSelect } from "../../shared"; +import { AmountInputField } from "../../shared/ui/components/editor/items/amount-input-field"; +import { HoverCardTotalsSummary } from "../../shared/ui/components/editor/items/hover-card-total-summary"; +import { ItemDataTableRowActions } from "../../shared/ui/components/editor/items/items-data-table-row-actions"; +import { PercentageInputField } from "../../shared/ui/components/editor/items/percentage-input-field"; +import { QuantityInputField } from "../../shared/ui/components/editor/items/quantity-input-field"; +import { useProformaContext } from "../pages/update/context"; -import { AmountInputField } from "./amount-input-field"; -import { HoverCardTotalsSummary } from "./hover-card-total-summary"; -import { ItemDataTableRowActions } from "./items-data-table-row-actions"; -import { PercentageInputField } from "./percentage-input-field"; -import { QuantityInputField } from "./quantity-input-field"; - -export interface InvoiceItemFormData { +export interface ProformaItemFormData { id: string; // ← mapea RHF field.id aquí description: string; quantity: number | ""; @@ -26,16 +25,16 @@ export interface InvoiceItemFormData { taxes_amount: number | ""; total_amount: number | ""; // readonly calculado } -export interface InvoiceFormData { - items: InvoiceItemFormData[]; +export interface ProformaFormData { + items: ProformaItemFormData[]; } -export function useItemsColumns(): ColumnDef[] { - const { t, readOnly, currency_code, language_code } = useInvoiceContext(); - const { control } = useFormContext(); +export function useProformaItemsColumns(): ColumnDef[] { + const { t, readOnly, currency_code, language_code } = useProformaContext(); + const { control } = useFormContext(); // Atención: Memoizar siempre para evitar reconstrucciones y resets de estado de tabla - return React.useMemo[]>( + return React.useMemo[]>( () => [ { id: "position", @@ -244,7 +243,7 @@ export function useItemsColumns(): ColumnDef[] { control={control} name={`items.${row.index}.tax_codes`} render={({ field }) => ( - ; }; @@ -21,27 +23,18 @@ export function useUpdateProforma() { const dataSource = useDataSource(); const schema = UpdateProformaByIdRequestSchema; - return useMutation< - InvoiceFormData, - Error, - UpdateCustomerInvoicePayload, - UpdateCustomerInvoiceContext - >({ - mutationKey: ["customer-invoice:update"], //, customerId], + return useMutation({ + mutationKey: ["proforma:update"], //, customerId], mutationFn: async (payload) => { const { id: invoiceId, data } = payload; - console.log(payload); - if (!invoiceId) { throw new Error("customerInvoiceId is required"); } const result = schema.safeParse(data); if (!result.success) { - console.log(result); - // Construye errores detallados const validationErrors = result.error.issues.map((err) => ({ field: err.path.join("."), @@ -59,7 +52,7 @@ export function useUpdateProforma() { // Refresca inmediatamente el detalle queryClient.setQueryData( - CUSTOMER_INVOICE_QUERY_KEY(invoiceId), + PROFORMA_QUERY_KEY(invoiceId), updated ); @@ -67,7 +60,7 @@ export function useUpdateProforma() { // queryClient.invalidateQueries({ queryKey: CUSTOMER_QUERY_KEY(customerId) }); // Invalida el listado para refrescar desde servidor - queryClient.invalidateQueries({ queryKey: CUSTOMER_INVOICES_LIST_KEY }); + queryClient.invalidateQueries({ queryKey: PROFORMAS_QUERY_KEY() }); }, }); } diff --git a/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts b/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts index 0e3f2dec..8a526b75 100644 --- a/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts +++ b/modules/customer-invoices/src/web/proformas/hooks/use-proformas-query.ts @@ -1,18 +1,19 @@ import type { CriteriaDTO } from "@erp/core"; import { useDataSource } from "@erp/core/hooks"; +import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria"; import { type DefaultError, type QueryKey, useQuery } from "@tanstack/react-query"; import type { ProformaSummaryPage } from "../schema/proforma.api.schema"; -export const PROFORMAS_QUERY_KEY = (criteria: CriteriaDTO): QueryKey => [ +export const PROFORMAS_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey => [ "proforma", { - pageNumber: criteria.pageNumber ?? 0, - pageSize: criteria.pageSize ?? 10, - q: criteria.q ?? "", - filters: criteria.filters ?? [], - orderBy: criteria.orderBy ?? "", - order: criteria.order ?? "", + pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX, + pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE, + q: criteria?.q ?? "", + filters: criteria?.filters ?? [], + orderBy: criteria?.orderBy ?? "", + order: criteria?.order ?? "", }, ]; @@ -36,6 +37,6 @@ export const useProformasQuery = (options?: ProformasQueryOptions) => { }); }, enabled, - placeholderData: (previousData, previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`) + placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`) }); }; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/context/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/context/index.ts new file mode 100644 index 00000000..1608c32d --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/context/index.ts @@ -0,0 +1 @@ +export * from "./proforma-context"; diff --git a/modules/customer-invoices/src/web/context/invoice-context.tsx b/modules/customer-invoices/src/web/proformas/pages/update/context/proforma-context.tsx similarity index 55% rename from modules/customer-invoices/src/web/context/invoice-context.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/context/proforma-context.tsx index fa47356f..14136b57 100644 --- a/modules/customer-invoices/src/web/context/invoice-context.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/context/proforma-context.tsx @@ -1,12 +1,20 @@ -import { TaxCatalogProvider } from '@erp/core'; -import { TFunction } from 'i18next'; -import { PropsWithChildren, createContext, useCallback, useContext, useMemo, useState } from "react"; -import { useTranslation } from "../i18n"; -import { MODULE_NAME } from '../manifest'; +import type { TaxCatalogProvider } from "@erp/core"; +import type { TFunction } from "i18next"; +import { + type PropsWithChildren, + createContext, + useCallback, + useContext, + useMemo, + useState, +} from "react"; -export type InvoiceContextValue = { +import { useTranslation } from "../../../../i18n"; +import type { MODULE_NAME } from "../../../../manifest"; + +export type ProformaContextValue = { company_id: string; - invoice_id: string; + proforma_id: string; status: string; currency_code: string; language_code: string; @@ -22,11 +30,11 @@ export type InvoiceContextValue = { changeIsProforma: (value: boolean) => void; }; -const InvoiceContext = createContext(null); +const ProformaContext = createContext(null); -export interface InvoiceProviderParams { +export interface ProformaProviderParams { taxCatalog: TaxCatalogProvider; - invoice_id: string; + proforma_id: string; company_id: string; status: string; // default "draft" language_code?: string; // default "es" @@ -37,10 +45,17 @@ export interface InvoiceProviderParams { children: React.ReactNode; } -export const InvoiceProvider = ({ taxCatalog: initialTaxCatalog, invoice_id, company_id, status: initialStatus = "draft", language_code: initialLang = "es", - currency_code: initialCurrency = "EUR", readOnly: initialReadOnly = false, - is_proforma: initialProforma = true, children }: PropsWithChildren) => { - +export const ProformaProvider = ({ + taxCatalog: initialTaxCatalog, + proforma_id, + company_id, + status: initialStatus = "draft", + language_code: initialLang = "es", + currency_code: initialCurrency = "EUR", + readOnly: initialReadOnly = false, + is_proforma: initialProforma = true, + children, +}: PropsWithChildren) => { const { t } = useTranslation(); // Estado interno local para campos dinámicos @@ -57,12 +72,11 @@ export const InvoiceProvider = ({ taxCatalog: initialTaxCatalog, invoice_id, com const setIsProformaMemo = useCallback((is_proforma: boolean) => setIsProforma(is_proforma), []); const setReadOnlyMemo = useCallback((readOnly: boolean) => setReadOnly(readOnly), []); - const value = useMemo(() => { - + const value = useMemo(() => { return { t, - invoice_id, + proforma_id, company_id, status, language_code, @@ -76,17 +90,30 @@ export const InvoiceProvider = ({ taxCatalog: initialTaxCatalog, invoice_id, com changeCurrency: setCurrencyMemo, changeIsProforma: setIsProformaMemo, setReadOnly: setReadOnlyMemo, - } - }, [t, readOnly, company_id, invoice_id, status, language_code, currency_code, is_proforma, taxCatalog, setLanguageMemo, setCurrencyMemo, setIsProformaMemo, setReadOnlyMemo]); + }; + }, [ + t, + readOnly, + company_id, + proforma_id, + status, + language_code, + currency_code, + is_proforma, + taxCatalog, + setLanguageMemo, + setCurrencyMemo, + setIsProformaMemo, + setReadOnlyMemo, + ]); - return {children}; + return {children}; }; - -export function useInvoiceContext(): InvoiceContextValue { - const context = useContext(InvoiceContext); +export function useProformaContext(): ProformaContextValue { + const context = useContext(ProformaContext); if (!context) { - throw new Error("useInvoiceContext must be used within "); + throw new Error("useProformaContext must be used within "); } return context; -} \ No newline at end of file +} diff --git a/modules/customer-invoices/src/web/proformas/pages/update/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/index.ts new file mode 100644 index 00000000..ff0a0125 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/index.ts @@ -0,0 +1 @@ +export * from "./proforma-update-page"; diff --git a/modules/customer-invoices/src/web/pages/update/invoice-update-comp.tsx b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx similarity index 51% rename from modules/customer-invoices/src/web/pages/update/invoice-update-comp.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx index 2ecbfc2d..1f9bace8 100644 --- a/modules/customer-invoices/src/web/pages/update/invoice-update-comp.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-comp.tsx @@ -6,32 +6,32 @@ import { useId, useMemo } from "react"; import { type FieldErrors, FormProvider } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import { useInvoiceContext } from "../../context"; -import { useUpdateProforma } from "../../hooks"; -import { useTranslation } from "../../i18n"; +import { useTranslation } from "../../../i18n"; +import { ProformaDtoAdapter } from "../../adapters"; +import { useUpdateProforma } from "../../hooks/use-proforma-update-mutation"; import { - type InvoiceFormData, - InvoiceFormSchema, type Proforma, - defaultCustomerInvoiceFormData, - invoiceDtoToFormAdapter, -} from "../../schemas"; + type ProformaFormData, + ProformaFormSchema, + defaultProformaFormData, +} from "../../schema"; -import { InvoiceUpdateForm } from "./invoice-update-form"; +import { useProformaContext } from "./context"; +import { ProformaUpdateForm } from "./proforma-update-form"; -export type InvoiceUpdateCompProps = { - invoice: Proforma; +export type ProformaUpdateCompProps = { + proforma: Proforma; }; -export const InvoiceUpdateComp = ({ invoice: invoiceData }: InvoiceUpdateCompProps) => { +export const ProformaUpdateComp = ({ proforma: proformaData }: ProformaUpdateCompProps) => { const { t } = useTranslation(); const navigate = useNavigate(); const formId = useId(); - const context = useInvoiceContext(); - const { invoice_id } = context; + const context = useProformaContext(); + const { proforma_id } = context; - const isPending = !invoiceData; + const isPending = !proformaData; const { mutate, @@ -41,23 +41,23 @@ export const InvoiceUpdateComp = ({ invoice: invoiceData }: InvoiceUpdateCompPro } = useUpdateProforma(); const initialValues = useMemo(() => { - return invoiceData - ? invoiceDtoToFormAdapter.fromDto(invoiceData, context) - : defaultCustomerInvoiceFormData; - }, [invoiceData, context]); + return proformaData + ? ProformaDtoAdapter.fromDto(proformaData, context) + : defaultProformaFormData; + }, [proformaData, context]); - const form = useHookForm({ - resolverSchema: InvoiceFormSchema, + const form = useHookForm({ + resolverSchema: ProformaFormSchema, initialValues, - disabled: !invoiceData || isUpdating, + disabled: !proformaData || isUpdating, }); - const handleSubmit = (formData: InvoiceFormData) => { + const handleSubmit = (formData: ProformaFormData) => { console.log("Guardo factura"); - const dto = invoiceDtoToFormAdapter.toDto(formData, context); + const dto = ProformaDtoAdapter.toDto(formData, context); console.log("dto => ", dto); mutate( - { id: invoice_id, data: dto as Partial }, + { id: proforma_id, data: dto as Partial }, { onSuccess: () => showSuccessToast(t("pages.update.success.title"), t("pages.update.success.message")), @@ -67,13 +67,13 @@ export const InvoiceUpdateComp = ({ invoice: invoiceData }: InvoiceUpdateCompPro }; const handleReset = () => - form.reset((invoiceData as unknown as InvoiceFormData) ?? defaultCustomerInvoiceFormData); + form.reset((proformaData as unknown as ProformaFormData) ?? defaultProformaFormData); const handleBack = () => { navigate(-1); }; - const handleError = (errors: FieldErrors) => { + const handleError = (errors: FieldErrors) => { console.error("Errores en el formulario:", errors); // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario }; @@ -85,26 +85,24 @@ export const InvoiceUpdateComp = ({ invoice: invoiceData }: InvoiceUpdateCompPro backIcon description={t("pages.edit.description")} rightSlot={ - <> - - + } - title={`${t("pages.edit.title")} #${invoiceData.invoice_number}`} + title={`${t("pages.proformas.edit.title")} #${proformaData.invoice_number}`} /> - void; - onError: (errors: FieldErrors) => void; + onSubmit: (data: ProformaFormData) => void; + onError: (errors: FieldErrors) => void; className?: string; } -export const InvoiceUpdateForm = ({ +export const ProformaUpdateForm = ({ formId, onSubmit, onError, className, -}: InvoiceUpdateFormProps) => { - const form = useFormContext(); +}: ProformaUpdateFormProps) => { + const form = useFormContext(); return (
- - + +
- +
- +
-
+
); diff --git a/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-page.tsx b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-page.tsx new file mode 100644 index 00000000..c13b7a86 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/proforma-update-page.tsx @@ -0,0 +1,51 @@ +import { SpainTaxCatalogProvider } from "@erp/core"; +import { useUrlParamId } from "@erp/core/hooks"; +import { ErrorAlert } from "@erp/customers/components"; +import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; +import { useMemo } from "react"; + +import { useTranslation } from "../../../i18n"; +import { useProformaQuery } from "../../hooks"; + +import { ProformaProvider } from "./context"; +import { ProformaUpdateComp } from "./proforma-update-comp"; +import { ProformaEditorSkeleton } from "./ui/components"; + +export const ProformaUpdatePage = () => { + const { t } = useTranslation(); + const proforma_id = useUrlParamId(); + const taxCatalog = useMemo(() => SpainTaxCatalogProvider(), []); + + const proformaQuery = useProformaQuery(proforma_id, { enabled: !!proforma_id }); + const { data: proformaData, isLoading, isError, error } = proformaQuery; + + if (isLoading) { + return ; + } + + if (isError || !proformaData) { + return ( + + + + + ); + } + + // Monta el contexto aquí, así todo lo que esté dentro puede usar hooks + return ( + + + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts new file mode 100644 index 00000000..8119968d --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/index.ts @@ -0,0 +1,4 @@ +export * from "./items"; +export * from "./proforma-basic-info-fields"; +export * from "./proforma-totals"; +export * from "./recipient"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts new file mode 100644 index 00000000..d183d2e4 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/index.ts @@ -0,0 +1 @@ +export * from "./proforma-items-editor"; diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/invoice-items-editor.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx similarity index 76% rename from modules/customer-invoices/src/web/shared/ui/components/editor/invoice-items-editor.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx index 04c16bb5..2e42d5cb 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/invoice-items-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/items/proforma-items-editor.tsx @@ -1,11 +1,10 @@ import { FieldDescription, FieldGroup, FieldLegend, FieldSet } from "@repo/shadcn-ui/components"; import type { ComponentProps } from "react"; -import { useTranslation } from "../../../../i18n"; +import { useTranslation } from "../../../../../../i18n"; +import { ItemsEditor } from "../../components"; -import { ItemsEditor } from "./items"; - -export const InvoiceItems = (props: ComponentProps<"fieldset">) => { +export const ProformaItems = (props: ComponentProps<"fieldset">) => { const { t } = useTranslation(); return ( diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/invoice-basic-info-fields.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx similarity index 90% rename from modules/customer-invoices/src/web/shared/ui/components/editor/invoice-basic-info-fields.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx index ee73527b..375c98c2 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/invoice-basic-info-fields.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-basic-info-fields.tsx @@ -3,12 +3,12 @@ import { FieldDescription, FieldGroup, FieldLegend, FieldSet } from "@repo/shadc import type { ComponentProps } from "react"; import { useFormContext } from "react-hook-form"; -import { useTranslation } from "../../../../i18n"; -import type { InvoiceFormData } from "../../../../schemas"; +import { useTranslation } from "../../../../../i18n"; +import type { ProformaFormData } from "../../../../schema"; -export const InvoiceBasicInfoFields = (props: ComponentProps<"fieldset">) => { +export const ProformaBasicInfoFields = (props: ComponentProps<"fieldset">) => { const { t } = useTranslation(); - const { control } = useFormContext(); + const { control } = useFormContext(); return (
diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx new file mode 100644 index 00000000..c37f0757 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/proforma-totals.tsx @@ -0,0 +1,155 @@ +import { formatCurrency } from "@erp/core"; +import { + FieldDescription, + FieldGroup, + FieldLegend, + FieldSet, + Separator, +} from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { ReceiptIcon } from "lucide-react"; +import type { ComponentProps } from "react"; +import { useFormContext, useWatch } from "react-hook-form"; + +import { useTranslation } from "../../../../../i18n"; +import { PercentageInputField } from "../../../../../shared/ui/"; +import type { ProformaFormData } from "../../../../schema"; +import { useProformaContext } from "../../context"; + +export const ProformaTotals = (props: ComponentProps<"fieldset">) => { + const { t } = useTranslation(); + const { control } = useFormContext(); + const { currency_code, language_code, readOnly, taxCatalog } = useProformaContext(); + + const displayTaxes = useWatch({ control, name: "taxes", defaultValue: [] }); + const subtotal_amount = useWatch({ control, name: "subtotal_amount", defaultValue: 0 }); + const items_discount_amount = useWatch({ + control, + name: "items_discount_amount", + defaultValue: 0, + }); + const discount_amount = useWatch({ control, name: "discount_amount", defaultValue: 0 }); + const taxable_amount = useWatch({ control, name: "taxable_amount", defaultValue: 0 }); + const taxes_amount = useWatch({ control, name: "taxes_amount", defaultValue: 0 }); + const total_amount = useWatch({ control, name: "total_amount", defaultValue: 0 }); + + return ( +
+ + + {t("form_groups.totals.title")} + + + {t("form_groups.totals.description")} + +
+ {/* Sección: Subtotal y Descuentos */} +
+ Subtotal sin descuentos + + {formatCurrency(subtotal_amount, 2, currency_code, language_code)} + +
+ +
+
+ Descuento en líneas +
+ + -{formatCurrency(items_discount_amount, 2, currency_code, language_code)} + +
+ +
+
+ Descuento global + +
+ + -{formatCurrency(discount_amount, 2, currency_code, language_code)} + +
+ + {/* Sección: Base Imponible */} +
+ Base imponible + + {formatCurrency(taxable_amount, 2, currency_code, language_code)} + +
+
+ + + + {/* Sección: Impuestos */} +
+

+ Impuestos y retenciones +

+ + {taxCatalog.groups().map((group) => { + // Filtra impuestos de ese grupo + const taxesInGroup = displayTaxes?.filter((item) => { + const tax = taxCatalog.findByCode(item.tax_code).match( + (t) => t, + () => undefined + ); + return tax?.group === group; + }); + + // Si el grupo no tiene impuestos, no renderiza nada + if (taxesInGroup?.length === 0) return null; + + return ( +
+ {taxesInGroup?.map((item) => { + const tax = taxCatalog.findByCode(item.tax_code).match( + (t) => t, + () => undefined + ); + return ( +
+ {tax?.name} + + {formatCurrency(item.taxes_amount, 2, currency_code, language_code)} + +
+ ); + })} +
+ ); + })} + +
+ Total de impuestos + + {formatCurrency(taxes_amount, 2, currency_code, language_code)} + +
+
+ + + +
+ Total de la factura + + {formatCurrency(total_amount, 2, currency_code, language_code)} + +
+
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/index.ts new file mode 100644 index 00000000..36a4a99c --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/index.ts @@ -0,0 +1,2 @@ +export * from "./proforma-recipient"; +export * from "./proforma-recipient-modal-selector-field"; diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/recipient/recipient-modal-selector-field.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/proforma-recipient-modal-selector-field.tsx similarity index 57% rename from modules/customer-invoices/src/web/shared/ui/components/editor/recipient/recipient-modal-selector-field.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/proforma-recipient-modal-selector-field.tsx index 4ac1e55c..7a2e5fec 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/recipient/recipient-modal-selector-field.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/proforma-recipient-modal-selector-field.tsx @@ -1,18 +1,17 @@ import { CustomerModalSelector } from "@erp/customers/components"; import { Field, FieldLabel } from "@repo/shadcn-ui/components"; -import { cn } from '@repo/shadcn-ui/lib/utils'; -import { CustomerSummary } from 'node_modules/@erp/customers/src/web/schemas'; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import type { CustomerSummary } from "node_modules/@erp/customers/src/web/schemas"; +import { type Control, Controller, type FieldPath, type FieldValues } from "react-hook-form"; -import { Control, Controller, FieldPath, FieldValues } from "react-hook-form"; - -type CustomerModalSelectorFieldProps = { +type RecipientModalSelectorFieldProps = { control: Control; name: FieldPath; label?: string; description?: string; - orientation?: "vertical" | "horizontal" | "responsive", + orientation?: "vertical" | "horizontal" | "responsive"; disabled?: boolean; required?: boolean; @@ -28,19 +27,17 @@ export function RecipientModalSelectorField({ label, description, - orientation = 'vertical', - + orientation = "vertical", disabled = false, required = false, readOnly = false, className, initialRecipient = {}, -}: CustomerModalSelectorFieldProps) { +}: RecipientModalSelectorFieldProps) { const isDisabled = disabled; const isReadOnly = readOnly && !disabled; - return ( ({ const { name, value, onChange, onBlur, ref } = field; return ( - - {label && {label}} + + {label && ( + + {label} + + )} ); diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/recipient/invoice-recipient.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/proforma-recipient.tsx similarity index 80% rename from modules/customer-invoices/src/web/shared/ui/components/editor/recipient/invoice-recipient.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/proforma-recipient.tsx index be72998e..d905054e 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/recipient/invoice-recipient.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/blocks/recipient/proforma-recipient.tsx @@ -2,11 +2,11 @@ import { FieldDescription, FieldGroup, FieldLegend, FieldSet } from "@repo/shadc import type { ComponentProps } from "react"; import { useFormContext } from "react-hook-form"; -import { useTranslation } from "../../../../../i18n"; +import { useTranslation } from "../../../../../../i18n"; -import { RecipientModalSelectorField } from "./recipient-modal-selector-field"; +import { RecipientModalSelectorField } from "./proforma-recipient-modal-selector-field"; -export const InvoiceRecipient = (props: ComponentProps<"fieldset">) => { +export const ProformaRecipient = (props: ComponentProps<"fieldset">) => { const { t } = useTranslation(); const { control, getValues } = useFormContext(); diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/index.ts new file mode 100644 index 00000000..6dd28b1a --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/index.ts @@ -0,0 +1,2 @@ +export * from "./items-editor"; +export * from "./proforma-editor-skeleton"; diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/index.ts b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/index.ts new file mode 100644 index 00000000..538df18b --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/index.ts @@ -0,0 +1 @@ +export * from "./items-editor"; diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/items/item-row-editor.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx similarity index 90% rename from modules/customer-invoices/src/web/shared/ui/components/editor/items/item-row-editor.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx index 50387dd3..acaadd95 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/items/item-row-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/item-row-editor.tsx @@ -1,19 +1,19 @@ import { Button, Input, Label, Textarea } from "@repo/shadcn-ui/components"; import { useFormContext } from "react-hook-form"; -import type { InvoiceFormData, InvoiceItemFormData } from "../../../../../schemas"; +import type { ProformaFormData, ProformaItemFormData } from "../../../../../schema"; export function ItemRowEditor({ row, index, onClose, }: { - row: InvoiceItemFormData; + row: ProformaItemFormData; index: number; onClose: () => void; }) { // Editor simple reutilizando el mismo RHF - const { register } = useFormContext(); + const { register } = useFormContext(); return (

Edit line #{index + 1}

diff --git a/modules/customer-invoices/src/web/shared/ui/components/editor/items/items-editor.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx similarity index 78% rename from modules/customer-invoices/src/web/shared/ui/components/editor/items/items-editor.tsx rename to modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx index b93a9074..fb12f06c 100644 --- a/modules/customer-invoices/src/web/shared/ui/components/editor/items/items-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/items-editor/items-editor.tsx @@ -1,31 +1,34 @@ +/** biome-ignore-all lint/complexity/noForEach: */ +/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: */ + +import { useProformaItemsColumns } from "@erp/customer-invoices/web/proformas/hooks"; import { DataTable, useWithRowSelection } from "@repo/rdx-ui/components"; import { useMemo } from "react"; import { useFieldArray, useFormContext } from "react-hook-form"; -import { useInvoiceContext } from "../../../../../context"; -import { useInvoiceAutoRecalc } from "../../../../../hooks"; -import { useTranslation } from "../../../../../i18n"; -import { type InvoiceFormData, defaultCustomerInvoiceItemFormData } from "../../../../../schemas"; +import { useProformaAutoRecalc } from "../../../../../../hooks"; +import { useTranslation } from "../../../../../../i18n"; +import { type ProformaFormData, defaultProformaItemFormData } from "../../../../../schema"; +import { useProformaContext } from "../../../context"; import { ItemRowEditor } from "./item-row-editor"; -import { useItemsColumns } from "./use-items-columns"; -const createEmptyItem = () => defaultCustomerInvoiceItemFormData; +const createEmptyItem = () => defaultProformaItemFormData; export const ItemsEditor = () => { const { t } = useTranslation(); - const context = useInvoiceContext(); - const form = useFormContext(); + const context = useProformaContext(); + const form = useFormContext(); const { control } = form; - useInvoiceAutoRecalc(form, context); + useProformaAutoRecalc(form, context); const { fields, append, remove, move, insert, update } = useFieldArray({ control, name: "items", }); - const baseColumns = useWithRowSelection(useItemsColumns(), true); + const baseColumns = useWithRowSelection(useProformaItemsColumns(), true); const columns = useMemo(() => baseColumns, [baseColumns]); return ( diff --git a/modules/customer-invoices/src/web/proformas/pages/update/ui/components/proforma-editor-skeleton.tsx b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/proforma-editor-skeleton.tsx new file mode 100644 index 00000000..660afc63 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/pages/update/ui/components/proforma-editor-skeleton.tsx @@ -0,0 +1,31 @@ +// components/CustomerSkeleton.tsx +import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; + +import { useTranslation } from "../../../../../i18n"; + +export const ProformaEditorSkeleton = () => { + const { t } = useTranslation(); + return ( + +
+