# services/inventory/app/api/food_safety_alerts.py """ Food Safety Alerts API - ATOMIC CRUD operations on FoodSafetyAlert model """ 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 from shared.auth.access_control import require_user_role from shared.routing import RouteBuilder from app.core.database import get_db from app.services.food_safety_service import FoodSafetyService from app.schemas.food_safety import ( FoodSafetyAlertCreate, FoodSafetyAlertUpdate, FoodSafetyAlertResponse ) logger = structlog.get_logger() route_builder = RouteBuilder('inventory') router = APIRouter(tags=["food-safety-alerts"]) async def get_food_safety_service() -> FoodSafetyService: """Get food safety service instance""" return FoodSafetyService() @router.post( route_builder.build_base_route("food-safety/alerts"), response_model=FoodSafetyAlertResponse, status_code=status.HTTP_201_CREATED ) @require_user_role(['admin', 'owner', 'member']) async def create_food_safety_alert( alert_data: FoodSafetyAlertCreate, tenant_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), food_safety_service: FoodSafetyService = Depends(get_food_safety_service), db: AsyncSession = Depends(get_db) ): """Create a food safety alert""" try: alert_data.tenant_id = tenant_id alert = await food_safety_service.create_food_safety_alert( db, alert_data, user_id=UUID(current_user["sub"]) ) logger.info("Food safety alert created", alert_id=str(alert.id), alert_type=alert.alert_type) return alert except Exception as e: logger.error("Error creating food safety alert", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create food safety alert" ) @router.get( route_builder.build_base_route("food-safety/alerts"), response_model=List[FoodSafetyAlertResponse] ) async def get_food_safety_alerts( tenant_id: UUID = Path(...), alert_type: Optional[str] = Query(None, description="Filter by alert type"), severity: Optional[str] = Query(None, description="Filter by severity"), status_filter: Optional[str] = Query(None, description="Filter by status"), unresolved_only: bool = Query(True, description="Show only unresolved alerts"), skip: int = Query(0, ge=0, description="Number of alerts to skip"), limit: int = Query(100, ge=1, le=1000, description="Number of alerts to return"), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Get food safety alerts with filtering""" try: where_conditions = ["tenant_id = :tenant_id"] params = {"tenant_id": tenant_id} if alert_type: where_conditions.append("alert_type = :alert_type") params["alert_type"] = alert_type if severity: where_conditions.append("severity = :severity") params["severity"] = severity if status_filter: where_conditions.append("status = :status") params["status"] = status_filter elif unresolved_only: where_conditions.append("status NOT IN ('resolved', 'dismissed')") where_clause = " AND ".join(where_conditions) query = f""" SELECT * FROM food_safety_alerts WHERE {where_clause} ORDER BY created_at DESC LIMIT :limit OFFSET :skip """ params.update({"limit": limit, "skip": skip}) result = await db.execute(query, params) alerts = result.fetchall() return [ FoodSafetyAlertResponse(**dict(alert)) for alert in alerts ] except Exception as e: logger.error("Error getting food safety alerts", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve food safety alerts" ) @router.get( route_builder.build_resource_detail_route("food-safety/alerts", "alert_id"), response_model=FoodSafetyAlertResponse ) async def get_food_safety_alert( alert_id: UUID = Path(...), tenant_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Get specific food safety alert""" try: query = "SELECT * FROM food_safety_alerts WHERE id = :alert_id AND tenant_id = :tenant_id" result = await db.execute(query, {"alert_id": alert_id, "tenant_id": tenant_id}) alert = result.fetchone() if not alert: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Food safety alert not found" ) return FoodSafetyAlertResponse(**dict(alert)) except HTTPException: raise except Exception as e: logger.error("Error getting food safety alert", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve food safety alert" ) @router.put( route_builder.build_resource_detail_route("food-safety/alerts", "alert_id"), response_model=FoodSafetyAlertResponse ) @require_user_role(['admin', 'owner', 'member']) async def update_food_safety_alert( alert_data: FoodSafetyAlertUpdate, tenant_id: UUID = Path(...), alert_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Update a food safety alert""" try: alert_query = "SELECT * FROM food_safety_alerts WHERE id = :alert_id AND tenant_id = :tenant_id" result = await db.execute(alert_query, {"alert_id": alert_id, "tenant_id": tenant_id}) alert_record = result.fetchone() if not alert_record: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Food safety alert not found" ) update_fields = alert_data.dict(exclude_unset=True) if update_fields: set_clauses = [] params = {"alert_id": alert_id, "tenant_id": tenant_id} for field, value in update_fields.items(): set_clauses.append(f"{field} = :{field}") params[field] = value set_clauses.append("updated_at = NOW()") set_clauses.append("updated_by = :updated_by") params["updated_by"] = UUID(current_user["sub"]) update_query = f""" UPDATE food_safety_alerts SET {', '.join(set_clauses)} WHERE id = :alert_id AND tenant_id = :tenant_id """ await db.execute(update_query, params) await db.commit() result = await db.execute(alert_query, {"alert_id": alert_id, "tenant_id": tenant_id}) updated_alert = result.fetchone() logger.info("Food safety alert updated", alert_id=str(alert_id)) return FoodSafetyAlertResponse(**dict(updated_alert)) except HTTPException: raise except Exception as e: logger.error("Error updating food safety alert", alert_id=str(alert_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update food safety alert" ) @router.delete( route_builder.build_resource_detail_route("food-safety/alerts", "alert_id"), status_code=status.HTTP_204_NO_CONTENT ) @require_user_role(['admin', 'owner']) async def delete_food_safety_alert( alert_id: UUID = Path(...), tenant_id: UUID = Path(...), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Delete food safety alert""" try: query = "DELETE FROM food_safety_alerts WHERE id = :alert_id AND tenant_id = :tenant_id" result = await db.execute(query, {"alert_id": alert_id, "tenant_id": tenant_id}) if result.rowcount == 0: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Food safety alert not found" ) await db.commit() return None except HTTPException: raise except Exception as e: logger.error("Error deleting food safety alert", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete food safety alert" )