""" Export API - Data Export Endpoints Endpoints for exporting entity data to CSV/Excel. Available entities: tickets, customers, subscriptions, field_agents, sales_orders Authorization: - platform_admin, client_admin, contractor_admin: Full access - project_manager, sales_manager, dispatcher: Project-scoped access - field_agent, sales_agent: No access """ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from typing import List import logging from datetime import datetime from app.api.deps import get_db, get_current_user from app.models.user import User from app.schemas.export import ( ExportRequest, AvailableColumnsResponse, ColumnMetadata ) from app.services.export_service import ExportService from app.config.export_columns import get_available_columns router = APIRouter() logger = logging.getLogger(__name__) # ============================================ # GET AVAILABLE COLUMNS # ============================================ @router.get("/{entity}/columns", response_model=AvailableColumnsResponse) async def get_available_export_columns( entity: str, current_user: User = Depends(get_current_user) ): """ Get list of available columns for export. Returns column metadata including: - key: Column identifier for API requests - label: Human-readable header - requires_join: Whether column needs database join (affects performance) Available entities: - tickets: Work orders/tickets - customers: Customer records - subscriptions: Active service subscriptions - field_agents: Field agent users - sales_orders: Sales orders Authorization: project_manager+, dispatcher+, admins """ # Check authorization if not ExportService.can_user_export(current_user): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You do not have permission to export data" ) # Get columns for entity columns_data = get_available_columns(entity) if not columns_data: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Entity '{entity}' not found or has no exportable columns" ) # Convert to schema columns = [ColumnMetadata(**col_data) for col_data in columns_data] return AvailableColumnsResponse( entity=entity, columns=columns ) # ============================================ # EXPORT DATA # ============================================ @router.post("/{entity}/export") async def export_entity_data( entity: str, request: ExportRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Export entity data to CSV/Excel. Request body: - columns: List of column keys to export (get from /columns endpoint) - filters: Optional filters to apply (project_id, status, etc.) - format: Export format ('csv' or 'excel') Response: File download Limits: - Max file size: 1MB - If file exceeds limit, reduce columns or add filters Example filters: - tickets: project_id, status, priority, ticket_type, region_id - customers: client_id, is_active, region_id - subscriptions: project_id, status, service_type, region_id - field_agents: contractor_id, is_active, status - sales_orders: project_id, status, is_processed, region_id Authorization: project_manager+, dispatcher+, admins """ try: # Ensure entity in request matches path parameter if request.entity != entity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Entity mismatch: path='{entity}', body='{request.entity}'" ) # Call export service file_buffer = ExportService.export_entity( db=db, entity=request.entity, columns=request.columns, filters=request.filters, format=request.format, current_user=current_user, calculated_fields=request.calculated_fields, aggregations=request.aggregations, group_by=request.group_by ) # Prepare filename timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S') filename = f"{entity}_export_{timestamp}.{request.format}" # Determine media type if request.format == 'csv': media_type = 'text/csv' elif request.format == 'excel': media_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' else: media_type = 'application/octet-stream' # Return file as streaming response return StreamingResponse( file_buffer, media_type=media_type, headers={ 'Content-Disposition': f'attachment; filename="{filename}"', 'Content-Length': str(file_buffer.getbuffer().nbytes) } ) except HTTPException: raise except Exception as e: logger.error(f"Export failed: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Export failed: {str(e)}" ) # ============================================ # EXPORT PRESETS (Future Feature) # ============================================ @router.get("/{entity}/presets") async def get_export_presets( entity: str, current_user: User = Depends(get_current_user) ): """ Get saved export presets for entity. Presets are pre-configured column selections for common export scenarios. Example presets for tickets: - basic: id, ticket_name, status, priority, customer_name - detailed: All fields - analytics: status, priority, created_at, completed_at, sla_violated Future feature: Currently returns empty list. Authorization: project_manager+, dispatcher+, admins """ # Check authorization if not ExportService.can_user_export(current_user): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You do not have permission to export data" ) # TODO: Implement saved presets (future feature) return { "entity": entity, "presets": [] }