Files
bakery-ia/services/inventory/app/api/batch.py

150 lines
5.1 KiB
Python
Raw Normal View History

2025-12-05 20:07:01 +01:00
# services/inventory/app/api/batch.py
"""
Inventory Batch API - Batch operations for enterprise dashboards
Phase 2 optimization: Eliminate N+1 query patterns by fetching inventory data
for multiple tenants in a single request.
"""
from fastapi import APIRouter, Depends, HTTPException, Body
from typing import List, Dict, Any
from uuid import UUID
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
import structlog
import asyncio
from app.core.database import get_db
from app.services.dashboard_service import DashboardService
from app.services.inventory_service import InventoryService
from shared.auth.decorators import get_current_user_dep
router = APIRouter(tags=["inventory-batch"])
logger = structlog.get_logger()
class InventorySummaryBatchRequest(BaseModel):
"""Request model for batch inventory summary"""
tenant_ids: List[str] = Field(..., description="List of tenant IDs", max_length=100)
class InventorySummary(BaseModel):
"""Inventory summary for a single tenant"""
tenant_id: str
total_value: float
out_of_stock_count: int
low_stock_count: int
adequate_stock_count: int
total_ingredients: int
@router.post("/batch/inventory-summary", response_model=Dict[str, InventorySummary])
async def get_inventory_summary_batch(
request: InventorySummaryBatchRequest = Body(...),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Get inventory summary for multiple tenants in a single request.
Optimized for enterprise dashboards to eliminate N+1 query patterns.
Fetches inventory data for all tenants in parallel.
Args:
request: Batch request with tenant IDs
Returns:
Dictionary mapping tenant_id -> inventory summary
Example:
POST /api/v1/inventory/batch/inventory-summary
{
"tenant_ids": ["tenant-1", "tenant-2", "tenant-3"]
}
Response:
{
"tenant-1": {"tenant_id": "tenant-1", "total_value": 15000, ...},
"tenant-2": {"tenant_id": "tenant-2", "total_value": 12000, ...},
"tenant-3": {"tenant_id": "tenant-3", "total_value": 18000, ...}
}
"""
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 inventory summaries",
tenant_count=len(request.tenant_ids)
)
async def fetch_tenant_inventory(tenant_id: str) -> tuple[str, InventorySummary]:
"""Fetch inventory summary for a single tenant"""
try:
tenant_uuid = UUID(tenant_id)
dashboard_service = DashboardService(
inventory_service=InventoryService(),
food_safety_service=None
)
overview = await dashboard_service.get_inventory_overview(db, tenant_uuid)
return tenant_id, InventorySummary(
tenant_id=tenant_id,
total_value=float(overview.get('total_value', 0)),
out_of_stock_count=int(overview.get('out_of_stock_count', 0)),
low_stock_count=int(overview.get('low_stock_count', 0)),
adequate_stock_count=int(overview.get('adequate_stock_count', 0)),
total_ingredients=int(overview.get('total_ingredients', 0))
)
except Exception as e:
logger.warning(
"Failed to fetch inventory for tenant in batch",
tenant_id=tenant_id,
error=str(e)
)
return tenant_id, InventorySummary(
tenant_id=tenant_id,
total_value=0.0,
out_of_stock_count=0,
low_stock_count=0,
adequate_stock_count=0,
total_ingredients=0
)
# Fetch all tenant inventory data in parallel
tasks = [fetch_tenant_inventory(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 inventory fetch", error=str(result))
continue
tenant_id, summary = result
result_dict[tenant_id] = summary
logger.info(
"Batch inventory 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 inventory summary", error=str(e), exc_info=True)
raise HTTPException(
status_code=500,
detail=f"Failed to fetch batch inventory summaries: {str(e)}"
)