feat: redesign Letzshop products tab with product listing view

Products Tab Changes:
- Converted to product listing page similar to /admin/marketplace-products
- Added Import/Export buttons in header
- Added product stats cards (total, active, inactive, last sync)
- Added search and filter functionality
- Added product table with pagination
- Import modal for single URL or all languages

Settings Tab Changes:
- Moved batch size setting from products tab
- Moved include inactive checkbox from products tab
- Added export behavior info box

Export Changes:
- New POST endpoint exports all languages (FR, DE, EN)
- CSV files written to exports/letzshop/{vendor_code}/ for scheduler pickup
- Letzshop scheduler can fetch files from this location

API Changes:
- Added vendor_id filter to /admin/vendor-products/stats endpoint
- Added POST /admin/vendors/{id}/export/letzshop for folder export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-20 21:44:59 +01:00
parent 44c11181fd
commit d46b676e77
6 changed files with 692 additions and 258 deletions

View File

@@ -206,11 +206,12 @@ def get_vendor_products(
@router.get("/stats", response_model=VendorProductStats)
def get_vendor_product_stats(
vendor_id: int | None = Query(None, description="Filter stats by vendor ID"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""Get vendor product statistics for admin dashboard."""
stats = vendor_product_service.get_product_stats(db)
stats = vendor_product_service.get_product_stats(db, vendor_id=vendor_id)
return VendorProductStats(**stats)

View File

@@ -363,3 +363,83 @@ def export_vendor_products_letzshop(
"Content-Disposition": f'attachment; filename="{filename}"',
},
)
class LetzshopExportRequest(BaseModel):
"""Request body for Letzshop export to pickup folder."""
include_inactive: bool = False
@router.post("/{vendor_identifier}/export/letzshop")
def export_vendor_products_letzshop_to_folder(
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
request: LetzshopExportRequest = None,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Export vendor products to Letzshop pickup folder (Admin only).
Generates CSV files for all languages (FR, DE, EN) and places them in a folder
that Letzshop scheduler can fetch from. This is the preferred method for
automated product sync.
**Behavior:**
- Creates CSV files for each language (fr, de, en)
- Places files in: exports/letzshop/{vendor_code}/
- Filename format: {vendor_code}_products_{language}.csv
Returns:
JSON with export status and file paths
"""
import os
from pathlib import Path as FilePath
from app.services.letzshop_export_service import letzshop_export_service
vendor = vendor_service.get_vendor_by_identifier(db, vendor_identifier)
include_inactive = request.include_inactive if request else False
# Create export directory
export_dir = FilePath(f"exports/letzshop/{vendor.vendor_code.lower()}")
export_dir.mkdir(parents=True, exist_ok=True)
exported_files = []
languages = ["fr", "de", "en"]
for lang in languages:
try:
csv_content = letzshop_export_service.export_vendor_products(
db=db,
vendor_id=vendor.id,
language=lang,
include_inactive=include_inactive,
)
filename = f"{vendor.vendor_code.lower()}_products_{lang}.csv"
filepath = export_dir / filename
with open(filepath, "w", encoding="utf-8") as f:
f.write(csv_content)
exported_files.append({
"language": lang,
"filename": filename,
"path": str(filepath),
"size_bytes": os.path.getsize(filepath),
})
except Exception as e:
exported_files.append({
"language": lang,
"error": str(e),
})
return {
"success": True,
"message": f"Exported {len([f for f in exported_files if 'error' not in f])} language(s) to {export_dir}",
"vendor_code": vendor.vendor_code,
"export_directory": str(export_dir),
"files": exported_files,
}