# Product Architecture

## Overview

The product management system uses an **independent copy pattern** where store products (`Product`) are fully independent entities that can optionally reference a marketplace source (`MarketplaceProduct`) for display purposes only.

## Core Principles

| Principle | Description |
|-----------|-------------|
| **Full Independence** | Store products have all their own fields - no inheritance or fallback to marketplace |
| **Optional Source Reference** | `marketplace_product_id` is nullable - products can be created directly |
| **No Reset Functionality** | No "reset to source" - products are independent from the moment of creation |
| **Source for Display Only** | Source comparison info is read-only, used for "view original" display |

---

## Architecture Diagram

```
┌─────────────────────────────────────────────────────────────────────┐
│                      MarketplaceProduct                              │
│  (Central Repository - raw imported data from marketplaces)         │
│                                                                      │
│  - marketplace_product_id (unique)                                   │
│  - gtin, mpn, sku                                                    │
│  - brand, price_cents, sale_price_cents                              │
│  - is_digital, product_type_enum                                     │
│  - translations (via MarketplaceProductTranslation)                  │
└──────────────────────────────────────────────────────────────────────┘

                           ╳ No runtime dependency
                           │
                           │ Optional FK (for "view source" display only)
                           │ marketplace_product_id (nullable)
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         Product                                      │
│  (Store's Independent Product - fully standalone)                   │
│                                                                      │
│  === IDENTIFIERS ===                                                 │
│  - store_id (required)                                              │
│  - store_sku                                                        │
│  - gtin, gtin_type                                                   │
│                                                                      │
│  === PRODUCT TYPE (own columns) ===                                  │
│  - is_digital (Boolean)                                              │
│  - product_type (String: physical, digital, service, subscription)  │
│                                                                      │
│  === PRICING ===                                                     │
│  - price_cents, sale_price_cents                                     │
│  - currency, tax_rate_percent                                        │
│                                                                      │
│  === CONTENT ===                                                     │
│  - brand, condition, availability                                    │
│  - primary_image_url, additional_images                              │
│  - translations (via ProductTranslation)                             │
│                                                                      │
│  === STATUS ===                                                      │
│  - is_active, is_featured                                            │
│                                                                      │
│  === SUPPLIER ===                                                    │
│  - supplier, cost_cents                                              │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Product Creation Patterns

### 1. From Marketplace Source (Import)

When copying from a marketplace product:
- All fields are **copied** at creation time
- `marketplace_product_id` is set for source reference
- No ongoing relationship - product is immediately independent

```python
# Service copies all fields at import time
product = Product(
    store_id=store.id,
    marketplace_product_id=marketplace_product.id,  # Source reference
    # All fields copied - no inheritance
    brand=marketplace_product.brand,
    price=marketplace_product.price,
    is_digital=marketplace_product.is_digital,
    product_type=marketplace_product.product_type_enum,
    # ... all other fields
)
```

### 2. Direct Creation (No Marketplace Source)

Stores can create products directly without a marketplace source:

```python
product = Product(
    store_id=store.id,
    marketplace_product_id=None,  # No source
    store_sku="DIRECT_001",
    brand="MyBrand",
    price=29.99,
    is_digital=True,
    product_type="digital",
    is_active=True,
)
```

---

## Key Fields

### Product Type Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `is_digital` | Boolean | `False` | Whether product is digital (no physical shipping) |
| `product_type` | String(20) | `"physical"` | Product type: physical, digital, service, subscription |

These are **independent columns** on Product, not derived from MarketplaceProduct.

### Source Reference

| Field | Type | Nullable | Description |
|-------|------|----------|-------------|
| `marketplace_product_id` | Integer FK | **Yes** | Optional reference to source MarketplaceProduct |

---

## Inventory Handling

Digital and physical products have different inventory behavior:

```python
@property
def has_unlimited_inventory(self) -> bool:
    """Digital products have unlimited inventory."""
    return self.is_digital

@property
def total_inventory(self) -> int:
    """Get total inventory across all locations."""
    if self.is_digital:
        return Product.UNLIMITED_INVENTORY  # 999999
    return sum(inv.quantity for inv in self.inventory_entries)
```

---

## Source Comparison (Display Only)

For products with a marketplace source, we provide comparison info for display:

```python
def get_source_comparison_info(self) -> dict:
    """Get current values with source values for comparison.

    Used for "view original source" display feature.
    """
    mp = self.marketplace_product
    return {
        "price": self.price,
        "price_source": mp.price if mp else None,
        "brand": self.brand,
        "brand_source": mp.brand if mp else None,
        # ... other fields
    }
```

This is **read-only** - there's no mechanism to "reset" to source values.

---

## UI Behavior

### Detail Page

| Product Type | Source Info Card | Edit Button Text |
|-------------|------------------|------------------|
| Marketplace-sourced | Shows source info with "View Source" link | "Edit Overrides" |
| Directly created | Shows "Direct Creation" badge | "Edit Product" |

### Info Banner

- **Marketplace-sourced**: Purple banner - "Store Product Catalog Entry"
- **Directly created**: Blue banner - "Directly Created Product"

---

## Database Schema

### Product Table Key Columns

```sql
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    store_id INTEGER NOT NULL REFERENCES stores(id),
    marketplace_product_id INTEGER REFERENCES marketplace_products(id),  -- Nullable!

    -- Product Type (independent columns)
    is_digital BOOLEAN DEFAULT FALSE,
    product_type VARCHAR(20) DEFAULT 'physical',

    -- Identifiers
    store_sku VARCHAR,
    gtin VARCHAR,
    gtin_type VARCHAR(10),
    brand VARCHAR,

    -- Pricing (in cents)
    price_cents INTEGER,
    sale_price_cents INTEGER,
    currency VARCHAR(3) DEFAULT 'EUR',
    tax_rate_percent INTEGER DEFAULT 17,
    availability VARCHAR,

    -- Media
    primary_image_url VARCHAR,
    additional_images JSON,

    -- Status
    is_active BOOLEAN DEFAULT TRUE,
    is_featured BOOLEAN DEFAULT FALSE,

    -- Timestamps
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

-- Index for product type queries
CREATE INDEX idx_product_is_digital ON products(is_digital);
```

---

## Migration History

| Migration | Description |
|-----------|-------------|
| `x2c3d4e5f6g7` | Made `marketplace_product_id` nullable |
| `y3d4e5f6g7h8` | Added `is_digital` and `product_type` columns to products |

---

## API Endpoints

### Create Product (Admin)

```
POST /api/v1/admin/store-products
{
    "store_id": 1,
    "translations": {
        "en": {"title": "Product Name", "description": "..."},
        "fr": {"title": "Nom du produit", "description": "..."}
    },
    "store_sku": "SKU001",
    "brand": "BrandName",
    "price": 29.99,
    "is_digital": false,
    "is_active": true
}
```

### Update Product (Admin)

```
PATCH /api/v1/admin/store-products/{id}
{
    "is_digital": true,
    "price": 39.99,
    "translations": {
        "en": {"title": "Updated Name"}
    }
}
```

---

## Testing

Key test scenarios:

1. **Direct Product Creation** - Create without marketplace source
2. **Digital Product Inventory** - Verify unlimited inventory for digital
3. **is_digital Column** - Verify it's an independent column, not derived
4. **Source Comparison** - Verify read-only source info display

See:
- `tests/unit/models/database/test_product.py`
- `tests/integration/api/v1/admin/test_store_products.py`
