# app/modules/marketplace/services/marketplace_widgets.py """ Marketplace dashboard widget provider. Provides widgets for marketplace-related data on store and admin dashboards. Implements the DashboardWidgetProviderProtocol. Widgets provided: - recent_imports: List of recent import jobs with status """ import logging from sqlalchemy.orm import Session from app.modules.contracts.widgets import ( BreakdownWidget, DashboardWidget, ListWidget, WidgetBreakdownItem, WidgetContext, WidgetListItem, ) logger = logging.getLogger(__name__) class MarketplaceWidgetProvider: """ Widget provider for marketplace module. Provides dashboard widgets for import jobs and other marketplace data. """ @property def widgets_category(self) -> str: return "marketplace" def _map_status_to_display(self, status: str) -> str: """Map job status to widget status indicator.""" status_map = { "pending": "neutral", "processing": "warning", "completed": "success", "completed_with_errors": "warning", "failed": "error", } return status_map.get(status, "neutral") def get_store_widgets( self, db: Session, store_id: int, context: WidgetContext | None = None, ) -> list[DashboardWidget]: """ Get marketplace widgets for a store dashboard. Args: db: Database session store_id: ID of the store context: Optional filtering/scoping context Returns: List of DashboardWidget objects for the store """ from app.modules.marketplace.models import MarketplaceImportJob limit = context.limit if context else 5 # Get recent imports for this store jobs = ( db.query(MarketplaceImportJob) .filter(MarketplaceImportJob.store_id == store_id) .order_by(MarketplaceImportJob.created_at.desc()) .limit(limit) .all() ) items = [ WidgetListItem( id=job.id, title=f"Import #{job.id}", subtitle=f"{job.marketplace} - {job.language.upper()}", status=self._map_status_to_display(job.status), timestamp=job.created_at, url=f"/store/marketplace/imports/{job.id}", metadata={ "total_processed": job.total_processed or 0, "imported_count": job.imported_count or 0, "error_count": job.error_count or 0, "status": job.status, }, ) for job in jobs ] # Get total count for "view all" indicator total_count = ( db.query(MarketplaceImportJob) .filter(MarketplaceImportJob.store_id == store_id) .count() ) return [ DashboardWidget( key="marketplace.recent_imports", widget_type="list", title="Recent Imports", category="marketplace", data=ListWidget( items=items, total_count=total_count, view_all_url="/store/marketplace/imports", ), icon="download", description="Latest product import jobs", order=20, ) ] def get_platform_widgets( self, db: Session, platform_id: int, context: WidgetContext | None = None, ) -> list[DashboardWidget]: """ Get marketplace widgets for the admin/platform dashboard. Args: db: Database session platform_id: ID of the platform context: Optional filtering/scoping context Returns: List of DashboardWidget objects for the platform """ from sqlalchemy.orm import joinedload from app.modules.marketplace.models import MarketplaceImportJob from app.modules.tenancy.services.platform_service import platform_service limit = context.limit if context else 5 # Get store IDs for this platform via platform service store_ids = platform_service.get_store_ids_for_platform(db, platform_id) # Get recent imports across all stores in the platform jobs = ( db.query(MarketplaceImportJob) .options(joinedload(MarketplaceImportJob.store)) .filter(MarketplaceImportJob.store_id.in_(store_ids)) .order_by(MarketplaceImportJob.created_at.desc()) .limit(limit) .all() ) items = [ WidgetListItem( id=job.id, title=f"Import #{job.id}", subtitle=job.store.name if job.store else "Unknown Store", status=self._map_status_to_display(job.status), timestamp=job.created_at, url=f"/admin/marketplace/imports/{job.id}", metadata={ "store_id": job.store_id, "store_code": job.store.store_code if job.store else None, "marketplace": job.marketplace, "total_processed": job.total_processed or 0, "imported_count": job.imported_count or 0, "error_count": job.error_count or 0, "status": job.status, }, ) for job in jobs ] # Get total count for platform total_count = ( db.query(MarketplaceImportJob) .filter(MarketplaceImportJob.store_id.in_(store_ids_subquery)) .count() ) widgets = [ DashboardWidget( key="marketplace.recent_imports", widget_type="list", title="Recent Imports", category="marketplace", data=ListWidget( items=items, total_count=total_count, view_all_url="/admin/marketplace/letzshop", ), icon="download", description="Latest product import jobs across all stores", order=20, ) ] # 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.store_name)).label( "unique_stores" ), 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_stores, 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() __all__ = ["MarketplaceWidgetProvider", "marketplace_widget_provider"]