# services/inventory/app/api/stock.py """ API endpoints for stock management """ from typing import List, Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.services.inventory_service import InventoryService from app.schemas.inventory import ( StockCreate, StockUpdate, StockResponse, StockMovementCreate, StockMovementResponse, StockFilter ) from shared.auth.decorators import get_current_user_dep from shared.auth.tenant_access import verify_tenant_access_dep router = APIRouter(prefix="/stock", tags=["stock"]) # Helper function to extract user ID from user object def get_current_user_id(current_user: dict = Depends(get_current_user_dep)) -> UUID: """Extract user ID from current user context""" user_id = current_user.get('user_id') if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User ID not found in context" ) return UUID(user_id) @router.post("/", response_model=StockResponse) async def add_stock( stock_data: StockCreate, tenant_id: UUID = Depends(verify_tenant_access_dep), user_id: UUID = Depends(get_current_user_id), db: AsyncSession = Depends(get_db) ): """Add new stock entry""" try: service = InventoryService() stock = await service.add_stock(stock_data, tenant_id, user_id) return stock except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to add stock" ) @router.post("/consume") async def consume_stock( ingredient_id: UUID, quantity: float = Query(..., gt=0, description="Quantity to consume"), reference_number: Optional[str] = Query(None, description="Reference number (e.g., production order)"), notes: Optional[str] = Query(None, description="Additional notes"), fifo: bool = Query(True, description="Use FIFO (First In, First Out) method"), tenant_id: UUID = Depends(verify_tenant_access_dep), user_id: UUID = Depends(get_current_user_id), db: AsyncSession = Depends(get_db) ): """Consume stock for production""" try: service = InventoryService() consumed_items = await service.consume_stock( ingredient_id, quantity, tenant_id, user_id, reference_number, notes, fifo ) return { "ingredient_id": str(ingredient_id), "total_quantity_consumed": quantity, "consumed_items": consumed_items, "method": "FIFO" if fifo else "LIFO" } except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to consume stock" ) @router.get("/ingredient/{ingredient_id}", response_model=List[StockResponse]) async def get_ingredient_stock( ingredient_id: UUID, include_unavailable: bool = Query(False, description="Include unavailable stock"), tenant_id: UUID = Depends(verify_tenant_access_dep), db: AsyncSession = Depends(get_db) ): """Get stock entries for an ingredient""" try: service = InventoryService() stock_entries = await service.get_stock_by_ingredient( ingredient_id, tenant_id, include_unavailable ) return stock_entries except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get ingredient stock" ) @router.get("/expiring", response_model=List[dict]) async def get_expiring_stock( days_ahead: int = Query(7, ge=1, le=365, description="Days ahead to check for expiring items"), tenant_id: UUID = Depends(verify_tenant_access_dep), db: AsyncSession = Depends(get_db) ): """Get stock items expiring within specified days""" try: service = InventoryService() expiring_items = await service.check_expiration_alerts(tenant_id, days_ahead) return expiring_items except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get expiring stock" ) @router.get("/low-stock", response_model=List[dict]) async def get_low_stock( tenant_id: UUID = Depends(verify_tenant_access_dep), db: AsyncSession = Depends(get_db) ): """Get ingredients with low stock levels""" try: service = InventoryService() low_stock_items = await service.check_low_stock_alerts(tenant_id) return low_stock_items except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get low stock items" ) @router.get("/summary", response_model=dict) async def get_stock_summary( tenant_id: UUID = Depends(verify_tenant_access_dep), db: AsyncSession = Depends(get_db) ): """Get stock summary for dashboard""" try: service = InventoryService() summary = await service.get_inventory_summary(tenant_id) return summary.dict() except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get stock summary" )