refactor: product independence - remove inheritance pattern

Change Product/ProductTranslation from "override/inheritance" pattern
(NULL = inherit from marketplace) to "independent copy" pattern
(all fields populated at creation).

Key changes:
- Remove OVERRIDABLE_FIELDS, effective_* properties, reset_* methods
- Rename get_override_info() → get_source_comparison_info()
- Update copy_to_vendor_catalog() to copy ALL fields + translations
- Replace effective_* with direct field access in services
- Remove *_overridden fields from schema, keep *_source for comparison
- Add migration to populate NULL fields from marketplace products

The marketplace_product_id FK is kept for "view original source" feature.
Rollback tag: v1.0.0-pre-product-independence

🤖 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-24 23:41:20 +01:00
parent 4ba911e263
commit 508e121a0e
10 changed files with 444 additions and 418 deletions

View File

@@ -130,103 +130,76 @@ class TestProductModel:
assert product.marketplace_product is not None
assert product.inventory_entries == [] # No inventory yet
def test_product_effective_properties(
def test_product_get_source_comparison_info(
self, db, test_vendor, test_marketplace_product
):
"""Test Product effective properties with override pattern."""
# First, set some values on the marketplace product
test_marketplace_product.price_numeric = 100.00
test_marketplace_product.brand = "SourceBrand"
db.commit()
db.refresh(test_marketplace_product)
"""Test get_source_comparison_info method for 'view original source' feature.
# Create product without overrides
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
)
db.add(product)
db.commit()
db.refresh(product)
# Should inherit from marketplace product
assert product.effective_price == 100.00
assert product.effective_brand == "SourceBrand"
# Now override the price
product.price = 89.99
db.commit()
db.refresh(product)
# Should use the override
assert product.effective_price == 89.99
# Brand still inherited
assert product.effective_brand == "SourceBrand"
def test_product_reset_to_source(self, db, test_vendor, test_marketplace_product):
"""Test reset_to_source methods."""
# Set up marketplace product values (use cents internally)
Products are independent entities with all fields populated at creation.
Source values are kept for comparison only, not inheritance.
"""
# Set up marketplace product values
test_marketplace_product.price_cents = 10000 # €100.00
test_marketplace_product.brand = "SourceBrand"
db.commit()
# Create product with overrides (price property converts euros to cents)
# Create product with its own values (independent copy pattern)
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
price_cents=8999, # €89.99
brand="OverrideBrand",
price_cents=8999, # €89.99 - vendor's price
brand="VendorBrand", # Vendor's brand
)
db.add(product)
db.commit()
db.refresh(product)
assert product.effective_price == 89.99
assert product.effective_brand == "OverrideBrand"
info = product.get_source_comparison_info()
# Reset price_cents to source (OVERRIDABLE_FIELDS now uses _cents names)
product.reset_field_to_source("price_cents")
db.commit()
db.refresh(product)
# Product has its own price
assert info["price"] == 89.99
assert info["price_cents"] == 8999
assert info["price_source"] == 100.00 # Original marketplace price
assert product.price_cents is None
assert product.price is None # Property returns None when cents is None
assert product.effective_price == 100.00 # Now inherits from marketplace
# Product has its own brand
assert info["brand"] == "VendorBrand"
assert info["brand_source"] == "SourceBrand" # Original marketplace brand
# Reset all fields
product.reset_all_to_source()
db.commit()
db.refresh(product)
# No more *_overridden keys in the pattern
assert "price_overridden" not in info
assert "brand_overridden" not in info
assert product.brand is None
assert product.effective_brand == "SourceBrand" # Now inherits
def test_product_fields_are_independent(
self, db, test_vendor, test_marketplace_product
):
"""Test that product fields don't inherit from marketplace product.
def test_product_get_override_info(self, db, test_vendor, test_marketplace_product):
"""Test get_override_info method."""
test_marketplace_product.price_numeric = 100.00
Products are independent entities - NULL fields stay NULL,
no inheritance/fallback logic.
"""
# Set up marketplace product values
test_marketplace_product.price_cents = 10000
test_marketplace_product.brand = "SourceBrand"
db.commit()
# Create product without copying values
product = Product(
vendor_id=test_vendor.id,
marketplace_product_id=test_marketplace_product.id,
price=89.99, # Override
# brand not set - will inherit
# Not copying price_cents or brand
)
db.add(product)
db.commit()
db.refresh(product)
info = product.get_override_info()
# Fields should be NULL (not inherited)
assert product.price_cents is None
assert product.price is None
assert product.brand is None
# Price is overridden
assert info["price"] == 89.99
assert info["price_overridden"] is True
# But we can still see the source values for comparison
info = product.get_source_comparison_info()
assert info["price_source"] == 100.00
# Brand is inherited
assert info["brand"] == "SourceBrand"
assert info["brand_overridden"] is False
assert info["brand_source"] == "SourceBrand"