Files
orion/app/modules/inventory/services/inventory_metrics.py
Samir Boulahtit aad18c27ab
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running
refactor: remove all backward compatibility code across 70 files
Clean up 28 backward compatibility instances identified in the codebase.
The app is not live, so all shims are replaced with the target architecture:

- Remove legacy Inventory.location column (use bin_location exclusively)
- Remove dashboard _extract_metric_value helper (use flat metrics dict)
- Remove legacy stat field duplicates (total_stores, total_imports, etc.)
- Remove 13 re-export shims and class aliases across modules
- Remove module-enabling JSON fallback (use PlatformModule junction table)
- Remove menu_to_legacy_format() conversion (return dataclasses directly)
- Remove title/description from MarketplaceProductBase schema
- Clean billing convenience method docstrings
- Clean test fixtures and backward-compat comments
- Add PlatformModule seeding to init_production.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:20:29 +01:00

318 lines
10 KiB
Python

# 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"]