# Unified Order Schema Implementation

## Overview

This document describes the unified order schema that consolidates all order types (direct and marketplace) into a single `orders` table with snapshotted customer and address data.

## Design Decision: Option B - Single Unified Table

After analyzing the gap between internal orders and Letzshop orders, we chose **Option B: Full Import to Order Table** with the following key principles:

1. **Single `orders` table** for all channels (direct, letzshop, future marketplaces)
2. **Customer/address snapshots** preserved at order time (not just FK references)
3. **Products must exist** in catalog - GTIN lookup errors trigger investigation
4. **Inactive customers** created for marketplace imports until they register on storefront
5. **No separate `letzshop_orders` table** - eliminates sync issues

## Schema Design

### Order Table

The `orders` table now includes:

```
orders
├── Identity
│   ├── id (PK)
│   ├── store_id (FK → stores)
│   ├── customer_id (FK → customers)
│   └── order_number (unique)
│
├── Channel/Source
│   ├── channel (direct | letzshop)
│   ├── external_order_id
│   ├── external_shipment_id
│   ├── external_order_number
│   └── external_data (JSON - raw marketplace data)
│
├── Status
│   └── status (pending | processing | shipped | delivered | cancelled | refunded)
│
├── Financials
│   ├── subtotal (nullable for marketplace)
│   ├── tax_amount
│   ├── shipping_amount
│   ├── discount_amount
│   ├── total_amount
│   └── currency
│
├── Customer Snapshot
│   ├── customer_first_name
│   ├── customer_last_name
│   ├── customer_email
│   ├── customer_phone
│   └── customer_locale
│
├── Shipping Address Snapshot
│   ├── ship_first_name
│   ├── ship_last_name
│   ├── ship_company
│   ├── ship_address_line_1
│   ├── ship_address_line_2
│   ├── ship_city
│   ├── ship_postal_code
│   └── ship_country_iso
│
├── Billing Address Snapshot
│   ├── bill_first_name
│   ├── bill_last_name
│   ├── bill_company
│   ├── bill_address_line_1
│   ├── bill_address_line_2
│   ├── bill_city
│   ├── bill_postal_code
│   └── bill_country_iso
│
├── Tracking
│   ├── shipping_method
│   ├── tracking_number
│   └── tracking_provider
│
├── Notes
│   ├── customer_notes
│   └── internal_notes
│
└── Timestamps
    ├── order_date (when customer placed order)
    ├── confirmed_at
    ├── shipped_at
    ├── delivered_at
    ├── cancelled_at
    ├── created_at
    └── updated_at
```

### OrderItem Table

The `order_items` table includes:

```
order_items
├── Identity
│   ├── id (PK)
│   ├── order_id (FK → orders)
│   └── product_id (FK → products, NOT NULL)
│
├── Product Snapshot
│   ├── product_name
│   ├── product_sku
│   ├── gtin
│   └── gtin_type (ean13, upc, isbn, etc.)
│
├── Pricing
│   ├── quantity
│   ├── unit_price
│   └── total_price
│
├── External References
│   ├── external_item_id (Letzshop inventory unit ID)
│   └── external_variant_id
│
├── Item State (marketplace confirmation)
│   └── item_state (confirmed_available | confirmed_unavailable)
│
└── Inventory
    ├── inventory_reserved
    └── inventory_fulfilled
```

## Status Mapping

| Letzshop State | Order Status | Description |
|----------------|--------------|-------------|
| `unconfirmed` | `pending` | Order received, awaiting confirmation |
| `confirmed` | `processing` | Items confirmed, being prepared |
| `confirmed` + tracking | `shipped` | Shipped with tracking info |
| `declined` | `cancelled` | All items declined |

## Customer Handling

When importing marketplace orders:

1. Look up customer by `(store_id, email)`
2. If not found, create with `is_active=False`
3. Customer becomes active when they register on storefront
4. Customer info is always snapshotted in order (regardless of customer record)

This ensures:
- Customer history is preserved even if customer info changes
- Marketplace customers can later claim their order history
- No data loss if customer record is modified

## Shipping Workflows

### Scenario 1: Letzshop Auto-Shipping

When using Letzshop's shipping service:

