Files
mt-drugs/backend/app/main.py
2026-01-19 02:53:44 -05:00

315 lines
11 KiB
Python

from fastapi import FastAPI, Depends, HTTPException, APIRouter
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime
from .database import engine, get_db, Base
from .models import Drug, DrugVariant, Dispensing
from pydantic import BaseModel
# Create tables
Base.metadata.create_all(bind=engine)
app = FastAPI(title="Drug Inventory API")
# CORS middleware for frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, restrict this
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Create a router with /api prefix
router = APIRouter(prefix="/api")
# Pydantic schemas
class DrugCreate(BaseModel):
name: str
description: str = None
class DrugUpdate(BaseModel):
name: str = None
description: str = None
class DrugResponse(BaseModel):
id: int
name: str
description: str = None
class Config:
from_attributes = True
class DrugVariantCreate(BaseModel):
strength: str
quantity: float
unit: str = "units"
low_stock_threshold: float = 10
class DrugVariantUpdate(BaseModel):
strength: str = None
quantity: float = None
unit: str = None
low_stock_threshold: float = None
class DrugVariantResponse(BaseModel):
id: int
drug_id: int
strength: str
quantity: float
unit: str
low_stock_threshold: float
class Config:
from_attributes = True
class DrugWithVariantsResponse(BaseModel):
id: int
name: str
description: str = None
variants: List[DrugVariantResponse] = []
class Config:
from_attributes = True
class DispensingCreate(BaseModel):
drug_variant_id: int
quantity: float
animal_name: Optional[str] = None
user_name: str
notes: Optional[str] = None
class DispensingResponse(BaseModel):
id: int
drug_variant_id: int
quantity: float
animal_name: Optional[str] = None
user_name: str
notes: Optional[str] = None
dispensed_at: datetime
class Config:
from_attributes = True
# Routes
@router.get("/")
def read_root():
return {"message": "Drug Inventory API"}
@router.get("/drugs", response_model=List[DrugWithVariantsResponse])
def list_drugs(db: Session = Depends(get_db)):
"""Get all drugs with their variants"""
drugs = db.query(Drug).all()
result = []
for drug in drugs:
variants = db.query(DrugVariant).filter(DrugVariant.drug_id == drug.id).all()
drug_dict = drug.__dict__.copy()
drug_dict['variants'] = variants
result.append(drug_dict)
return result
@router.get("/drugs/low-stock", response_model=List[DrugWithVariantsResponse])
def low_stock_drugs(db: Session = Depends(get_db)):
"""Get drugs with low stock variants"""
# Get variants that are low on stock
low_stock_variants = db.query(DrugVariant).filter(
DrugVariant.quantity <= DrugVariant.low_stock_threshold
).all()
# Get unique drug IDs
drug_ids = list(set(v.drug_id for v in low_stock_variants))
drugs = db.query(Drug).filter(Drug.id.in_(drug_ids)).all()
result = []
for drug in drugs:
variants = [v for v in low_stock_variants if v.drug_id == drug.id]
drug_dict = drug.__dict__.copy()
drug_dict['variants'] = variants
result.append(drug_dict)
return result
@router.get("/drugs/{drug_id}", response_model=DrugWithVariantsResponse)
def get_drug(drug_id: int, db: Session = Depends(get_db)):
"""Get a specific drug with its variants"""
drug = db.query(Drug).filter(Drug.id == drug_id).first()
if not drug:
raise HTTPException(status_code=404, detail="Drug not found")
variants = db.query(DrugVariant).filter(DrugVariant.drug_id == drug.id).all()
drug_dict = drug.__dict__.copy()
drug_dict['variants'] = variants
return drug_dict
@router.post("/drugs", response_model=DrugWithVariantsResponse)
def create_drug(drug: DrugCreate, db: Session = Depends(get_db)):
"""Create a new drug"""
# Check if drug name already exists
existing = db.query(Drug).filter(Drug.name == drug.name).first()
if existing:
raise HTTPException(status_code=400, detail="Drug with this name already exists")
db_drug = Drug(name=drug.name, description=drug.description)
db.add(db_drug)
db.commit()
db.refresh(db_drug)
# Return drug with empty variants list
drug_dict = db_drug.__dict__.copy()
drug_dict['variants'] = []
return drug_dict
@router.put("/drugs/{drug_id}", response_model=DrugWithVariantsResponse)
def update_drug(drug_id: int, drug_update: DrugUpdate, db: Session = Depends(get_db)):
"""Update a drug"""
drug = db.query(Drug).filter(Drug.id == drug_id).first()
if not drug:
raise HTTPException(status_code=404, detail="Drug not found")
for field, value in drug_update.dict(exclude_unset=True).items():
setattr(drug, field, value)
db.commit()
db.refresh(drug)
variants = db.query(DrugVariant).filter(DrugVariant.drug_id == drug.id).all()
drug_dict = drug.__dict__.copy()
drug_dict['variants'] = variants
return drug_dict
@router.delete("/drugs/{drug_id}")
def delete_drug(drug_id: int, db: Session = Depends(get_db)):
"""Delete a drug and all its variants"""
drug = db.query(Drug).filter(Drug.id == drug_id).first()
if not drug:
raise HTTPException(status_code=404, detail="Drug not found")
# Delete all variants first
db.query(DrugVariant).filter(DrugVariant.drug_id == drug_id).delete()
# Delete the drug
db.delete(drug)
db.commit()
return {"message": "Drug and all variants deleted successfully"}
# Drug Variant endpoints
@router.post("/drugs/{drug_id}/variants", response_model=DrugVariantResponse)
def create_drug_variant(drug_id: int, variant: DrugVariantCreate, db: Session = Depends(get_db)):
"""Create a new variant for a drug"""
# Check if drug exists
drug = db.query(Drug).filter(Drug.id == drug_id).first()
if not drug:
raise HTTPException(status_code=404, detail="Drug not found")
# Check if variant with same strength already exists for this drug
existing = db.query(DrugVariant).filter(
DrugVariant.drug_id == drug_id,
DrugVariant.strength == variant.strength
).first()
if existing:
raise HTTPException(status_code=400, detail="Variant with this strength already exists for this drug")
db_variant = DrugVariant(
drug_id=drug_id,
strength=variant.strength,
quantity=variant.quantity,
unit=variant.unit,
low_stock_threshold=variant.low_stock_threshold
)
db.add(db_variant)
db.commit()
db.refresh(db_variant)
return db_variant
@router.get("/variants/{variant_id}", response_model=DrugVariantResponse)
def get_drug_variant(variant_id: int, db: Session = Depends(get_db)):
"""Get a specific drug variant"""
variant = db.query(DrugVariant).filter(DrugVariant.id == variant_id).first()
if not variant:
raise HTTPException(status_code=404, detail="Drug variant not found")
return variant
@router.put("/variants/{variant_id}", response_model=DrugVariantResponse)
def update_drug_variant(variant_id: int, variant_update: DrugVariantUpdate, db: Session = Depends(get_db)):
"""Update a drug variant"""
variant = db.query(DrugVariant).filter(DrugVariant.id == variant_id).first()
if not variant:
raise HTTPException(status_code=404, detail="Drug variant not found")
for field, value in variant_update.dict(exclude_unset=True).items():
setattr(variant, field, value)
db.commit()
db.refresh(variant)
return variant
@router.delete("/variants/{variant_id}")
def delete_drug_variant(variant_id: int, db: Session = Depends(get_db)):
"""Delete a drug variant"""
variant = db.query(DrugVariant).filter(DrugVariant.id == variant_id).first()
if not variant:
raise HTTPException(status_code=404, detail="Drug variant not found")
db.delete(variant)
db.commit()
return {"message": "Drug variant deleted successfully"}
# Dispensing endpoints
@router.post("/dispense", response_model=DispensingResponse)
def dispense_drug(dispensing: DispensingCreate, db: Session = Depends(get_db)):
"""Record a drug dispensing and reduce inventory"""
# Check if drug variant exists
variant = db.query(DrugVariant).filter(DrugVariant.id == dispensing.drug_variant_id).first()
if not variant:
raise HTTPException(status_code=404, detail="Drug variant not found")
# Check if enough quantity available
if variant.quantity < dispensing.quantity:
raise HTTPException(
status_code=400,
detail=f"Insufficient quantity. Available: {variant.quantity}, Requested: {dispensing.quantity}"
)
# Reduce variant quantity
variant.quantity -= dispensing.quantity
db.commit()
# Create dispensing record
db_dispensing = Dispensing(**dispensing.dict())
db.add(db_dispensing)
db.commit()
db.refresh(db_dispensing)
return db_dispensing
@router.get("/dispense/history", response_model=List[DispensingResponse])
def list_dispensings(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
"""Get dispensing records (audit log)"""
return db.query(Dispensing).order_by(Dispensing.dispensed_at.desc()).offset(skip).limit(limit).all()
@router.get("/drugs/{drug_id}/dispense/history", response_model=List[DispensingResponse])
def get_drug_dispensings(drug_id: int, db: Session = Depends(get_db)):
"""Get dispensing history for a specific drug (all variants)"""
# Verify drug exists
drug = db.query(Drug).filter(Drug.id == drug_id).first()
if not drug:
raise HTTPException(status_code=404, detail="Drug not found")
# Get all variant IDs for this drug
variant_ids = db.query(DrugVariant.id).filter(DrugVariant.drug_id == drug_id).subquery()
return db.query(Dispensing).filter(Dispensing.drug_variant_id.in_(variant_ids)).order_by(Dispensing.dispensed_at.desc()).all()
@router.get("/variants/{variant_id}/dispense/history", response_model=List[DispensingResponse])
def get_variant_dispensings(variant_id: int, db: Session = Depends(get_db)):
"""Get dispensing history for a specific drug variant"""
# Verify variant exists
variant = db.query(DrugVariant).filter(DrugVariant.id == variant_id).first()
if not variant:
raise HTTPException(status_code=404, detail="Drug variant not found")
return db.query(Dispensing).filter(Dispensing.drug_variant_id == variant_id).order_by(Dispensing.dispensed_at.desc()).all()
# Include router with /api prefix
app.include_router(router)