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:
@@ -94,6 +94,13 @@ class LetzshopOrder(Base, TimestampMixin):
|
||||
) # Store as string to preserve format
|
||||
currency = Column(String(10), default="EUR")
|
||||
|
||||
# Customer preferences (for invoicing)
|
||||
customer_locale = Column(String(10), nullable=True) # en, fr, de
|
||||
|
||||
# Shipping/billing country
|
||||
shipping_country_iso = Column(String(5), nullable=True) # LU, DE, FR, etc.
|
||||
billing_country_iso = Column(String(5), nullable=True)
|
||||
|
||||
# Raw data storage (for debugging/auditing)
|
||||
raw_order_data = Column(JSON, nullable=True)
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ class Product(Base, TimestampMixin):
|
||||
# === VENDOR REFERENCE ===
|
||||
vendor_sku = Column(String, index=True) # Vendor's internal SKU
|
||||
|
||||
# === PRODUCT IDENTIFIERS ===
|
||||
# GTIN (Global Trade Item Number) - barcode for EAN matching with orders
|
||||
# Populated from MarketplaceProduct.gtin during product import
|
||||
gtin = Column(String(50), index=True) # EAN/UPC barcode number
|
||||
gtin_type = Column(String(20)) # Format: gtin13, gtin14, gtin12, gtin8, isbn13, isbn10
|
||||
|
||||
# === OVERRIDABLE FIELDS (NULL = inherit from marketplace_product) ===
|
||||
# Pricing
|
||||
price = Column(Float)
|
||||
@@ -209,14 +215,37 @@ class Product(Base, TimestampMixin):
|
||||
|
||||
# === INVENTORY PROPERTIES ===
|
||||
|
||||
# Constant for unlimited inventory (digital products)
|
||||
UNLIMITED_INVENTORY = 999999
|
||||
|
||||
@property
|
||||
def has_unlimited_inventory(self) -> bool:
|
||||
"""Check if product has unlimited inventory.
|
||||
|
||||
Digital products have unlimited inventory by default.
|
||||
They don't require physical stock tracking.
|
||||
"""
|
||||
return self.is_digital
|
||||
|
||||
@property
|
||||
def total_inventory(self) -> int:
|
||||
"""Calculate total inventory across all locations."""
|
||||
"""Calculate total inventory across all locations.
|
||||
|
||||
Digital products return unlimited inventory.
|
||||
"""
|
||||
if self.has_unlimited_inventory:
|
||||
return self.UNLIMITED_INVENTORY
|
||||
return sum(inv.quantity for inv in self.inventory_entries)
|
||||
|
||||
@property
|
||||
def available_inventory(self) -> int:
|
||||
"""Calculate available inventory (total - reserved)."""
|
||||
"""Calculate available inventory (total - reserved).
|
||||
|
||||
Digital products return unlimited inventory since they
|
||||
don't have physical stock constraints.
|
||||
"""
|
||||
if self.has_unlimited_inventory:
|
||||
return self.UNLIMITED_INVENTORY
|
||||
return sum(inv.available_quantity for inv in self.inventory_entries)
|
||||
|
||||
# === OVERRIDE INFO METHOD ===
|
||||
|
||||
Reference in New Issue
Block a user