# app/modules/orders/services/order_metrics.py """ Metrics provider for the orders module. Provides metrics for: - Order counts and status - Revenue metrics - Invoice statistics """ import logging from datetime import datetime, timedelta 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 OrderMetricsProvider: """ Metrics provider for orders module. Provides order and revenue metrics for store and platform dashboards. """ @property def metrics_category(self) -> str: return "orders" def get_store_metrics( self, db: Session, store_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get order metrics for a specific store. Provides: - Total orders - Orders by status - Revenue metrics """ from app.modules.orders.models import Order, OrderItem try: # Total orders total_orders = ( db.query(Order).filter(Order.store_id == store_id).count() ) # Orders in period (default to last 30 days) date_from = context.date_from if context else None if date_from is None: date_from = datetime.utcnow() - timedelta(days=30) orders_in_period_query = db.query(Order).filter( Order.store_id == store_id, Order.created_at >= date_from, ) if context and context.date_to: orders_in_period_query = orders_in_period_query.filter( Order.created_at <= context.date_to ) orders_in_period = orders_in_period_query.count() # Total order items total_order_items = ( db.query(OrderItem) .join(Order, Order.id == OrderItem.order_id) .filter(Order.store_id == store_id) .count() ) # Revenue (sum of order totals) - if total_amount field exists try: total_revenue = ( db.query(func.sum(Order.total_amount)) .filter(Order.store_id == store_id) .scalar() or 0 ) revenue_in_period = ( db.query(func.sum(Order.total_amount)) .filter( Order.store_id == store_id, Order.created_at >= date_from, ) .scalar() or 0 ) except Exception: # Field may not exist total_revenue = 0 revenue_in_period = 0 # Average order value avg_order_value = round(total_revenue / total_orders, 2) if total_orders > 0 else 0 return [ MetricValue( key="orders.total", value=total_orders, label="Total Orders", category="orders", icon="shopping-cart", description="Total orders received", ), MetricValue( key="orders.in_period", value=orders_in_period, label="Recent Orders", category="orders", icon="clock", description="Orders in the selected period", ), MetricValue( key="orders.total_items", value=total_order_items, label="Total Items Sold", category="orders", icon="box", description="Total order items", ), MetricValue( key="orders.total_revenue", value=float(total_revenue), label="Total Revenue", category="orders", icon="currency-euro", unit="EUR", description="Total revenue from orders", ), MetricValue( key="orders.revenue_period", value=float(revenue_in_period), label="Period Revenue", category="orders", icon="trending-up", unit="EUR", description="Revenue in the selected period", ), MetricValue( key="orders.avg_value", value=avg_order_value, label="Avg Order Value", category="orders", icon="calculator", unit="EUR", description="Average order value", ), ] except Exception as e: logger.warning(f"Failed to get order store metrics: {e}") return [] def get_platform_metrics( self, db: Session, platform_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get order metrics aggregated for a platform. Aggregates order data across all stores. """ from app.modules.orders.models import Order from app.modules.tenancy.services.platform_service import platform_service try: # Get all store IDs for this platform via platform service store_ids = platform_service.get_store_ids_for_platform(db, platform_id) # Total orders total_orders = ( db.query(Order).filter(Order.store_id.in_(store_ids)).count() ) # Orders in period (default to last 30 days) date_from = context.date_from if context else None if date_from is None: date_from = datetime.utcnow() - timedelta(days=30) orders_in_period_query = db.query(Order).filter( Order.store_id.in_(store_ids), Order.created_at >= date_from, ) if context and context.date_to: orders_in_period_query = orders_in_period_query.filter( Order.created_at <= context.date_to ) orders_in_period = orders_in_period_query.count() # Stores with orders stores_with_orders = ( db.query(func.count(func.distinct(Order.store_id))) .filter(Order.store_id.in_(store_ids)) .scalar() or 0 ) # Revenue metrics try: total_revenue = ( db.query(func.sum(Order.total_amount)) .filter(Order.store_id.in_(store_ids)) .scalar() or 0 ) revenue_in_period = ( db.query(func.sum(Order.total_amount)) .filter( Order.store_id.in_(store_ids), Order.created_at >= date_from, ) .scalar() or 0 ) except Exception: total_revenue = 0 revenue_in_period = 0 # Average order value avg_order_value = round(total_revenue / total_orders, 2) if total_orders > 0 else 0 return [ MetricValue( key="orders.total", value=total_orders, label="Total Orders", category="orders", icon="shopping-cart", description="Total orders across all stores", ), MetricValue( key="orders.in_period", value=orders_in_period, label="Recent Orders", category="orders", icon="clock", description="Orders in the selected period", ), MetricValue( key="orders.stores_with_orders", value=stores_with_orders, label="Stores with Orders", category="orders", icon="store", description="Stores that have received orders", ), MetricValue( key="orders.total_revenue", value=float(total_revenue), label="Total Revenue", category="orders", icon="currency-euro", unit="EUR", description="Total revenue across platform", ), MetricValue( key="orders.revenue_period", value=float(revenue_in_period), label="Period Revenue", category="orders", icon="trending-up", unit="EUR", description="Revenue in the selected period", ), MetricValue( key="orders.avg_value", value=avg_order_value, label="Avg Order Value", category="orders", icon="calculator", unit="EUR", description="Average order value", ), ] except Exception as e: logger.warning(f"Failed to get order platform metrics: {e}") return [] def get_customer_order_metrics( self, db: Session, store_id: int, customer_id: int, context: MetricsContext | None = None, ) -> list[MetricValue]: """ Get order metrics for a specific customer. This is an entity-level method (not dashboard-level) that provides order statistics for a specific customer. Used by customer detail pages. Args: db: Database session store_id: Store ID (for ownership verification) customer_id: Customer ID context: Optional filtering context Returns: List of MetricValue objects for this customer's order activity """ from app.modules.orders.models import Order try: # Base query for customer orders base_query = db.query(Order).filter( Order.customer_id == customer_id, Order.store_id == store_id, ) # Total orders total_orders = base_query.count() # Revenue stats revenue_query = db.query( func.sum(Order.total_amount_cents).label("total_spent_cents"), func.avg(Order.total_amount_cents).label("avg_order_cents"), func.max(Order.created_at).label("last_order_date"), func.min(Order.created_at).label("first_order_date"), ).filter( Order.customer_id == customer_id, Order.store_id == store_id, ) stats = revenue_query.first() total_spent_cents = stats.total_spent_cents or 0 avg_order_cents = stats.avg_order_cents or 0 last_order_date = stats.last_order_date first_order_date = stats.first_order_date # Convert cents to currency total_spent = total_spent_cents / 100 avg_order_value = avg_order_cents / 100 if avg_order_cents else 0.0 return [ MetricValue( key="customer.total_orders", value=total_orders, label="Total Orders", category="customer_orders", icon="shopping-bag", description="Total orders placed by this customer", ), MetricValue( key="customer.total_spent", value=round(total_spent, 2), label="Total Spent", category="customer_orders", icon="currency-euro", unit="EUR", description="Total amount spent by this customer", ), MetricValue( key="customer.avg_order_value", value=round(avg_order_value, 2), label="Avg Order Value", category="customer_orders", icon="calculator", unit="EUR", description="Average order value for this customer", ), MetricValue( key="customer.last_order_date", value=last_order_date.isoformat() if last_order_date else "", label="Last Order", category="customer_orders", icon="calendar", description="Date of most recent order", ), MetricValue( key="customer.first_order_date", value=first_order_date.isoformat() if first_order_date else "", label="First Order", category="customer_orders", icon="calendar-plus", description="Date of first order", ), ] except Exception as e: logger.warning(f"Failed to get customer order metrics: {e}") return [] # Singleton instance order_metrics_provider = OrderMetricsProvider() __all__ = ["OrderMetricsProvider", "order_metrics_provider"]