feat: add Letzshop vendor directory with sync and admin management

- Add LetzshopVendorCache model to store cached vendor data from Letzshop API
- Create LetzshopVendorSyncService for syncing vendor directory
- Add Celery task for background vendor sync
- Create admin page at /admin/letzshop/vendor-directory with:
  - Stats dashboard (total, claimed, unclaimed vendors)
  - Searchable/filterable vendor list
  - "Sync Now" button to trigger sync
  - Ability to create platform vendors from Letzshop cache
- Add API endpoints for vendor directory management
- Add Pydantic schemas for API responses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 20:35:46 +01:00
parent 78b14a4b00
commit ccfbbcb804
13 changed files with 2571 additions and 46 deletions

View File

@@ -1,19 +1,22 @@
# app/tasks/celery_tasks/letzshop.py
"""
Celery tasks for Letzshop historical order imports.
Celery tasks for Letzshop integration.
Wraps the existing process_historical_import function for Celery execution.
Includes:
- Historical order imports
- Vendor directory sync
"""
import logging
from datetime import UTC, datetime
from typing import Callable
from typing import Any, Callable
from app.core.celery_config import celery_app
from app.services.admin_notification_service import admin_notification_service
from app.services.letzshop import LetzshopClientError
from app.services.letzshop.credentials_service import LetzshopCredentialsService
from app.services.letzshop.order_service import LetzshopOrderService
from app.services.letzshop.vendor_sync_service import LetzshopVendorSyncService
from app.tasks.celery_tasks.base import DatabaseTask
from models.database.letzshop import LetzshopHistoricalImportJob
@@ -270,3 +273,78 @@ def process_historical_import(self, job_id: int, vendor_id: int):
db.commit()
raise # Re-raise for Celery retry
# =============================================================================
# Vendor Directory Sync
# =============================================================================
@celery_app.task(
bind=True,
base=DatabaseTask,
name="app.tasks.celery_tasks.letzshop.sync_vendor_directory",
max_retries=2,
default_retry_delay=300,
autoretry_for=(Exception,),
retry_backoff=True,
)
def sync_vendor_directory(self) -> dict[str, Any]:
"""
Celery task to sync Letzshop vendor directory.
Fetches all vendors from Letzshop's public GraphQL API and updates
the local letzshop_vendor_cache table.
This task should be scheduled to run periodically (e.g., daily)
via Celery beat.
Returns:
dict: Sync statistics including created, updated, and error counts.
"""
with self.get_db() as db:
try:
logger.info("Starting Letzshop vendor directory sync...")
sync_service = LetzshopVendorSyncService(db)
def progress_callback(page: int, fetched: int, total: int):
"""Log progress during sync."""
logger.info(f"Vendor sync progress: page {page}, {fetched}/{total} vendors")
stats = sync_service.sync_all_vendors(progress_callback=progress_callback)
logger.info(
f"Vendor directory sync completed: "
f"{stats.get('created', 0)} created, "
f"{stats.get('updated', 0)} updated, "
f"{stats.get('errors', 0)} errors"
)
# Send admin notification if there were errors
if stats.get("errors", 0) > 0:
admin_notification_service.notify_system_info(
db=db,
title="Letzshop Vendor Sync Completed with Errors",
message=(
f"Synced {stats.get('total_fetched', 0)} vendors. "
f"Errors: {stats.get('errors', 0)}"
),
details=stats,
)
db.commit()
return stats
except Exception as e:
logger.error(f"Vendor directory sync failed: {e}", exc_info=True)
# Notify admins of failure
admin_notification_service.notify_critical_error(
db=db,
error_type="Vendor Directory Sync",
error_message=f"Failed to sync Letzshop vendor directory: {str(e)[:200]}",
details={"error": str(e)},
)
db.commit()
raise # Re-raise for Celery retry