1. Order confirmed → `status = processing`
2. Letzshop auto-creates shipment with their carrier
3. Operator picks & packs
4. Operator clicks "Retrieve Shipping Info"
5. App fetches tracking from Letzshop API
6. Order updated → `status = shipped`

### Scenario 2: Store Own Shipping

When store uses their own carrier:

1. Order confirmed → `status = processing`
2. Operator picks & packs with own carrier
3. Operator enters tracking info in app
4. App sends tracking to Letzshop API
5. Order updated → `status = shipped`

## Removed: LetzshopOrder Table

The `letzshop_orders` table has been removed. All data now goes directly into the unified `orders` table with `channel = 'letzshop'`.

### Migration of Existing References

- `LetzshopFulfillmentQueue.letzshop_order_id` → `order_id` (FK to `orders`)
- `LetzshopSyncLog` - unchanged (no order reference)
- `LetzshopHistoricalImportJob` - unchanged (no order reference)

## Files Modified

| File | Changes |
|------|---------|
| `models/database/order.py` | Complete rewrite with snapshots |
| `models/database/letzshop.py` | Removed `LetzshopOrder`, updated `LetzshopFulfillmentQueue` |
| `models/schema/order.py` | Updated schemas for new structure |
| `models/schema/letzshop.py` | Updated schemas for unified Order model |
| `app/services/order_service.py` | Unified service with `create_letzshop_order()` |
| `app/services/letzshop/order_service.py` | Updated to use unified Order model |
| `app/api/v1/admin/letzshop.py` | Updated endpoints for unified model |
| `alembic/versions/c1d2e3f4a5b6_unified_order_schema.py` | Migration |

## API Endpoints

All Letzshop order endpoints now use the unified Order model:

| Endpoint | Description |
|----------|-------------|
| `GET /admin/letzshop/stores/{id}/orders` | List orders with `channel='letzshop'` filter |
| `GET /admin/letzshop/orders/{id}` | Get order detail with items |
| `POST /admin/letzshop/stores/{id}/orders/{id}/confirm` | Confirm items via `external_item_id` |
| `POST /admin/letzshop/stores/{id}/orders/{id}/reject` | Decline items via `external_item_id` |
| `POST /admin/letzshop/stores/{id}/orders/{id}/items/{item_id}/confirm` | Confirm single item |
| `POST /admin/letzshop/stores/{id}/orders/{id}/items/{item_id}/decline` | Decline single item |

## Order Number Format

| Channel | Format | Example |
|---------|--------|---------|
| Direct | `ORD-{store_id}-{date}-{random}` | `ORD-1-20251219-A1B2C3` |
| Letzshop | `LS-{store_id}-{letzshop_order_number}` | `LS-1-ORD-123456` |

## Error Handling

### Product Not Found by GTIN

When importing a Letzshop order, if a product cannot be found by its GTIN:

```python
raise ValidationException(
    f"Product not found for GTIN {gtin}. "
    f"Please ensure the product catalog is in sync."
)
```

This is intentional - the Letzshop catalog is sourced from the store catalog, so missing products indicate a sync issue that must be investigated.

## Future Considerations

### Performance at Scale

As the orders table grows, consider:

1. **Partitioning** by `order_date` or `store_id`
2. **Archiving** old orders to separate tables
3. **Read replicas** for reporting queries
4. **Materialized views** for dashboard statistics

### Additional Marketplaces

The schema supports additional channels:

```python
channel = Column(String(50))  # direct, letzshop, amazon, ebay, etc.
```

Each marketplace would use:
- `external_order_id` - Marketplace order ID
- `external_shipment_id` - Marketplace shipment ID
- `external_order_number` - Display order number
- `external_data` - Raw marketplace data (JSON)

## Implementation Status

- [x] Order model with snapshots
- [x] OrderItem model with GTIN fields
- [x] LetzshopFulfillmentQueue updated
- [x] LetzshopOrder removed
- [x] Database migration created
- [x] Order schemas updated
- [x] Unified order service created
- [x] Letzshop order service updated
- [x] Letzshop schemas updated
- [x] API endpoints updated
- [x] Frontend updated
  - [x] Orders tab template (status badges, filters, table)
  - [x] Order detail page (snapshots, items, tracking)
  - [x] JavaScript (API params, response handling)
  - [x] Tracking modal (tracking_provider field)
  - [x] Order items modal (items array, item_state)
