feat: add unified admin Marketplace Letzshop page
- Add new Marketplace section in admin sidebar with Letzshop sub-item
- Remove old Import and Letzshop Orders items from Product Catalog
- Create unified Letzshop management page with 3 tabs:
- Products tab: Import/Export functionality
- Orders tab: Order management with confirm/reject/tracking
- Settings tab: API credentials and CSV URLs
- Add unified jobs table showing imports, exports, and order syncs
- Implement vendor autocomplete using Tom Select library (CDN + fallback)
- Add /vendors/{vendor_id}/jobs API endpoint for unified job listing
- Move database queries to service layer (LetzshopOrderService)
- Add LetzshopJobItem and LetzshopJobsListResponse schemas
- Include Tom Select CSS/JS assets as local fallback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from models.database.letzshop import (
|
||||
LetzshopSyncLog,
|
||||
VendorLetzshopCredentials,
|
||||
)
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -316,3 +317,101 @@ class LetzshopOrderService:
|
||||
.all()
|
||||
)
|
||||
return items, total
|
||||
|
||||
# =========================================================================
|
||||
# Unified Jobs Operations
|
||||
# =========================================================================
|
||||
|
||||
def list_letzshop_jobs(
|
||||
self,
|
||||
vendor_id: int,
|
||||
job_type: str | None = None,
|
||||
status: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 20,
|
||||
) -> tuple[list[dict[str, Any]], int]:
|
||||
"""
|
||||
List unified Letzshop-related jobs for a vendor.
|
||||
|
||||
Combines product imports from marketplace_import_jobs and
|
||||
order syncs from letzshop_sync_logs.
|
||||
|
||||
Args:
|
||||
vendor_id: Vendor ID
|
||||
job_type: Filter by type ('import', 'order_sync', or None for all)
|
||||
status: Filter by status
|
||||
skip: Pagination offset
|
||||
limit: Pagination limit
|
||||
|
||||
Returns:
|
||||
Tuple of (jobs_list, total_count) where jobs_list contains dicts
|
||||
with id, type, status, created_at, started_at, completed_at,
|
||||
records_processed, records_succeeded, records_failed.
|
||||
"""
|
||||
jobs = []
|
||||
|
||||
# Product imports from marketplace_import_jobs
|
||||
if job_type in (None, "import"):
|
||||
import_query = self.db.query(MarketplaceImportJob).filter(
|
||||
MarketplaceImportJob.vendor_id == vendor_id,
|
||||
MarketplaceImportJob.marketplace == "Letzshop",
|
||||
)
|
||||
if status:
|
||||
import_query = import_query.filter(
|
||||
MarketplaceImportJob.status == status
|
||||
)
|
||||
|
||||
import_jobs = import_query.order_by(
|
||||
MarketplaceImportJob.created_at.desc()
|
||||
).all()
|
||||
|
||||
for job in import_jobs:
|
||||
jobs.append(
|
||||
{
|
||||
"id": job.id,
|
||||
"type": "import",
|
||||
"status": job.status,
|
||||
"created_at": job.created_at,
|
||||
"started_at": job.started_at,
|
||||
"completed_at": job.completed_at,
|
||||
"records_processed": job.total_processed or 0,
|
||||
"records_succeeded": (job.imported_count or 0)
|
||||
+ (job.updated_count or 0),
|
||||
"records_failed": job.error_count or 0,
|
||||
}
|
||||
)
|
||||
|
||||
# Order syncs from letzshop_sync_logs
|
||||
if job_type in (None, "order_sync"):
|
||||
sync_query = self.db.query(LetzshopSyncLog).filter(
|
||||
LetzshopSyncLog.vendor_id == vendor_id,
|
||||
LetzshopSyncLog.operation_type == "order_import",
|
||||
)
|
||||
if status:
|
||||
sync_query = sync_query.filter(LetzshopSyncLog.status == status)
|
||||
|
||||
sync_logs = sync_query.order_by(LetzshopSyncLog.created_at.desc()).all()
|
||||
|
||||
for log in sync_logs:
|
||||
jobs.append(
|
||||
{
|
||||
"id": log.id,
|
||||
"type": "order_sync",
|
||||
"status": log.status,
|
||||
"created_at": log.created_at,
|
||||
"started_at": log.started_at,
|
||||
"completed_at": log.completed_at,
|
||||
"records_processed": log.records_processed or 0,
|
||||
"records_succeeded": log.records_succeeded or 0,
|
||||
"records_failed": log.records_failed or 0,
|
||||
}
|
||||
)
|
||||
|
||||
# Sort all jobs by created_at descending
|
||||
jobs.sort(key=lambda x: x["created_at"], reverse=True)
|
||||
|
||||
# Get total count and apply pagination
|
||||
total = len(jobs)
|
||||
jobs = jobs[skip : skip + limit]
|
||||
|
||||
return jobs, total
|
||||
|
||||
Reference in New Issue
Block a user