-
-
Notifications
You must be signed in to change notification settings - Fork 0
Inbound and Inventory
Real-time stock is a projection of the append-only transaction log. Nothing writes
stock.qty directly — movements are events, and the inventory service applies them.
Goods-in → stock
POST /api/txlog/events {GoodsReceived} ─► outbox relay ─► txlog.stream (Kafka) ─► inventory projection ─► stock
Line transactions (all order types)
POST /api/orders/{id}/lines/{lineNo}/transactions
─► order-management appends GoodsReceived / Picked / StockAdjusted to txlog (via its outbox)
─► relay ─► txlog.stream ─► inventory projection moves stock.qty
INBOUND = receipts (+), OUTBOUND = picks (−), COUNT / ADJUSTMENT = signed adjustments. The
actor is recorded on every transaction (authenticated when security is on).
-
POST /api/txlog/eventswrites the immutable event and an outbox row in one transaction; a scheduled relay publishes totxlog.streamin globalpositionorder. -
GET /api/txlog/events?afterPosition=is the global replay feed used to (re)build read models — and to stream confirmations back to the host (see Host Integration). - UPDATE/DELETE on the event table are blocked by a trigger.
- Consumes
txlog.stream; movement events (GoodsReceived,PutawayCompleted/StockMoved,Picked,StockAdjusted,StockStatusChanged) movestock.qty(per warehouse × SKU × batch × location × HU × status) and advance a projection cursor. -
Idempotent: every applied event is recorded in a
processed_eventinbox keyed oneventId, so redelivery/replay is a no-op and the read model can be rebuilt from the log. -
Stock follows the HU: for HU-bound events (
GoodsReceived,Picked,StockAdjusted,StockStatusChanged) the projection books the bucket to the HU's current registered location, not the location stamped in the event. Count lines capture their location at task creation; by the time a station count confirms the tote may have moved. A log line at INFO records when the two diverge. Deliberate from→to moves (PutawayCompleted,StockMoved) keep their explicit locations. When the HU has no registered location the event location is used as-is. - Serves SKU-wide and location-scoped availability/reservations (ATP) used by allocation.
-
Per-location occupancy (
POST /api/inventory/locations/occupied, body{ "locationIds": [...] }→{ "occupiedLocationIds": [...] }): the subset of the given locations that physically hold any stock row or handling unit. Slotting put-away uses it to exclude occupied slots from its candidate set before scoring (see Slotting and Replenishment). - Batch/lot & serial instances are created at goods-in (ADR 0001).
Physical HU instances (barcode, type, location, status) are managed via
/api/inventory/handling-units (CRUD). Type and location are set at registration and
are not editable through the registry — the PUT /{id} endpoint preserves the existing
huTypeId and locationId from the stored record regardless of what is sent in the
request body. Changing an HU's type or moving it to a different location can only happen
through a controlled process (e.g. a maintenance or QA work cycle). The registry UI
enforces this by disabling the Type and Location dropdowns when editing an existing HU.
Transport lifecycle location booking (PUT /api/inventory/handling-units/{id}/location,
body { locationId: uuid | null }, 404 for unknown HU): the controlled path the full PUT
defers to. Flow-orchestrator calls this endpoint as a best-effort side effect at two points in
the induction lifecycle:
-
RETRIEVE completion — books
locationId = null(caller does not know the position): the inventory service resolves this to the warehouse's UNKNOWN operational location (FREE_SPACE/QUARANTINE, lazily created in master-data) so no null ever reachesstock.location_id. The HU and its stock rows are visible in the Stock Overview at UNKNOWN while the tote is away from its slot. -
Return-leg STORE completion — books the source
locationIdback: the tote is physically back in its slot. On the first booking of an HU into a storage slot (i.e. the first STORE after goods-in), the service also stampshandling_unit.stored_at(V7 migration, nullable) with the current timestamp. The difference between goods-in receipt andstored_atis the dock-to-stock time surfaced in the Inbound dashboard.
The endpoint moves all stock rows riding in the HU to the new location in the same
transaction, so the Stock Overview always reflects the tote's current physical position.
Rows that would collide at the target location (same SKU, batch, and status, differing
only by location — e.g. a counting adjustment that previously landed on a stale slot) are
merged into one summed row before the update; the deletes are flushed to the database
before the location updates so the unique-bucket constraint (stock_bucket_uniq) is never
violated and the booking never rolls back. A UoM mismatch across merged rows logs a WARN.
The HU row and its stock rows are never out of sync.
Stock at UNKNOWN (HUs in transit or at a workplace whose exact position is unknown): remains
fully visible in the Stock Overview so admins can see and resolve it, but contributes zero to
availability / ATP and can never be reserved — the allocation guard excludes it from both
warehouse-wide and location-scoped availability checks, and any reservation attempt targeting
UNKNOWN is rejected with InsufficientStock. The UNKNOWN location is resolved from master-data
(GET /api/master-data/locations/operational?kind=UNKNOWN; openwcs.inventory.master-data-base-url,
default http://localhost:8081; id cached per warehouse in memory). If master-data is unreachable,
a null booking is rejected with 503; availability reads degrade gracefully (exclusion skipped,
UNKNOWN may transiently count as available).
Implemented by InventoryClient in flow-orchestrator (OPENWCS_INVENTORY_BASE_URL, default
http://localhost:8082, 2 s connect/read timeouts). A booking failure is logged as a warning
and never breaks the transport pipeline.
The Stock Overview screen lists current stock rows (SKU × location × HU × status). Each row has a Mark for counting button that creates an ad-hoc BLIND count task scoped to that location and SKU without leaving the screen:
- Calls
POST /api/counting/taskswithscopeType: LOCATION,countType: BLIND,tolerance: 1, and a singlecellsentry{ locationId, skuId }. - While the request is in-flight the button shows "Adding…" and is disabled for that row.
- On success a green notice confirms the task was created (e.g. Count task created for SKU-001 at A-01-01); on failure the error is shown in the existing red alert bar.
- Rows where
locationIdorskuIdis missing (e.g. unallocated stock) have the button disabled.
The created task appears in the stock-counting screen immediately and follows the normal
capture → variance → reconcile → StockAdjusted lifecycle (see the counting service row
in Services). For ASRS-family locations the task is created with routingStatus = PENDING
and the background CountRoutingScheduler routes the tote to an active GTP STOCK_COUNT station
within a minute, retrying on failure (see Goods-to-Person Stations).
Every counting lifecycle transition is logged at INFO (normal path) or WARN (degraded path) so that a count task can be traced end-to-end from production logs:
| Event | Level | Key fields |
|---|---|---|
| Task created | INFO | task id, scope type/ref, count type, origin, line count, tolerance; recount of task <id> suffix for recounts |
| Task claimed by operator | INFO | task id, operator |
| Task counted (operator submit) | INFO | task id, operator, line count, count type |
| Line reconciled — within tolerance (adjusted) | INFO | task id, line id, variance, tolerance, delta, SKU, location, HU, actor, event id |
| Line reconciled — exact match (no adjustment) | DEBUG | task id, line id, SKU, location |
| Line flagged for recount | INFO | task id, line id, variance, tolerance, SKU, location |
| Task reconciled (no recounts) | INFO | task id, actor, approved and adjusted counts |
| Task reconciled with recounts | INFO | task id, actor, out-of-tolerance count, follow-up task id |
| Station blind-count: first count differs | INFO | task id, line id, first counted qty (expected qty withheld) |
| Station blind-count: counts agree, adjusted | INFO | task id, line id, agreed qty, delta, SKU, location, HU, actor, event id |
| Station blind-count: third count required | INFO | task id, line id |
| Station blind-count: count matches system qty | INFO | line id, counted qty, SKU, HU |
| Task auto-reconciled (all lines terminal) | INFO | task id, line count |
| Task deleted | INFO | task id, scope type/ref, origin |
| Count routing: FAILED | WARN | task id, scope type/ref, attempt count, reason; stays OPEN for retry |
| Count routing: success | INFO | task id, scope type/ref, new status, reason |
| Count cell presentation requested | INFO | tote HU code, SKU, qty, station id, task id, line id, flow entry id |
| Count cell: no HU at location | DEBUG | location id, SKU id |
| Schedule emitted task | INFO | schedule id/name, task id, scope type/ref, count type, next due, cadence |
A host can post a signed stock correction via POST /api/host/inventory/adjustments
(Host Integration); it is appended to the txlog as a StockAdjusted event and applied by
the projection like any other movement.
Related: Outbound Flow · Architecture.
openWCS — open-source Warehouse Control System · summarized from build.md & docs/AS-BUILT.md (the repo docs are authoritative).
Design
Flows
- Areas
- Inbound and Inventory
- Slotting and Replenishment
- Goods-to-Person Stations
- Outbound Flow
- Equipment Integration
- Transport Overview
- Process Designer
- Mobile Process Designer
- Hardware Visualisation
- Host Integration
Reporting & Dashboards
Operations