263 lines
8.7 KiB
Python
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["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"
|
|
)
|