Files
bakery-ia/services/inventory/app/api/food_safety_alerts.py
2025-10-29 06:58:05 +01:00

263 lines
8.7 KiB
Python

# 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["user_id"])
)
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["user_id"])
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"
)