From d24bb89294fd1abf9ddd3c9d198827021b8f3946 Mon Sep 17 00:00:00 2001 From: AlexeerCT Date: Fri, 1 May 2026 23:32:40 -0500 Subject: [PATCH] feat: enhance invoice creation and editing experience - Added a new `isCreate` prop to manage the invoice creation state in the invoice top fields component. - Implemented a conditional invoice type selection for the export operation in the invoice top fields. - Updated sidebar module links to simplify URL parameters for invoice exports. - Refactored invoice type handling in the dashboard to improve dynamic title generation based on operation type and invoice type. - Adjusted server-side logic to enforce required parameters for invoice creation based on operation type. - Cleaned up unused filters in the invoice page to streamline the user interface. --- .../invoices/edit/invoice-top-fields.svelte | 32 +++++++- .../src/lib/components/sidebar/modules.ts | 2 +- .../components/sidebar/team-switcher.svelte | 29 +------ .../routes/dashboard/invoices/+page.svelte | 75 ++++++++----------- .../invoices/edit/[id]/+page.server.ts | 7 +- .../dashboard/invoices/edit/[id]/+page.svelte | 38 ++++++---- 6 files changed, 92 insertions(+), 91 deletions(-) diff --git a/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte b/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte index 633aa249..4b82e31d 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte @@ -18,6 +18,7 @@ defaultInvoiceType = undefined, invoiceType = undefined, isSettings = false, + isCreate = false, highlightFieldId = null, onDismissHighlightForField = undefined }: { @@ -29,6 +30,7 @@ defaultInvoiceType?: string | null; invoiceType?: string; isSettings?: boolean; + isCreate?: boolean; highlightFieldId?: string | null; onDismissHighlightForField?: (fieldKey: string) => void; } = $props(); @@ -233,16 +235,38 @@ id="invoice-field-invoice_type" tabindex="-1" class={cn( - 'min-w-[100px] flex-1 space-y-1 rounded-md outline-none', + 'min-w-[140px] flex-[1.2] space-y-1 rounded-md outline-none', hl('invoice_type') )} > -

- {formData.invoice_type ? `${formData.invoice_type}` : '...'} -

