# ================================================================ # services/data/app/api/sales.py - FIXED VERSION # ================================================================ """Sales data API endpoints with improved error handling""" from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Query, Response from fastapi.responses import StreamingResponse from sqlalchemy.ext.asyncio import AsyncSession from typing import List, Optional import uuid from datetime import datetime import base64 import structlog from app.core.database import get_db from app.core.auth import get_current_user, AuthInfo from app.services.sales_service import SalesService from app.services.data_import_service import DataImportService from app.services.messaging import data_publisher from app.schemas.sales import ( SalesDataCreate, SalesDataResponse, SalesDataQuery, SalesDataImport, SalesImportResult, SalesValidationResult, SalesExportRequest ) router = APIRouter() logger = structlog.get_logger() @router.post("/", response_model=SalesDataResponse) async def create_sales_record( sales_data: SalesDataCreate, db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Create a new sales record""" try: logger.debug("API: Creating sales record", product=sales_data.product_name, quantity=sales_data.quantity_sold) record = await SalesService.create_sales_record(sales_data, db) # Publish event (with error handling) try: await data_publisher.publish_sales_created({ "tenant_id": str(sales_data.tenant_id), "product_name": sales_data.product_name, "quantity_sold": sales_data.quantity_sold, "revenue": sales_data.revenue, "source": sales_data.source, "timestamp": datetime.utcnow().isoformat() }) except Exception as pub_error: logger.warning("Failed to publish sales created event", error=str(pub_error)) # Continue processing - event publishing failure shouldn't break the API logger.debug("Successfully created sales record", record_id=record.id) return record except Exception as e: logger.error("Failed to create sales record", error=str(e)) import traceback logger.error("Sales creation traceback", traceback=traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to create sales record: {str(e)}") @router.post("/query", response_model=List[SalesDataResponse]) async def get_sales_data( query: SalesDataQuery, db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Get sales data by query parameters""" try: logger.debug("API: Querying sales data", tenant_id=query.tenant_id) records = await SalesService.get_sales_data(query, db) logger.debug("Successfully retrieved sales data", count=len(records)) return records except Exception as e: logger.error("Failed to query sales data", error=str(e)) raise HTTPException(status_code=500, detail=f"Failed to query sales data: {str(e)}") @router.post("/import", response_model=SalesImportResult) async def import_sales_data( tenant_id: str = Form(...), file_format: str = Form(...), file: UploadFile = File(...), db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Import sales data from file""" try: logger.debug("API: Importing sales data", tenant_id=tenant_id, format=file_format, filename=file.filename) # Read file content content = await file.read() file_content = content.decode('utf-8') # Process import result = await DataImportService.process_upload( tenant_id, file_content, file_format, db ) if result["success"]: # Publish event (with error handling) try: await data_publisher.publish_data_imported({ "tenant_id": tenant_id, "type": "bulk_import", "format": file_format, "filename": file.filename, "records_created": result["records_created"], "timestamp": datetime.utcnow().isoformat() }) except Exception as pub_error: logger.warning("Failed to publish data imported event", error=str(pub_error)) # Continue processing logger.debug("Import completed", success=result["success"], records_created=result.get("records_created", 0)) return result except Exception as e: logger.error("Failed to import sales data", error=str(e)) import traceback logger.error("Sales import traceback", traceback=traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to import sales data: {str(e)}") @router.post("/import/json", response_model=SalesImportResult) async def import_sales_json( import_data: SalesDataImport, db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Import sales data from JSON""" try: logger.debug("API: Importing JSON sales data", tenant_id=import_data.tenant_id) result = await DataImportService.process_upload( str(import_data.tenant_id), import_data.data, import_data.data_format, db ) if result["success"]: # Publish event (with error handling) try: await data_publisher.publish_data_imported({ "tenant_id": str(import_data.tenant_id), "type": "json_import", "records_created": result["records_created"], "timestamp": datetime.utcnow().isoformat() }) except Exception as pub_error: logger.warning("Failed to publish JSON import event", error=str(pub_error)) # Continue processing logger.debug("JSON import completed", success=result["success"], records_created=result.get("records_created", 0)) return result except Exception as e: logger.error("Failed to import JSON sales data", error=str(e)) import traceback logger.error("JSON import traceback", traceback=traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to import JSON sales data: {str(e)}") @router.post("/import/validate", response_model=SalesValidationResult) async def validate_import_data( import_data: SalesDataImport, current_user: AuthInfo = Depends(get_current_user) ): """Validate import data before processing""" try: logger.debug("API: Validating import data", tenant_id=import_data.tenant_id) validation = await DataImportService.validate_import_data( import_data.model_dump() ) logger.debug("Validation completed", is_valid=validation.get("is_valid", False)) return validation except Exception as e: logger.error("Failed to validate import data", error=str(e)) raise HTTPException(status_code=500, detail=f"Failed to validate import data: {str(e)}") @router.get("/import/template/{format_type}") async def get_import_template( format_type: str, current_user: AuthInfo = Depends(get_current_user) ): """Get import template for specified format""" try: logger.debug("API: Getting import template", format=format_type) template = await DataImportService.get_import_template(format_type) if "error" in template: logger.warning("Template generation error", error=template["error"]) raise HTTPException(status_code=400, detail=template["error"]) logger.debug("Template generated successfully", format=format_type) if format_type.lower() == "csv": return Response( content=template["template"], media_type="text/csv", headers={"Content-Disposition": f"attachment; filename={template['filename']}"} ) elif format_type.lower() == "json": return Response( content=template["template"], media_type="application/json", headers={"Content-Disposition": f"attachment; filename={template['filename']}"} ) elif format_type.lower() in ["excel", "xlsx"]: return Response( content=base64.b64decode(template["template"]), media_type=template["content_type"], headers={"Content-Disposition": f"attachment; filename={template['filename']}"} ) else: return template except HTTPException: raise except Exception as e: logger.error("Failed to generate import template", error=str(e)) import traceback logger.error("Template generation traceback", traceback=traceback.format_exc()) raise HTTPException(status_code=500, detail=f"Failed to generate template: {str(e)}") @router.get("/analytics/{tenant_id}") async def get_sales_analytics( tenant_id: str, start_date: Optional[datetime] = Query(None, description="Start date"), end_date: Optional[datetime] = Query(None, description="End date"), db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Get sales analytics for tenant""" try: logger.debug("API: Getting sales analytics", tenant_id=tenant_id) analytics = await SalesService.get_sales_analytics( tenant_id, start_date, end_date, db ) logger.debug("Analytics generated successfully", tenant_id=tenant_id) return analytics except Exception as e: logger.error("Failed to generate sales analytics", error=str(e)) raise HTTPException(status_code=500, detail=f"Failed to generate analytics: {str(e)}") @router.post("/export/{tenant_id}") async def export_sales_data( tenant_id: str, export_format: str = Query("csv", description="Export format: csv, excel, json"), start_date: Optional[datetime] = Query(None, description="Start date"), end_date: Optional[datetime] = Query(None, description="End date"), products: Optional[List[str]] = Query(None, description="Filter by products"), db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Export sales data in specified format""" try: logger.debug("API: Exporting sales data", tenant_id=tenant_id, format=export_format) export_result = await SalesService.export_sales_data( tenant_id, export_format, start_date, end_date, products, db ) if not export_result: raise HTTPException(status_code=404, detail="No data found for export") logger.debug("Export completed successfully", tenant_id=tenant_id, format=export_format) return StreamingResponse( iter([export_result["content"]]), media_type=export_result["media_type"], headers={"Content-Disposition": f"attachment; filename={export_result['filename']}"} ) except HTTPException: raise except Exception as e: logger.error("Failed to export sales data", error=str(e)) raise HTTPException(status_code=500, detail=f"Failed to export sales data: {str(e)}") @router.delete("/{record_id}") async def delete_sales_record( record_id: str, db: AsyncSession = Depends(get_db), current_user: AuthInfo = Depends(get_current_user) ): """Delete a sales record""" try: logger.debug("API: Deleting sales record", record_id=record_id) success = await SalesService.delete_sales_record(record_id, db) if not success: raise HTTPException(status_code=404, detail="Sales record not found") logger.debug("Sales record deleted successfully", record_id=record_id) return {"status": "success", "message": "Sales record deleted successfully"} except HTTPException: raise except Exception as e: logger.error("Failed to delete sales record", error=str(e)) raise HTTPException(status_code=500, detail=f"Failed to delete sales record: {str(e)}")