# app/modules/inventory/services/inventory_metrics.py """ Metrics provider for the inventory module. Provides metrics for: - Inventory quantities - Stock levels - Low stock alerts """ import logging from typing import TYPE_CHECKING from sqlalchemy import func from sqlalchemy.orm import Session from app.modules.contracts.metrics import ( MetricsContext, MetricValue, ) if TYPE_CHECKING: pass logger = logging.getLogger(__name__) class InventoryMetricsProvider: """ Metrics provider for inventory module. Provides stock and inventory metrics for store and platform dashboards. """ @property def metrics_category(self) -> str: return "inventory" def get_store_metrics( self, db: Session, store_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get inventory metrics for a specific store. Provides: - Total inventory quantity - Reserved quantity - Available quantity - Inventory locations - Low stock items """ from app.modules.inventory.models import Inventory try: # Total inventory total_quantity = ( db.query(func.sum(Inventory.quantity)) .filter(Inventory.store_id == store_id) .scalar() or 0 ) # Reserved inventory reserved_quantity = ( db.query(func.sum(Inventory.reserved_quantity)) .filter(Inventory.store_id == store_id) .scalar() or 0 ) # Available inventory available_quantity = int(total_quantity) - int(reserved_quantity) # Inventory entries (SKU/location combinations) inventory_entries = ( db.query(Inventory).filter(Inventory.store_id == store_id).count() ) # Unique locations unique_locations = ( db.query(func.count(func.distinct(Inventory.bin_location))) .filter(Inventory.store_id == store_id) .scalar() or 0 ) # Low stock items (quantity < 10 and > 0) low_stock_items = ( db.query(Inventory) .filter( Inventory.store_id == store_id, Inventory.quantity > 0, Inventory.quantity < 10, ) .count() ) # Out of stock items (quantity = 0) out_of_stock_items = ( db.query(Inventory) .filter(Inventory.store_id == store_id, Inventory.quantity == 0) .count() ) return [ MetricValue( key="inventory.total_quantity", value=int(total_quantity), label="Total Stock", category="inventory", icon="package", unit="items", description="Total inventory quantity", ), MetricValue( key="inventory.reserved_quantity", value=int(reserved_quantity), label="Reserved", category="inventory", icon="lock", unit="items", description="Inventory reserved for orders", ), MetricValue( key="inventory.available_quantity", value=available_quantity, label="Available", category="inventory", icon="check", unit="items", description="Inventory available for sale", ), MetricValue( key="inventory.entries", value=inventory_entries, label="SKU/Location Entries", category="inventory", icon="list", description="Total inventory entries", ), MetricValue( key="inventory.locations", value=unique_locations, label="Locations", category="inventory", icon="map-pin", description="Unique storage locations", ), MetricValue( key="inventory.low_stock", value=low_stock_items, label="Low Stock", category="inventory", icon="alert-triangle", description="Items with quantity < 10", ), MetricValue( key="inventory.out_of_stock", value=out_of_stock_items, label="Out of Stock", category="inventory", icon="x-circle", description="Items with zero quantity", ), ] except Exception as e: logger.warning(f"Failed to get inventory store metrics: {e}") return [] def get_platform_metrics( self, db: Session, platform_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get inventory metrics aggregated for a platform. Aggregates stock data across all stores. """ from app.modules.inventory.models import Inventory from app.modules.tenancy.models import StorePlatform try: # Get all store IDs for this platform using StorePlatform junction table store_ids = ( db.query(StorePlatform.store_id) .filter( StorePlatform.platform_id == platform_id, StorePlatform.is_active == True, ) .subquery() ) # Total inventory total_quantity = ( db.query(func.sum(Inventory.quantity)) .filter(Inventory.store_id.in_(store_ids)) .scalar() or 0 ) # Reserved inventory reserved_quantity = ( db.query(func.sum(Inventory.reserved_quantity)) .filter(Inventory.store_id.in_(store_ids)) .scalar() or 0 ) # Available inventory available_quantity = int(total_quantity) - int(reserved_quantity) # Total inventory entries inventory_entries = ( db.query(Inventory).filter(Inventory.store_id.in_(store_ids)).count() ) # Stores with inventory stores_with_inventory = ( db.query(func.count(func.distinct(Inventory.store_id))) .filter(Inventory.store_id.in_(store_ids)) .scalar() or 0 ) # Low stock items across platform low_stock_items = ( db.query(Inventory) .filter( Inventory.store_id.in_(store_ids), Inventory.quantity > 0, Inventory.quantity < 10, ) .count() ) # Out of stock items out_of_stock_items = ( db.query(Inventory) .filter(Inventory.store_id.in_(store_ids), Inventory.quantity == 0) .count() ) return [ MetricValue( key="inventory.total_quantity", value=int(total_quantity), label="Total Stock", category="inventory", icon="package", unit="items", description="Total inventory across all stores", ), MetricValue( key="inventory.reserved_quantity", value=int(reserved_quantity), label="Reserved", category="inventory", icon="lock", unit="items", description="Inventory reserved for orders", ), MetricValue( key="inventory.available_quantity", value=available_quantity, label="Available", category="inventory", icon="check", unit="items", description="Inventory available for sale", ), MetricValue( key="inventory.entries", value=inventory_entries, label="Total Entries", category="inventory", icon="list", description="Total inventory entries across stores", ), MetricValue( key="inventory.stores_with_inventory", value=stores_with_inventory, label="Stores with Stock", category="inventory", icon="store", description="Stores managing inventory", ), MetricValue( key="inventory.low_stock", value=low_stock_items, label="Low Stock Items", category="inventory", icon="alert-triangle", description="Items with quantity < 10", ), MetricValue( key="inventory.out_of_stock", value=out_of_stock_items, label="Out of Stock", category="inventory", icon="x-circle", description="Items with zero quantity", ), ] except Exception as e: logger.warning(f"Failed to get inventory platform metrics: {e}") return [] # Singleton instance inventory_metrics_provider = InventoryMetricsProvider() __all__ = ["InventoryMetricsProvider", "inventory_metrics_provider"]