241 lines
7.8 KiB
Python
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"
|
|
)
|