From bedc979b12c9f42e8462d74432bc4c74a28cce3e Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Thu, 25 Dec 2025 00:30:33 +0100 Subject: [PATCH] feat: show Jobs tab for all vendors when no filter selected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /admin/letzshop/jobs API endpoint for all jobs across vendors - Update list_letzshop_jobs service to support optional vendor_id - Remove x-if condition from Jobs tab button and panel - Update JS to use global or vendor-specific endpoint based on selection - Update jobs table subtitle to show context 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/api/v1/admin/letzshop.py | 31 +++++++++ app/services/letzshop/order_service.py | 67 ++++++++++++------- app/templates/admin/marketplace-letzshop.html | 14 ++-- .../admin/partials/letzshop-jobs-table.html | 5 +- static/admin/js/marketplace-letzshop.js | 20 +++--- 5 files changed, 91 insertions(+), 46 deletions(-) diff --git a/app/api/v1/admin/letzshop.py b/app/api/v1/admin/letzshop.py index 471603dc..53059480 100644 --- a/app/api/v1/admin/letzshop.py +++ b/app/api/v1/admin/letzshop.py @@ -702,6 +702,37 @@ def trigger_vendor_sync( # ============================================================================ +@router.get( + "/jobs", + response_model=LetzshopJobsListResponse, +) +def list_all_letzshop_jobs( + job_type: str | None = Query(None, description="Filter: import, export, order_sync, historical_import"), + status: str | None = Query(None, description="Filter by status"), + skip: int = Query(0, ge=0), + limit: int = Query(20, ge=1, le=100), + db: Session = Depends(get_db), + current_admin: User = Depends(get_current_admin_api), +): + """ + Get unified list of all Letzshop-related jobs across all vendors. + Combines product imports, exports, and order syncs. + """ + order_service = get_order_service(db) + + jobs_data, total = order_service.list_letzshop_jobs( + vendor_id=None, + job_type=job_type, + status=status, + skip=skip, + limit=limit, + ) + + jobs = [LetzshopJobItem(**job) for job in jobs_data] + + return LetzshopJobsListResponse(jobs=jobs, total=total) + + @router.get( "/vendors/{vendor_id}/jobs", response_model=LetzshopJobsListResponse, diff --git a/app/services/letzshop/order_service.py b/app/services/letzshop/order_service.py index 116d59fb..53f835b0 100644 --- a/app/services/letzshop/order_service.py +++ b/app/services/letzshop/order_service.py @@ -607,29 +607,37 @@ class LetzshopOrderService: def list_letzshop_jobs( self, - vendor_id: int, + vendor_id: int | None = None, job_type: str | None = None, status: str | None = None, skip: int = 0, limit: int = 20, ) -> tuple[list[dict[str, Any]], int]: """ - List unified Letzshop-related jobs for a vendor. + List unified Letzshop-related jobs for a vendor or all vendors. Combines product imports, historical order imports, and order syncs. + If vendor_id is None, returns jobs across all vendors. """ jobs = [] - # Fetch vendor info once for all jobs - vendor = self.get_vendor(vendor_id) - vendor_name = vendor.name if vendor else None - vendor_code = vendor.vendor_code if vendor else None + # Fetch vendor info - for single vendor or build lookup for all vendors + if vendor_id: + vendor = self.get_vendor(vendor_id) + vendor_lookup = {vendor_id: (vendor.name if vendor else None, vendor.vendor_code if vendor else None)} + else: + # Build lookup for all vendors when showing all jobs + from models.database.vendor import Vendor + vendors = self.db.query(Vendor.id, Vendor.name, Vendor.vendor_code).all() + vendor_lookup = {v.id: (v.name, v.vendor_code) for v in vendors} # Historical order imports from letzshop_historical_import_jobs if job_type in (None, "historical_import"): - hist_query = self.db.query(LetzshopHistoricalImportJob).filter( - LetzshopHistoricalImportJob.vendor_id == vendor_id, - ) + hist_query = self.db.query(LetzshopHistoricalImportJob) + if vendor_id: + hist_query = hist_query.filter( + LetzshopHistoricalImportJob.vendor_id == vendor_id, + ) if status: hist_query = hist_query.filter( LetzshopHistoricalImportJob.status == status @@ -640,6 +648,7 @@ class LetzshopOrderService: ).all() for job in hist_jobs: + v_name, v_code = vendor_lookup.get(job.vendor_id, (None, None)) jobs.append( { "id": job.id, @@ -652,9 +661,9 @@ class LetzshopOrderService: "records_succeeded": (job.orders_imported or 0) + (job.orders_updated or 0), "records_failed": job.orders_skipped or 0, - "vendor_id": vendor_id, - "vendor_name": vendor_name, - "vendor_code": vendor_code, + "vendor_id": job.vendor_id, + "vendor_name": v_name, + "vendor_code": v_code, "current_phase": job.current_phase, "error_message": job.error_message, } @@ -663,9 +672,12 @@ class LetzshopOrderService: # Product imports from marketplace_import_jobs if job_type in (None, "import"): import_query = self.db.query(MarketplaceImportJob).filter( - MarketplaceImportJob.vendor_id == vendor_id, MarketplaceImportJob.marketplace == "Letzshop", ) + if vendor_id: + import_query = import_query.filter( + MarketplaceImportJob.vendor_id == vendor_id, + ) if status: import_query = import_query.filter( MarketplaceImportJob.status == status @@ -676,6 +688,7 @@ class LetzshopOrderService: ).all() for job in import_jobs: + v_name, v_code = vendor_lookup.get(job.vendor_id, (None, None)) jobs.append( { "id": job.id, @@ -688,24 +701,26 @@ class LetzshopOrderService: "records_succeeded": (job.imported_count or 0) + (job.updated_count or 0), "records_failed": job.error_count or 0, - "vendor_id": vendor_id, - "vendor_name": vendor_name, - "vendor_code": vendor_code, + "vendor_id": job.vendor_id, + "vendor_name": v_name, + "vendor_code": v_code, } ) # Order syncs from letzshop_sync_logs if job_type in (None, "order_sync"): sync_query = self.db.query(LetzshopSyncLog).filter( - LetzshopSyncLog.vendor_id == vendor_id, LetzshopSyncLog.operation_type == "order_import", ) + if vendor_id: + sync_query = sync_query.filter(LetzshopSyncLog.vendor_id == vendor_id) if status: sync_query = sync_query.filter(LetzshopSyncLog.status == status) sync_logs = sync_query.order_by(LetzshopSyncLog.created_at.desc()).all() for log in sync_logs: + v_name, v_code = vendor_lookup.get(log.vendor_id, (None, None)) jobs.append( { "id": log.id, @@ -717,9 +732,9 @@ class LetzshopOrderService: "records_processed": log.records_processed or 0, "records_succeeded": log.records_succeeded or 0, "records_failed": log.records_failed or 0, - "vendor_id": vendor_id, - "vendor_name": vendor_name, - "vendor_code": vendor_code, + "vendor_id": log.vendor_id, + "vendor_name": v_name, + "vendor_code": v_code, "error_details": log.error_details, } ) @@ -727,9 +742,10 @@ class LetzshopOrderService: # Product exports from letzshop_sync_logs if job_type in (None, "export"): export_query = self.db.query(LetzshopSyncLog).filter( - LetzshopSyncLog.vendor_id == vendor_id, LetzshopSyncLog.operation_type == "product_export", ) + if vendor_id: + export_query = export_query.filter(LetzshopSyncLog.vendor_id == vendor_id) if status: export_query = export_query.filter(LetzshopSyncLog.status == status) @@ -738,6 +754,7 @@ class LetzshopOrderService: ).all() for log in export_logs: + v_name, v_code = vendor_lookup.get(log.vendor_id, (None, None)) jobs.append( { "id": log.id, @@ -749,10 +766,10 @@ class LetzshopOrderService: "records_processed": log.records_processed or 0, "records_succeeded": log.records_succeeded or 0, "records_failed": log.records_failed or 0, - "vendor_id": vendor_id, - "vendor_name": vendor_name, - "vendor_code": vendor_code, - "error_details": log.error_details, # Include export file details + "vendor_id": log.vendor_id, + "vendor_name": v_name, + "vendor_code": v_code, + "error_details": log.error_details, } ) diff --git a/app/templates/admin/marketplace-letzshop.html b/app/templates/admin/marketplace-letzshop.html index 7cfd5573..3451a0a9 100644 --- a/app/templates/admin/marketplace-letzshop.html +++ b/app/templates/admin/marketplace-letzshop.html @@ -125,9 +125,7 @@ {{ tab_button('products', 'Products', tab_var='activeTab', icon='cube') }} {{ tab_button('orders', 'Orders', tab_var='activeTab', icon='shopping-cart', count_var='orderStats.pending') }} {{ tab_button('exceptions', 'Exceptions', tab_var='activeTab', icon='exclamation-circle', count_var='exceptionStats.pending') }} - + {{ tab_button('jobs', 'Jobs', tab_var='activeTab', icon='collection') }} @@ -155,12 +153,10 @@ {% include 'admin/partials/letzshop-exceptions-tab.html' %} {{ endtab_panel() }} - - + + {{ tab_panel('jobs', tab_var='activeTab') }} + {% include 'admin/partials/letzshop-jobs-table.html' %} + {{ endtab_panel() }} diff --git a/app/templates/admin/partials/letzshop-jobs-table.html b/app/templates/admin/partials/letzshop-jobs-table.html index 034ef720..7ed21f7d 100644 --- a/app/templates/admin/partials/letzshop-jobs-table.html +++ b/app/templates/admin/partials/letzshop-jobs-table.html @@ -6,7 +6,10 @@

Recent Jobs

-

Product imports, exports, and order sync history

+

+ Product imports, exports, and order sync history + All Letzshop jobs across all vendors +