refactor: remove all backward compatibility code across 70 files
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

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>
This commit is contained in:
2026-02-15 13:20:29 +01:00
parent b0db8133a0
commit aad18c27ab
70 changed files with 501 additions and 841 deletions

View File

@@ -7,9 +7,6 @@ enabled modules. Each module provides its own metrics via the MetricsProvider pr
Dashboard widgets are collected via the WidgetAggregator service, which discovers
DashboardWidgetProvider implementations from all enabled modules.
For backward compatibility, this also falls back to the analytics stats_service
for comprehensive statistics that haven't been migrated to the provider pattern yet.
"""
import logging
@@ -20,7 +17,7 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api
from app.core.database import get_db
from app.modules.contracts.widgets import BreakdownWidget, ListWidget
from app.modules.contracts.widgets import BreakdownWidget, ListWidget, WidgetListItem
from app.modules.core.schemas.dashboard import (
AdminDashboardResponse,
ImportStatsResponse,
@@ -70,19 +67,7 @@ def _get_platform_id(request: Request, current_admin: UserContext) -> int:
return 1
def _extract_metric_value(
metrics: dict[str, list], category: str, key: str, default: int | float = 0
) -> int | float:
"""Extract a specific metric value from categorized metrics."""
if category not in metrics:
return default
for metric in metrics[category]:
if metric.key == key:
return metric.value
return default
def _widget_list_item_to_dict(item) -> dict[str, Any]:
def _widget_list_item_to_dict(item: WidgetListItem) -> dict[str, Any]:
"""Convert a WidgetListItem to a dictionary for API response."""
return {
"id": item.id,
@@ -95,15 +80,14 @@ def _widget_list_item_to_dict(item) -> dict[str, Any]:
}
def _extract_widget_items(
widgets: dict[str, list], category: str, key: str
def _get_list_widget_items(
widgets: dict[str, list], key: str
) -> list[dict[str, Any]]:
"""Extract items from a list widget for backward compatibility."""
if category not in widgets:
return []
for widget in widgets[category]:
if widget.key == key and isinstance(widget.data, ListWidget):
return [_widget_list_item_to_dict(item) for item in widget.data.items]
"""Extract items from a list widget by key, searching all categories."""
for category_widgets in widgets.values():
for widget in category_widgets:
if widget.key == key and isinstance(widget.data, ListWidget):
return [_widget_list_item_to_dict(item) for item in widget.data.items]
return []
@@ -116,59 +100,32 @@ def get_admin_dashboard(
"""Get admin dashboard with platform statistics (Admin only)."""
platform_id = _get_platform_id(request, current_admin)
# Get aggregated metrics from all enabled modules
metrics = stats_aggregator.get_admin_dashboard_stats(db=db, platform_id=platform_id)
# Get flat metrics from all enabled modules
metrics = stats_aggregator.get_admin_stats_flat(db=db, platform_id=platform_id)
# Get aggregated widgets from all enabled modules
widgets = widget_aggregator.get_admin_dashboard_widgets(db=db, platform_id=platform_id)
# Extract user stats from tenancy module
total_users = _extract_metric_value(metrics, "tenancy", "tenancy.total_users", 0)
active_users = _extract_metric_value(metrics, "tenancy", "tenancy.active_users", 0)
inactive_users = _extract_metric_value(metrics, "tenancy", "tenancy.inactive_users", 0)
admin_users = _extract_metric_value(metrics, "tenancy", "tenancy.admin_users", 0)
activation_rate = _extract_metric_value(
metrics, "tenancy", "tenancy.user_activation_rate", 0
)
# Extract store stats from tenancy module
total_stores = _extract_metric_value(metrics, "tenancy", "tenancy.total_stores", 0)
verified_stores = _extract_metric_value(
metrics, "tenancy", "tenancy.verified_stores", 0
)
pending_stores = _extract_metric_value(
metrics, "tenancy", "tenancy.pending_stores", 0
)
inactive_stores = _extract_metric_value(
metrics, "tenancy", "tenancy.inactive_stores", 0
)
# Extract recent_stores from tenancy widget (backward compatibility)
recent_stores = _extract_widget_items(widgets, "tenancy", "tenancy.recent_stores")
# Extract recent_imports from marketplace widget (backward compatibility)
recent_imports = _extract_widget_items(widgets, "marketplace", "marketplace.recent_imports")
return AdminDashboardResponse(
platform={
"name": "Multi-Tenant Ecommerce Platform",
"version": "1.0.0",
},
users=UserStatsResponse(
total_users=int(total_users),
active_users=int(active_users),
inactive_users=int(inactive_users),
admin_users=int(admin_users),
activation_rate=float(activation_rate),
total_users=int(metrics.get("tenancy.total_users", 0)),
active_users=int(metrics.get("tenancy.active_users", 0)),
inactive_users=int(metrics.get("tenancy.inactive_users", 0)),
admin_users=int(metrics.get("tenancy.admin_users", 0)),
activation_rate=float(metrics.get("tenancy.user_activation_rate", 0)),
),
stores=StoreStatsResponse(
total=int(total_stores),
verified=int(verified_stores),
pending=int(pending_stores),
inactive=int(inactive_stores),
total=int(metrics.get("tenancy.total_stores", 0)),
verified=int(metrics.get("tenancy.verified_stores", 0)),
pending=int(metrics.get("tenancy.pending_stores", 0)),
inactive=int(metrics.get("tenancy.inactive_stores", 0)),
),
recent_stores=recent_stores,
recent_imports=recent_imports,
recent_stores=_get_list_widget_items(widgets, "tenancy.recent_stores"),
recent_imports=_get_list_widget_items(widgets, "marketplace.recent_imports"),
)
@@ -181,37 +138,17 @@ def get_comprehensive_stats(
"""Get comprehensive platform statistics (Admin only)."""
platform_id = _get_platform_id(request, current_admin)
# Get aggregated metrics
metrics = stats_aggregator.get_admin_dashboard_stats(db=db, platform_id=platform_id)
# Extract product stats from catalog module
total_products = _extract_metric_value(metrics, "catalog", "catalog.total_products", 0)
# Extract marketplace stats
unique_marketplaces = _extract_metric_value(
metrics, "marketplace", "marketplace.unique_marketplaces", 0
)
unique_brands = _extract_metric_value(
metrics, "marketplace", "marketplace.unique_brands", 0
)
# Extract store stats
unique_stores = _extract_metric_value(metrics, "tenancy", "tenancy.total_stores", 0)
# Extract inventory stats
inventory_entries = _extract_metric_value(metrics, "inventory", "inventory.entries", 0)
inventory_quantity = _extract_metric_value(
metrics, "inventory", "inventory.total_quantity", 0
)
# Get flat metrics from all enabled modules
metrics = stats_aggregator.get_admin_stats_flat(db=db, platform_id=platform_id)
return StatsResponse(
total_products=int(total_products),
unique_brands=int(unique_brands),
total_products=int(metrics.get("catalog.total_products", 0)),
unique_brands=int(metrics.get("marketplace.unique_brands", 0)),
unique_categories=0, # TODO: Add category tracking
unique_marketplaces=int(unique_marketplaces),
unique_stores=int(unique_stores),
total_inventory_entries=int(inventory_entries),
total_inventory_quantity=int(inventory_quantity),
unique_marketplaces=int(metrics.get("marketplace.unique_marketplaces", 0)),
unique_stores=int(metrics.get("tenancy.total_stores", 0)),
total_inventory_entries=int(metrics.get("inventory.entries", 0)),
total_inventory_quantity=int(metrics.get("inventory.total_quantity", 0)),
)
@@ -261,89 +198,39 @@ def get_platform_statistics(
"""Get comprehensive platform statistics (Admin only)."""
platform_id = _get_platform_id(request, current_admin)
# Get aggregated metrics from all enabled modules
metrics = stats_aggregator.get_admin_dashboard_stats(db=db, platform_id=platform_id)
# User stats from tenancy
total_users = _extract_metric_value(metrics, "tenancy", "tenancy.total_users", 0)
active_users = _extract_metric_value(metrics, "tenancy", "tenancy.active_users", 0)
inactive_users = _extract_metric_value(metrics, "tenancy", "tenancy.inactive_users", 0)
admin_users = _extract_metric_value(metrics, "tenancy", "tenancy.admin_users", 0)
activation_rate = _extract_metric_value(
metrics, "tenancy", "tenancy.user_activation_rate", 0
)
# Store stats from tenancy
total_stores = _extract_metric_value(metrics, "tenancy", "tenancy.total_stores", 0)
verified_stores = _extract_metric_value(
metrics, "tenancy", "tenancy.verified_stores", 0
)
pending_stores = _extract_metric_value(
metrics, "tenancy", "tenancy.pending_stores", 0
)
inactive_stores = _extract_metric_value(
metrics, "tenancy", "tenancy.inactive_stores", 0
)
# Product stats from catalog
total_products = _extract_metric_value(metrics, "catalog", "catalog.total_products", 0)
active_products = _extract_metric_value(
metrics, "catalog", "catalog.active_products", 0
)
# Order stats from orders
total_orders = _extract_metric_value(metrics, "orders", "orders.total", 0)
# Import stats from marketplace
total_imports = _extract_metric_value(
metrics, "marketplace", "marketplace.total_imports", 0
)
pending_imports = _extract_metric_value(
metrics, "marketplace", "marketplace.pending_imports", 0
)
processing_imports = _extract_metric_value(
metrics, "marketplace", "marketplace.processing_imports", 0
)
completed_imports = _extract_metric_value(
metrics, "marketplace", "marketplace.successful_imports", 0
)
failed_imports = _extract_metric_value(
metrics, "marketplace", "marketplace.failed_imports", 0
)
import_success_rate = _extract_metric_value(
metrics, "marketplace", "marketplace.success_rate", 0
)
# Get flat metrics from all enabled modules
metrics = stats_aggregator.get_admin_stats_flat(db=db, platform_id=platform_id)
return PlatformStatsResponse(
users=UserStatsResponse(
total_users=int(total_users),
active_users=int(active_users),
inactive_users=int(inactive_users),
admin_users=int(admin_users),
activation_rate=float(activation_rate),
total_users=int(metrics.get("tenancy.total_users", 0)),
active_users=int(metrics.get("tenancy.active_users", 0)),
inactive_users=int(metrics.get("tenancy.inactive_users", 0)),
admin_users=int(metrics.get("tenancy.admin_users", 0)),
activation_rate=float(metrics.get("tenancy.user_activation_rate", 0)),
),
stores=StoreStatsResponse(
total=int(total_stores),
verified=int(verified_stores),
pending=int(pending_stores),
inactive=int(inactive_stores),
total=int(metrics.get("tenancy.total_stores", 0)),
verified=int(metrics.get("tenancy.verified_stores", 0)),
pending=int(metrics.get("tenancy.pending_stores", 0)),
inactive=int(metrics.get("tenancy.inactive_stores", 0)),
),
products=ProductStatsResponse(
total_products=int(total_products),
active_products=int(active_products),
total_products=int(metrics.get("catalog.total_products", 0)),
active_products=int(metrics.get("catalog.active_products", 0)),
out_of_stock=0, # TODO: Implement
),
orders=OrderStatsBasicResponse(
total_orders=int(total_orders),
total_orders=int(metrics.get("orders.total", 0)),
pending_orders=0, # TODO: Implement status tracking
completed_orders=0, # TODO: Implement status tracking
),
imports=ImportStatsResponse(
total=int(total_imports),
pending=int(pending_imports),
processing=int(processing_imports),
completed=int(completed_imports),
failed=int(failed_imports),
success_rate=float(import_success_rate),
total=int(metrics.get("marketplace.total_imports", 0)),
pending=int(metrics.get("marketplace.pending_imports", 0)),
processing=int(metrics.get("marketplace.processing_imports", 0)),
completed=int(metrics.get("marketplace.successful_imports", 0)),
failed=int(metrics.get("marketplace.failed_imports", 0)),
success_rate=float(metrics.get("marketplace.success_rate", 0)),
),
)

View File

@@ -205,7 +205,9 @@ async def get_platform_menu_config(
)
# Use user's preferred language, falling back to middleware-resolved language
language = current_user.preferred_language or getattr(request.state, "language", "en")
language = current_user.preferred_language or getattr(
request.state, "language", "en"
)
return _build_menu_config_response(
items, frontend_type, language=language, platform_id=platform_id
@@ -279,7 +281,10 @@ async def bulk_update_platform_menu_visibility(
f"{len(update_data.visibility)} items for platform {platform.code} ({frontend_type.value})"
)
return {"success": True, "message": f"Updated {len(update_data.visibility)} menu items"}
return {
"success": True,
"message": f"Updated {len(update_data.visibility)} menu items",
}
@router.post("/platforms/{platform_id}/reset")
@@ -334,7 +339,9 @@ async def get_user_menu_config(
)
# Use user's preferred language, falling back to middleware-resolved language
language = current_user.preferred_language or getattr(request.state, "language", "en")
language = current_user.preferred_language or getattr(
request.state, "language", "en"
)
return _build_menu_config_response(
items, FrontendType.ADMIN, language=language, user_id=current_user.id
@@ -386,7 +393,9 @@ async def reset_user_menu_config(
f"[MENU_CONFIG] Super admin {current_user.email} reset their personal menu config (hide all)"
)
return MenuActionResponse(success=True, message="Menu configuration reset - all items hidden")
return MenuActionResponse(
success=True, message="Menu configuration reset - all items hidden"
)
@router.post("/user/show-all", response_model=MenuActionResponse)
@@ -486,22 +495,31 @@ async def get_rendered_admin_menu(
)
# Use user's preferred language, falling back to middleware-resolved language
language = current_user.preferred_language or getattr(request.state, "language", "en")
language = current_user.preferred_language or getattr(
request.state, "language", "en"
)
# Translate section and item labels
# menu is a list of DiscoveredMenuSection dataclasses
sections = []
for section in menu.get("sections", []):
for section in menu:
# Translate item labels
translated_items = []
for item in section.get("items", []):
translated_item = item.copy()
translated_item["label"] = _translate_label(item.get("label"), language)
translated_items.append(translated_item)
for item in section.items:
translated_items.append(
{
"id": item.id,
"label": _translate_label(item.label_key, language),
"icon": item.icon,
"url": item.route,
"super_admin_only": item.is_super_admin_only,
}
)
sections.append(
MenuSectionResponse(
id=section["id"],
label=_translate_label(section.get("label"), language),
id=section.id,
label=_translate_label(section.label_key, language),
items=translated_items,
)
)

View File

@@ -492,42 +492,6 @@ class MenuDiscoveryService:
return None
def menu_to_legacy_format(
self,
sections: list[DiscoveredMenuSection],
) -> dict:
"""
Convert discovered menu sections to legacy registry format.
This allows gradual migration by using new discovery with old rendering.
Args:
sections: List of DiscoveredMenuSection
Returns:
Dict in ADMIN_MENU_REGISTRY/STORE_MENU_REGISTRY format
"""
return {
"sections": [
{
"id": section.id,
"label": section.label_key, # Note: key not resolved
"super_admin_only": section.is_super_admin_only,
"items": [
{
"id": item.id,
"label": item.label_key, # Note: key not resolved
"icon": item.icon,
"url": item.route,
"super_admin_only": item.is_super_admin_only,
}
for item in section.items
],
}
for section in sections
]
}
# Singleton instance
menu_discovery_service = MenuDiscoveryService()

View File

@@ -102,7 +102,9 @@ class MenuService:
# Validate menu item exists in registry
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)
if menu_item_id not in all_items:
logger.warning(f"Unknown menu item: {menu_item_id} for {frontend_type.value}")
logger.warning(
f"Unknown menu item: {menu_item_id} for {frontend_type.value}"
)
return False
# Check module enablement if platform is specified
@@ -159,9 +161,7 @@ class MenuService:
# Filter by module enablement if platform is specified
if platform_id:
module_service.get_module_menu_items(
db, platform_id, frontend_type
)
module_service.get_module_menu_items(db, platform_id, frontend_type)
# Only keep items from enabled modules (or items not associated with any module)
all_items = module_service.filter_menu_items_by_modules(
db, platform_id, all_items, frontend_type
@@ -228,7 +228,7 @@ class MenuService:
user_id: int | None = None,
is_super_admin: bool = False,
store_code: str | None = None,
) -> dict:
) -> list:
"""
Get filtered menu structure for frontend rendering.
@@ -248,10 +248,9 @@ class MenuService:
store_code: Store code for URL placeholder replacement (store frontend)
Returns:
Filtered menu structure ready for rendering
List of DiscoveredMenuSection ready for rendering
"""
# Use the module-driven discovery service to get filtered menu
sections = menu_discovery_service.get_menu_for_frontend(
return menu_discovery_service.get_menu_for_frontend(
db=db,
frontend_type=frontend_type,
platform_id=platform_id,
@@ -260,9 +259,6 @@ class MenuService:
store_code=store_code,
)
# Convert to legacy format for backwards compatibility with existing templates
return menu_discovery_service.menu_to_legacy_format(sections)
# =========================================================================
# Menu Configuration (Super Admin)
# =========================================================================
@@ -349,10 +345,10 @@ class MenuService:
Returns:
List of MenuItemConfig with current visibility state
"""
shown_items = self._get_shown_items(
db, FrontendType.ADMIN, user_id=user_id
shown_items = self._get_shown_items(db, FrontendType.ADMIN, user_id=user_id)
mandatory_items = menu_discovery_service.get_mandatory_item_ids(
FrontendType.ADMIN
)
mandatory_items = menu_discovery_service.get_mandatory_item_ids(FrontendType.ADMIN)
# Get all menu items from discovery service
all_items = menu_discovery_service.get_all_menu_items(FrontendType.ADMIN)
@@ -576,7 +572,9 @@ class MenuService:
# Create records with is_visible=False for all non-mandatory items
all_items = menu_discovery_service.get_all_menu_item_ids(FrontendType.ADMIN)
mandatory_items = menu_discovery_service.get_mandatory_item_ids(FrontendType.ADMIN)
mandatory_items = menu_discovery_service.get_mandatory_item_ids(
FrontendType.ADMIN
)
for item_id in all_items:
if item_id not in mandatory_items:
@@ -665,7 +663,9 @@ class MenuService:
# Create records with is_visible=True for all non-mandatory items
all_items = menu_discovery_service.get_all_menu_item_ids(FrontendType.ADMIN)
mandatory_items = menu_discovery_service.get_mandatory_item_ids(FrontendType.ADMIN)
mandatory_items = menu_discovery_service.get_mandatory_item_ids(
FrontendType.ADMIN
)
for item_id in all_items:
if item_id not in mandatory_items:
@@ -717,11 +717,17 @@ class MenuService:
return q.filter(AdminMenuConfig.user_id == user_id)
# Check if any visible records exist (valid opt-in config)
visible_count = scope_query().filter(
AdminMenuConfig.is_visible == True # noqa: E712
).count()
visible_count = (
scope_query()
.filter(
AdminMenuConfig.is_visible == True # noqa: E712
)
.count()
)
if visible_count > 0:
logger.debug(f"Config already exists with {visible_count} visible items, skipping init")
logger.debug(
f"Config already exists with {visible_count} visible items, skipping init"
)
return False # Already initialized
# Check if ANY records exist (even is_visible=False from old opt-out model)
@@ -730,7 +736,9 @@ class MenuService:
# Clean up old records first
deleted = scope_query().delete(synchronize_session="fetch")
db.flush() # Ensure deletes are applied before inserts
logger.info(f"Cleaned up {deleted} old menu config records before initialization")
logger.info(
f"Cleaned up {deleted} old menu config records before initialization"
)
# Get all menu items for this frontend
all_items = menu_discovery_service.get_all_menu_item_ids(frontend_type)