https and ssl integration on the backend, frontend and started integration of logistics app as a separate frontend
This commit is contained in:
@ -1 +1,2 @@
|
||||
from .data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
|
||||
from .slots_data import slots
|
||||
|
@ -1,7 +1,8 @@
|
||||
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber
|
||||
from app.models import ContactPerson, Address, Dewar, Proposal, Shipment, Puck, Sample, DewarType, DewarSerialNumber, Slot
|
||||
from datetime import datetime
|
||||
import random
|
||||
import uuid
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
|
||||
dewar_types = [
|
||||
@ -44,8 +45,14 @@ return_addresses = [
|
||||
]
|
||||
|
||||
# Utilize a function to generate unique IDs
|
||||
def generate_unique_id():
|
||||
return str(uuid.uuid4())
|
||||
def generate_unique_id(length=16):
|
||||
base_string = f"{time.time()}{random.randint(0, 10 ** 6)}"
|
||||
hash_object = hashlib.sha256(base_string.encode())
|
||||
hash_digest = hash_object.hexdigest()
|
||||
short_unique_id = ''.join(random.choices(hash_digest, k=length))
|
||||
return short_unique_id
|
||||
|
||||
|
||||
|
||||
# Define dewars with unique IDs
|
||||
dewars = [
|
||||
@ -54,20 +61,20 @@ dewars = [
|
||||
dewar_serial_number_id=2, tracking_number='TRACK123',
|
||||
return_address_id=1, contact_person_id=1, status='Ready for Shipping',
|
||||
ready_date=datetime.strptime('2023-09-30', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
|
||||
returning_date=None, qrcode=generate_unique_id()
|
||||
returning_date=None, unique_id=generate_unique_id()
|
||||
),
|
||||
Dewar(
|
||||
id=2, dewar_name='Dewar Two', dewar_type_id=3,
|
||||
dewar_serial_number_id=1, tracking_number='TRACK124',
|
||||
return_address_id=2, contact_person_id=2, status='In Preparation',
|
||||
ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, qrcode=generate_unique_id()
|
||||
ready_date=None, shipping_date=None, arrival_date=None, returning_date=None, unique_id=generate_unique_id()
|
||||
),
|
||||
Dewar(
|
||||
id=3, dewar_name='Dewar Three', dewar_type_id=2,
|
||||
dewar_serial_number_id=3, tracking_number='TRACK125',
|
||||
return_address_id=1, contact_person_id=3, status='Not Shipped',
|
||||
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'), shipping_date=None, arrival_date=None,
|
||||
returning_date=None, qrcode=''
|
||||
returning_date=None, unique_id=None
|
||||
),
|
||||
Dewar(
|
||||
id=4, dewar_name='Dewar Four', dewar_type_id=2,
|
||||
@ -75,7 +82,7 @@ dewars = [
|
||||
return_address_id=1, contact_person_id=3, status='Delayed',
|
||||
ready_date=datetime.strptime('2024-01-01', '%Y-%m-%d'),
|
||||
shipping_date=datetime.strptime('2024-01-02', '%Y-%m-%d'),
|
||||
arrival_date=None, returning_date=None, qrcode=''
|
||||
arrival_date=None, returning_date=None, unique_id=None
|
||||
),
|
||||
Dewar(
|
||||
id=5, dewar_name='Dewar Five', dewar_type_id=1,
|
||||
@ -83,7 +90,7 @@ dewars = [
|
||||
return_address_id=1, contact_person_id=3, status='Returned',
|
||||
arrival_date=datetime.strptime('2024-01-03', '%Y-%m-%d'),
|
||||
returning_date=datetime.strptime('2024-01-07', '%Y-%m-%d'),
|
||||
qrcode=''
|
||||
unique_id=None
|
||||
),
|
||||
]
|
||||
|
||||
|
36
backend/app/data/slots_data.py
Normal file
36
backend/app/data/slots_data.py
Normal file
@ -0,0 +1,36 @@
|
||||
from datetime import datetime, timedelta
|
||||
from app.models import Slot
|
||||
|
||||
slotQRCodes = [
|
||||
"A1-X06SA", "A2-X06SA", "A3-X06SA", "A4-X06SA", "A5-X06SA",
|
||||
"B1-X06SA", "B2-X06SA", "B3-X06SA", "B4-X06SA", "B5-X06SA",
|
||||
"C1-X06SA", "C2-X06SA", "C3-X06SA", "C4-X06SA", "C5-X06SA",
|
||||
"D1-X06SA", "D2-X06SA", "D3-X06SA", "D4-X06SA", "D5-X06SA",
|
||||
"A1-X10SA", "A2-X10SA", "A3-X10SA", "A4-X10SA", "A5-X10SA",
|
||||
"B1-X10SA", "B2-X10SA", "B3-X10SA", "B4-X10SA", "B5-X10SA",
|
||||
"C1-X10SA", "C2-X10SA", "C3-X10SA", "C4-X10SA", "C5-X10SA",
|
||||
"D1-X10SA", "D2-X10SA", "D3-X10SA", "D4-X10SA", "D5-X10SA",
|
||||
"NB1", "NB2", "NB3", "NB4", "NB5", "NB6",
|
||||
"X10SA-beamline", "X06SA-beamline", "X06DA-beamline",
|
||||
"X10SA-outgoing", "X06-outgoing"
|
||||
]
|
||||
|
||||
def timedelta_to_str(td: timedelta) -> str:
|
||||
days, seconds = td.days, td.seconds
|
||||
hours = days * 24 + seconds // 3600
|
||||
minutes = (seconds % 3600) // 60
|
||||
return f'PT{hours}H{minutes}M'
|
||||
|
||||
slots = [
|
||||
Slot(
|
||||
id=str(i + 1), # Convert id to string to match your schema
|
||||
qr_code=qrcode,
|
||||
label=qrcode.split('-')[0],
|
||||
qr_base=qrcode.split('-')[1] if '-' in qrcode else '',
|
||||
occupied=False,
|
||||
needs_refill=False,
|
||||
last_refill=datetime.utcnow(),
|
||||
time_until_refill=timedelta_to_str(timedelta(hours=24)) # Serialize timedelta to ISO 8601 string
|
||||
)
|
||||
for i, qrcode in enumerate(slotQRCodes)
|
||||
]
|
@ -1,7 +1,9 @@
|
||||
# database.py
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app import models
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" # Use appropriate path or database URL
|
||||
|
||||
@ -10,7 +12,6 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Dependency
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
@ -19,22 +20,18 @@ def get_db():
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
# Import models inside function to avoid circular dependency
|
||||
from app import models
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
|
||||
def load_sample_data(session: Session):
|
||||
# Import models inside function to avoid circular dependency
|
||||
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers
|
||||
|
||||
from app import models
|
||||
from app.data import contacts, return_addresses, dewars, proposals, shipments, pucks, samples, dewar_types, serial_numbers, slots
|
||||
|
||||
# If any data already exists, skip seeding
|
||||
if session.query(models.ContactPerson).first():
|
||||
return
|
||||
|
||||
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers)
|
||||
session.commit()
|
||||
session.add_all(contacts + return_addresses + dewars + proposals + shipments + pucks + samples + dewar_types + serial_numbers + slots)
|
||||
session.commit()
|
@ -73,13 +73,13 @@ class Dewar(Base):
|
||||
shipping_date = Column(Date, nullable=True)
|
||||
arrival_date = Column(Date, nullable=True)
|
||||
returning_date = Column(Date, nullable=True)
|
||||
unique_id = Column(String(36), default=lambda: str(uuid.uuid4()), unique=True, index=True, nullable=True)
|
||||
qrcode = Column(String, nullable=True)
|
||||
unique_id = Column(String, unique=True, index=True, nullable=True)
|
||||
shipment_id = Column(Integer, ForeignKey("shipments.id"))
|
||||
return_address_id = Column(Integer, ForeignKey("addresses.id"))
|
||||
contact_person_id = Column(Integer, ForeignKey("contact_persons.id"))
|
||||
|
||||
shipment = relationship("Shipment", back_populates="dewars")
|
||||
events = relationship("LogisticsEvent", back_populates="dewar")
|
||||
return_address = relationship("Address")
|
||||
contact_person = relationship("ContactPerson")
|
||||
pucks = relationship("Puck", back_populates="dewar")
|
||||
@ -132,22 +132,30 @@ class Sample(Base):
|
||||
puck_id = Column(Integer, ForeignKey('pucks.id'))
|
||||
puck = relationship("Puck", back_populates="samples")
|
||||
|
||||
|
||||
class Slot(Base):
|
||||
__tablename__ = "slots"
|
||||
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
qr_code = Column(String, unique=True, index=True)
|
||||
label = Column(String)
|
||||
qr_base = Column(String, nullable=True)
|
||||
occupied = Column(Boolean, default=False)
|
||||
needs_refill = Column(Boolean, default=False)
|
||||
time_until_refill = Column(Interval, nullable=True)
|
||||
last_refill = Column(DateTime, default=datetime.utcnow)
|
||||
time_until_refill = Column(Integer) # store as total seconds
|
||||
|
||||
@property
|
||||
def calculate_time_until_refill(self):
|
||||
if self.last_refill and self.time_until_refill:
|
||||
return self.last_refill + self.time_until_refill - datetime.utcnow()
|
||||
return None
|
||||
|
||||
class LogisticsEvent(Base):
|
||||
__tablename__ = "logistics_events"
|
||||
__tablename__ = 'logistics_events'
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
dewar_id = Column(Integer, ForeignKey('dewars.id'), nullable=False)
|
||||
slot_id = Column(String, ForeignKey('slots.id'), nullable=True)
|
||||
event_type = Column(String, nullable=False)
|
||||
timestamp = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
dewar = relationship("Dewar")
|
||||
slot = relationship("Slot")
|
||||
dewar = relationship("Dewar", back_populates="events")
|
@ -1,6 +1,4 @@
|
||||
import os
|
||||
import tempfile # <-- Add this import
|
||||
import xml.etree.ElementTree as ET
|
||||
import os, tempfile, time, random, hashlib
|
||||
from fastapi import APIRouter, HTTPException, status, Depends, Response
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from typing import List
|
||||
@ -38,9 +36,12 @@ from app.crud import get_shipments, get_shipment_by_id # Import CRUD functions
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def generate_unique_id(db: Session) -> str:
|
||||
def generate_unique_id(db: Session, length: int = 16) -> str:
|
||||
while True:
|
||||
unique_id = str(uuid.uuid4())
|
||||
base_string = f"{time.time()}{random.randint(0, 10 ** 6)}"
|
||||
hash_object = hashlib.sha256(base_string.encode())
|
||||
hash_digest = hash_object.hexdigest()
|
||||
unique_id = ''.join(random.choices(hash_digest, k=length))
|
||||
existing_dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id).first()
|
||||
if not existing_dewar:
|
||||
break
|
||||
|
@ -1,107 +1,113 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
from app.models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel
|
||||
from app.schemas import LogisticsEventCreate, SlotCreate, Slot as SlotSchema, Dewar as DewarSchema
|
||||
from app.schemas import LogisticsEventCreate, Slot as SlotSchema, Dewar as DewarSchema
|
||||
from app.database import get_db
|
||||
from app.data import slots_data
|
||||
import logging
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("/dewars", response_model=List[DewarSchema])
|
||||
async def get_all_prouts(db: Session = Depends(get_db)):
|
||||
try:
|
||||
dewars = db.query(DewarModel).all()
|
||||
logging.info(f"Retrieved {len(dewars)} dewars from the database")
|
||||
return dewars
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
async def get_all_dewars(db: Session = Depends(get_db)):
|
||||
dewars = db.query(DewarModel).all()
|
||||
return dewars
|
||||
|
||||
@router.get("/dewar/{qr_code}", response_model=DewarSchema)
|
||||
async def get_dewar_by_qr_code(qr_code: str, db: Session = Depends(get_db)):
|
||||
logger.info(f"Received qr_code: {qr_code}")
|
||||
|
||||
trimmed_qr_code = qr_code.strip()
|
||||
logger.info(f"Trimmed qr_code after stripping: {trimmed_qr_code}")
|
||||
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
|
||||
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
|
||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id.strip()).first()
|
||||
if not dewar:
|
||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||
return dewar
|
||||
|
||||
try:
|
||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == trimmed_qr_code).first()
|
||||
logger.info(f"Query Result: {dewar}")
|
||||
|
||||
if dewar:
|
||||
return dewar
|
||||
else:
|
||||
logger.error(f"Dewar not found for unique_id: {trimmed_qr_code}")
|
||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
@router.post("/dewar/scan", response_model=LogisticsEventCreate)
|
||||
@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
|
||||
|
||||
try:
|
||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
|
||||
if not dewar:
|
||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
|
||||
if not dewar:
|
||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
||||
|
||||
if transaction_type == 'incoming':
|
||||
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
|
||||
if not slot or slot.occupied:
|
||||
raise HTTPException(status_code=404, detail="Slot not found or already occupied")
|
||||
slot.occupied = True
|
||||
log_event(db, dewar.id, slot.id, 'incoming')
|
||||
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")
|
||||
slot.occupied = True
|
||||
|
||||
elif transaction_type == 'beamline':
|
||||
log_event(db, dewar.id, None, 'beamline')
|
||||
elif transaction_type == 'outgoing':
|
||||
if not slot or not slot.occupied:
|
||||
raise HTTPException(status_code=400, detail="Slot not found or not occupied")
|
||||
slot.occupied = False
|
||||
|
||||
elif transaction_type == 'outgoing':
|
||||
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
|
||||
if not slot or not slot.occupied:
|
||||
raise HTTPException(status_code=404, detail="Slot not found or not occupied")
|
||||
slot.occupied = False
|
||||
log_event(db, dewar.id, slot.id, 'outgoing')
|
||||
# Log the event
|
||||
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
|
||||
|
||||
elif transaction_type == 'release':
|
||||
slot = db.query(SlotModel).filter(SlotModel.id == location_qr_code).first()
|
||||
if not slot or not slot.occupied:
|
||||
raise HTTPException(status_code=404, detail="Slot not found or not occupied")
|
||||
slot.occupied = False
|
||||
log_event(db, dewar.id, slot.id, 'released')
|
||||
db.commit()
|
||||
return {"message": "Status updated successfully"}
|
||||
|
||||
db.commit()
|
||||
logger.info(f"Status updated successfully for Dewar ID: {dewar.id}")
|
||||
return {"message": "Status updated successfully"}
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
def log_event(db: Session, dewar_id: int, slot_id: int, event_type: str):
|
||||
new_event = LogisticsEventModel(dewar_id=dewar_id if dewar_id else None, slot_id=slot_id, event_type=event_type)
|
||||
def log_event(db: Session, dewar_id: int, slot_id: str, event_type: str):
|
||||
new_event = LogisticsEventModel(dewar_id=dewar_id, slot_id=slot_id, event_type=event_type)
|
||||
db.add(new_event)
|
||||
db.commit()
|
||||
logger.info(f"Logged event {event_type} for Dewar ID: {dewar_id}")
|
||||
|
||||
@router.get("/slots/refill-status", response_model=List[SlotSchema])
|
||||
|
||||
# Convert SQLAlchemy model to dictionary (if necessary)
|
||||
def slot_to_dict(slot: SlotModel) -> dict:
|
||||
return {
|
||||
"id": slot.id,
|
||||
"qr_code": slot.qr_code,
|
||||
"label": slot.label,
|
||||
"qr_base": slot.qr_base,
|
||||
"occupied": slot.occupied,
|
||||
"needs_refill": slot.needs_refill,
|
||||
"last_refill": slot.last_refill.isoformat(),
|
||||
"time_until_refill": str(slot.time_until_refill) if slot.time_until_refill else None # Ensure correct format
|
||||
}
|
||||
|
||||
@router.get("/slots", response_model=List[dict])
|
||||
def read_slots(db: Session = Depends(get_db)):
|
||||
return [slot_to_dict(slot) for slot in db.query(SlotModel).all()]
|
||||
|
||||
|
||||
@router.get("/dewars/refill-status", response_model=List[DewarSchema])
|
||||
async def refill_status(db: Session = Depends(get_db)):
|
||||
slots_needing_refill = db.query(SlotModel).filter(SlotModel.needs_refill == True).all()
|
||||
dewars = db.query(DewarModel).all()
|
||||
result = []
|
||||
current_time = datetime.utcnow()
|
||||
|
||||
for slot in slots_needing_refill:
|
||||
time_until_next_refill = slot.last_refill + timedelta(hours=24) - current_time
|
||||
result.append({
|
||||
'slot_id': slot.id,
|
||||
'needs_refill': slot.needs_refill,
|
||||
'time_until_refill': str(time_until_next_refill)
|
||||
})
|
||||
for dewar in dewars:
|
||||
last_refill_event = (
|
||||
db.query(LogisticsEventModel)
|
||||
.filter(
|
||||
LogisticsEventModel.dewar_id == dewar.id,
|
||||
LogisticsEventModel.event_type == 'refill'
|
||||
)
|
||||
.order_by(LogisticsEventModel.timestamp.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
time_until_refill = None
|
||||
if last_refill_event:
|
||||
time_until_refill = last_refill_event.timestamp + timedelta(hours=24) - current_time
|
||||
|
||||
dewar_data = DewarSchema(
|
||||
id=dewar.id,
|
||||
dewar_name=dewar.dewar_name,
|
||||
unique_id=dewar.unique_id,
|
||||
time_until_refill=str(time_until_refill) if time_until_refill else None # Ensure correct format
|
||||
)
|
||||
result.append(dewar_data)
|
||||
|
||||
return result
|
@ -184,7 +184,6 @@ class DewarBase(BaseModel):
|
||||
shipping_date: Optional[date]
|
||||
arrival_date: Optional[date]
|
||||
returning_date: Optional[date]
|
||||
qrcode: str
|
||||
contact_person_id: Optional[int]
|
||||
return_address_id: Optional[int]
|
||||
pucks: List[PuckCreate] = []
|
||||
@ -216,7 +215,6 @@ class DewarUpdate(BaseModel):
|
||||
shipping_date: Optional[date] = None
|
||||
arrival_date: Optional[date] = None
|
||||
returning_date: Optional[date] = None
|
||||
qrcode: Optional[str] = None
|
||||
contact_person_id: Optional[int] = None
|
||||
address_id: Optional[int] = None
|
||||
|
||||
@ -273,20 +271,20 @@ class ShipmentCreate(BaseModel):
|
||||
class UpdateShipmentComments(BaseModel):
|
||||
comments: str
|
||||
|
||||
|
||||
class LogisticsEventCreate(BaseModel):
|
||||
dewar_qr_code: str
|
||||
location_qr_code: str
|
||||
transaction_type: str
|
||||
|
||||
class SlotCreate(BaseModel):
|
||||
id: int
|
||||
class Slot(BaseModel):
|
||||
id: str
|
||||
qr_code: str
|
||||
label: str
|
||||
qr_base: str
|
||||
occupied: bool
|
||||
needs_refill: bool
|
||||
last_refill: datetime
|
||||
occupied: bool
|
||||
|
||||
class Slot(BaseModel):
|
||||
slot_id: int
|
||||
needs_refill: bool
|
||||
time_until_refill: str
|
||||
|
||||
class Config:
|
||||
|
Reference in New Issue
Block a user