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:
@@ -227,3 +227,131 @@ class TestProductModel:
|
||||
assert info["brand"] == "SourceBrand"
|
||||
assert info["brand_overridden"] is False
|
||||
assert info["brand_source"] == "SourceBrand"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.database
|
||||
@pytest.mark.inventory
|
||||
class TestProductInventoryProperties:
|
||||
"""Test Product inventory properties including digital product handling."""
|
||||
|
||||
def test_physical_product_no_inventory_returns_zero(
|
||||
self, db, test_vendor, test_marketplace_product
|
||||
):
|
||||
"""Test physical product with no inventory entries returns 0."""
|
||||
# Ensure product is physical
|
||||
test_marketplace_product.is_digital = False
|
||||
db.commit()
|
||||
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
marketplace_product_id=test_marketplace_product.id,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
assert product.is_digital is False
|
||||
assert product.has_unlimited_inventory is False
|
||||
assert product.total_inventory == 0
|
||||
assert product.available_inventory == 0
|
||||
|
||||
def test_physical_product_with_inventory(
|
||||
self, db, test_vendor, test_marketplace_product
|
||||
):
|
||||
"""Test physical product calculates inventory from entries."""
|
||||
from models.database.inventory import Inventory
|
||||
|
||||
test_marketplace_product.is_digital = False
|
||||
db.commit()
|
||||
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
marketplace_product_id=test_marketplace_product.id,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
# Add inventory entries
|
||||
inv1 = Inventory(
|
||||
product_id=product.id,
|
||||
vendor_id=test_vendor.id,
|
||||
location="WAREHOUSE_A",
|
||||
quantity=100,
|
||||
reserved_quantity=10,
|
||||
)
|
||||
inv2 = Inventory(
|
||||
product_id=product.id,
|
||||
vendor_id=test_vendor.id,
|
||||
location="WAREHOUSE_B",
|
||||
quantity=50,
|
||||
reserved_quantity=5,
|
||||
)
|
||||
db.add_all([inv1, inv2])
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
assert product.has_unlimited_inventory is False
|
||||
assert product.total_inventory == 150 # 100 + 50
|
||||
assert product.available_inventory == 135 # (100-10) + (50-5)
|
||||
|
||||
def test_digital_product_has_unlimited_inventory(
|
||||
self, db, test_vendor, test_marketplace_product
|
||||
):
|
||||
"""Test digital product returns unlimited inventory."""
|
||||
test_marketplace_product.is_digital = True
|
||||
db.commit()
|
||||
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
marketplace_product_id=test_marketplace_product.id,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
assert product.is_digital is True
|
||||
assert product.has_unlimited_inventory is True
|
||||
assert product.total_inventory == Product.UNLIMITED_INVENTORY
|
||||
assert product.available_inventory == Product.UNLIMITED_INVENTORY
|
||||
|
||||
def test_digital_product_ignores_inventory_entries(
|
||||
self, db, test_vendor, test_marketplace_product
|
||||
):
|
||||
"""Test digital product returns unlimited even with inventory entries."""
|
||||
from models.database.inventory import Inventory
|
||||
|
||||
test_marketplace_product.is_digital = True
|
||||
db.commit()
|
||||
|
||||
product = Product(
|
||||
vendor_id=test_vendor.id,
|
||||
marketplace_product_id=test_marketplace_product.id,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
# Add inventory entries (e.g., for license keys)
|
||||
inv = Inventory(
|
||||
product_id=product.id,
|
||||
vendor_id=test_vendor.id,
|
||||
location="DIGITAL_LICENSES",
|
||||
quantity=10,
|
||||
reserved_quantity=2,
|
||||
)
|
||||
db.add(inv)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
# Digital product should still return unlimited
|
||||
assert product.has_unlimited_inventory is True
|
||||
assert product.total_inventory == Product.UNLIMITED_INVENTORY
|
||||
assert product.available_inventory == Product.UNLIMITED_INVENTORY
|
||||
|
||||
def test_unlimited_inventory_constant(self):
|
||||
"""Test UNLIMITED_INVENTORY constant value."""
|
||||
assert Product.UNLIMITED_INVENTORY == 999999
|
||||
# Should be large enough to never cause "insufficient inventory"
|
||||
assert Product.UNLIMITED_INVENTORY > 100000
|
||||
|
||||
Reference in New Issue
Block a user