Update all frontend templates and JavaScript to use new unified Order model: - Orders tab: use status field, processing/cancelled values, items array - Order detail: use snapshot fields, items array, tracking_provider - JavaScript: update API params (status vs sync_status), orderStats fields - Tracking modal: use tracking_provider instead of tracking_carrier - Order items modal: use items array with item_state field All status mappings: - pending → pending (unconfirmed) - processing → confirmed (at least one item available) - cancelled → declined (all items unavailable) - shipped → shipped (with tracking) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
8.7 KiB
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:
- Single
orderstable for all channels (direct, letzshop, future marketplaces) - Customer/address snapshots preserved at order time (not just FK references)
- Products must exist in catalog - GTIN lookup errors trigger investigation
- Inactive customers created for marketplace imports until they register on storefront
- No separate
letzshop_orderstable - eliminates sync issues
Schema Design
Order Table
The orders table now includes:
orders
├── Identity
│ ├── id (PK)
│ ├── vendor_id (FK → vendors)
│ ├── 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:
- Look up customer by
(vendor_id, email) - If not found, create with
is_active=False - Customer becomes active when they register on storefront
- 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:
- Order confirmed →
status = processing - Letzshop auto-creates shipment with their carrier
- Operator picks & packs
- Operator clicks "Retrieve Shipping Info"
- App fetches tracking from Letzshop API
- Order updated →
status = shipped
Scenario 2: Vendor Own Shipping
When vendor uses their own carrier:
- Order confirmed →
status = processing - Operator picks & packs with own carrier
- Operator enters tracking info in app
- App sends tracking to Letzshop API
- 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 toorders)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/vendors/{id}/orders |
List orders with channel='letzshop' filter |
GET /admin/letzshop/orders/{id} |
Get order detail with items |
POST /admin/letzshop/vendors/{id}/orders/{id}/confirm |
Confirm items via external_item_id |
POST /admin/letzshop/vendors/{id}/orders/{id}/reject |
Decline items via external_item_id |
POST /admin/letzshop/vendors/{id}/orders/{id}/items/{item_id}/confirm |
Confirm single item |
POST /admin/letzshop/vendors/{id}/orders/{id}/items/{item_id}/decline |
Decline single item |
Order Number Format
| Channel | Format | Example |
|---|---|---|
| Direct | ORD-{vendor_id}-{date}-{random} |
ORD-1-20251219-A1B2C3 |
| Letzshop | LS-{vendor_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:
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 vendor catalog, so missing products indicate a sync issue that must be investigated.
Future Considerations
Performance at Scale
As the orders table grows, consider:
- Partitioning by
order_dateorvendor_id - Archiving old orders to separate tables
- Read replicas for reporting queries
- Materialized views for dashboard statistics
Additional Marketplaces
The schema supports additional channels:
channel = Column(String(50)) # direct, letzshop, amazon, ebay, etc.
Each marketplace would use:
external_order_id- Marketplace order IDexternal_shipment_id- Marketplace shipment IDexternal_order_number- Display order numberexternal_data- Raw marketplace data (JSON)
Implementation Status
- Order model with snapshots
- OrderItem model with GTIN fields
- LetzshopFulfillmentQueue updated
- LetzshopOrder removed
- Database migration created
- Order schemas updated
- Unified order service created
- Letzshop order service updated
- Letzshop schemas updated
- API endpoints updated
- Frontend updated
- Orders tab template (status badges, filters, table)
- Order detail page (snapshots, items, tracking)
- JavaScript (API params, response handling)
- Tracking modal (tracking_provider field)
- Order items modal (items array, item_state)