diff --git a/backend/api/v1/common/tenant_crud_routes.py b/backend/api/v1/common/tenant_crud_routes.py index 51a99305..9a3c65ae 100644 --- a/backend/api/v1/common/tenant_crud_routes.py +++ b/backend/api/v1/common/tenant_crud_routes.py @@ -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( diff --git a/backend/api/v1/modules/a76/invoices/services.py b/backend/api/v1/modules/a76/invoices/services.py index c74fea63..ed8f9c38 100644 --- a/backend/api/v1/modules/a76/invoices/services.py +++ b/backend/api/v1/modules/a76/invoices/services.py @@ -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 diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/dto.py b/backend/api/v1/modules/public/reference_data/invoice_types/dto.py index 7e84a068..0488a2a2 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/dto.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/dto.py @@ -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) diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py index 936d7fb3..e0ce0866 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py @@ -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, diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py index f011769f..151163dd 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py @@ -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"), ] diff --git a/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts b/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts index 8b132732..67bdae54 100644 --- a/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts +++ b/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts @@ -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( - `/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( + `/v1/public/refrence_data/invoice-types?${params.toString()}` + ); + }, /** * Obtiene un tipo de factura por key diff --git a/frontend/src/lib/assets/favicon.svg b/frontend/src/lib/assets/favicon.svg index cc5dc66a..1e26f0a3 100644 --- a/frontend/src/lib/assets/favicon.svg +++ b/frontend/src/lib/assets/favicon.svg @@ -1 +1,13 @@ -svelte-logo \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte index 6b523b0c..94004867 100644 --- a/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte @@ -12,10 +12,14 @@ let { open = $bindable(false), item = $bindable(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: "", diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 60662981..cbc38c14 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -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", }, ], }, diff --git a/frontend/src/routes/dashboard/invoices/+page.server.ts b/frontend/src/routes/dashboard/invoices/+page.server.ts index 8b9ed8bb..4e3b8795 100644 --- a/frontend/src/routes/dashboard/invoices/+page.server.ts +++ b/frontend/src/routes/dashboard/invoices/+page.server.ts @@ -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 }; } }; diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte index c9fee531..0a837d10 100644 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/invoices/+page.svelte @@ -1,6 +1,7 @@