OMS ↔ WMS: inventory source, push, ship-back, and tracking sync
Last updated 2026-10-13
How the two apps talk: inventory source resolution, idempotent push-to-WMS, WMS ship callbacks, and channel tracking sync.
In wms_sync mode, OMS and WMS form a closed loop: OMS reads WMS inventory, pushes orders down as outbound shipments, WMS executes picking/packing/shipping, then ships callbacks flow back up to OMS. This article maps each link.
Inventory source resolution#
- oms_inventory_source_configs holds the tenant default (warehouse_id NULL) plus per-warehouse overrides; resolveInventorySource picks the effective mode per order.
- In wms_sync mode the OMS inventory ledger is frozen read-only — ATP checks read WMS qty_available instead.
Push down: order → WMS outbound shipment#
- push-to-wms converts lines one by one into wms_order_lines. Because item_id is already the wms_items id, no SKU mapping is needed.
- The outbound shipment is created in commit status, which locks inventory via lockInventoryForCommit. On success the OMS order moves to released.
- One OMS order can split into several shipments (split_index / split_total); the detail page lists all of them, keeping the first for backward compatibility.
Ship-back: WMS → OMS#
- When WMS ships an outbound shipment, the ship hook accumulates fulfilled_qty on the matching OMS lines and fills in line-level tracking/carrier (split shipments accumulate across multiple ships).
- The order-level status is derived from all lines: all shipped → fulfilled, otherwise partial_fulfilled; order-level tracking/carrier/shipped_at are filled in (existing values are not overwritten).
- Each ship also writes a shipped event into oms_wms_status_events with the tracking number and shipped lines, feeding the WMS timeline and channel sync.
Tracking sync back to sales channels#
- On ship, OMS fires a fire-and-forget tracking sync to the sales channel (e.g. Shopify fulfillments; split shipments map to line items via channel_line_item_id). A failed sync never blocks the ship response.
- Sync status lands on tracking_sync_status (synced / failed / skipped) with append-only logs; retry-tracking-sync retries the latest failure, optionally for one split.
- Only the shipped event triggers a sync; delivered does not.
Line status on the order detail page is derived, not stored: fulfilled_qty = 0 → pending; partial → partial; fulfilled_qty ≥ ordered_qty → fulfilled.