aaredb/backend/app/routers/logistics.py

162 lines
6.5 KiB
Python

from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
from ..models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel
from ..schemas import LogisticsEventCreate, SlotSchema, Dewar as DewarSchema
from ..database import get_db
import logging
from datetime import datetime, timedelta
router = APIRouter()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def calculate_time_until_refill(last_refill: Optional[datetime], refill_interval_hours: int = 24) -> int:
refill_interval = timedelta(hours=refill_interval_hours)
now = datetime.now()
if not last_refill:
return -1 # Sentinel value indicating no refill event recorded
time_until_next_refill = last_refill + refill_interval - now
return max(0, int(time_until_next_refill.total_seconds()))
@router.get("/slots", response_model=List[SlotSchema])
async def get_all_slots(db: Session = Depends(get_db)):
slots = db.query(SlotModel).all()
slots_with_refill_time = []
for slot in slots:
time_until_refill = None
if slot.dewar_unique_id:
logger.info(f"Fetching last refill event for dewar: {slot.dewar_unique_id}")
try:
last_refill_event = db.query(LogisticsEventModel) \
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
.filter(
DewarModel.unique_id == slot.dewar_unique_id,
LogisticsEventModel.event_type == "refill"
) \
.order_by(LogisticsEventModel.timestamp.desc()) \
.first()
except Exception as e:
logger.error(f"Error querying last refill event: {str(e)}")
last_refill_event = None
if last_refill_event:
last_refill = last_refill_event.timestamp
time_until_refill = calculate_time_until_refill(last_refill)
logger.info(f"Slot ID: {slot.id}, Last Refill: {last_refill}, Time Until Refill: {time_until_refill}")
else:
logger.warning(f"Slot ID: {slot.id} for dewar id '{slot.dewar_unique_id}' has no refill events.")
time_until_refill = -1
else:
logger.warning(f"Slot ID: {slot.id} has no dewar associated.")
time_until_refill = -1
# Ensure Dewar.name is optional if it may not exist in Dewar schema.
dewar_name = slot.dewar.name if slot.dewar and hasattr(slot.dewar, 'name') else None
slot_data = SlotSchema(
id=slot.id,
qr_code=slot.qr_code,
label=slot.label,
qr_base=slot.qr_base,
occupied=slot.occupied,
needs_refill=slot.needs_refill,
dewar_unique_id=slot.dewar_unique_id,
dewar_name=dewar_name, # Using the optional dewar_name
time_until_refill=(time_until_refill or -1)
)
slots_with_refill_time.append(slot_data)
return slots_with_refill_time
@router.get("/dewars", response_model=List[DewarSchema])
async def get_all_dewars(db: Session = Depends(get_db)):
dewars = db.query(DewarModel).all()
return dewars
@router.post("/dewar/refill", response_model=dict)
async def refill_dewar(qr_code: str, db: Session = Depends(get_db)):
logger.info(f"Refilling dewar with QR code: {qr_code}")
if not isinstance(qr_code, str):
raise HTTPException(status_code=400, detail="Invalid QR code format")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == qr_code.strip()).first()
if not dewar:
logger.error("Dewar not found")
raise HTTPException(status_code=404, detail="Dewar not found")
now = datetime.now()
new_event = LogisticsEventModel(
dewar_id=dewar.id,
slot_id=None,
event_type="refill",
timestamp=now,
action_details=f"{dewar.unique_id} refilled"
)
db.add(new_event)
db.commit()
time_until_refill_seconds = calculate_time_until_refill(now)
logger.info(f"Dewar refilled successfully with time_until_refill: {time_until_refill_seconds}")
return {"message": "Dewar refilled successfully", "time_until_refill": time_until_refill_seconds}
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
logger.info(f"Received request for dewar with unique_id: {unique_id}")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id.strip()).first()
if not dewar:
logger.warning(f"Dewar with unique_id '{unique_id}' not found.")
raise HTTPException(status_code=404, detail="Dewar not found")
logger.info(f"Returning dewar: {dewar}")
return dewar
@router.post("/dewar/scan", response_model=dict)
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
dewar_qr_code = event_data.dewar_qr_code
location_qr_code = event_data.location_qr_code
transaction_type = event_data.transaction_type
logger.info(f"Scanning dewar {dewar_qr_code} for slot {location_qr_code} with transaction type {transaction_type}")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
if transaction_type == 'incoming':
if not slot or slot.occupied:
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
logger.info(f"Associating dewar {dewar.unique_id} with slot {slot.qr_code}")
slot.dewar_unique_id = dewar.unique_id # Properly associate with the unique_id
slot.occupied = True
elif transaction_type == 'outgoing':
if not slot or not slot.occupied or slot.dewar_unique_id != dewar.unique_id:
raise HTTPException(status_code=400, detail="Slot not found or dewar not associated with slot")
logger.info(f"Disassociating dewar {dewar.unique_id} from slot {slot.qr_code}")
slot.dewar_unique_id = None # Remove the association
slot.occupied = False
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
db.commit()
return {"message": "Status updated successfully"}
def log_event(db: Session, dewar_id: int, slot_id: int, event_type: str):
new_event = LogisticsEventModel(dewar_id=dewar_id, slot_id=slot_id, event_type=event_type)
db.add(new_event)
db.commit()