# 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" )