Initial commit - production deployment
This commit is contained in:
262
services/inventory/app/api/food_safety_alerts.py
Normal file
262
services/inventory/app/api/food_safety_alerts.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# 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"
|
||||
)
|
||||
Reference in New Issue
Block a user