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:
@@ -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
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# app/tasks/letzshop_tasks.py
|
||||
"""Background tasks for Letzshop historical order imports."""
|
||||
"""Background tasks for Letzshop integration."""
|
||||
|
||||
import logging
|
||||
from datetime import UTC, datetime
|
||||
from typing import Callable
|
||||
from typing import Any, Callable
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
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 models.database.letzshop import LetzshopHistoricalImportJob
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -262,3 +263,80 @@ def process_historical_import(job_id: int, vendor_id: int):
|
||||
db.close()
|
||||
except Exception as close_error:
|
||||
logger.error(f"Job {job_id}: Error closing database session: {close_error}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Vendor Directory Sync
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def sync_letzshop_vendor_directory() -> dict[str, Any]:
|
||||
"""
|
||||
Sync Letzshop vendor directory to local cache.
|
||||
|
||||
This task fetches all vendors from Letzshop's public GraphQL API
|
||||
and updates the local letzshop_vendor_cache table.
|
||||
|
||||
Should be run periodically (e.g., daily) via Celery beat.
|
||||
|
||||
Returns:
|
||||
Dictionary with sync statistics.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
stats = {}
|
||||
|
||||
try:
|
||||
logger.info("Starting Letzshop vendor directory sync task...")
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Vendor directory sync failed: {e}", exc_info=True)
|
||||
|
||||
# Notify admins of failure
|
||||
try:
|
||||
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()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise
|
||||
|
||||
finally:
|
||||
if hasattr(db, "close") and callable(db.close):
|
||||
try:
|
||||
db.close()
|
||||
except Exception as close_error:
|
||||
logger.error(f"Error closing database session: {close_error}")
|
||||
|
||||
Reference in New Issue
Block a user