# app/api/v1/admin/inventory.py """ Admin inventory management endpoints. Provides inventory management capabilities for administrators: - View inventory across all vendors - View vendor-specific inventory - Set/adjust inventory on behalf of vendors - Low stock alerts and reporting Admin Context: Uses admin JWT authentication. Vendor selection is passed as a request parameter. """ import logging from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db from app.services.inventory_service import inventory_service from models.database.user import User from models.schema.inventory import ( AdminInventoryAdjust, AdminInventoryCreate, AdminInventoryListResponse, AdminInventoryLocationsResponse, AdminInventoryStats, AdminLowStockItem, AdminVendorsWithInventoryResponse, InventoryAdjust, InventoryCreate, InventoryMessageResponse, InventoryResponse, InventoryUpdate, ProductInventorySummary, ) router = APIRouter(prefix="/inventory") logger = logging.getLogger(__name__) # ============================================================================ # List & Statistics Endpoints # ============================================================================ @router.get("", response_model=AdminInventoryListResponse) def get_all_inventory( skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=500), vendor_id: int | None = Query(None, description="Filter by vendor"), location: str | None = Query(None, description="Filter by location"), low_stock: int | None = Query(None, ge=0, description="Filter items below threshold"), search: str | None = Query(None, description="Search by product title or SKU"), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Get inventory across all vendors with filtering. Allows admins to view and filter inventory across the platform. """ return inventory_service.get_all_inventory_admin( db=db, skip=skip, limit=limit, vendor_id=vendor_id, location=location, low_stock=low_stock, search=search, ) @router.get("/stats", response_model=AdminInventoryStats) def get_inventory_stats( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get platform-wide inventory statistics.""" return inventory_service.get_inventory_stats_admin(db) @router.get("/low-stock", response_model=list[AdminLowStockItem]) def get_low_stock_items( threshold: int = Query(10, ge=0, description="Stock threshold"), vendor_id: int | None = Query(None, description="Filter by vendor"), limit: int = Query(50, ge=1, le=200), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get items with low stock levels.""" return inventory_service.get_low_stock_items_admin( db=db, threshold=threshold, vendor_id=vendor_id, limit=limit, ) @router.get("/vendors", response_model=AdminVendorsWithInventoryResponse) def get_vendors_with_inventory( db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get list of vendors that have inventory entries.""" return inventory_service.get_vendors_with_inventory_admin(db) @router.get("/locations", response_model=AdminInventoryLocationsResponse) def get_inventory_locations( vendor_id: int | None = Query(None, description="Filter by vendor"), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get list of unique inventory locations.""" return inventory_service.get_inventory_locations_admin(db, vendor_id) # ============================================================================ # Vendor-Specific Endpoints # ============================================================================ @router.get("/vendors/{vendor_id}", response_model=AdminInventoryListResponse) def get_vendor_inventory( vendor_id: int, skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=500), location: str | None = Query(None, description="Filter by location"), low_stock: int | None = Query(None, ge=0, description="Filter items below threshold"), db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get inventory for a specific vendor.""" return inventory_service.get_vendor_inventory_admin( db=db, vendor_id=vendor_id, skip=skip, limit=limit, location=location, low_stock=low_stock, ) @router.get("/products/{product_id}", response_model=ProductInventorySummary) def get_product_inventory( product_id: int, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Get inventory summary for a specific product across all locations.""" return inventory_service.get_product_inventory_admin(db, product_id) # ============================================================================ # Inventory Modification Endpoints # ============================================================================ @router.post("/set", response_model=InventoryResponse) def set_inventory( inventory_data: AdminInventoryCreate, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Set exact inventory quantity for a product at a location. Admin version - requires explicit vendor_id in request body. """ # Verify vendor exists inventory_service.verify_vendor_exists(db, inventory_data.vendor_id) # Convert to standard schema for service service_data = InventoryCreate( product_id=inventory_data.product_id, location=inventory_data.location, quantity=inventory_data.quantity, ) result = inventory_service.set_inventory( db=db, vendor_id=inventory_data.vendor_id, inventory_data=service_data, ) logger.info( f"Admin {current_admin.email} set inventory for product {inventory_data.product_id} " f"at {inventory_data.location}: {inventory_data.quantity} units" ) db.commit() return result @router.post("/adjust", response_model=InventoryResponse) def adjust_inventory( adjustment: AdminInventoryAdjust, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """ Adjust inventory by adding or removing quantity. Positive quantity = add stock, negative = remove stock. Admin version - requires explicit vendor_id in request body. """ # Verify vendor exists inventory_service.verify_vendor_exists(db, adjustment.vendor_id) # Convert to standard schema for service service_data = InventoryAdjust( product_id=adjustment.product_id, location=adjustment.location, quantity=adjustment.quantity, ) result = inventory_service.adjust_inventory( db=db, vendor_id=adjustment.vendor_id, inventory_data=service_data, ) sign = "+" if adjustment.quantity >= 0 else "" logger.info( f"Admin {current_admin.email} adjusted inventory for product {adjustment.product_id} " f"at {adjustment.location}: {sign}{adjustment.quantity} units" f"{f' (reason: {adjustment.reason})' if adjustment.reason else ''}" ) db.commit() return result @router.put("/{inventory_id}", response_model=InventoryResponse) def update_inventory( inventory_id: int, inventory_update: InventoryUpdate, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Update inventory entry fields.""" # Get inventory to find vendor_id inventory = inventory_service.get_inventory_by_id_admin(db, inventory_id) result = inventory_service.update_inventory( db=db, vendor_id=inventory.vendor_id, inventory_id=inventory_id, inventory_update=inventory_update, ) logger.info(f"Admin {current_admin.email} updated inventory {inventory_id}") db.commit() return result @router.delete("/{inventory_id}", response_model=InventoryMessageResponse) def delete_inventory( inventory_id: int, db: Session = Depends(get_db), current_admin: User = Depends(get_current_admin_api), ): """Delete inventory entry.""" # Get inventory to find vendor_id and log details inventory = inventory_service.get_inventory_by_id_admin(db, inventory_id) vendor_id = inventory.vendor_id product_id = inventory.product_id location = inventory.location inventory_service.delete_inventory( db=db, vendor_id=vendor_id, inventory_id=inventory_id, ) logger.info( f"Admin {current_admin.email} deleted inventory {inventory_id} " f"(product {product_id} at {location})" ) db.commit() return InventoryMessageResponse(message="Inventory deleted successfully")