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

@@ -507,3 +507,120 @@ class LetzshopHistoricalImportStartResponse(BaseModel):
job_id: int
status: str = "pending"
message: str = "Historical import job started"
# ============================================================================
# Vendor Directory Schemas (Letzshop Marketplace Cache)
# ============================================================================
class LetzshopCachedVendorItem(BaseModel):
"""Schema for a cached Letzshop vendor in list view."""
id: int
letzshop_id: str
slug: str
name: str
company_name: str | None = None
email: str | None = None
phone: str | None = None
website: str | None = None
city: str | None = None
categories: list[str] = []
is_active: bool = True
is_claimed: bool = False
claimed_by_vendor_id: int | None = None
last_synced_at: datetime | None = None
letzshop_url: str
class LetzshopCachedVendorDetail(BaseModel):
"""Schema for detailed cached Letzshop vendor."""
id: int
letzshop_id: str
slug: str
name: str
company_name: str | None = None
description_en: str | None = None
description_fr: str | None = None
description_de: str | None = None
email: str | None = None
phone: str | None = None
fax: str | None = None
website: str | None = None
street: str | None = None
street_number: str | None = None
city: str | None = None
zipcode: str | None = None
country_iso: str | None = None
latitude: str | None = None
longitude: str | None = None
categories: list[str] = []
background_image_url: str | None = None
social_media_links: list[str] = []
opening_hours_en: str | None = None
opening_hours_fr: str | None = None
opening_hours_de: str | None = None
representative_name: str | None = None
representative_title: str | None = None
is_active: bool = True
is_claimed: bool = False
claimed_by_vendor_id: int | None = None
claimed_at: datetime | None = None
last_synced_at: datetime | None = None
letzshop_url: str
class LetzshopVendorDirectoryStats(BaseModel):
"""Schema for vendor directory cache statistics."""
total_vendors: int = 0
active_vendors: int = 0
claimed_vendors: int = 0
unclaimed_vendors: int = 0
unique_cities: int = 0
last_synced_at: str | None = None
class LetzshopVendorDirectoryStatsResponse(BaseModel):
"""Response schema for vendor directory stats endpoint."""
success: bool = True
stats: LetzshopVendorDirectoryStats
class LetzshopCachedVendorListResponse(BaseModel):
"""Response schema for vendor directory list endpoint."""
success: bool = True
vendors: list[LetzshopCachedVendorItem]
total: int
page: int
limit: int
has_more: bool
class LetzshopCachedVendorDetailResponse(BaseModel):
"""Response schema for vendor directory detail endpoint."""
success: bool = True
vendor: LetzshopCachedVendorDetail
class LetzshopVendorDirectorySyncResponse(BaseModel):
"""Response schema for vendor directory sync trigger."""
success: bool = True
message: str
task_id: str | None = None
mode: str = "celery"
class LetzshopCreateVendorFromCacheResponse(BaseModel):
"""Response schema for creating vendor from Letzshop cache."""
success: bool = True
message: str
vendor: dict[str, Any] | None = None
letzshop_vendor_slug: str