New alert service
This commit is contained in:
160
services/sales/app/api/batch.py
Normal file
160
services/sales/app/api/batch.py
Normal file
@@ -0,0 +1,160 @@
|
||||
# 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)}"
|
||||
)
|
||||
Reference in New Issue
Block a user