feat: Enhance invoice management with operation and invoice type filters, update dialog defaults, and modify routes for improved functionality

This commit is contained in:
2025-12-12 08:44:09 -06:00
parent e5f6162ffb
commit a07aeb7b12
11 changed files with 238 additions and 60 deletions

View File

@@ -9,6 +9,7 @@ export interface InvoiceType {
description: string;
note?: string;
type?: string;
operation?: string;
}
export interface InvoiceTypeListResponse {
@@ -40,11 +41,20 @@ export const invoiceTypesApi = {
* Lista todos los tipos de factura con paginación
* @param page - Número de página (por defecto 1)
* @param pageSize - Tamaño de página (por defecto 50)
* @param operation - Filtrar por tipo de operación (imp, exp)
*/
list: (page = 1, pageSize = 50) =>
api.get<InvoiceTypeListResponse>(
`/v1/public/refrence_data/invoice-types?page=${page}&page_size=${pageSize}`
),
list: (page = 1, pageSize = 50, operation?: string) => {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString()
});
if (operation) {
params.append('operation', operation);
}
return api.get<InvoiceTypeListResponse>(
`/v1/public/refrence_data/invoice-types?${params.toString()}`
);
},
/**
* Obtiene un tipo de factura por key

View File

@@ -1 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="techGradient" x1="16" y1="16" x2="48" y2="48" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#00F2FE" /> <stop offset="100%" stop-color="#4FACFE" /> </linearGradient>
</defs>
<rect width="64" height="64" rx="18" fill="#0F172A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32 14L46 26V40L32 52L18 40V26L32 14ZM32 20.5L23 28.2V35.8L32 43.5L41 35.8V28.2L32 20.5Z" fill="url(#techGradient)"/>
<path d="M32 20.5V30M32 34V43.5" stroke="#0F172A" stroke-width="2" stroke-linecap="round"/>
<path d="M23 35.8L32 30M41 35.8L32 30" stroke="#0F172A" stroke-width="2" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 781 B

View File

@@ -12,10 +12,14 @@
let {
open = $bindable(false),
item = $bindable<Invoice | null>(null),
defaultOperationType,
defaultInvoiceType,
onSuccess
}: {
open: boolean;
item?: Invoice | null;
defaultOperationType?: 'imp' | 'exp';
defaultInvoiceType?: string;
onSuccess?: () => void;
} = $props();
@@ -122,8 +126,8 @@
function resetForm() {
formData = {
operation_type: "imp",
invoice_type: "",
operation_type: defaultOperationType || "imp",
invoice_type: defaultInvoiceType || "",
invoice_number: "",
project_number: "",
purchase_order: "",

View File

@@ -323,19 +323,19 @@ export function getSidebarData(): SidebarData {
items: [
{
title: m["sidebar.import_invoices.temporary"](),
url: "/dashboard/invoices",
url: "/dashboard/invoices?operation_type=imp&invoice_type=TEM",
},
{
title: m["sidebar.import_invoices.definitive"](),
url: "/dashboard/invoices",
url: "/dashboard/invoices?operation_type=imp&invoice_type=DEF",
},
{
title: m["sidebar.import_invoices.mexican_purchases"](),
url: "/dashboard/invoices",
url: "/dashboard/invoices?operation_type=imp&invoice_type=MEX",
},
{
title: m["sidebar.import_invoices.regime_change"](),
url: "/dashboard/invoices",
url: "/dashboard/invoices?operation_type=imp&invoice_type=CR",
}
],
},
@@ -346,11 +346,11 @@ export function getSidebarData(): SidebarData {
items: [
{
title: m["sidebar.export_invoices.exportation"](),
url: "/dashboard/invoices",
url: "/dashboard/invoices?operation_type=exp",
},
{
title: m["sidebar.export_invoices.repair"](),
url: "/dashboard/invoices",
url: "/dashboard/invoices?operation_type=exp&invoice_type=REPAR",
},
],
},

View File

@@ -44,6 +44,7 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
// Obtener filtro de tipo de operación
const operationType = url.searchParams.get('operation_type');
const invoiceType = url.searchParams.get('invoice_type');
// Construir parámetros de consulta
const params = new URLSearchParams({
@@ -57,6 +58,11 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
params.append('operation_type', operationType);
}
// Agregar filtro de invoice_type si existe
if (invoiceType) {
params.append('invoice_type', invoiceType);
}
// Usar authenticatedFetch para manejar automáticamente el refresh de tokens
const response = await authenticatedFetch(
`v1/a76/invoices?${params.toString()}`,
@@ -75,7 +81,8 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
error: 'Error al cargar facturas',
companies: parentData.companies || [],
currentCompanyId: companyId,
operationType: operationType || 'all'
operationType: operationType || 'all',
invoiceType: invoiceType || null
};
}
@@ -88,7 +95,8 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
page_size: data.page_size || 50,
companies: parentData.companies || [],
currentCompanyId: companyId,
operationType: operationType || 'all'
operationType: operationType || 'all',
invoiceType: invoiceType || null
};
} catch (error) {
console.error('Error loading invoices:', error);
@@ -98,7 +106,9 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
page: 1,
page_size: 50,
error: 'Error al cargar facturas',
companies: parentData.companies || []
companies: parentData.companies || [],
operationType: 'all',
invoiceType: null
};
}
};

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { invoicesApi, type Invoice } from '$lib/api/dashboard/a76/invoices';
import { invoiceTypesApi, type InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types';
import DataTable from '$lib/components/dashboard/invoices/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/invoices/columns.js';
import CreateEditDialog from '$lib/components/dashboard/invoices/create-edit-dialog.svelte';
@@ -24,6 +25,7 @@
companies: any[];
currentCompanyId?: number;
operationType?: string;
invoiceType?: string | null;
}
let { data }: { data: PageData } = $props();
@@ -36,6 +38,9 @@
// Estado para el filtro de tipo (inicializado desde data del servidor)
let selectedType = $state<string>(data.operationType || 'all');
let selectedInvoiceType = $state<string | null>(data.invoiceType || null);
let availableInvoiceTypes = $state<InvoiceType[]>([]);
let loadingInvoiceTypes = $state(false);
// Actualizar URL cuando cambia el filtro
function handleTypeChange(value: string) {
@@ -43,12 +48,59 @@
const url = new URL(window.location.href);
if (value === 'all') {
url.searchParams.delete('operation_type');
url.searchParams.delete('invoice_type');
selectedInvoiceType = null;
} else {
url.searchParams.set('operation_type', value);
// Mantener invoice_type si existe
if (selectedInvoiceType) {
url.searchParams.set('invoice_type', selectedInvoiceType);
}
}
goto(url.toString(), { keepFocus: true, noScroll: true });
}
// Actualizar URL cuando cambia el filtro de invoice_type
function handleInvoiceTypeChange(value: string) {
selectedInvoiceType = value === 'all' ? null : value;
const url = new URL(window.location.href);
if (!selectedInvoiceType) {
url.searchParams.delete('invoice_type');
} else {
url.searchParams.set('invoice_type', selectedInvoiceType);
}
if (selectedType !== 'all') {
url.searchParams.set('operation_type', selectedType);
}
goto(url.toString(), { keepFocus: true, noScroll: true });
}
// Cargar tipos de factura disponibles según operation_type
async function loadInvoiceTypes(operationType: string) {
if (operationType === 'all') {
availableInvoiceTypes = [];
return;
}
loadingInvoiceTypes = true;
try {
const response = await invoiceTypesApi.list(1, 100, operationType);
if (response.data) {
availableInvoiceTypes = response.data.items;
}
} catch (e) {
console.error('Error loading invoice types:', e);
availableInvoiceTypes = [];
} finally {
loadingInvoiceTypes = false;
}
}
// Efecto para cargar tipos de factura cuando cambia selectedType
$effect(() => {
loadInvoiceTypes(selectedType);
});
// Sincronizar token de cookies a localStorage al montar el componente
onMount(() => {
if (browser) {
@@ -120,6 +172,8 @@
currentPage = data.page || 1;
totalItems = data.total || 0;
error = data.error || null;
selectedInvoiceType = data.invoiceType || null;
selectedType = data.operationType || 'all';
});
async function loadMore() {
@@ -129,11 +183,19 @@
error = null;
try {
const filters: any = {};
if (selectedType !== 'all') {
filters.operation_type = selectedType;
}
if (selectedInvoiceType) {
filters.invoice_type = selectedInvoiceType;
}
const response = await invoicesApi.list(
companyStore.activeCompany.id,
currentPage + 1,
pageSize,
selectedType !== 'all' ? { operation_type: selectedType } : undefined
Object.keys(filters).length > 0 ? filters : undefined
);
if (response.error) {
@@ -245,6 +307,41 @@
<Select.Item value="exp">Exportación</Select.Item>
</Select.Content>
</Select.Root>
{#if selectedType !== 'all' && availableInvoiceTypes.length > 0}
<Select.Root
type="single"
value={selectedInvoiceType || 'all'}
onValueChange={handleInvoiceTypeChange}
disabled={loadingInvoiceTypes}
>
<Select.Trigger class="w-[250px]">
<span class="truncate">
{#if loadingInvoiceTypes}
Cargando...
{:else if selectedInvoiceType}
{(() => {
const found = availableInvoiceTypes.find(t => t.key === selectedInvoiceType);
return found ? `${found.key} - ${found.description}` : selectedInvoiceType;
})()}
{:else}
Todos los tipos
{/if}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="all">Todos los tipos</Select.Item>
{#each availableInvoiceTypes as invType}
<Select.Item value={invType.key}>
<span class="block truncate max-w-[300px]" title={`${invType.key} - ${invType.description}`}>
{invType.key} - {invType.description}
</span>
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
{/if}
<Button variant="outline" onclick={reloadData}>
<RefreshCw class="mr-2" size={16} />
Actualizar
@@ -268,6 +365,8 @@
<CreateEditDialog
bind:open={showCreateDialog}
bind:item={selectedInvoice}
defaultOperationType={selectedType !== 'all' ? selectedType as 'imp' | 'exp' : undefined}
defaultInvoiceType={selectedInvoiceType || undefined}
onSuccess={handleSuccess}
/>