feat: enhance Letzshop order import with EAN matching and stats

- Add historical order import with pagination support
- Add customer_locale, shipping_country_iso, billing_country_iso columns
- Add gtin/gtin_type columns to Product table for EAN matching
- Fix order stats to count all orders server-side (not just visible page)
- Add GraphQL introspection script with tracking workaround tests
- Enrich inventory units with EAN, MPN, SKU, product name
- Add LetzshopOrderStats schema for proper status counts

Migrations:
- a9a86cef6cca: Add locale and country fields to letzshop_orders
- cb88bc9b5f86: Add gtin columns to products table

🤖 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-18 21:04:33 +01:00
parent 6d6c8b44d3
commit 0ab10128ae
17 changed files with 3451 additions and 94 deletions

View File

@@ -361,6 +361,9 @@ def list_vendor_letzshop_orders(
sync_status=sync_status,
)
# Get order stats for all statuses
stats = order_service.get_order_stats(vendor_id)
return LetzshopOrderListResponse(
orders=[
LetzshopOrderResponse(
@@ -392,6 +395,7 @@ def list_vendor_letzshop_orders(
total=total,
skip=skip,
limit=limit,
stats=stats,
)
@@ -430,6 +434,10 @@ def trigger_vendor_sync(
try:
with creds_service.create_client(vendor_id) as client:
shipments = client.get_unconfirmed_shipments()
logger.info(
f"Letzshop sync for vendor {vendor_id}: "
f"fetched {len(shipments)} unconfirmed shipments from API"
)
orders_imported = 0
orders_updated = 0
@@ -524,3 +532,118 @@ def list_vendor_letzshop_jobs(
jobs = [LetzshopJobItem(**job) for job in jobs_data]
return LetzshopJobsListResponse(jobs=jobs, total=total)
# ============================================================================
# Historical Import
# ============================================================================
@router.post(
"/vendors/{vendor_id}/import-history",
)
def import_historical_orders(
vendor_id: int = Path(..., description="Vendor ID"),
state: str = Query("confirmed", description="Shipment state to import"),
max_pages: int | None = Query(None, ge=1, le=100, description="Max pages to fetch"),
match_products: bool = Query(True, description="Match EANs to local products"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Import historical orders from Letzshop.
Fetches all shipments with the specified state (default: confirmed)
and imports them into the database. Supports pagination and EAN matching.
Returns statistics on imported/updated/skipped orders and product matching.
"""
order_service = get_order_service(db)
creds_service = get_credentials_service(db)
try:
vendor = order_service.get_vendor_or_raise(vendor_id)
except VendorNotFoundError:
raise ResourceNotFoundException("Vendor", str(vendor_id))
# Verify credentials exist
try:
creds_service.get_credentials_or_raise(vendor_id)
except CredentialsNotFoundError:
raise ValidationException(
f"Letzshop credentials not configured for vendor {vendor.name}"
)
# Fetch all shipments with pagination
try:
with creds_service.create_client(vendor_id) as client:
logger.info(
f"Starting historical import for vendor {vendor_id}, state={state}, max_pages={max_pages}"
)
shipments = client.get_all_shipments_paginated(
state=state,
page_size=50,
max_pages=max_pages,
)
logger.info(f"Fetched {len(shipments)} {state} shipments from Letzshop")
# Import shipments
stats = order_service.import_historical_shipments(
vendor_id=vendor_id,
shipments=shipments,
match_products=match_products,
)
db.commit()
# Update sync status
creds_service.update_sync_status(
vendor_id,
"success",
None,
)
logger.info(
f"Historical import completed: {stats['imported']} imported, "
f"{stats['updated']} updated, {stats['skipped']} skipped"
)
return {
"success": True,
"message": f"Historical import completed: {stats['imported']} imported, {stats['updated']} updated",
"statistics": stats,
}
except LetzshopClientError as e:
creds_service.update_sync_status(vendor_id, "failed", str(e))
raise ValidationException(f"Letzshop API error: {e}")
@router.get(
"/vendors/{vendor_id}/import-summary",
)
def get_import_summary(
vendor_id: int = Path(..., description="Vendor ID"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Get summary statistics for imported Letzshop orders.
Returns total orders, unique customers, and breakdowns by state/locale/country.
"""
order_service = get_order_service(db)
try:
order_service.get_vendor_or_raise(vendor_id)
except VendorNotFoundError:
raise ResourceNotFoundException("Vendor", str(vendor_id))
summary = order_service.get_historical_import_summary(vendor_id)
return {
"success": True,
"summary": summary,
}