Order Service Proposal
Source: docs/proposals/order-service-proposal.md
# Order/Transaction Service Proposal
**Status:** Draft
**Author:** Claude Code + Dan
**Date:** 2026-02-05
---
## Overview
RGL8R needs a unified Order/Transaction service that serves two complementary use cases:
1. **Pre-Order (Real-Time):** Compliance check and landed cost estimate before accepting an order
2. **Post-Order (Batch):** Order data ingestion for audit linkage and SKU-level analytics
Both use cases share the same underlying data (catalog, compliance rules, rates) but differ in timing and interface.
---
## Use Cases
### Pre-Order: Real-Time Quote API
**User Story:** As an importer, I want to check compliance and estimate landed cost before accepting an order, so I can make informed decisions and avoid surprises.
**Trigger:** Customer's checkout flow, order management system, or manual check
**Input:**
```typescript
interface QuoteRequest {
skus: Array<{
sku: string
quantity: number
unitValue?: number // For duty calculation
}>
origin: {
country: string
postalCode?: string
}
destination: {
country: string
postalCode?: string
}
carrier?: string // Optional: for shipping estimate
serviceLevel?: string // Optional: for shipping estimate
}
```
**Output:**
```typescript
interface QuoteResponse {
requestId: string
timestamp: string
// Compliance screening
compliance: {
trade: {
atRisk: Array<{
sku: string
measureCode: string
measureName: string
estimatedDuty: number
confidence: number
}>
needsReview: Array<{ sku: string; reason: string }>
cleared: string[]
}
restrictions: Array<{
sku: string
type: 'PROHIBITED' | 'RESTRICTED' | 'LICENSED'
description: string
}>
}
// Cost estimates (when carrier/service provided)
estimates?: {
shipping: number
duties: number
taxes: number
fees: number
landedCost: number
currency: string
}
// Actionable warnings
warnings: string[]
// Evidence for audit trail
evidence: {
simaConfigVersion: string
catalogDataAsOf: string
rateDataAsOf?: string
}
}
```
**Endpoint:** `POST /api/orders/quote`
**SLA:** < 500ms response time (cached catalog + rules)
---
### Post-Order: Batch Ingestion
**User Story:** As an importer, I want to upload my order history so RGL8R can link shipments to SKUs and provide SKU-level analytics and catalog fix recommendations.
**Trigger:** Manual CSV upload or scheduled sync
**Input Format (CSV):**
```csv
order_number,sku,quantity,tracking_number,ship_date,carrier,destination_country
ORD-12345,ABC123,2,1Z999AA10123456784,2026-01-15,UPS,US
ORD-12345,XYZ789,1,1Z999AA10123456784,2026-01-15,UPS,US
```
**Processing:**
1. Parse and validate CSV
2. Upsert into `order_line_items` table
3. Link to existing shipments via `tracking_number`
4. Trigger SKU-level finding enrichment
5. Generate catalog mismatch recommendations
**Endpoint:** `POST /api/orders/upload`
**Output:**
```typescript
interface OrderUploadResponse {
jobId: string
status: 'pending' | 'processing' | 'completed' | 'failed'
stats: {
rowsProcessed: number
rowsSkipped: number
ordersCreated: number
shipmentsLinked: number
skuMismatchesFound: number
}
}
```
---
## Data Model
### Core Tables
```prisma
// Order header (one per order)
model Order {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
tenantId String @map("tenant_id") @db.Uuid
orderNumber String @map("order_number") @db.VarChar(100)
orderDate DateTime? @map("order_date") @db.Date
sourceSystem String? @map("source_system") @db.VarChar(50) // shopify, wms, manual
customerRef String? @map("customer_ref") @db.VarChar(100)
destinationCountry String? @map("destination_country") @db.VarChar(2)
destinationPostal String? @map("destination_postal") @db.VarChar(20)
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
lineItems OrderLineItem[]
@@unique([tenantId, sourceSystem, orderNumber], name: "tenant_source_order")
@@index([tenantId, orderDate])
@@map("orders")
@@schema("app")
}
// Order line items (SKU-level)
model OrderLineItem {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
tenantId String @map("tenant_id") @db.Uuid
orderId String @map("order_id") @db.Uuid
sku String @db.VarChar(100)
quantity Int @default(1)
unitValue Decimal? @map("unit_value") @db.Decimal(12, 2)
trackingNumber String? @map("tracking_number") @db.VarChar(100)
shipDate DateTime? @map("ship_date") @db.Date
carrier String? @db.VarChar(50)
serviceLevel String? @map("service_level") @db.VarChar(100)
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
@@unique([orderId, sku, trackingNumber], name: "order_sku_tracking")
@@index([tenantId, sku])
@@index([tenantId, trackingNumber])
@@map("order_line_items")
@@schema("app")
}
// Quote requests (audit trail for pre-order checks)
model QuoteRequest {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
tenantId String @map("tenant_id") @db.Uuid
requestPayload Json @map("request_payload")
responsePayload Json? @map("response_payload")
durationMs Int? @map("duration_ms")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
@@index([tenantId, createdAt])
@@map("quote_requests")
@@schema("app")
}
```
### Linkage Updates
Add to existing `Shipment` model:
```prisma
// In Shipment model, add:
orderLineItems OrderLineItem[] @relation("ShipmentOrderLines")
```
Add to existing `Finding` model:
```prisma
// In Finding model, add:
sku String? @db.VarChar(100)
orderNumber String? @map("order_number") @db.VarChar(100)
catalogMismatch Json? @map("catalog_mismatch") // { field: 'weight', catalog: 10, billed: 15 }
```
---
## API Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/orders/quote` | POST | Real-time compliance/cost check |
| `/api/orders/upload` | POST | Batch order data ingestion |
| `/api/orders` | GET | List orders with filters |
| `/api/orders/:id` | GET | Order detail with line items + linked shipments |
| `/api/orders/sku-summary` | GET | SKU-level cost/finding aggregation |
---
## Processing Flow
### Pre-Order Quote Flow
```
Request → Validate SKUs exist in catalog
→ Check SIMA coverage for each SKU
→ Check restrictions (future: denied party, embargoes)
→ Calculate duty estimates (if destination provided)
→ Calculate shipping estimate (if carrier provided)
→ Log request for audit trail
→ Return response
```
**Performance considerations:**
- Cache SIMA rules in memory (reload on config change)
- Cache catalog data per tenant (TTL-based)
- No DB writes in hot path (log async)
### Post-Order Ingestion Flow
```
Upload CSV → Create Job
→ Parse rows
→ Upsert Order + OrderLineItems
→ Match tracking_number → Shipment
→ For each linked shipment:
→ Look up SKU catalog data (SkuAttribute)
→ Compare catalog dims/weight vs billed
→ If mismatch: create CATALOG_DATA_MISMATCH finding
→ Enrich existing findings with SKU
→ Generate summary stats
→ Complete job
```
---
## Catalog Mismatch Detection
When order data links a shipment to SKUs, compare:
| Field | Catalog Source | Billed Source | Finding Type |
|-------|---------------|---------------|--------------|
| Weight | SkuAttribute.attributes.weight | Shipment.billedWeight | WEIGHT_MISMATCH |
| Length | SkuAttribute.attributes.length | Shipment.billedLength | DIM_MISMATCH |
| Width | SkuAttribute.attributes.width | Shipment.billedWidth | DIM_MISMATCH |
| Height | SkuAttribute.attributes.height | Shipment.billedHeight | DIM_MISMATCH |
**Output:** Two recommendations per mismatch:
1. **Carrier dispute** (if catalog is correct): "Carrier billed 15 lbs, catalog shows 10 lbs"
2. **Catalog fix** (if carrier is correct): "Update SKU ABC123 weight from 10 lbs → 15 lbs"
The system flags for review; human decides which is correct.
---
## Integration with Existing Modules
### SHIP Integration
- OrderLineItem.trackingNumber → Shipment.trackingNumber
- Enables SKU-level cost allocation
- Enables catalog mismatch detection
### TRADE Integration
- Quote API uses SIMA rules from `packages/modules/trade`
- Order destination triggers compliance check
- Pre-order warnings surface SIMA exposure before commitment
### Catalog Integration
- SkuAttribute table provides dims/weight for comparison
- AttributeValidation provides confidence scores
- Mismatches feed back into catalog improvement loop
---
## Implementation Phases
### Phase A: Post-Order Ingestion (Week 1)
- [ ] Add Order, OrderLineItem to Prisma schema
- [ ] Add sku, orderNumber to Finding model
- [ ] Create `POST /api/orders/upload` endpoint
- [ ] Implement CSV parsing and upsert
- [ ] Implement tracking → shipment linkage
- [ ] Implement SKU enrichment on existing findings
### Phase B: Catalog Mismatch Detection (Week 2)
- [ ] Add catalogMismatch field to Finding
- [ ] Implement comparison logic (catalog vs billed)
- [ ] Create CATALOG_DATA_MISMATCH finding type
- [ ] Surface in /audit endpoint with recommendation
### Phase C: Pre-Order Quote API (Week 3)
- [ ] Add QuoteRequest table for audit trail
- [ ] Create `POST /api/orders/quote` endpoint
- [ ] Implement SIMA check integration
- [ ] Implement response caching
- [ ] Add rate limiting
### Phase D: UI Integration (Week 4)
- [ ] Order list view
- [ ] SKU cost summary view
- [ ] Catalog mismatch recommendations UI
- [ ] Quote API playground/tester
---
## Open Questions
1. **Multi-SKU shipments:** How to allocate shipment costs across SKUs?
- Option A: Equal split
- Option B: Proportional by weight
- Option C: Proportional by value
- **Recommendation:** Proportional by quantity (simplest, matches v1.2 addendum)
2. **Catalog source of truth:** When catalog ≠billed, which is right?
- **Recommendation:** Flag for human review, don't auto-correct either
3. **Quote API authentication:** Same JWT auth or lighter API key for real-time?
- **Recommendation:** Same auth, add rate limiting
4. **Order source systems:** Support direct integrations (Shopify, etc.) or CSV-only for MVP?
- **Recommendation:** CSV-only for March, integrations in Q2
---
## References
- `specs/rgl8r-ship-module-prd-v1.2-addendum.md` - SKU analytics, order ingestion spec
- `specs/rgl8r-ship-rate-calc-v3.md` - Rate calculation engine (Phase 2)
- `packages/modules/trade/src/sima.ts` - SIMA validation logic
- `apps/api/prisma/schema.prisma` - Current schema