# Orders Data Model

Entity relationships and database schema for the orders module.

## Entity Relationship Overview

```
Store 1──* Order 1──* OrderItem *──1 Product
                 │          │
                 │          └──? OrderItemException
                 │
                 └──* Invoice

Store 1──1 StoreInvoiceSettings
Store 1──* CustomerOrderStats *──1 Customer
```

## Models

### Order

Unified order model for all sales channels (direct store orders and marketplace orders). Stores customer and address data as snapshots at order time. All monetary amounts are stored as integer cents (e.g., €105.91 = 10591).

| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| `id` | Integer | PK | Primary key |
| `store_id` | Integer | FK, not null, indexed | Reference to store |
| `customer_id` | Integer | FK, not null, indexed | Reference to customer |
| `order_number` | String(100) | not null, indexed | Unique order identifier |
| `channel` | String(50) | default "direct", indexed | Order source: "direct" or "letzshop" |
| `external_order_id` | String(100) | nullable, indexed | Marketplace order ID |
| `external_shipment_id` | String(100) | nullable, indexed | Marketplace shipment ID |
| `external_order_number` | String(100) | nullable | Marketplace order number |
| `external_data` | JSON | nullable | Raw marketplace data for debugging |
| `status` | String(50) | default "pending", indexed | pending, processing, shipped, delivered, cancelled, refunded |
| `subtotal_cents` | Integer | nullable | Subtotal in cents |
| `tax_amount_cents` | Integer | nullable | Tax in cents |
| `shipping_amount_cents` | Integer | nullable | Shipping cost in cents |
| `discount_amount_cents` | Integer | nullable | Discount in cents |
| `total_amount_cents` | Integer | not null | Total in cents |
| `currency` | String(10) | default "EUR" | Currency code |
| `vat_regime` | String(20) | nullable | domestic, oss, reverse_charge, origin, exempt |
| `vat_rate` | Numeric(5,2) | nullable | VAT rate percentage |
| `vat_rate_label` | String(100) | nullable | Human-readable VAT label |
| `vat_destination_country` | String(2) | nullable | Destination country ISO code |
| `customer_first_name` | String(100) | not null | Customer first name snapshot |
| `customer_last_name` | String(100) | not null | Customer last name snapshot |
| `customer_email` | String(255) | not null | Customer email snapshot |
| `customer_phone` | String(50) | nullable | Customer phone |
| `customer_locale` | String(10) | nullable | Customer locale (en, fr, de, lb) |
| `ship_*` | Various | not null | Shipping address fields (first_name, last_name, company, address_line_1/2, city, postal_code, country_iso) |
| `bill_*` | Various | not null | Billing address fields (same structure as shipping) |
| `shipping_method` | String(100) | nullable | Shipping method |
| `tracking_number` | String(100) | nullable | Tracking number |
| `tracking_provider` | String(100) | nullable | Tracking provider |
| `tracking_url` | String(500) | nullable | Full tracking URL |
| `shipment_number` | String(100) | nullable | Carrier shipment number |
| `shipping_carrier` | String(50) | nullable | Carrier code (greco, colissimo, etc.) |
| `customer_notes` | Text | nullable | Notes from customer |
| `internal_notes` | Text | nullable | Internal notes |
| `order_date` | DateTime | not null, tz-aware | When customer placed order |
| `confirmed_at` | DateTime | nullable, tz-aware | When order was confirmed |
| `shipped_at` | DateTime | nullable, tz-aware | When order was shipped |
| `delivered_at` | DateTime | nullable, tz-aware | When order was delivered |
| `cancelled_at` | DateTime | nullable, tz-aware | When order was cancelled |
| `created_at` | DateTime | tz-aware | Record creation time |
| `updated_at` | DateTime | tz-aware | Record update time |

**Composite Indexes**: `(store_id, status)`, `(store_id, channel)`, `(store_id, order_date)`

### OrderItem

Individual line items in an order with product snapshot at order time.

| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| `id` | Integer | PK | Primary key |
| `order_id` | Integer | FK, not null, indexed | Reference to order |
| `product_id` | Integer | FK, not null | Reference to product |
| `product_name` | String(255) | not null | Product name snapshot |
| `product_sku` | String(100) | nullable | Product SKU snapshot |
| `gtin` | String(50) | nullable | EAN/UPC/ISBN code |
| `gtin_type` | String(20) | nullable | GTIN type (ean13, upc, isbn, etc.) |
| `quantity` | Integer | not null | Units ordered |
| `unit_price_cents` | Integer | not null | Price per unit in cents |
| `total_price_cents` | Integer | not null | Total price for line in cents |
| `external_item_id` | String(100) | nullable | Marketplace inventory unit ID |
| `external_variant_id` | String(100) | nullable | Marketplace variant ID |
| `item_state` | String(50) | nullable | confirmed_available or confirmed_unavailable |
| `inventory_reserved` | Boolean | default False | Whether inventory is reserved |
| `inventory_fulfilled` | Boolean | default False | Whether inventory is fulfilled |
| `shipped_quantity` | Integer | default 0, not null | Units shipped so far |
| `needs_product_match` | Boolean | default False, indexed | Product not found by GTIN during import |
| `created_at` | DateTime | tz-aware | Record creation time |
| `updated_at` | DateTime | tz-aware | Record update time |

### OrderItemException

Tracks unmatched order items requiring admin/store resolution. Created when a marketplace order contains a GTIN that doesn't match any product in the store's catalog.

| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| `id` | Integer | PK | Primary key |
| `order_item_id` | Integer | FK, unique, not null | Reference to order item (cascade delete) |
| `store_id` | Integer | FK, not null, indexed | Denormalized store reference |
| `original_gtin` | String(50) | nullable, indexed | Original GTIN from marketplace |
| `original_product_name` | String(500) | nullable | Original product name from marketplace |
| `original_sku` | String(100) | nullable | Original SKU from marketplace |
| `exception_type` | String(50) | default "product_not_found", not null | product_not_found, gtin_mismatch, duplicate_gtin |
| `status` | String(50) | default "pending", not null, indexed | pending, resolved, ignored |
| `resolved_product_id` | Integer | FK, nullable | Assigned product after resolution |
| `resolved_at` | DateTime | nullable, tz-aware | When exception was resolved |
| `resolved_by` | Integer | FK, nullable | Who resolved it |
| `resolution_notes` | Text | nullable | Notes about the resolution |
| `created_at` | DateTime | tz-aware | Record creation time |
| `updated_at` | DateTime | tz-aware | Record update time |

**Composite Indexes**: `(store_id, status)`, `(store_id, original_gtin)`

### Invoice

Invoice record with snapshots of seller/buyer details. Stores complete invoice data including snapshots at creation time for audit.

| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| `id` | Integer | PK | Primary key |
| `store_id` | Integer | FK, not null, indexed | Reference to store |
| `order_id` | Integer | FK, nullable, indexed | Reference to order |
| `invoice_number` | String(50) | not null | Invoice identifier |
| `invoice_date` | DateTime | not null, tz-aware | Date of invoice |
| `status` | String(20) | default "draft", not null | draft, issued, paid, cancelled |
| `seller_details` | JSON | not null | Snapshot: {merchant_name, address, city, postal_code, country, vat_number} |
| `buyer_details` | JSON | not null | Snapshot: {name, email, address, city, postal_code, country, vat_number} |
| `line_items` | JSON | not null | Snapshot: [{description, quantity, unit_price_cents, total_cents, sku, ean}] |
| `vat_regime` | String(20) | default "domestic", not null | domestic, oss, reverse_charge, origin, exempt |
| `destination_country` | String(2) | nullable | Destination country ISO for OSS |
| `vat_rate` | Numeric(5,2) | not null | VAT rate percentage |
| `vat_rate_label` | String(50) | nullable | Human-readable VAT rate label |
| `currency` | String(3) | default "EUR", not null | Currency code |
| `subtotal_cents` | Integer | not null | Subtotal before VAT in cents |
| `vat_amount_cents` | Integer | not null | VAT amount in cents |
| `total_cents` | Integer | not null | Total after VAT in cents |
| `payment_terms` | Text | nullable | Payment terms description |
| `bank_details` | JSON | nullable | IBAN and BIC snapshot |
| `footer_text` | Text | nullable | Custom footer text |
| `pdf_generated_at` | DateTime | nullable, tz-aware | When PDF was generated |
| `pdf_path` | String(500) | nullable | Path to stored PDF |
| `notes` | Text | nullable | Internal notes |
| `created_at` | DateTime | tz-aware | Record creation time |
| `updated_at` | DateTime | tz-aware | Record update time |

