
Updated Dewar API methods to use protected endpoints for enhanced security and consistency. Added `pgroups` handling in various frontend components and modified the LogisticsView contact field for clarity. Simplified backend router imports for better readability.
418 lines
14 KiB
Python
418 lines
14 KiB
Python
from fastapi import APIRouter, HTTPException, status, Query, Depends
|
|
from sqlalchemy.orm import Session
|
|
from typing import List
|
|
from datetime import date
|
|
import json
|
|
import logging
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from app.models import (
|
|
Shipment as ShipmentModel,
|
|
Contact as ContactModel,
|
|
Address as AddressModel,
|
|
Proposal as ProposalModel,
|
|
Dewar as DewarModel,
|
|
LogisticsEvent,
|
|
SampleEvent,
|
|
PuckEvent,
|
|
)
|
|
from app.schemas import (
|
|
ShipmentCreate,
|
|
UpdateShipmentComments,
|
|
Shipment as ShipmentSchema,
|
|
Contact as ContactSchema,
|
|
Sample as SampleSchema,
|
|
loginData,
|
|
Dewar,
|
|
)
|
|
from app.database import get_db
|
|
from app.crud import get_shipment_by_id, get_shipments
|
|
from app.routers.auth import get_current_user
|
|
|
|
shipment_router = APIRouter()
|
|
|
|
|
|
def default_serializer(obj):
|
|
if isinstance(obj, date):
|
|
return obj.isoformat()
|
|
raise TypeError(f"Type {type(obj)} not serializable")
|
|
|
|
|
|
@shipment_router.get("", response_model=List[ShipmentSchema])
|
|
async def fetch_shipments(
|
|
active_pgroup: str = Query(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: loginData = Depends(get_current_user),
|
|
):
|
|
# Validate that the active_pgroup belongs to the user
|
|
if active_pgroup not in current_user.pgroups:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid pgroup provided.",
|
|
)
|
|
|
|
# Query shipments matching the active_pgroup
|
|
shipments = (
|
|
db.query(ShipmentModel)
|
|
.filter(ShipmentModel.pgroups.like(f"%{active_pgroup}%"))
|
|
.all()
|
|
)
|
|
|
|
return shipments
|
|
|
|
|
|
@shipment_router.get("/shipments", response_model=List[ShipmentSchema])
|
|
async def get_all_shipments(db: Session = Depends(get_db)):
|
|
try:
|
|
shipments = get_shipments(db)
|
|
return shipments
|
|
except SQLAlchemyError as e:
|
|
logging.error(f"Database error occurred: {e}")
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
|
|
|
|
|
@shipment_router.get("/{shipment_id}/dewars", response_model=List[Dewar])
|
|
async def get_dewars_by_shipment_id(shipment_id: int, db: Session = Depends(get_db)):
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
dewars = db.query(DewarModel).filter(DewarModel.shipment_id == shipment_id).all()
|
|
if not dewars:
|
|
raise HTTPException(status_code=404, detail="No dewars found for this shipment")
|
|
|
|
return dewars
|
|
|
|
|
|
@shipment_router.post(
|
|
"", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED
|
|
)
|
|
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
|
|
contact = (
|
|
db.query(ContactModel).filter(ContactModel.id == shipment.contact_id).first()
|
|
)
|
|
return_address = (
|
|
db.query(AddressModel)
|
|
.filter(AddressModel.id == shipment.return_address_id)
|
|
.first()
|
|
)
|
|
proposal = (
|
|
db.query(ProposalModel).filter(ProposalModel.id == shipment.proposal_id).first()
|
|
)
|
|
|
|
if not (contact or return_address or proposal):
|
|
raise HTTPException(status_code=404, detail="Associated entity not found")
|
|
|
|
db_shipment = ShipmentModel(
|
|
shipment_name=shipment.shipment_name,
|
|
shipment_date=shipment.shipment_date,
|
|
shipment_status=shipment.shipment_status,
|
|
comments=shipment.comments,
|
|
contact_id=contact.id,
|
|
return_address_id=return_address.id,
|
|
proposal_id=proposal.id,
|
|
pgroups=shipment.pgroups,
|
|
)
|
|
|
|
# Handling dewars association
|
|
if shipment.dewars:
|
|
dewar_ids = [dewar.dewar_id for dewar in shipment.dewars]
|
|
dewars = db.query(DewarModel).filter(DewarModel.id.in_(dewar_ids)).all()
|
|
if len(dewars) != len(shipment.dewars):
|
|
raise HTTPException(status_code=404, detail="One or more dewars not found")
|
|
db_shipment.dewars.extend(dewars)
|
|
|
|
db.add(db_shipment)
|
|
db.commit()
|
|
db.refresh(db_shipment)
|
|
|
|
return db_shipment
|
|
|
|
|
|
@shipment_router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_shipment(shipment_id: int, db: Session = Depends(get_db)):
|
|
# Fetch the shipment
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
# Check associated dewars
|
|
dewars = db.query(DewarModel).filter(DewarModel.shipment_id == shipment_id).all()
|
|
if dewars:
|
|
# Ensure dewars, pucks, and samples have no associated events
|
|
for dewar in dewars:
|
|
if (
|
|
db.query(LogisticsEvent)
|
|
.filter(LogisticsEvent.dewar_id == dewar.id)
|
|
.first()
|
|
):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Dewar {dewar.id} has associated logistics events."
|
|
f"Shipment cannot be deleted.",
|
|
)
|
|
|
|
for puck in dewar.pucks:
|
|
if db.query(PuckEvent).filter(PuckEvent.puck_id == puck.id).first():
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Puck {puck.id}"
|
|
f" in Dewar {dewar.id}"
|
|
f" has associated puck events."
|
|
f"Shipment cannot be deleted.",
|
|
)
|
|
|
|
for sample in puck.samples:
|
|
if (
|
|
db.query(SampleEvent)
|
|
.filter(SampleEvent.sample_id == sample.id)
|
|
.first()
|
|
):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Sample {sample.id}"
|
|
f" in Puck {puck.id}"
|
|
f" has associated sample events."
|
|
f"Shipment cannot be deleted.",
|
|
)
|
|
|
|
# If no events are found, proceed to delete the shipment
|
|
for dewar in dewars:
|
|
for puck in dewar.pucks:
|
|
for sample in puck.samples:
|
|
db.delete(sample) # Delete associated samples
|
|
db.delete(puck) # Delete associated pucks
|
|
db.delete(dewar) # Delete the dewar itself
|
|
|
|
# Finally, delete the shipment
|
|
db.delete(shipment)
|
|
db.commit()
|
|
return
|
|
|
|
|
|
@shipment_router.put("/{shipment_id}", response_model=ShipmentSchema)
|
|
async def update_shipment(
|
|
shipment_id: int, updated_shipment: ShipmentCreate, db: Session = Depends(get_db)
|
|
):
|
|
print(
|
|
"Received payload:",
|
|
json.dumps(updated_shipment.dict(), indent=2, default=default_serializer),
|
|
)
|
|
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
# Validate relationships by IDs
|
|
contact_person = (
|
|
db.query(ContactModel)
|
|
.filter(ContactModel.id == updated_shipment.contact_person_id)
|
|
.first()
|
|
)
|
|
return_address = (
|
|
db.query(AddressModel)
|
|
.filter(AddressModel.id == updated_shipment.return_address_id)
|
|
.first()
|
|
)
|
|
if not contact_person:
|
|
raise HTTPException(status_code=404, detail="Contact person not found")
|
|
if not return_address:
|
|
raise HTTPException(status_code=404, detail="Return address not found")
|
|
|
|
# Update shipment details
|
|
shipment.shipment_name = updated_shipment.shipment_name
|
|
shipment.shipment_date = updated_shipment.shipment_date
|
|
shipment.shipment_status = updated_shipment.shipment_status
|
|
shipment.comments = updated_shipment.comments
|
|
shipment.contact_person_id = updated_shipment.contact_person_id
|
|
shipment.return_address_id = updated_shipment.return_address_id
|
|
|
|
# Process and update dewars' details
|
|
for dewar_data in updated_shipment.dewars:
|
|
dewar = (
|
|
db.query(DewarModel).filter(DewarModel.id == dewar_data.dewar_id).first()
|
|
)
|
|
if not dewar:
|
|
raise HTTPException(
|
|
status_code=404, detail=f"Dewar with ID {dewar_data.dewar_id} not found"
|
|
)
|
|
|
|
update_fields = dewar_data.dict(exclude_unset=True)
|
|
for key, value in update_fields.items():
|
|
if key == "contact_person_id":
|
|
contact_person = (
|
|
db.query(ContactModel).filter(ContactModel.id == value).first()
|
|
)
|
|
if not contact_person:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Contact person with ID {value}"
|
|
f"for Dewar {dewar_data.dewar_id} not found",
|
|
)
|
|
if key == "return_address_id":
|
|
address = (
|
|
db.query(AddressModel).filter(AddressModel.id == value).first()
|
|
)
|
|
if not address:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Address with ID {value}"
|
|
f"for Dewar {dewar_data.dewar_id} not found",
|
|
)
|
|
|
|
for key, value in update_fields.items():
|
|
if key != "dewar_id":
|
|
setattr(dewar, key, value)
|
|
|
|
db.commit()
|
|
db.refresh(shipment)
|
|
return shipment
|
|
|
|
|
|
@shipment_router.post("/{shipment_id}/add_dewar", response_model=ShipmentSchema)
|
|
async def add_dewar_to_shipment(
|
|
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
|
|
):
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
dewar = db.query(DewarModel).filter(DewarModel.id == dewar_id).first()
|
|
if not dewar:
|
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
|
|
|
if dewar not in shipment.dewars:
|
|
shipment.dewars.append(dewar)
|
|
db.commit()
|
|
db.refresh(shipment)
|
|
return shipment
|
|
|
|
|
|
@shipment_router.delete(
|
|
"/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema
|
|
)
|
|
async def remove_dewar_from_shipment(
|
|
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
|
|
):
|
|
# Fetch the shipment
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
# Check if the dewar belongs to the shipment
|
|
dewar = (
|
|
db.query(DewarModel)
|
|
.filter(DewarModel.id == dewar_id, DewarModel.shipment_id == shipment_id)
|
|
.first()
|
|
)
|
|
if not dewar:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Dewar with ID {dewar_id} not found in shipment {shipment_id}",
|
|
)
|
|
|
|
# Check for logistics events associated with the dewar
|
|
logistics_event_exists = (
|
|
db.query(LogisticsEvent).filter(LogisticsEvent.dewar_id == dewar_id).first()
|
|
)
|
|
if logistics_event_exists:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Dewar {dewar_id} has " f" logistics events. Removal not allowed.",
|
|
)
|
|
|
|
# Check associated pucks and their events
|
|
for puck in dewar.pucks:
|
|
puck_event_exists = (
|
|
db.query(PuckEvent).filter(PuckEvent.puck_id == puck.id).first()
|
|
)
|
|
if puck_event_exists:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Puck {puck.id} in Dewar {dewar_id}"
|
|
f" has associated events. Removal not allowed.",
|
|
)
|
|
|
|
# Check associated samples and their events
|
|
for sample in puck.samples:
|
|
sample_event_exists = (
|
|
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).first()
|
|
)
|
|
if sample_event_exists:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Sample {sample.id} "
|
|
f"in Puck {puck.id} "
|
|
f"has associated events. Removal not allowed.",
|
|
)
|
|
|
|
# Perform cascade deletion: Delete samples, pucks, and the dewar
|
|
for puck in dewar.pucks:
|
|
for sample in puck.samples:
|
|
db.delete(sample) # Delete associated samples
|
|
db.delete(puck) # Delete associated puck
|
|
db.delete(dewar) # Finally, delete the dewar itself
|
|
|
|
db.commit()
|
|
db.refresh(shipment)
|
|
|
|
return shipment
|
|
|
|
|
|
@shipment_router.get("/contact_persons", response_model=List[ContactSchema])
|
|
async def get_shipment_contact_persons(db: Session = Depends(get_db)):
|
|
contact_persons = db.query(ContactModel).all()
|
|
return contact_persons
|
|
|
|
|
|
@shipment_router.get("/{shipment_id}/samples", response_model=List[SampleSchema])
|
|
async def get_samples_in_shipment(shipment_id: int, db: Session = Depends(get_db)):
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if shipment is None:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
samples = []
|
|
for dewar in shipment.dewars:
|
|
for puck in dewar.pucks:
|
|
samples.extend(puck.samples)
|
|
|
|
return samples
|
|
|
|
|
|
@shipment_router.get(
|
|
"/shipments/{shipment_id}/dewars/{dewar_id}/samples",
|
|
response_model=List[SampleSchema],
|
|
)
|
|
async def get_samples_in_dewar(
|
|
shipment_id: int, dewar_id: int, db: Session = Depends(get_db)
|
|
):
|
|
shipment = get_shipment_by_id(db, shipment_id)
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
dewar = next((d for d in shipment.dewars if d.id == dewar_id), None)
|
|
if not dewar:
|
|
raise HTTPException(status_code=404, detail="Dewar not found")
|
|
|
|
samples = []
|
|
for puck in dewar.pucks:
|
|
for sample in puck.samples:
|
|
samples.append(sample)
|
|
|
|
return samples
|
|
|
|
|
|
@shipment_router.put("/{shipment_id}/comments", response_model=ShipmentSchema)
|
|
async def update_shipment_comments(
|
|
shipment_id: int,
|
|
comments_data: UpdateShipmentComments,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
|
|
if not shipment:
|
|
raise HTTPException(status_code=404, detail="Shipment not found")
|
|
|
|
shipment.comments = comments_data.comments
|
|
db.commit()
|
|
db.refresh(shipment)
|
|
return shipment
|