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

@@ -15,7 +15,8 @@ ServiceType = TypeVar("ServiceType")
class TenantCRUDRoutes(
Generic[CreateSchemaType, UpdateSchemaType, ResponseSchemaType, ServiceType]
Generic[CreateSchemaType, UpdateSchemaType,
ResponseSchemaType, ServiceType]
):
"""
Generic CRUD routes factory for tenant-scoped resources
@@ -74,7 +75,8 @@ class TenantCRUDRoutes(
prefix: str,
tags: list[str],
resource_name: str = "Resource",
id_name: Optional[str] = None, # For parent resources (e.g., "pedimento_id")
# For parent resources (e.g., "pedimento_id")
id_name: Optional[str] = None,
id_type: Type = int, # Type of the ID (int, str, etc.)
parent_id_name: Optional[
str
@@ -128,9 +130,15 @@ class TenantCRUDRoutes(
le=self.max_page_size,
description="Page size",
),
status: Optional[str] = Query(None, description="Filter by status"),
status: Optional[str] = Query(
None, description="Filter by status"),
operation_type: Optional[str] = Query(
None, description="Filter by operation type"),
invoice_type: Optional[str] = Query(
None, description="Filter by invoice type"),
db: Session = Depends(self.db_dependency),
current_user: Dict[str, Any] = Depends(self.auth_dependency),
current_user: Dict[str, Any] = Depends(
self.auth_dependency),
):
tenant_id = validate_access_to_resource(
db, company_id, current_user
@@ -140,6 +148,10 @@ class TenantCRUDRoutes(
filters = {}
if status:
filters["status"] = status
if operation_type:
filters["operation_type"] = operation_type
if invoice_type:
filters["invoice_type"] = invoice_type
items, total = self.service.get_all(
db, tenant_id, company_id, skip, page_size, filters
@@ -172,7 +184,8 @@ class TenantCRUDRoutes(
description="Page size",
),
db: Session = Depends(self.db_dependency),
current_user: Dict[str, Any] = Depends(self.auth_dependency),
current_user: Dict[str, Any] = Depends(
self.auth_dependency),
):
tenant_id = validate_access_to_resource(
db, company_id, current_user
@@ -211,7 +224,8 @@ class TenantCRUDRoutes(
**path_params,
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
parent_id = path_params.get(self.parent_id_name)
# Try method with 4 params (pedimento_id, tenant_id, company_id)
@@ -225,7 +239,8 @@ class TenantCRUDRoutes(
db, parent_id, tenant_id, company_id
)
else:
resource = self.service.get(db, parent_id, tenant_id, company_id)
resource = self.service.get(
db, parent_id, tenant_id, company_id)
if not resource:
raise HTTPException(
@@ -249,7 +264,8 @@ class TenantCRUDRoutes(
db: Session = Depends(self.db_dependency),
current_user: Dict[str, Any] = Depends(self.auth_dependency),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
resource = self.service.get_by_id(
db, resource_id, tenant_id, company_id
@@ -264,10 +280,10 @@ class TenantCRUDRoutes(
# POST route
if self.parent_id_name:
# Child resource - needs parent_id from path
# Create a closure to capture the schema type
create_schema = self.create_schema
@self.router.post(
"/",
response_model=self.response_schema,
@@ -281,17 +297,18 @@ class TenantCRUDRoutes(
db: Session = Depends(self.db_dependency),
current_user: Dict[str, Any] = Depends(self.auth_dependency),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
# For child resources, parent_id validation would go here
resource = self.service.create(db, data, tenant_id, company_id)
return resource
else:
# Parent resource - no parent_id needed
# Create a closure to capture the schema type
create_schema = self.create_schema
@self.router.post(
"/",
response_model=self.response_schema,
@@ -305,7 +322,8 @@ class TenantCRUDRoutes(
db: Session = Depends(self.db_dependency),
current_user: Dict[str, Any] = Depends(self.auth_dependency),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
resource = self.service.create(db, data, tenant_id, company_id)
return resource
@@ -314,10 +332,10 @@ class TenantCRUDRoutes(
# For child resources: PUT / (parent_id comes from path)
if self.parent_id_name:
# Child resource
# Create a closure to capture the schema type
update_schema = self.update_schema
@self.router.put(
"/",
response_model=self.response_schema,
@@ -331,7 +349,8 @@ class TenantCRUDRoutes(
current_user: Dict[str, Any] = Depends(self.auth_dependency),
**path_params,
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
parent_id = path_params.get(self.parent_id_name)
resource = self.service.update(
@@ -346,10 +365,10 @@ class TenantCRUDRoutes(
else:
# Parent resource
# Create a closure to capture the schema type
update_schema = self.update_schema
@self.router.put(
f"/{{{self.id_name}}}",
response_model=self.response_schema,
@@ -366,7 +385,8 @@ class TenantCRUDRoutes(
current_user: Dict[str, Any] = Depends(self.auth_dependency),
):
f"""Update {self.resource_name}"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
resource = self.service.update(
db, resource_id, tenant_id, data, company_id
@@ -395,10 +415,12 @@ class TenantCRUDRoutes(
current_user: Dict[str, Any] = Depends(self.auth_dependency),
**path_params,
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
parent_id = path_params.get(self.parent_id_name)
success = self.service.delete(db, parent_id, tenant_id, company_id)
success = self.service.delete(
db, parent_id, tenant_id, company_id)
if not success:
raise HTTPException(
@@ -422,9 +444,11 @@ class TenantCRUDRoutes(
db: Session = Depends(self.db_dependency),
current_user: Dict[str, Any] = Depends(self.auth_dependency),
):
tenant_id = validate_access_to_resource(db, company_id, current_user)
tenant_id = validate_access_to_resource(
db, company_id, current_user)
success = self.service.delete(db, resource_id, tenant_id, company_id)
success = self.service.delete(
db, resource_id, tenant_id, company_id)
if not success:
raise HTTPException(

View File

@@ -44,6 +44,9 @@ class InvoiceService:
if filters.get("operation_type"):
query = query.filter(
models.InvoiceHeader.operation_type == filters["operation_type"])
if filters.get("invoice_type"):
query = query.filter(
models.InvoiceHeader.invoice_type == filters["invoice_type"])
if filters.get("invoice_number"):
query = query.filter(models.InvoiceHeader.invoice_number.ilike(
f"%{filters['invoice_number']}%"))
@@ -53,6 +56,10 @@ class InvoiceService:
f"%{filters['pedimento']}%")
)
if not filters.get("invoice_type") and filters.get("operation_type") == "exp":
query = query.filter(
models.InvoiceHeader.operation_type != "REPAR")
total = query.count()
items = query.offset(skip).limit(limit).all()
return items, total

View File

@@ -8,5 +8,6 @@ class InvoiceTypeDTO(BaseModel):
description: str
note: Optional[str] = None
type: Optional[str] = None
operation: Optional[str] = None
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict
from typing import Any, Dict, Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
@@ -11,17 +11,28 @@ from .models import InvoiceType
router = APIRouter(prefix="/invoice-types")
@router.get("/", response_model=Dict[str, Any])
@router.get("/", response_model=dict)
def list_invoice_types(
page: int = Query(1, ge=1, description="Número de página"),
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=100),
type: Optional[str] = Query(None, description="Filter by type"),
operation: Optional[str] = Query(None, description="Filter by operation type (imp, exp, both)"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
skip = (page - 1) * page_size
query = db.query(InvoiceType)
items = query.offset(skip).limit(page_size).all()
# Filter by operation if provided
if operation:
query = query.filter(
(InvoiceType.operation == operation) | (
InvoiceType.operation == "both")
)
if type == "imp" and operation == "CR":
query = query.filter(InvoiceType.operation != "exp")
total = query.count()
items = query.offset((page - 1) * page_size).limit(page_size).all()
return {
"items": [InvoiceTypeDTO.model_validate(obj) for obj in items],
"total": total,

View File

@@ -30,47 +30,47 @@ seed = [
),
# === TIPOS DE EXPORTACION ===
("DONAC", "DONACION", "", "both", "both"),
("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "both"),
("DONAC", "DONACION", "", "both", "exp"),
("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "exp"),
(
"MATDE",
"MATERIA PRIMA O MATERIAL DEVUELTO",
"ESTE PROCESO CONSISTE EN SOLO DESCARGAR LAS PARTES DADAS DE ALTA EN MATERIALES QUE SON RETORNADAS SIN NINGUNA MODIFICACION (A1)",
"material",
"both",
"exp",
),
(
"NODES",
"NO HACE DESCARGA",
"ESTE PROCESO DE ACTUALIZACION CONSISTE EN EXPORTAR UNA MERCANCIA Y NO DESCARGAR, POR LO TANTO NO EXISTE REPORTE DE DESCARGAS Y NO AFECTA SALDOS.",
"both",
"both",
"exp",
),
(
"PTERM",
"PRODUCTO TERMINADO Y VIRTUALES",
"EL PRODUCTO TERMINADO Y VIRTUALES DESCARGARAN: 1) APARTIR DE LOS COMPONENTES DE CADA PRODUCTO TERMINADO REGISTRADO EN LAS PARTIDAS DE EXPORTACION. 2) POR PARTE, CON LAS OPCIONES DE PODER DESCARGAR POR SUSTITUTO Y POR CLASE EN CASO DE INSUFICIENCIAS DEL COMPONENTE.",
"material",
"both",
"exp",
),
(
"REPAR",
"REPARACION",
"PROCESO QUE CONSISTE EN DOS ETAPAS: 1) DESCARGA EL PRODUCTO DE REPARACION QUE SE IMPORTO PARA REPARA, 2) DESCARGA EL LISTADO DE COMPONENTES QUE SE AGREGO AL PRODUCTO DE REPARACION",
"material",
"both",
"exp",
),
("SCRAP", "SCRAP", "", "both", "both"),
("SCRAP", "SCRAP", "", "both", "exp"),
(
"VEMEX",
"VENTAS EN MEXICO",
"ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.",
"both",
"both",
"exp",
),
("VIRTU", "VIRTUALES", "", "material", "both"),
("VIRTU", "VIRTUALES", "", "material", "exp"),
# === ACTIVOS FIJOS (AMBAS OPERACIONES) ===
("AFIJO", "ACTIVO FIJO", "", "fixed asset", "both"),
("REEXP", "REEXPEDICION", "", "fixed asset", "both"),
("AFIJO", "ACTIVO FIJO", "", "fixed asset", "exp"),
("REEXP", "REEXPEDICION", "", "fixed asset", "exp"),
]

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}
/>