**Unique Constraint**: `(store_id, invoice_number)`
**Composite Indexes**: `(store_id, invoice_date)`, `(store_id, status)`

### StoreInvoiceSettings

Per-store invoice configuration including merchant details, VAT number, invoice numbering, and payment information.

| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| `id` | Integer | PK | Primary key |
| `store_id` | Integer | FK, unique, not null | One-to-one with store |
| `merchant_name` | String(255) | not null | Legal merchant name |
| `merchant_address` | String(255) | nullable | Street address |
| `merchant_city` | String(100) | nullable | City |
| `merchant_postal_code` | String(20) | nullable | Postal code |
| `merchant_country` | String(2) | default "LU", not null | ISO country code |
| `vat_number` | String(50) | nullable | VAT number (e.g., "LU12345678") |
| `is_vat_registered` | Boolean | default True, not null | VAT registration status |
| `is_oss_registered` | Boolean | default False, not null | OSS registration status |
| `oss_registration_country` | String(2) | nullable | OSS registration country |
| `invoice_prefix` | String(20) | default "INV", not null | Invoice number prefix |
| `invoice_next_number` | Integer | default 1, not null | Next invoice number counter |
| `invoice_number_padding` | Integer | default 5, not null | Zero-padding width |
| `payment_terms` | Text | nullable | Payment terms description |
| `bank_name` | String(255) | nullable | Bank name |
| `bank_iban` | String(50) | nullable | IBAN |
| `bank_bic` | String(20) | nullable | BIC |
| `footer_text` | Text | nullable | Custom footer text |
| `default_vat_rate` | Numeric(5,2) | default 17.00, not null | Default VAT rate |
| `created_at` | DateTime | tz-aware | Record creation time |
| `updated_at` | DateTime | tz-aware | Record update time |

### CustomerOrderStats

Aggregated order statistics per customer per store. Separates order stats from customer profile data.

| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| `id` | Integer | PK | Primary key |
| `store_id` | Integer | FK, not null, indexed | Reference to store |
| `customer_id` | Integer | FK, not null, indexed | Reference to customer |
| `total_orders` | Integer | default 0, not null | Total number of orders |
| `total_spent_cents` | Integer | default 0, not null | Total amount spent in cents |
| `last_order_date` | DateTime | nullable, tz-aware | Date of most recent order |
| `first_order_date` | DateTime | nullable, tz-aware | Date of first order |
| `created_at` | DateTime | tz-aware | Record creation time |
| `updated_at` | DateTime | tz-aware | Record update time |

**Unique Constraint**: `(store_id, customer_id)`

## Enums

### InvoiceStatus

| Value | Description |
|-------|-------------|
| `draft` | Invoice created but not finalized |
| `issued` | Invoice sent to customer |
| `paid` | Payment received |
| `cancelled` | Invoice cancelled |

### VATRegime

| Value | Description |
|-------|-------------|
| `domestic` | Same country as seller |
| `oss` | EU cross-border with OSS registration |
| `reverse_charge` | B2B with valid VAT number |
| `origin` | Cross-border without OSS (use origin VAT) |
| `exempt` | VAT exempt |

## Design Patterns

- **Money as cents**: All monetary values stored as integer cents for precision
- **Snapshots**: Customer, address, seller, and product data captured at order/invoice time
- **Marketplace support**: External reference fields for marketplace-specific IDs and data
- **Timezone-aware dates**: All DateTime fields include timezone info
- **Composite indexes**: Optimized for common query patterns (store + status, store + date)
