Refactor - API lazy loading
This commit is contained in:
+168
-14
@@ -1,6 +1,7 @@
|
||||
from fastapi import FastAPI, Depends, HTTPException, APIRouter, status, Response
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime, timedelta, date
|
||||
import math
|
||||
@@ -263,6 +264,34 @@ class DrugWithVariantsResponse(BaseModel):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class VariantSummaryResponse(BaseModel):
|
||||
"""Lightweight variant summary returned by GET /drugs list — no packs or batches."""
|
||||
id: int
|
||||
drug_id: int
|
||||
strength: str
|
||||
quantity: float
|
||||
unit: str
|
||||
low_stock_threshold: float
|
||||
has_inventory_history: bool = False
|
||||
expired_quantity: float = 0.0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DrugSummaryResponse(BaseModel):
|
||||
"""Lightweight drug summary for the main list — variants without packs or batches."""
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
is_controlled: bool = False
|
||||
locations: List[str] = []
|
||||
variants: List[VariantSummaryResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class DispensingAllocationCreate(BaseModel):
|
||||
batch_id: int
|
||||
quantity: float
|
||||
@@ -399,6 +428,40 @@ def enrich_variant_with_batches(db: Session, variant: DrugVariant) -> Dict[str,
|
||||
return variant_dict
|
||||
|
||||
|
||||
def serialize_variant_with_packs(db: Session, variant: DrugVariant) -> Dict[str, Any]:
|
||||
"""Return variant data with packs but without batch details (level-2 detail)."""
|
||||
has_batch_history = (
|
||||
db.query(Batch.id)
|
||||
.filter(Batch.drug_variant_id == variant.id)
|
||||
.first()
|
||||
is not None
|
||||
)
|
||||
has_dispense_history = (
|
||||
db.query(Dispensing.id)
|
||||
.filter(Dispensing.drug_variant_id == variant.id)
|
||||
.first()
|
||||
is not None
|
||||
)
|
||||
packs = (
|
||||
db.query(VariantPack)
|
||||
.filter(VariantPack.drug_variant_id == variant.id)
|
||||
.order_by(VariantPack.is_active.desc(), VariantPack.id.asc())
|
||||
.all()
|
||||
)
|
||||
return {
|
||||
"id": variant.id,
|
||||
"drug_id": variant.drug_id,
|
||||
"strength": variant.strength,
|
||||
"quantity": variant.quantity,
|
||||
"unit": variant.unit,
|
||||
"base_unit": variant.unit,
|
||||
"low_stock_threshold": variant.low_stock_threshold,
|
||||
"has_inventory_history": has_batch_history or has_dispense_history,
|
||||
"packs": [serialize_variant_pack(pack) for pack in packs],
|
||||
"batches": [],
|
||||
}
|
||||
|
||||
|
||||
def serialize_batch_response(db: Session, batch: Batch) -> Dict[str, Any]:
|
||||
location = db.query(Location).filter(Location.id == batch.location_id).first()
|
||||
pack = None
|
||||
@@ -866,21 +929,112 @@ def admin_change_password(user_id: int, password_data: AdminPasswordChange, db:
|
||||
def read_root():
|
||||
return {"message": "Drug Inventory API"}
|
||||
|
||||
@router.get("/drugs", response_model=List[DrugWithVariantsResponse])
|
||||
@router.get("/drugs", response_model=List[DrugSummaryResponse])
|
||||
def list_drugs(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
"""Get all drugs with their variants"""
|
||||
"""Get all drugs with lightweight variant summaries (no packs or batches)."""
|
||||
drugs = db.query(Drug).all()
|
||||
if not drugs:
|
||||
return []
|
||||
|
||||
drug_ids = [d.id for d in drugs]
|
||||
|
||||
all_variants = (
|
||||
db.query(DrugVariant)
|
||||
.filter(DrugVariant.drug_id.in_(drug_ids))
|
||||
.all()
|
||||
)
|
||||
variants_by_drug: Dict[int, list] = {}
|
||||
for v in all_variants:
|
||||
variants_by_drug.setdefault(v.drug_id, []).append(v)
|
||||
|
||||
variant_ids = [v.id for v in all_variants]
|
||||
if not variant_ids:
|
||||
return [
|
||||
{
|
||||
"id": d.id,
|
||||
"name": d.name,
|
||||
"description": d.description,
|
||||
"is_controlled": bool(d.is_controlled),
|
||||
"locations": [],
|
||||
"variants": [],
|
||||
}
|
||||
for d in drugs
|
||||
]
|
||||
|
||||
# Variant IDs that have ever had a batch received
|
||||
batch_history_ids = set(
|
||||
row[0]
|
||||
for row in db.query(Batch.drug_variant_id)
|
||||
.filter(Batch.drug_variant_id.in_(variant_ids))
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
|
||||
# Variant IDs that have ever had a dispense
|
||||
dispense_history_ids = set(
|
||||
row[0]
|
||||
for row in db.query(Dispensing.drug_variant_id)
|
||||
.filter(Dispensing.drug_variant_id.in_(variant_ids))
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
|
||||
# Sum of expired active batch stock per variant
|
||||
today = date.today()
|
||||
expired_rows = (
|
||||
db.query(Batch.drug_variant_id, func.sum(Batch.quantity))
|
||||
.filter(
|
||||
Batch.drug_variant_id.in_(variant_ids),
|
||||
Batch.quantity > 0,
|
||||
Batch.expiry_date < today,
|
||||
)
|
||||
.group_by(Batch.drug_variant_id)
|
||||
.all()
|
||||
)
|
||||
expired_by_variant: Dict[int, float] = {row[0]: float(row[1]) for row in expired_rows}
|
||||
|
||||
# Distinct location names per drug (from active batch stock)
|
||||
location_rows = (
|
||||
db.query(DrugVariant.drug_id, Location.name)
|
||||
.join(Batch, Batch.drug_variant_id == DrugVariant.id)
|
||||
.join(Location, Location.id == Batch.location_id)
|
||||
.filter(
|
||||
DrugVariant.drug_id.in_(drug_ids),
|
||||
Batch.quantity > 0,
|
||||
)
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
locations_by_drug: Dict[int, list] = {}
|
||||
for drug_id, loc_name in location_rows:
|
||||
locations_by_drug.setdefault(drug_id, []).append(loc_name)
|
||||
|
||||
result = []
|
||||
for drug in drugs:
|
||||
variants = db.query(DrugVariant).filter(DrugVariant.drug_id == drug.id).all()
|
||||
drug_dict = {
|
||||
"id": drug.id,
|
||||
"name": drug.name,
|
||||
"description": drug.description,
|
||||
"is_controlled": bool(drug.is_controlled),
|
||||
"variants": [enrich_variant_with_batches(db, v) for v in variants],
|
||||
}
|
||||
result.append(drug_dict)
|
||||
drug_variants = variants_by_drug.get(drug.id, [])
|
||||
variant_summaries = [
|
||||
{
|
||||
"id": v.id,
|
||||
"drug_id": v.drug_id,
|
||||
"strength": v.strength,
|
||||
"quantity": v.quantity,
|
||||
"unit": v.unit,
|
||||
"low_stock_threshold": v.low_stock_threshold,
|
||||
"has_inventory_history": v.id in batch_history_ids or v.id in dispense_history_ids,
|
||||
"expired_quantity": expired_by_variant.get(v.id, 0.0),
|
||||
}
|
||||
for v in drug_variants
|
||||
]
|
||||
result.append(
|
||||
{
|
||||
"id": drug.id,
|
||||
"name": drug.name,
|
||||
"description": drug.description,
|
||||
"is_controlled": bool(drug.is_controlled),
|
||||
"locations": locations_by_drug.get(drug.id, []),
|
||||
"variants": variant_summaries,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@router.get("/drugs/low-stock", response_model=List[DrugWithVariantsResponse])
|
||||
@@ -910,18 +1064,18 @@ def low_stock_drugs(db: Session = Depends(get_db), current_user: User = Depends(
|
||||
|
||||
@router.get("/drugs/{drug_id}", response_model=DrugWithVariantsResponse)
|
||||
def get_drug(drug_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
"""Get a specific drug with its variants"""
|
||||
"""Get a specific drug with its variants and packs (no batch detail — level-2 detail)."""
|
||||
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 = {
|
||||
"id": drug.id,
|
||||
"name": drug.name,
|
||||
"description": drug.description,
|
||||
"is_controlled": bool(drug.is_controlled),
|
||||
"variants": [enrich_variant_with_batches(db, v) for v in variants],
|
||||
"variants": [serialize_variant_with_packs(db, v) for v in variants],
|
||||
}
|
||||
return drug_dict
|
||||
|
||||
|
||||
Reference in New Issue
Block a user