refactor: fix architecture violations with provider patterns and dependency inversion

Major changes:
- Add AuditProvider protocol for cross-module audit logging
- Move customer order operations to orders module (dependency inversion)
- Add customer order metrics via MetricsProvider pattern
- Fix missing db parameter in get_admin_context() calls
- Move ProductMedia relationship to catalog module (proper ownership)
- Add marketplace breakdown stats to marketplace_widgets

New files:
- contracts/audit.py - AuditProviderProtocol
- core/services/audit_aggregator.py - Aggregates audit providers
- monitoring/services/audit_provider.py - Monitoring audit implementation
- orders/services/customer_order_service.py - Customer order operations
- orders/routes/api/vendor_customer_orders.py - Customer order endpoints
- catalog/services/product_media_service.py - Product media service
- Architecture documentation for patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 21:32:32 +01:00
parent bd43e21940
commit 39dff4ab7d
34 changed files with 2751 additions and 407 deletions

View File

@@ -14,9 +14,11 @@ import logging
from sqlalchemy.orm import Session
from app.modules.contracts.widgets import (
BreakdownWidget,
DashboardWidget,
DashboardWidgetProviderProtocol,
ListWidget,
WidgetBreakdownItem,
WidgetContext,
WidgetListItem,
)
@@ -187,7 +189,7 @@ class MarketplaceWidgetProvider:
.count()
)
return [
widgets = [
DashboardWidget(
key="marketplace.recent_imports",
widget_type="list",
@@ -204,6 +206,82 @@ class MarketplaceWidgetProvider:
)
]
# Add marketplace breakdown widget
breakdown_widget = self._get_marketplace_breakdown_widget(db)
if breakdown_widget:
widgets.append(breakdown_widget)
return widgets
def _get_marketplace_breakdown_widget(
self,
db: Session,
) -> DashboardWidget | None:
"""
Get a breakdown widget showing statistics per marketplace.
Returns:
DashboardWidget with BreakdownWidget data, or None if no data
"""
from sqlalchemy import func
from app.modules.marketplace.models import MarketplaceProduct
try:
marketplace_stats = (
db.query(
MarketplaceProduct.marketplace,
func.count(MarketplaceProduct.id).label("total_products"),
func.count(func.distinct(MarketplaceProduct.vendor_name)).label(
"unique_vendors"
),
func.count(func.distinct(MarketplaceProduct.brand)).label(
"unique_brands"
),
)
.filter(MarketplaceProduct.marketplace.isnot(None))
.group_by(MarketplaceProduct.marketplace)
.all()
)
if not marketplace_stats:
return None
total_products = sum(stat.total_products for stat in marketplace_stats)
breakdown_items = [
WidgetBreakdownItem(
label=stat.marketplace or "Unknown",
value=stat.total_products,
secondary_value=stat.unique_vendors,
percentage=(
round(stat.total_products / total_products * 100, 1)
if total_products > 0
else 0
),
icon="globe",
)
for stat in marketplace_stats
]
return DashboardWidget(
key="marketplace.breakdown",
widget_type="breakdown",
title="Products by Marketplace",
category="marketplace",
data=BreakdownWidget(
items=breakdown_items,
total=total_products,
),
icon="chart-pie",
description="Product distribution across marketplaces",
order=30,
)
except Exception as e:
logger.warning(f"Failed to get marketplace breakdown widget: {e}")
return None
# Singleton instance
marketplace_widget_provider = MarketplaceWidgetProvider()