Create new services: inventory, recipes, suppliers
This commit is contained in:
0
services/inventory/app/api/__init__.py
Normal file
0
services/inventory/app/api/__init__.py
Normal file
231
services/inventory/app/api/classification.py
Normal file
231
services/inventory/app/api/classification.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# services/inventory/app/api/classification.py
|
||||
"""
|
||||
Product Classification API Endpoints
|
||||
AI-powered product classification for onboarding automation
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path
|
||||
from typing import List, Dict, Any
|
||||
from uuid import UUID
|
||||
from pydantic import BaseModel, Field
|
||||
import structlog
|
||||
|
||||
from app.services.product_classifier import ProductClassifierService, get_product_classifier
|
||||
from shared.auth.decorators import get_current_user_dep, get_current_tenant_id_dep
|
||||
|
||||
router = APIRouter(tags=["classification"])
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class ProductClassificationRequest(BaseModel):
|
||||
"""Request for single product classification"""
|
||||
product_name: str = Field(..., description="Product name to classify")
|
||||
sales_volume: float = Field(None, description="Total sales volume for context")
|
||||
sales_data: Dict[str, Any] = Field(default_factory=dict, description="Additional sales context")
|
||||
|
||||
|
||||
class BatchClassificationRequest(BaseModel):
|
||||
"""Request for batch product classification"""
|
||||
products: List[ProductClassificationRequest] = Field(..., description="Products to classify")
|
||||
|
||||
|
||||
class ProductSuggestionResponse(BaseModel):
|
||||
"""Response with product classification suggestion"""
|
||||
suggestion_id: str
|
||||
original_name: str
|
||||
suggested_name: str
|
||||
product_type: str
|
||||
category: str
|
||||
unit_of_measure: str
|
||||
confidence_score: float
|
||||
estimated_shelf_life_days: int = None
|
||||
requires_refrigeration: bool = False
|
||||
requires_freezing: bool = False
|
||||
is_seasonal: bool = False
|
||||
suggested_supplier: str = None
|
||||
notes: str = None
|
||||
|
||||
|
||||
class BusinessModelAnalysisResponse(BaseModel):
|
||||
"""Response with business model analysis"""
|
||||
model: str # production, retail, hybrid
|
||||
confidence: float
|
||||
ingredient_count: int
|
||||
finished_product_count: int
|
||||
ingredient_ratio: float
|
||||
recommendations: List[str]
|
||||
|
||||
|
||||
class BatchClassificationResponse(BaseModel):
|
||||
"""Response for batch classification"""
|
||||
suggestions: List[ProductSuggestionResponse]
|
||||
business_model_analysis: BusinessModelAnalysisResponse
|
||||
total_products: int
|
||||
high_confidence_count: int
|
||||
low_confidence_count: int
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/inventory/classify-product", response_model=ProductSuggestionResponse)
|
||||
async def classify_single_product(
|
||||
request: ProductClassificationRequest,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
classifier: ProductClassifierService = Depends(get_product_classifier)
|
||||
):
|
||||
"""Classify a single product for inventory creation"""
|
||||
try:
|
||||
# Verify tenant access
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(status_code=403, detail="Access denied to this tenant")
|
||||
|
||||
# Classify the product
|
||||
suggestion = classifier.classify_product(
|
||||
request.product_name,
|
||||
request.sales_volume
|
||||
)
|
||||
|
||||
# Convert to response format
|
||||
response = ProductSuggestionResponse(
|
||||
suggestion_id=str(UUID.uuid4()), # Generate unique ID for tracking
|
||||
original_name=suggestion.original_name,
|
||||
suggested_name=suggestion.suggested_name,
|
||||
product_type=suggestion.product_type.value,
|
||||
category=suggestion.category,
|
||||
unit_of_measure=suggestion.unit_of_measure.value,
|
||||
confidence_score=suggestion.confidence_score,
|
||||
estimated_shelf_life_days=suggestion.estimated_shelf_life_days,
|
||||
requires_refrigeration=suggestion.requires_refrigeration,
|
||||
requires_freezing=suggestion.requires_freezing,
|
||||
is_seasonal=suggestion.is_seasonal,
|
||||
suggested_supplier=suggestion.suggested_supplier,
|
||||
notes=suggestion.notes
|
||||
)
|
||||
|
||||
logger.info("Classified single product",
|
||||
product=request.product_name,
|
||||
classification=suggestion.product_type.value,
|
||||
confidence=suggestion.confidence_score,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to classify product",
|
||||
error=str(e), product=request.product_name, tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Classification failed: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/inventory/classify-products-batch", response_model=BatchClassificationResponse)
|
||||
async def classify_products_batch(
|
||||
request: BatchClassificationRequest,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
classifier: ProductClassifierService = Depends(get_product_classifier)
|
||||
):
|
||||
"""Classify multiple products for onboarding automation"""
|
||||
try:
|
||||
# Verify tenant access
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(status_code=403, detail="Access denied to this tenant")
|
||||
|
||||
if not request.products:
|
||||
raise HTTPException(status_code=400, detail="No products provided for classification")
|
||||
|
||||
# Extract product names and volumes
|
||||
product_names = [p.product_name for p in request.products]
|
||||
sales_volumes = {p.product_name: p.sales_volume for p in request.products if p.sales_volume}
|
||||
|
||||
# Classify products in batch
|
||||
suggestions = classifier.classify_products_batch(product_names, sales_volumes)
|
||||
|
||||
# Convert suggestions to response format
|
||||
suggestion_responses = []
|
||||
for suggestion in suggestions:
|
||||
suggestion_responses.append(ProductSuggestionResponse(
|
||||
suggestion_id=str(UUID.uuid4()),
|
||||
original_name=suggestion.original_name,
|
||||
suggested_name=suggestion.suggested_name,
|
||||
product_type=suggestion.product_type.value,
|
||||
category=suggestion.category,
|
||||
unit_of_measure=suggestion.unit_of_measure.value,
|
||||
confidence_score=suggestion.confidence_score,
|
||||
estimated_shelf_life_days=suggestion.estimated_shelf_life_days,
|
||||
requires_refrigeration=suggestion.requires_refrigeration,
|
||||
requires_freezing=suggestion.requires_freezing,
|
||||
is_seasonal=suggestion.is_seasonal,
|
||||
suggested_supplier=suggestion.suggested_supplier,
|
||||
notes=suggestion.notes
|
||||
))
|
||||
|
||||
# Analyze business model
|
||||
ingredient_count = sum(1 for s in suggestions if s.product_type.value == 'ingredient')
|
||||
finished_count = sum(1 for s in suggestions if s.product_type.value == 'finished_product')
|
||||
total = len(suggestions)
|
||||
ingredient_ratio = ingredient_count / total if total > 0 else 0
|
||||
|
||||
# Determine business model
|
||||
if ingredient_ratio >= 0.7:
|
||||
model = 'production'
|
||||
elif ingredient_ratio <= 0.3:
|
||||
model = 'retail'
|
||||
else:
|
||||
model = 'hybrid'
|
||||
|
||||
confidence = max(abs(ingredient_ratio - 0.5) * 2, 0.1)
|
||||
|
||||
recommendations = {
|
||||
'production': [
|
||||
'Focus on ingredient inventory management',
|
||||
'Set up recipe cost calculation',
|
||||
'Configure supplier relationships',
|
||||
'Enable production planning features'
|
||||
],
|
||||
'retail': [
|
||||
'Configure central baker relationships',
|
||||
'Set up delivery schedule tracking',
|
||||
'Enable finished product freshness monitoring',
|
||||
'Focus on sales forecasting'
|
||||
],
|
||||
'hybrid': [
|
||||
'Configure both ingredient and finished product management',
|
||||
'Set up flexible inventory categories',
|
||||
'Enable both production and retail features'
|
||||
]
|
||||
}
|
||||
|
||||
business_model_analysis = BusinessModelAnalysisResponse(
|
||||
model=model,
|
||||
confidence=confidence,
|
||||
ingredient_count=ingredient_count,
|
||||
finished_product_count=finished_count,
|
||||
ingredient_ratio=ingredient_ratio,
|
||||
recommendations=recommendations.get(model, [])
|
||||
)
|
||||
|
||||
# Count confidence levels
|
||||
high_confidence_count = sum(1 for s in suggestions if s.confidence_score >= 0.7)
|
||||
low_confidence_count = sum(1 for s in suggestions if s.confidence_score < 0.6)
|
||||
|
||||
response = BatchClassificationResponse(
|
||||
suggestions=suggestion_responses,
|
||||
business_model_analysis=business_model_analysis,
|
||||
total_products=len(suggestions),
|
||||
high_confidence_count=high_confidence_count,
|
||||
low_confidence_count=low_confidence_count
|
||||
)
|
||||
|
||||
logger.info("Batch classification complete",
|
||||
total_products=len(suggestions),
|
||||
business_model=model,
|
||||
high_confidence=high_confidence_count,
|
||||
low_confidence=low_confidence_count,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed batch classification",
|
||||
error=str(e), products_count=len(request.products), tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Batch classification failed: {str(e)}")
|
||||
208
services/inventory/app/api/ingredients.py
Normal file
208
services/inventory/app/api/ingredients.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# services/inventory/app/api/ingredients.py
|
||||
"""
|
||||
API endpoints for ingredient 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 (
|
||||
IngredientCreate,
|
||||
IngredientUpdate,
|
||||
IngredientResponse,
|
||||
InventoryFilter,
|
||||
PaginatedResponse
|
||||
)
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
from shared.auth.tenant_access import verify_tenant_access_dep
|
||||
|
||||
router = APIRouter(prefix="/ingredients", tags=["ingredients"])
|
||||
|
||||
# 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=IngredientResponse)
|
||||
async def create_ingredient(
|
||||
ingredient_data: IngredientCreate,
|
||||
tenant_id: UUID = Depends(verify_tenant_access_dep),
|
||||
user_id: UUID = Depends(get_current_user_id),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Create a new ingredient"""
|
||||
try:
|
||||
service = InventoryService()
|
||||
ingredient = await service.create_ingredient(ingredient_data, tenant_id, user_id)
|
||||
return ingredient
|
||||
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 create ingredient"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{ingredient_id}", response_model=IngredientResponse)
|
||||
async def get_ingredient(
|
||||
ingredient_id: UUID,
|
||||
tenant_id: UUID = Depends(verify_tenant_access_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get ingredient by ID"""
|
||||
try:
|
||||
service = InventoryService()
|
||||
ingredient = await service.get_ingredient(ingredient_id, tenant_id)
|
||||
|
||||
if not ingredient:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Ingredient not found"
|
||||
)
|
||||
|
||||
return ingredient
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get ingredient"
|
||||
)
|
||||
|
||||
|
||||
@router.put("/{ingredient_id}", response_model=IngredientResponse)
|
||||
async def update_ingredient(
|
||||
ingredient_id: UUID,
|
||||
ingredient_data: IngredientUpdate,
|
||||
tenant_id: UUID = Depends(verify_tenant_access_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Update ingredient"""
|
||||
try:
|
||||
service = InventoryService()
|
||||
ingredient = await service.update_ingredient(ingredient_id, ingredient_data, tenant_id)
|
||||
|
||||
if not ingredient:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Ingredient not found"
|
||||
)
|
||||
|
||||
return ingredient
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update ingredient"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/", response_model=List[IngredientResponse])
|
||||
async def list_ingredients(
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Number of records to return"),
|
||||
category: Optional[str] = Query(None, description="Filter by category"),
|
||||
is_active: Optional[bool] = Query(None, description="Filter by active status"),
|
||||
is_low_stock: Optional[bool] = Query(None, description="Filter by low stock status"),
|
||||
needs_reorder: Optional[bool] = Query(None, description="Filter by reorder needed"),
|
||||
search: Optional[str] = Query(None, description="Search in name, SKU, or barcode"),
|
||||
tenant_id: UUID = Depends(verify_tenant_access_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""List ingredients with filtering"""
|
||||
try:
|
||||
service = InventoryService()
|
||||
|
||||
# Build filters
|
||||
filters = {}
|
||||
if category:
|
||||
filters['category'] = category
|
||||
if is_active is not None:
|
||||
filters['is_active'] = is_active
|
||||
if is_low_stock is not None:
|
||||
filters['is_low_stock'] = is_low_stock
|
||||
if needs_reorder is not None:
|
||||
filters['needs_reorder'] = needs_reorder
|
||||
if search:
|
||||
filters['search'] = search
|
||||
|
||||
ingredients = await service.get_ingredients(tenant_id, skip, limit, filters)
|
||||
return ingredients
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to list ingredients"
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{ingredient_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_ingredient(
|
||||
ingredient_id: UUID,
|
||||
tenant_id: UUID = Depends(verify_tenant_access_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Soft delete ingredient (mark as inactive)"""
|
||||
try:
|
||||
service = InventoryService()
|
||||
ingredient = await service.update_ingredient(
|
||||
ingredient_id,
|
||||
{"is_active": False},
|
||||
tenant_id
|
||||
)
|
||||
|
||||
if not ingredient:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Ingredient not found"
|
||||
)
|
||||
|
||||
return None
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete ingredient"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{ingredient_id}/stock", response_model=List[dict])
|
||||
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"
|
||||
)
|
||||
167
services/inventory/app/api/stock.py
Normal file
167
services/inventory/app/api/stock.py
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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"
|
||||
)
|
||||
Reference in New Issue
Block a user