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:

  1. Geospatial warehouse selection — Haversine distance calculation to find nearest facilities
  2. Atomic inventory movement — guarded atomic updates preventing overselling
  3. 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

CapabilityMCP ToolParameters
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

MethodPathDescription
`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

EventPayloadFires 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 triggerAn 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