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:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user