Add more services
This commit is contained in:
494
services/inventory/app/api/dashboard.py
Normal file
494
services/inventory/app/api/dashboard.py
Normal file
@@ -0,0 +1,494 @@
|
||||
# ================================================================
|
||||
# services/inventory/app/api/dashboard.py
|
||||
# ================================================================
|
||||
"""
|
||||
Dashboard API endpoints for Inventory Service
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import structlog
|
||||
|
||||
from shared.auth.decorators import get_current_user_dep, get_current_tenant_id_dep
|
||||
from app.core.database import get_db
|
||||
from app.services.inventory_service import InventoryService
|
||||
from app.services.food_safety_service import FoodSafetyService
|
||||
from app.services.dashboard_service import DashboardService
|
||||
from app.schemas.dashboard import (
|
||||
InventoryDashboardSummary,
|
||||
FoodSafetyDashboard,
|
||||
BusinessModelInsights,
|
||||
InventoryAnalytics,
|
||||
DashboardFilter,
|
||||
AlertsFilter,
|
||||
StockStatusSummary,
|
||||
AlertSummary,
|
||||
RecentActivity
|
||||
)
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
|
||||
|
||||
|
||||
# ===== Dependency Injection =====
|
||||
|
||||
async def get_dashboard_service(db: AsyncSession = Depends(get_db)) -> DashboardService:
|
||||
"""Get dashboard service with dependencies"""
|
||||
return DashboardService(
|
||||
inventory_service=InventoryService(),
|
||||
food_safety_service=FoodSafetyService()
|
||||
)
|
||||
|
||||
|
||||
# ===== Main Dashboard Endpoints =====
|
||||
|
||||
@router.get("/tenants/{tenant_id}/summary", response_model=InventoryDashboardSummary)
|
||||
async def get_inventory_dashboard_summary(
|
||||
tenant_id: UUID = Path(...),
|
||||
filters: Optional[DashboardFilter] = None,
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get comprehensive inventory dashboard summary"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
summary = await dashboard_service.get_inventory_dashboard_summary(db, tenant_id, filters)
|
||||
|
||||
logger.info("Dashboard summary retrieved",
|
||||
tenant_id=str(tenant_id),
|
||||
total_ingredients=summary.total_ingredients)
|
||||
|
||||
return summary
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting dashboard summary",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve dashboard summary"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/food-safety", response_model=FoodSafetyDashboard)
|
||||
async def get_food_safety_dashboard(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
food_safety_service: FoodSafetyService = Depends(lambda: FoodSafetyService()),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get food safety dashboard data"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
dashboard = await food_safety_service.get_food_safety_dashboard(db, tenant_id)
|
||||
|
||||
logger.info("Food safety dashboard retrieved",
|
||||
tenant_id=str(tenant_id),
|
||||
compliance_percentage=dashboard.compliance_percentage)
|
||||
|
||||
return dashboard
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting food safety dashboard",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve food safety dashboard"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/analytics", response_model=InventoryAnalytics)
|
||||
async def get_inventory_analytics(
|
||||
tenant_id: UUID = Path(...),
|
||||
days_back: int = Query(30, ge=1, le=365, description="Number of days to analyze"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get advanced inventory analytics"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
analytics = await dashboard_service.get_inventory_analytics(db, tenant_id, days_back)
|
||||
|
||||
logger.info("Inventory analytics retrieved",
|
||||
tenant_id=str(tenant_id),
|
||||
days_analyzed=days_back)
|
||||
|
||||
return analytics
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting inventory analytics",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve inventory analytics"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/business-model", response_model=BusinessModelInsights)
|
||||
async def get_business_model_insights(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get business model insights based on inventory patterns"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
insights = await dashboard_service.get_business_model_insights(db, tenant_id)
|
||||
|
||||
logger.info("Business model insights retrieved",
|
||||
tenant_id=str(tenant_id),
|
||||
detected_model=insights.detected_model)
|
||||
|
||||
return insights
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting business model insights",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve business model insights"
|
||||
)
|
||||
|
||||
|
||||
# ===== Detailed Dashboard Data Endpoints =====
|
||||
|
||||
@router.get("/tenants/{tenant_id}/stock-status", response_model=List[StockStatusSummary])
|
||||
async def get_stock_status_by_category(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get stock status breakdown by category"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
stock_status = await dashboard_service.get_stock_status_by_category(db, tenant_id)
|
||||
|
||||
return stock_status
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting stock status by category",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve stock status by category"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/alerts-summary", response_model=List[AlertSummary])
|
||||
async def get_alerts_summary(
|
||||
tenant_id: UUID = Path(...),
|
||||
filters: Optional[AlertsFilter] = None,
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get alerts summary by type and severity"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
alerts_summary = await dashboard_service.get_alerts_summary(db, tenant_id, filters)
|
||||
|
||||
return alerts_summary
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting alerts summary",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve alerts summary"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/recent-activity", response_model=List[RecentActivity])
|
||||
async def get_recent_activity(
|
||||
tenant_id: UUID = Path(...),
|
||||
limit: int = Query(20, ge=1, le=100, description="Number of activities to return"),
|
||||
activity_types: Optional[List[str]] = Query(None, description="Filter by activity types"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get recent inventory activity"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
activities = await dashboard_service.get_recent_activity(
|
||||
db, tenant_id, limit, activity_types
|
||||
)
|
||||
|
||||
return activities
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting recent activity",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve recent activity"
|
||||
)
|
||||
|
||||
|
||||
# ===== Real-time Data Endpoints =====
|
||||
|
||||
@router.get("/tenants/{tenant_id}/live-metrics")
|
||||
async def get_live_metrics(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get real-time inventory metrics"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
metrics = await dashboard_service.get_live_metrics(db, tenant_id)
|
||||
|
||||
return {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"metrics": metrics,
|
||||
"cache_ttl": 60 # Seconds
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting live metrics",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve live metrics"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tenants/{tenant_id}/temperature-status")
|
||||
async def get_temperature_monitoring_status(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
food_safety_service: FoodSafetyService = Depends(lambda: FoodSafetyService()),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get current temperature monitoring status"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
temp_status = await food_safety_service.get_temperature_monitoring_status(db, tenant_id)
|
||||
|
||||
return {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"temperature_monitoring": temp_status
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting temperature status",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve temperature monitoring status"
|
||||
)
|
||||
|
||||
|
||||
# ===== Dashboard Configuration Endpoints =====
|
||||
|
||||
@router.get("/tenants/{tenant_id}/config")
|
||||
async def get_dashboard_config(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep)
|
||||
):
|
||||
"""Get dashboard configuration and settings"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
config = {
|
||||
"refresh_intervals": {
|
||||
"dashboard_cache_ttl": settings.DASHBOARD_CACHE_TTL,
|
||||
"alerts_refresh_interval": settings.ALERTS_REFRESH_INTERVAL,
|
||||
"temperature_log_interval": settings.TEMPERATURE_LOG_INTERVAL
|
||||
},
|
||||
"features": {
|
||||
"food_safety_enabled": settings.FOOD_SAFETY_ENABLED,
|
||||
"temperature_monitoring_enabled": settings.TEMPERATURE_MONITORING_ENABLED,
|
||||
"business_model_detection": settings.ENABLE_BUSINESS_MODEL_DETECTION
|
||||
},
|
||||
"thresholds": {
|
||||
"low_stock_default": settings.DEFAULT_LOW_STOCK_THRESHOLD,
|
||||
"reorder_point_default": settings.DEFAULT_REORDER_POINT,
|
||||
"expiration_warning_days": settings.EXPIRATION_WARNING_DAYS,
|
||||
"critical_expiration_hours": settings.CRITICAL_EXPIRATION_HOURS
|
||||
},
|
||||
"business_model_thresholds": {
|
||||
"central_bakery_ingredients": settings.CENTRAL_BAKERY_THRESHOLD_INGREDIENTS,
|
||||
"individual_bakery_ingredients": settings.INDIVIDUAL_BAKERY_THRESHOLD_INGREDIENTS
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting dashboard config",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve dashboard configuration"
|
||||
)
|
||||
|
||||
|
||||
# ===== Export and Reporting Endpoints =====
|
||||
|
||||
@router.get("/tenants/{tenant_id}/export/summary")
|
||||
async def export_dashboard_summary(
|
||||
tenant_id: UUID = Path(...),
|
||||
format: str = Query("json", description="Export format: json, csv, excel"),
|
||||
date_from: Optional[datetime] = Query(None, description="Start date for data export"),
|
||||
date_to: Optional[datetime] = Query(None, description="End date for data export"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
dashboard_service: DashboardService = Depends(get_dashboard_service),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Export dashboard summary data"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
if format.lower() not in ["json", "csv", "excel"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Unsupported export format. Use: json, csv, excel"
|
||||
)
|
||||
|
||||
export_data = await dashboard_service.export_dashboard_data(
|
||||
db, tenant_id, format, date_from, date_to
|
||||
)
|
||||
|
||||
logger.info("Dashboard data exported",
|
||||
tenant_id=str(tenant_id),
|
||||
format=format)
|
||||
|
||||
return export_data
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Error exporting dashboard data",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to export dashboard data"
|
||||
)
|
||||
|
||||
|
||||
# ===== Health and Status Endpoints =====
|
||||
|
||||
@router.get("/tenants/{tenant_id}/health")
|
||||
async def get_dashboard_health(
|
||||
tenant_id: UUID = Path(...),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: dict = Depends(get_current_user_dep)
|
||||
):
|
||||
"""Get dashboard service health status"""
|
||||
try:
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to tenant data"
|
||||
)
|
||||
|
||||
return {
|
||||
"service": "inventory-dashboard",
|
||||
"status": "healthy",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"tenant_id": str(tenant_id),
|
||||
"features": {
|
||||
"food_safety": "enabled",
|
||||
"temperature_monitoring": "enabled",
|
||||
"business_model_detection": "enabled",
|
||||
"real_time_alerts": "enabled"
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error getting dashboard health",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get dashboard health status"
|
||||
)
|
||||
Reference in New Issue
Block a user