# Marketplace Data Model Entity relationships and database schema for the marketplace module. ## Entity Relationship Diagram ``` ┌──────────────────────────┐ │ Store │ (from tenancy module) └──────┬───────────────────┘ │ 1 │ ┌────┴──────────────────────────────────────────────┐ │ │ │ │ ▼ 1 ▼ * ▼ 0..1 ▼ * ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ │ Marketplace │ │ Marketplace │ │ StoreLetzshop │ │ Letzshop │ │ ImportJob │ │ Product │ │ Credentials │ │ Order │ │ │ │ │ │ │ │ │ │ source_url │ │ gtin, mpn │ │ api_key_enc │ │ letzshop_id │ │ language │ │ sku, brand │ │ auto_sync │ │ sync_status │ │ status │ │ price_cents │ │ last_sync_at │ │ customer │ │ imported_cnt │ │ is_digital │ │ default_ │ │ total_amount │ │ error_count │ │ marketplace │ │ carrier │ │ tracking │ └──────┬───────┘ └──────┬───────┘ └───────────────┘ └──────┬───────┘ │ │ │ ▼ * ▼ * │ ┌──────────────┐ ┌──────────────────┐ │ │ Marketplace │ │ Marketplace │ │ │ ImportError │ │ Product │ │ │ │ │ Translation │ │ │ row_number │ │ │ │ │ error_type │ │ language │ │ │ error_msg │ │ title │ │ │ row_data │ │ description │ │ └──────────────┘ │ meta_title │ │ └──────────────────┘ │ │ ┌───────────────────────────────────────────────────────────┘ │ ▼ * ▼ * ▼ * ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ Letzshop │ │ Letzshop │ │ Letzshop │ │ FulfillmentQueue │ │ SyncLog │ │ Historical │ │ │ │ │ │ ImportJob │ │ operation │ │ operation_type │ │ │ │ payload │ │ direction │ │ current_phase │ │ status │ │ status │ │ current_page │ │ attempts │ │ records_* │ │ orders_processed │ └──────────────────┘ └──────────────────┘ └──────────────────┘ ┌──────────────────┐ ┌──────────────────┐ │ Letzshop │ │ Store │ │ StoreCache │ │ Onboarding │ │ │ │ │ │ letzshop_id │ │ status │ │ name, slug │ │ current_step │ │ claimed_by_ │ │ step_*_completed │ │ store_id │ │ skipped_by_admin │ └──────────────────┘ └──────────────────┘ ``` ## Models ### Product Import Pipeline #### MarketplaceProduct Canonical product data imported from marketplace sources. Serves as a staging area — stores publish selected products to their own catalog. | Field | Type | Description | |-------|------|-------------| | `marketplace_product_id` | String (unique) | Unique ID from marketplace | | `marketplace` | String | Source marketplace (e.g., "Letzshop") | | `gtin` | String | EAN/UPC barcode | | `mpn` | String | Manufacturer Part Number | | `sku` | String | Merchant's internal SKU | | `store_name` | String | Store name from marketplace | | `source_url` | String | CSV source URL | | `product_type_enum` | Enum | physical, digital, service, subscription | | `is_digital` | Boolean | Whether product is digital | | `digital_delivery_method` | Enum | download, email, in_app, streaming, license_key | | `brand` | String | Brand name | | `google_product_category` | String | Google Shopping category | | `condition` | String | new, used, refurbished | | `price_cents` | Integer | Price in cents | | `sale_price_cents` | Integer | Sale price in cents | | `currency` | String | Currency code (default EUR) | | `tax_rate_percent` | Decimal | VAT rate (default 17%) | | `image_link` | String | Main product image | | `additional_images` | JSON | Array of additional image URLs | | `attributes` | JSON | Flexible attributes | | `weight_grams` | Integer | Product weight | | `is_active` | Boolean | Whether product is active | **Relationships:** `translations` (MarketplaceProductTranslation), `store_products` (Product) #### MarketplaceProductTranslation Localized content for marketplace products. | Field | Type | Description | |-------|------|-------------| | `marketplace_product_id` | FK | Parent product | | `language` | String | Language code (en, fr, de, lb) | | `title` | String | Product title | | `description` | Text | Full description | | `short_description` | Text | Short description | | `meta_title` | String | SEO title | | `meta_description` | String | SEO description | | `url_slug` | String | URL-friendly slug | | `source_import_id` | Integer | Import job that created this | | `source_file` | String | Source CSV file | **Constraint:** Unique (`marketplace_product_id`, `language`) #### MarketplaceImportJob Tracks CSV product import jobs with progress and metrics. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK | Target store | | `user_id` | FK | Who triggered the import | | `marketplace` | String | Source marketplace (default "Letzshop") | | `source_url` | String | CSV feed URL | | `language` | String | Import language for translations | | `status` | String | pending, processing, completed, failed, completed_with_errors | | `imported_count` | Integer | New products imported | | `updated_count` | Integer | Existing products updated | | `error_count` | Integer | Failed rows | | `total_processed` | Integer | Total rows processed | | `error_message` | Text | Error message if failed | | `celery_task_id` | String | Background task ID | | `started_at` | DateTime | Processing start time | | `completed_at` | DateTime | Processing end time | **Relationships:** `store`, `user`, `errors` (MarketplaceImportError, cascade delete) #### MarketplaceImportError Detailed error records for individual import failures. | Field | Type | Description | |-------|------|-------------| | `import_job_id` | FK | Parent import job (cascade delete) | | `row_number` | Integer | Row in source CSV | | `identifier` | String | Product identifier (ID, GTIN, etc.) | | `error_type` | String | missing_title, missing_id, parse_error, validation_error | | `error_message` | String | Human-readable error | | `row_data` | JSON | Snapshot of key fields from failing row | ### Letzshop Order Integration #### StoreLetzshopCredentials Encrypted API credentials and sync settings per store. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK (unique) | One credential set per store | | `api_key_encrypted` | String | Fernet-encrypted API key | | `api_endpoint` | String | GraphQL endpoint URL | | `auto_sync_enabled` | Boolean | Enable automatic order sync | | `sync_interval_minutes` | Integer | Auto-sync interval (5-1440) | | `test_mode_enabled` | Boolean | Test mode flag | | `default_carrier` | String | Default shipping carrier | | `carrier_*_label_url` | String | Per-carrier label URL prefixes | | `last_sync_at` | DateTime | Last sync timestamp | | `last_sync_status` | String | success, failed, partial | | `last_sync_error` | Text | Error message if failed | #### LetzshopOrder Tracks orders imported from Letzshop marketplace. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK | Store that owns this order | | `letzshop_order_id` | String | Letzshop order GID | | `letzshop_shipment_id` | String | Letzshop shipment GID | | `letzshop_order_number` | String | Human-readable order number | | `external_order_number` | String | Customer-facing reference | | `shipment_number` | String | Carrier shipment number | | `local_order_id` | FK | Linked local order (if any) | | `letzshop_state` | String | Current Letzshop state | | `customer_email` | String | Customer email | | `customer_name` | String | Customer name | | `customer_locale` | String | Customer language for invoicing | | `total_amount` | String | Order total | | `currency` | String | Currency code | | `raw_order_data` | JSON | Full order data from Letzshop | | `inventory_units` | JSON | List of inventory units | | `sync_status` | String | pending, confirmed, rejected, shipped | | `shipping_carrier` | String | Carrier code | | `tracking_number` | String | Tracking number | | `tracking_url` | String | Full tracking URL | | `shipping_country_iso` | String | Shipping country | | `billing_country_iso` | String | Billing country | | `order_date` | DateTime | Original order date from Letzshop | #### LetzshopFulfillmentQueue Outbound operation queue with retry logic for Letzshop fulfillment. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK | Store | | `order_id` | FK | Linked order | | `operation` | String | confirm_item, decline_item, set_tracking | | `payload` | JSON | Operation data | | `status` | String | pending, processing, completed, failed | | `attempts` | Integer | Retry count | | `max_attempts` | Integer | Max retries (default 3) | | `error_message` | Text | Last error | | `response_data` | JSON | Response from Letzshop | #### LetzshopSyncLog Audit trail for all Letzshop sync operations. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK | Store | | `operation_type` | String | order_import, confirm_inventory, set_tracking, etc. | | `direction` | String | inbound, outbound | | `status` | String | success, failed, partial | | `records_processed` | Integer | Total records | | `records_succeeded` | Integer | Successful records | | `records_failed` | Integer | Failed records | | `error_details` | JSON | Detailed error info | | `started_at` | DateTime | Operation start | | `completed_at` | DateTime | Operation end | | `duration_seconds` | Integer | Total duration | | `triggered_by` | String | user_id, scheduler, webhook | #### LetzshopHistoricalImportJob Tracks progress of historical order imports for real-time progress polling. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK | Store | | `user_id` | FK | Who triggered | | `status` | String | pending, fetching, processing, completed, failed | | `current_phase` | String | confirmed, declined | | `current_page` | Integer | Current pagination page | | `total_pages` | Integer | Total pages | | `shipments_fetched` | Integer | Shipments fetched so far | | `orders_processed` | Integer | Orders processed | | `orders_imported` | Integer | New orders imported | | `orders_updated` | Integer | Updated existing orders | | `orders_skipped` | Integer | Duplicate orders skipped | | `products_matched` | Integer | Products matched by EAN | | `products_not_found` | Integer | Products not found | | `confirmed_stats` | JSON | Stats for confirmed phase | | `declined_stats` | JSON | Stats for declined phase | | `celery_task_id` | String | Background task ID | ### Supporting Models #### LetzshopStoreCache Cache of Letzshop marketplace store directory for browsing and claiming during signup. | Field | Type | Description | |-------|------|-------------| | `letzshop_id` | String (unique) | Letzshop store identifier | | `slug` | String | URL slug | | `name` | String | Store name | | `merchant_name` | String | Merchant name | | `is_active` | Boolean | Active on Letzshop | | `description_en/fr/de` | Text | Localized descriptions | | `email`, `phone`, `website` | String | Contact info | | `street`, `zipcode`, `city`, `country_iso` | String | Address | | `latitude`, `longitude` | Float | Geo coordinates | | `categories` | JSON | Category array | | `social_media_links` | JSON | Social links | | `claimed_by_store_id` | FK | Store that claimed this listing | | `claimed_at` | DateTime | When claimed | | `last_synced_at` | DateTime | Last directory sync | #### StoreOnboarding Tracks completion of mandatory onboarding steps for new stores. | Field | Type | Description | |-------|------|-------------| | `store_id` | FK (unique) | One record per store | | `status` | String | not_started, in_progress, completed, skipped | | `current_step` | Integer | Current onboarding step | | Step 1 | — | Merchant profile completion | | Step 2 | — | Letzshop API key setup + verification | | Step 3 | — | Product import (CSV URL set) | | Step 4 | — | Order sync (first sync job) | | `skipped_by_admin` | Boolean | Admin override | | `skipped_reason` | Text | Reason for skip |