315 lines
11 KiB
Python
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: str
|
|
user_name: str
|
|
notes: Optional[str] = None
|
|
|
|
class DispensingResponse(BaseModel):
|
|
id: int
|
|
drug_variant_id: int
|
|
quantity: float
|
|
animal_name: str
|
|
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)
|