fix: Letzshop page improvements - products, jobs, and tabs

1. Products tab now shows Letzshop marketplace products instead of vendor products
   - Uses /admin/products endpoint with marketplace=Letzshop filter
   - Fixed field names (image_link, price_numeric, sku vs vendor_sku)
   - Search now works with title, GTIN, SKU, brand

2. Jobs section moved to a separate tab
   - New "Jobs" tab between Exceptions and Settings
   - Tab watcher reloads data when switching tabs
   - Updated filter dropdown (removed export, added historical_import)

3. Product stats endpoint now accepts marketplace and vendor_name filters

🤖 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 22:34:55 +01:00
parent 2bdf0a977a
commit ee690e95c9
6 changed files with 102 additions and 55 deletions

View File

@@ -681,36 +681,59 @@ class MarketplaceProductService:
return result, total
def get_admin_product_stats(self, db: Session) -> dict:
"""Get product statistics for admin dashboard."""
def get_admin_product_stats(
self,
db: Session,
marketplace: str | None = None,
vendor_name: str | None = None,
) -> dict:
"""Get product statistics for admin dashboard.
Args:
db: Database session
marketplace: Optional filter by marketplace (e.g., 'Letzshop')
vendor_name: Optional filter by vendor name
"""
from sqlalchemy import func
total = db.query(func.count(MarketplaceProduct.id)).scalar() or 0
# Build base filter
base_filters = []
if marketplace:
base_filters.append(MarketplaceProduct.marketplace == marketplace)
if vendor_name:
base_filters.append(MarketplaceProduct.vendor_name == vendor_name)
active = (
db.query(func.count(MarketplaceProduct.id))
.filter(MarketplaceProduct.is_active == True) # noqa: E712
.scalar()
or 0
base_query = db.query(func.count(MarketplaceProduct.id))
if base_filters:
base_query = base_query.filter(*base_filters)
total = base_query.scalar() or 0
active_query = db.query(func.count(MarketplaceProduct.id)).filter(
MarketplaceProduct.is_active == True # noqa: E712
)
if base_filters:
active_query = active_query.filter(*base_filters)
active = active_query.scalar() or 0
inactive = total - active
digital = (
db.query(func.count(MarketplaceProduct.id))
.filter(MarketplaceProduct.is_digital == True) # noqa: E712
.scalar()
or 0
digital_query = db.query(func.count(MarketplaceProduct.id)).filter(
MarketplaceProduct.is_digital == True # noqa: E712
)
if base_filters:
digital_query = digital_query.filter(*base_filters)
digital = digital_query.scalar() or 0
physical = total - digital
marketplace_counts = (
db.query(
MarketplaceProduct.marketplace,
func.count(MarketplaceProduct.id),
)
.group_by(MarketplaceProduct.marketplace)
.all()
marketplace_query = db.query(
MarketplaceProduct.marketplace,
func.count(MarketplaceProduct.id),
)
if base_filters:
marketplace_query = marketplace_query.filter(*base_filters)
marketplace_counts = marketplace_query.group_by(
MarketplaceProduct.marketplace
).all()
by_marketplace = {mp or "unknown": count for mp, count in marketplace_counts}
return {