# services/sales/app/api/batch.py """ Sales Batch API - Batch operations for enterprise dashboards Phase 2 optimization: Eliminate N+1 query patterns by fetching data for multiple tenants in a single request. """ from fastapi import APIRouter, Depends, HTTPException, Body, Path from typing import List, Dict, Any from datetime import date from uuid import UUID from pydantic import BaseModel, Field import structlog import asyncio from app.services.sales_service import SalesService from shared.auth.decorators import get_current_user_dep from shared.routing import RouteBuilder from shared.auth.access_control import require_user_role route_builder = RouteBuilder('sales') router = APIRouter(tags=["sales-batch"]) logger = structlog.get_logger() def get_sales_service(): """Dependency injection for SalesService""" return SalesService() class SalesSummaryBatchRequest(BaseModel): """Request model for batch sales summary""" tenant_ids: List[str] = Field(..., description="List of tenant IDs", max_length=100) start_date: date = Field(..., description="Start date for sales period") end_date: date = Field(..., description="End date for sales period") class SalesSummary(BaseModel): """Sales summary for a single tenant""" tenant_id: str total_revenue: float total_orders: int average_order_value: float period_start: str period_end: str @router.post("/api/v1/batch/sales-summary", response_model=Dict[str, SalesSummary]) async def get_sales_summary_batch( request: SalesSummaryBatchRequest = Body(...), current_user: Dict[str, Any] = Depends(get_current_user_dep), sales_service: SalesService = Depends(get_sales_service) ): """ Get sales summary for multiple tenants in a single request. Optimized for enterprise dashboards to eliminate N+1 query patterns. Fetches sales data for all tenants in parallel. Args: request: Batch request with tenant IDs and date range Returns: Dictionary mapping tenant_id -> sales summary Example: POST /api/v1/sales/batch/sales-summary { "tenant_ids": ["tenant-1", "tenant-2", "tenant-3"], "start_date": "2025-01-01", "end_date": "2025-01-31" } Response: { "tenant-1": {"tenant_id": "tenant-1", "total_revenue": 50000, ...}, "tenant-2": {"tenant_id": "tenant-2", "total_revenue": 45000", ...}, "tenant-3": {"tenant_id": "tenant-3", "total_revenue": 52000, ...} } """ try: if len(request.tenant_ids) > 100: raise HTTPException( status_code=400, detail="Maximum 100 tenant IDs allowed per batch request" ) if not request.tenant_ids: return {} logger.info( "Batch fetching sales summaries", tenant_count=len(request.tenant_ids), start_date=str(request.start_date), end_date=str(request.end_date) ) async def fetch_tenant_sales(tenant_id: str) -> tuple[str, SalesSummary]: """Fetch sales summary for a single tenant""" try: tenant_uuid = UUID(tenant_id) summary = await sales_service.get_sales_analytics( tenant_uuid, request.start_date, request.end_date ) return tenant_id, SalesSummary( tenant_id=tenant_id, total_revenue=float(summary.get('total_revenue', 0)), total_orders=int(summary.get('total_orders', 0)), average_order_value=float(summary.get('average_order_value', 0)), period_start=str(request.start_date), period_end=str(request.end_date) ) except Exception as e: logger.warning( "Failed to fetch sales for tenant in batch", tenant_id=tenant_id, error=str(e) ) return tenant_id, SalesSummary( tenant_id=tenant_id, total_revenue=0.0, total_orders=0, average_order_value=0.0, period_start=str(request.start_date), period_end=str(request.end_date) ) # Fetch all tenant sales in parallel tasks = [fetch_tenant_sales(tid) for tid in request.tenant_ids] results = await asyncio.gather(*tasks, return_exceptions=True) # Build result dictionary result_dict = {} for result in results: if isinstance(result, Exception): logger.error("Exception in batch sales fetch", error=str(result)) continue tenant_id, summary = result result_dict[tenant_id] = summary logger.info( "Batch sales summaries retrieved", requested_count=len(request.tenant_ids), successful_count=len(result_dict) ) return result_dict except HTTPException: raise except Exception as e: logger.error("Error in batch sales summary", error=str(e), exc_info=True) raise HTTPException( status_code=500, detail=f"Failed to fetch batch sales summaries: {str(e)}" )