Fulfillment Module Technical Specifications
A sovereign, natively integrated warehouse management system — ZPL label generation, Haversine geospatial routing, atomic SQL inventory guards, multi-protocol scale parsing, and 10-format barcode scanning, all driven through Bridge MCP tools.
System & Optimization Metrics
Haversine Routing Latency
< 50ms per order allocation across unlimited warehouses
Inventory Updates
Atomic SQL WHERE clause guards — no overselling, no phantom stock
Bridge Hardware
ZPL thermal labels, multi-protocol scale parsing, HID barcode scanning
Geocoding
Nominatim/OpenStreetMap — no paid geocoding API required
Pick List Generation
Batch aggregation across multiple orders, single warehouse consolidation
Returns (RMA)
Conditional restock — new/opened items only, per-item condition tracking
1. Warehouse Routing & Allocation Logic
The fulfillment engine is the central routing and inventory orchestration layer. It handles three primary concerns:
- Geospatial warehouse selection — Haversine distance calculation to find nearest facilities
- Atomic inventory movement — guarded atomic updates preventing overselling
- Order allocation — Multi-SKU, multi-warehouse greedy allocation with split-order support
No external routing API required. All math is pure JavaScript using the Haversine formula.
1.1 Geospatial Warehouse Selection
For a given SKU and quantity, the engine pulls every active warehouse that has coordinates on file, checks live inventory for that SKU across all of them, and filters out any warehouse without enough sellable stock (on-hand minus what's already allocated to other orders). The remaining candidates are ranked nearest-first using the Haversine distance from the customer's location.
Returns all eligible warehouses sorted by distance, not just the best one. This lets the allocator implement fallback routing or split orders when one warehouse alone can't cover the order.
1.2 Greedy Multi-Warehouse Allocation
When an order comes in, the engine geocodes the shipping address if it doesn't already have coordinates, then walks each line item: it fetches the distance-ranked warehouse list for that SKU and allocates from the nearest warehouse first, moving to the next-nearest only if quantity remains unfulfilled. Each partial allocation is its own atomic, guarded stock movement, so a failure partway through never leaves inventory in an inconsistent state.
Split-order support: If Warehouse A has 3 units and the order needs 5, the engine allocates 3 from A, then 2 from B. Each allocation is atomic.
Events emitted: fulfillment.order_allocated (success) or fulfillment.allocation_failed (partial/full failure) — with the reason and any unfulfilled quantity included. Downstream modules (notifications, inventory dashboards) subscribe independently.
2. Atomic Inventory — The Guarded-Update Pattern
Every inventory change in the system — inbound receipt, allocation, outbound shipment, transfer, adjustment — goes through a single write path that enforces atomicity with a database-level guard clause, so the check ("is there enough stock?") and the update ("reserve it") happen as one indivisible operation.
2.1 Allocation — Guard Against Overselling
When an order allocates stock, the reserved quantity is incremented, but the update only commits if on-hand stock minus what's already reserved still covers the requested amount. If two concurrent orders both read "3 units available" and both try to allocate 3 units, the first succeeds and the second is rejected outright with an "insufficient stock" error — instead of both succeeding and driving the count to -3.
2.2 Outbound — Guard Against Negative Stock
The same pattern applies to outbound shipments: on-hand and reserved quantities are decremented together, but only if on-hand stock actually covers the quantity being shipped. Without this guard, an outbound movement could drive on-hand stock negative, making the inventory ledger meaningless.
2.3 Inventory Field Model
Each SKU-per-warehouse inventory record tracks:
- On-hand — physical units in the warehouse (increases on inbound, decreases on outbound)
- Allocated — reserved for pending orders (increases on allocation, decreases on outbound)
- Available — on-hand minus allocated (the sellable quantity)
- Reorder point — the threshold that triggers a low-stock alert
Low-stock event: Whenever available stock drops to or below the configured reorder point, a fulfillment.low_stock event fires with the SKU, warehouse, and remaining quantity — ready to wire into a purchase-order workflow.
2.4 Immutable Movement Ledger
Every stock movement — inbound, outbound, transfer, allocation, or adjustment — writes a new, immutable ledger entry recording the type, quantity, reason, and the user who triggered it. No updates, no deletes — only new entries. The ledger is the source of truth for inventory reconciliation and audit trail.
3. Haversine Geospatial Routing
Distance calculation uses the Haversine formula — great-circle distance on a sphere, computed directly from each warehouse's latitude/longitude and the customer's, accounting for the Earth's curvature.
Why Haversine? More complex spatial-indexing schemes are over-engineered for warehouse routing. Haversine is:
- Pure math, no external API calls, no index required
- Accurate to <0.5% for most terrestrial distances
- Handles the curvature of the Earth without projection errors
Geocoding — no paid API required: When a customer address has no coordinates on file, Aeion resolves them through a free OpenStreetMap-based lookup by default. A paid geocoding provider (Google Maps, Mapbox) can be swapped in for brands that need higher-volume bulk geocoding, with no change to how routing itself works.
4. Bridge — Hardware Integration
| Capability | MCP Tool | Parameters |
|---|---|---|
| Print ZPL thermal label | `hardware.printer.print_zpl` | `zpl: string, copies: number` |
| Print HTML packing slip | `hardware.printer.print_html` | `html: string, copies: number` |
| Read scale serial line | `hardware.serial.read_line` | `port?: string, timeoutMs: number, sendBeforeRead: string` |
| Scan barcode (HID) | `hardware.hid.scan_barcode` | `timeoutMs: number` |
5. Data Model
Fulfillment's data model covers:
- Warehouses — facility records with coordinates, capacity, and active/inactive status
- Inventory — per-SKU-per-warehouse stock levels: on-hand, allocated, available, and reorder point
- Movement ledger — an immutable record of every stock change, with type, reason, and the user who made it
- Shipments — carrier shipments with tracking numbers, labels, and manifests
- Fulfillments — order fulfillment records, linked back to their originating Commerce order
- Returns (RMA) — return authorizations with per-item condition tracking and restock flags
- Picking lists — batch pick lists aggregated across multiple orders
- Locations — bin/location codes within warehouses, shared with the Production module
- Shipping methods — carrier service levels with rate overrides
Warehouses own their bin/location records; inventory is tracked per SKU per warehouse; fulfillments link back to Commerce orders; returns and picking lists link back to their fulfillment records.
6. API Routes
| Method | Path | Description |
|---|---|---|
| `POST` | `/api/v1/fulfillment/warehouses/:id/geocode` | Geocode warehouse address → update coordinates |
| `POST` | `/api/v1/fulfillment/stock/receive` | Receive inbound stock (manual receipt) |
| `GET` | `/api/v1/fulfillment/optimal-origin` | Find nearest warehouse with stock (sku, qty, lat, lng) |
| `GET` | `/api/v1/fulfillment/analytics/overview` | Counts + time-series for warehouse, inventory, movements, shipments |
| `GET` | `/api/v1/fulfillment/analytics/low-stock` | Inventory items at or below their reorder point |
| `POST` | `/api/v1/fulfillment/picking-lists/generate` | Generate batch pick list for multiple orders, one warehouse |
| `GET` | `/api/v1/fulfillment/picking-lists/:id/print` | Print-ready view of a pick list |
| `POST` | `/api/v1/fulfillment/returns/:id/process` | Process RMA: conditional restock, status → completed |
| — | `/api/v1/fulfillment/bridge/*` | Bridge hardware routes (print, scan, scale) |
7. Events & Integration Points
| Event | Payload | Fires When |
|---|---|---|
| `fulfillment.order_allocated` | `{ orderId, orderNumber, allocations[] }` | Any successful allocation |
| `fulfillment.allocation_failed` | `{ orderId, sku, quantity, reason }` | Partial or total allocation failure |
| `fulfillment.low_stock` | `{ sku, warehouseId, available }` | Available stock drops to or below the reorder point |
| `fulfillment.return_processed` | `{ returnId, rmaNumber }` | An RMA is restocked and closed |
| `fulfillment.shipped` | Workflow trigger | An order ships, via the workflow engine |
8. Reliability & Correctness
Fulfillment's routing, allocation, and inventory logic is covered by an automated test suite that verifies the behaviors that matter for a warehouse operator's peace of mind:
- Warehouse selection consistently returns the nearest facility with available stock, correctly sorted
- Orders that can't be fully covered by the nearest warehouse split correctly across additional warehouses
- Allocation failures emit the correct event when no warehouse anywhere has stock
- Every inventory-guarded operation (allocation, outbound, inbound) behaves correctly under conflicting concurrent requests, including the negative-stock and overselling edge cases
- Return processing restocks new/opened items and correctly skips damaged or defective ones, firing the completion event exactly once
Core Architecture Decisions
Distance metric
Haversine (great-circle distance) — No external API, pure math, <0.5% error
Geocoding
OpenStreetMap by default — Free, no API key required
Inventory writes
Guarded atomic updates — Prevents overselling without row-level locking
Stock model
On-hand minus allocated = available — Separates physical stock from committed stock
Label format
ZPL II (203 dpi, 4x6") — Universal Zebra/Sato/DYMO thermal printer support
Scale protocols
Toledo + CAS + Ohaus — Covers 95%+ of industrial warehouse scales
Barcode inference
String-pattern heuristics — Fallback when scanner doesn't return format hint
Movement ledger
Immutable append-only — Audit trail, CFO reconciliation, fraud detection
RMA restock
Per-item condition check — Business logic: new/opened restocked, damaged dispositioned