+ {#if isCreate && !isSettings && formData.operation_type === 'exp'} + { + formData.invoice_type = v || ''; + }} + > + + + {filteredInvoiceTypes.find((t) => t.key === formData.invoice_type)?.description || + m.invoice_edit_form_invoice_type_placeholder()} + + + + {#each filteredInvoiceTypes as t} + {t.description} + {/each} + + + {:else} +

+ {formData.invoice_type ? `${formData.invoice_type}` : '...'} +

+ {/if} {/if} diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 41a544fe..08930b11 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -447,7 +447,7 @@ export function getSidebarData(): SidebarData { items: [ { title: m["sidebar.export_invoices.exportation"](), - url: "/dashboard/invoices?operation_type=exp&invoice_type=EXDEF", + url: "/dashboard/invoices?operation_type=exp", permission: "invoice.exp.view" }, { diff --git a/frontend/src/lib/components/sidebar/team-switcher.svelte b/frontend/src/lib/components/sidebar/team-switcher.svelte index ed91bfcc..c18b3458 100644 --- a/frontend/src/lib/components/sidebar/team-switcher.svelte +++ b/frontend/src/lib/components/sidebar/team-switcher.svelte @@ -73,32 +73,9 @@ } } - function normalizeIdentity(value: string | undefined | null): string { - return (value ?? '').trim().toLowerCase(); - } - - let tenantIdentitySet = $derived.by(() => { - const set = new Set(); - for (const tenant of userTenants) { - set.add(normalizeIdentity(tenant.name)); - set.add(normalizeIdentity(tenant.slug)); - } - set.delete(''); - return set; - }); - - // Excluir del listado de companias cualquier registro que realmente represente al tenant. - let myCompanies = $derived( - companyStore.companies.filter((company) => !tenantIdentitySet.has(normalizeIdentity(company.name))) - ); - - $effect(() => { - const active = companyStore.activeCompany; - if (!active) return; - if (!tenantIdentitySet.has(normalizeIdentity(active.name))) return; - if (myCompanies.length === 0) return; - void companyStore.setActiveCompany(myCompanies[0], true); - }); + // Misma lista que devuelve my-companies; no filtrar por tenant (nombre/slug equivalentes + // ocultaban la empresa creada al primer login). + let myCompanies = $derived(companyStore.companies); diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index cf1b1bbd..63cab881 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -275,26 +275,39 @@ } }); - // Título dinámico según el tipo de operación y factura + // Título dinámico según el tipo de operación y tipo de factura (i18n para casos clásicos; catálogo API para el resto) const viewTitle = $derived.by(() => { const op = filters.operation_type; - const type = filters.invoice_type; - + const type = (filters.invoice_type || '').trim(); + const base = m['invoice_list.titles.base'](); - + + const titleFromInvoiceTypesCatalog = (): string | null => { + if (!type || !data.invoiceTypes?.length) return null; + const hit = data.invoiceTypes.find((t: { key: string }) => t.key === type); + if (!hit?.description) return null; + return `${base} DE ${String(hit.description).trim().toUpperCase()}`; + }; + if (op === 'imp') { if (type === 'TEM') return `${base} ${m['invoice_list.titles.import_temporal']()}`; if (type === 'DEF') return `${base} ${m['invoice_list.titles.import_definitive']()}`; if (type === 'MEX') return `${base} ${m['invoice_list.titles.import_mexican']()}`; if (type === 'CR') return `${base} ${m['invoice_list.titles.import_regime_change']()}`; if (type === 'REP') return `${base} ${m['invoice_list.titles.import_repair']()}`; + const catalogTitle = titleFromInvoiceTypesCatalog(); + if (catalogTitle) return catalogTitle; return `${base} ${m['invoice_list.titles.import']()}`; - } else if (op === 'exp') { + } + + if (op === 'exp') { if (type === 'EXDEF') return `${base} ${m['invoice_list.titles.export_definitive']()}`; if (type === 'REPAR') return `${base} ${m['invoice_list.titles.export_repair']()}`; + const catalogTitle = titleFromInvoiceTypesCatalog(); + if (catalogTitle) return catalogTitle; return `${base} ${m['invoice_list.titles.export']()}`; } - + return m['invoice_list.header.title'](); }); @@ -1355,13 +1368,6 @@ } } - // Opciones de tipo de operación para el filtro - const operationTypeOptions = $derived.by(() => [ - { value: '', label: m.invoice_list_operation_types_all() }, - { value: 'imp', label: m.invoice_list_operation_types_import() }, - { value: 'exp', label: m.invoice_list_operation_types_export() } - ]); - // Todas las opciones de tipo de factura con su operación correspondiente const allInvoiceTypeOptions = $derived(() => { const options = [{ value: '', label: m.invoice_list_operation_types_all(), operation: 'both' }]; @@ -1445,35 +1451,6 @@

{m.invoice_list_header_description()}

- - -
+ {#if filters.operation_type === 'exp'} + + {/if} { parsedOperationType = operationTypeParam; } - // Para creación ('new'), es obligatorio tener operation_type e invoice_type - if (params.id === 'new' && (!parsedOperationType || !invoiceTypeParam)) { + // Para creación ('new'): siempre operation_type; invoice_type obligatorio solo en importación. + if (params.id === 'new' && !parsedOperationType) { + throw redirect(302, '/dashboard'); + } + if (params.id === 'new' && parsedOperationType === 'imp' && !invoiceTypeParam) { throw redirect(302, '/dashboard'); } diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte index 9b3ca724..78f4ac96 100644 --- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte @@ -498,14 +498,20 @@ let lastFetchedDate = $state(''); let originalInvoiceDate = $state(data.invoice?.invoice_date || ''); - // Derivados reactivos para el tipo de factura y operación + // Tipo de factura efectivo (cabecera + tabs): prioridad campos superiores, luego URL/factura. + // En importación sin tipo aún, se asume TEM como antes; en exportación sin tipo, cadena vacía (sin forzar TEM). let invoiceType = $derived.by(() => { - return ( - InvoiceTopFieldsFormData?.invoice_type || - data.filters?.invoice_type || - data.invoice?.invoice_type || - 'TEM' - ); // Default to TEM if not found + const top = InvoiceTopFieldsFormData?.invoice_type; + if (top !== undefined && top !== null && String(top).trim() !== '') { + return String(top).trim(); + } + if (data.filters?.invoice_type) return String(data.filters.invoice_type).trim(); + if (data.invoice?.invoice_type) return String(data.invoice.invoice_type).trim(); + const op = + InvoiceTopFieldsFormData?.operation_type || + data.invoice?.operation_type || + data.filters?.operation_type; + return op === 'imp' ? 'TEM' : ''; }); let operationTypeText = $derived.by(() => { @@ -1044,19 +1050,18 @@

{#if data.isCreate} - {@const invoiceType = generalFormData?.invoice_type || data.filters?.invoice_type} - {@const invoiceTypeInfo = invoiceType - ? data.invoiceTypes?.find((t) => t.key === invoiceType) - : null} {m.invoice_edit_new_title()} - {#if invoiceTypeInfo} - - - {invoiceTypeInfo.description} - - {/if} {:else} {m.invoice_edit_page_invoice_prefix()}{data.invoice.id} {/if} + {#if invoiceType} + {@const headerInvoiceTypeInfo = data.invoiceTypes?.find((t) => t.key === invoiceType)} + {#if headerInvoiceTypeInfo} + + - {headerInvoiceTypeInfo.description} + + {/if} + {/if}

{operationTypeText} @@ -1088,6 +1093,7 @@ defaultOperationType={data.filters?.operation_type ?? undefined} defaultInvoiceType={data.filters?.invoice_type ?? undefined} {invoiceType} + isCreate={data.isCreate === true} highlightFieldId={validationHighlightField} onDismissHighlightForField={dismissValidationHighlight} />