feat: make Product fully independent from MarketplaceProduct
- Add is_digital and product_type columns to Product model - Remove is_digital/product_type properties that derived from MarketplaceProduct - Update Create form with translation tabs, GTIN type, sale price, VAT rate, image - Update Edit form to allow editing is_digital (remove disabled state) - Add Availability field to Edit form - Fix Detail page for directly created products (no marketplace source) - Update vendor_product_service to handle new fields in create/update - Add VendorProductCreate/Update schema fields for translations and is_digital - Add unit tests for is_digital column and direct product creation - Add integration tests for create/update API with new fields - Create product-architecture.md documenting the independent copy pattern - Add migration y3d4e5f6g7h8 for is_digital and product_type columns 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -85,7 +85,7 @@ class VendorProductDetail(BaseModel):
|
||||
vendor_id: int
|
||||
vendor_name: str | None = None
|
||||
vendor_code: str | None = None
|
||||
marketplace_product_id: int
|
||||
marketplace_product_id: int | None = None # Optional for direct product creation
|
||||
vendor_sku: str | None = None
|
||||
# Product identifiers
|
||||
gtin: str | None = None
|
||||
@@ -149,10 +149,46 @@ class RemoveProductResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class TranslationUpdate(BaseModel):
|
||||
"""Translation data for a single language."""
|
||||
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class VendorProductCreate(BaseModel):
|
||||
"""Schema for creating a vendor product."""
|
||||
"""Schema for creating a vendor product (admin use - includes vendor_id)."""
|
||||
|
||||
vendor_id: int
|
||||
|
||||
# Translations by language code (en, fr, de, lu)
|
||||
translations: dict[str, TranslationUpdate] | None = None
|
||||
|
||||
# Product identifiers
|
||||
brand: str | None = None
|
||||
vendor_sku: str | None = None
|
||||
gtin: str | None = None
|
||||
gtin_type: str | None = None # ean13, ean8, upc, isbn
|
||||
|
||||
# Pricing
|
||||
price: float | None = None
|
||||
sale_price: float | None = None
|
||||
currency: str = "EUR"
|
||||
tax_rate_percent: int | None = 17 # Default Luxembourg VAT
|
||||
availability: str | None = None
|
||||
|
||||
# Image
|
||||
primary_image_url: str | None = None
|
||||
|
||||
# Status
|
||||
is_active: bool = True
|
||||
is_featured: bool = False
|
||||
is_digital: bool = False
|
||||
|
||||
|
||||
class VendorDirectProductCreate(BaseModel):
|
||||
"""Schema for vendor direct product creation (vendor_id from JWT token)."""
|
||||
|
||||
title: str
|
||||
brand: str | None = None
|
||||
vendor_sku: str | None = None
|
||||
@@ -162,14 +198,6 @@ class VendorProductCreate(BaseModel):
|
||||
availability: str | None = None
|
||||
is_active: bool = True
|
||||
is_featured: bool = False
|
||||
is_digital: bool = False
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class TranslationUpdate(BaseModel):
|
||||
"""Translation data for a single language."""
|
||||
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@@ -190,8 +218,10 @@ class VendorProductUpdate(BaseModel):
|
||||
sale_price: float | None = None # Optional sale price
|
||||
currency: str | None = None
|
||||
tax_rate_percent: int | None = None # 3, 8, 14, 17
|
||||
availability: str | None = None # in_stock, out_of_stock, preorder, backorder
|
||||
|
||||
# Status (is_digital is derived from marketplace product, not editable)
|
||||
# Status
|
||||
is_digital: bool | None = None
|
||||
is_active: bool | None = None
|
||||
is_featured: bool | None = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user