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

241 lines
7.8 KiB
Python

# services/inventory/app/api/temperature_logs.py
"""
Temperature Logs API - ATOMIC CRUD operations on TemperatureLog model
"""
from datetime import datetime
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 (
TemperatureLogCreate,
TemperatureLogResponse,
BulkTemperatureLogCreate
)
logger = structlog.get_logger()
route_builder = RouteBuilder('inventory')
router = APIRouter(tags=["temperature-logs"])
async def get_food_safety_service() -> FoodSafetyService:
"""Get food safety service instance"""
return FoodSafetyService()
@router.post(
route_builder.build_base_route("food-safety/temperature"),
response_model=TemperatureLogResponse,
status_code=status.HTTP_201_CREATED
)
@require_user_role(['admin', 'owner', 'member'])
async def log_temperature(
temp_data: TemperatureLogCreate,
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)
):
"""Log a temperature reading"""
try:
temp_data.tenant_id = tenant_id
temp_log = await food_safety_service.log_temperature(
db,
temp_data,
user_id=UUID(current_user["user_id"])
)
logger.info("Temperature logged",
location=temp_data.storage_location,
temperature=temp_data.temperature_celsius)
return temp_log
except Exception as e:
logger.error("Error logging temperature", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to log temperature"
)
@router.post(
route_builder.build_base_route("food-safety/temperature/bulk"),
response_model=List[TemperatureLogResponse],
status_code=status.HTTP_201_CREATED
)
@require_user_role(['admin', 'owner', 'member'])
async def bulk_log_temperatures(
bulk_data: BulkTemperatureLogCreate,
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)
):
"""Bulk log temperature readings"""
try:
for reading in bulk_data.readings:
reading.tenant_id = tenant_id
temp_logs = await food_safety_service.bulk_log_temperatures(
db,
bulk_data.readings,
user_id=UUID(current_user["user_id"])
)
logger.info("Bulk temperature logging completed",
count=len(bulk_data.readings))
return temp_logs
except Exception as e:
logger.error("Error bulk logging temperatures", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to bulk log temperatures"
)
@router.get(
route_builder.build_base_route("food-safety/temperature"),
response_model=List[TemperatureLogResponse]
)
async def get_temperature_logs(
tenant_id: UUID = Path(...),
location: Optional[str] = Query(None, description="Filter by storage location"),
equipment_id: Optional[str] = Query(None, description="Filter by equipment ID"),
date_from: Optional[datetime] = Query(None, description="Start date for filtering"),
date_to: Optional[datetime] = Query(None, description="End date for filtering"),
violations_only: bool = Query(False, description="Show only temperature violations"),
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Number of records to return"),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Get temperature logs with filtering"""
try:
where_conditions = ["tenant_id = :tenant_id"]
params = {"tenant_id": tenant_id}
if location:
where_conditions.append("storage_location ILIKE :location")
params["location"] = f"%{location}%"
if equipment_id:
where_conditions.append("equipment_id = :equipment_id")
params["equipment_id"] = equipment_id
if date_from:
where_conditions.append("recorded_at >= :date_from")
params["date_from"] = date_from
if date_to:
where_conditions.append("recorded_at <= :date_to")
params["date_to"] = date_to
if violations_only:
where_conditions.append("is_within_range = false")
where_clause = " AND ".join(where_conditions)
query = f"""
SELECT * FROM temperature_logs
WHERE {where_clause}
ORDER BY recorded_at DESC
LIMIT :limit OFFSET :skip
"""
params.update({"limit": limit, "skip": skip})
result = await db.execute(query, params)
logs = result.fetchall()
return [
TemperatureLogResponse(**dict(log))
for log in logs
]
except Exception as e:
logger.error("Error getting temperature logs", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve temperature logs"
)
@router.get(
route_builder.build_resource_detail_route("food-safety/temperature", "log_id"),
response_model=TemperatureLogResponse
)
async def get_temperature_log(
log_id: UUID = Path(...),
tenant_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Get specific temperature log"""
try:
query = "SELECT * FROM temperature_logs WHERE id = :log_id AND tenant_id = :tenant_id"
result = await db.execute(query, {"log_id": log_id, "tenant_id": tenant_id})
log = result.fetchone()
if not log:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Temperature log not found"
)
return TemperatureLogResponse(**dict(log))
except HTTPException:
raise
except Exception as e:
logger.error("Error getting temperature log", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve temperature log"
)
@router.delete(
route_builder.build_resource_detail_route("food-safety/temperature", "log_id"),
status_code=status.HTTP_204_NO_CONTENT
)
@require_user_role(['admin', 'owner'])
async def delete_temperature_log(
log_id: UUID = Path(...),
tenant_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Delete temperature log"""
try:
query = "DELETE FROM temperature_logs WHERE id = :log_id AND tenant_id = :tenant_id"
result = await db.execute(query, {"log_id": log_id, "tenant_id": tenant_id})
if result.rowcount == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Temperature log not found"
)
await db.commit()
return None
except HTTPException:
raise
except Exception as e:
logger.error("Error deleting temperature log", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to delete temperature log"
)