Files
bakery-ia/services/forecasting/app/api/forecasts.py
2025-10-06 15:27:01 +02:00

146 lines
4.9 KiB
Python

# services/forecasting/app/api/forecasts.py
"""
Forecasts API - Atomic CRUD operations on Forecast model
"""
import structlog
from fastapi import APIRouter, Depends, HTTPException, status, Query, Path
from typing import List, Optional
from datetime import date, datetime
import uuid
from app.services.forecasting_service import EnhancedForecastingService
from app.schemas.forecasts import ForecastResponse
from shared.database.base import create_database_manager
from app.core.config import settings
from shared.routing import RouteBuilder
route_builder = RouteBuilder('forecasting')
logger = structlog.get_logger()
router = APIRouter(tags=["forecasts"])
def get_enhanced_forecasting_service():
"""Dependency injection for EnhancedForecastingService"""
database_manager = create_database_manager(settings.DATABASE_URL, "forecasting-service")
return EnhancedForecastingService(database_manager)
@router.get(
route_builder.build_base_route("forecasts"),
response_model=List[ForecastResponse]
)
async def list_forecasts(
tenant_id: str = Path(..., description="Tenant ID"),
inventory_product_id: Optional[str] = Query(None, description="Filter by product ID"),
start_date: Optional[date] = Query(None, description="Start date filter"),
end_date: Optional[date] = Query(None, description="End date filter"),
limit: int = Query(50, ge=1, le=1000),
offset: int = Query(0, ge=0),
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
):
"""List forecasts with optional filters"""
try:
logger.info("Listing forecasts", tenant_id=tenant_id)
forecasts = await enhanced_forecasting_service.list_forecasts(
tenant_id=tenant_id,
inventory_product_id=inventory_product_id,
start_date=start_date,
end_date=end_date,
limit=limit,
offset=offset
)
return forecasts
except Exception as e:
logger.error("Failed to list forecasts", error=str(e), tenant_id=tenant_id)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve forecasts"
)
@router.get(
route_builder.build_resource_detail_route("forecasts", "forecast_id"),
response_model=ForecastResponse
)
async def get_forecast(
tenant_id: str = Path(..., description="Tenant ID"),
forecast_id: str = Path(..., description="Forecast ID"),
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
):
"""Get a specific forecast by ID"""
try:
logger.info("Getting forecast", tenant_id=tenant_id, forecast_id=forecast_id)
forecast = await enhanced_forecasting_service.get_forecast(
tenant_id=tenant_id,
forecast_id=uuid.UUID(forecast_id)
)
if not forecast:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Forecast not found"
)
return forecast
except HTTPException:
raise
except ValueError as e:
logger.error("Invalid forecast ID", error=str(e))
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid forecast ID format"
)
except Exception as e:
logger.error("Failed to get forecast", error=str(e), tenant_id=tenant_id)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve forecast"
)
@router.delete(
route_builder.build_resource_detail_route("forecasts", "forecast_id")
)
async def delete_forecast(
tenant_id: str = Path(..., description="Tenant ID"),
forecast_id: str = Path(..., description="Forecast ID"),
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
):
"""Delete a specific forecast"""
try:
logger.info("Deleting forecast", tenant_id=tenant_id, forecast_id=forecast_id)
success = await enhanced_forecasting_service.delete_forecast(
tenant_id=tenant_id,
forecast_id=uuid.UUID(forecast_id)
)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Forecast not found"
)
return {"message": "Forecast deleted successfully"}
except HTTPException:
raise
except ValueError as e:
logger.error("Invalid forecast ID", error=str(e))
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid forecast ID format"
)
except Exception as e:
logger.error("Failed to delete forecast", error=str(e), tenant_id=tenant_id)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to delete forecast"
)