Files
orion/app/modules/customers/services/customer_metrics.py
Samir Boulahtit ff852f1ab3 fix: use metrics provider pattern for merchant dashboard stats
The merchant dashboard was showing subscription count as "Total Stores".
Add get_merchant_metrics() to MetricsProviderProtocol and implement it
in tenancy, billing, and customer providers. Dashboard now fetches real
stats from a new /merchants/core/dashboard/stats endpoint and displays
4 cards: active subscriptions, total stores, customers, team members.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 21:28:59 +01:00

245 lines
7.8 KiB
Python

# app/modules/customers/services/customer_metrics.py
"""
Metrics provider for the customers module.
Provides metrics for:
- Customer counts
- New customers
- Customer addresses
"""
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 CustomerMetricsProvider:
"""
Metrics provider for customers module.
Provides customer-related metrics for store and platform dashboards.
"""
@property
def metrics_category(self) -> str:
return "customers"
def get_store_metrics(
self,
db: Session,
store_id: int,
context: MetricsContext | None = None,
) -> list[MetricValue]:
"""
Get customer metrics for a specific store.
Provides:
- Total customers
- New customers (in period)
- Customers with addresses
"""
from app.modules.customers.models import Customer, CustomerAddress
try:
# Total customers
total_customers = (
db.query(Customer).filter(Customer.store_id == store_id).count()
)
# New customers (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)
new_customers_query = db.query(Customer).filter(
Customer.store_id == store_id,
Customer.created_at >= date_from,
)
if context and context.date_to:
new_customers_query = new_customers_query.filter(
Customer.created_at <= context.date_to
)
new_customers = new_customers_query.count()
# Customers with addresses
customers_with_addresses = (
db.query(func.count(func.distinct(CustomerAddress.customer_id)))
.join(Customer, Customer.id == CustomerAddress.customer_id)
.filter(Customer.store_id == store_id)
.scalar()
or 0
)
return [
MetricValue(
key="customers.total",
value=total_customers,
label="Total Customers",
category="customers",
icon="users",
description="Total number of customers",
),
MetricValue(
key="customers.new",
value=new_customers,
label="New Customers",
category="customers",
icon="user-plus",
description="Customers acquired in the period",
),
MetricValue(
key="customers.with_addresses",
value=customers_with_addresses,
label="With Addresses",
category="customers",
icon="map-pin",
description="Customers who have saved addresses",
),
]
except Exception as e:
logger.warning(f"Failed to get customer store metrics: {e}")
return []
def get_platform_metrics(
self,
db: Session,
platform_id: int,
context: MetricsContext | None = None,
) -> list[MetricValue]:
"""
Get customer metrics aggregated for a platform.
For platforms, aggregates customer data across all stores.
"""
from app.modules.customers.models import Customer
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 customers across all stores
total_customers = (
db.query(Customer).filter(Customer.store_id.in_(store_ids)).count()
)
# Unique customers (by email across platform)
unique_customer_emails = (
db.query(func.count(func.distinct(Customer.email)))
.filter(Customer.store_id.in_(store_ids))
.scalar()
or 0
)
# New customers (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)
new_customers_query = db.query(Customer).filter(
Customer.store_id.in_(store_ids),
Customer.created_at >= date_from,
)
if context and context.date_to:
new_customers_query = new_customers_query.filter(
Customer.created_at <= context.date_to
)
new_customers = new_customers_query.count()
return [
MetricValue(
key="customers.total",
value=total_customers,
label="Total Customers",
category="customers",
icon="users",
description="Total customer records across all stores",
),
MetricValue(
key="customers.unique_emails",
value=unique_customer_emails,
label="Unique Customers",
category="customers",
icon="user",
description="Unique customer emails across platform",
),
MetricValue(
key="customers.new",
value=new_customers,
label="New Customers",
category="customers",
icon="user-plus",
description="Customers acquired in the period",
),
]
except Exception as e:
logger.warning(f"Failed to get customer platform metrics: {e}")
return []
def get_merchant_metrics(
self,
db: Session,
merchant_id: int,
context: MetricsContext | None = None,
) -> list[MetricValue]:
"""
Get customer metrics scoped to a merchant.
Aggregates customer counts across all stores owned by the merchant.
"""
from app.modules.customers.models import Customer
from app.modules.tenancy.models import Store
try:
merchant_store_ids = (
db.query(Store.id)
.filter(Store.merchant_id == merchant_id)
.subquery()
)
total_customers = (
db.query(Customer)
.filter(Customer.store_id.in_(merchant_store_ids))
.count()
)
return [
MetricValue(
key="customers.total",
value=total_customers,
label="Total Customers",
category="customers",
icon="users",
description="Total customers across all merchant stores",
),
]
except Exception as e:
logger.warning(f"Failed to get customer merchant metrics: {e}")
return []
# Singleton instance
customer_metrics_provider = CustomerMetricsProvider()
__all__ = ["CustomerMetricsProvider", "customer_metrics_provider"]