Skip to content
theGreenGuy edited this page Jun 19, 2026 · 2 revisions

Areas

Built (the Master Data Areas epic). A first-class, hierarchical Area entity in master-data, plus the location areaId link and the area-scoped mobile-work it enables (per-operator reservation of count lines and replenishment tasks).

An area is a named, nestable region of a warehouse used to scope operator work: a building, a level, an aisle range, a zone. Areas are owned by master-data and are first-class catalog objects (create, edit, archive), exactly like SKUs, locations or storage blocks.

The model

  • An area belongs to a warehouse and may nest inside another area via parentAreaId, so areas form a tree (a level contains aisles, an aisle contains zones, …). New table area (Flyway V19).
  • A location carries an optional areaId (location.area_id), so a location belongs to at most one area. An area's effective scope is itself plus every descendant area beneath it.
  • Guards: a parent area must be in the same warehouse (cross-warehouse parenting is rejected), and the parent chain may not form a cycle (an area cannot be its own ancestor). Both are validated on create and update.
  • There is no persistent operator → area binding. An operator chooses (or scans) their area on the handheld; the chosen areaId lives only in the running process instance's data object.

API (/api/master-data/areas)

RBAC-gated like the rest of master-data CRUD; see contracts/openapi/master-data.yaml.

Method / path Purpose
GET /api/master-data/areas?warehouseId=&parentAreaId= List areas. Omit parentAreaId for top-level areas; supply it for the direct children of an area.
POST /api/master-data/areas Create an area (name/code, warehouse, optional parentAreaId).
GET /api/master-data/areas/{id} Read one area.
PUT /api/master-data/areas/{id} Edit an area (rename, re-parent within guards).
DELETE /api/master-data/areas/{id} Soft archive the area.
GET /api/master-data/areas/{id}/location-ids?recursive=true Resolve the area to the set of location UUIDs in its subtree. A recursive CTE walks the area tree from {id} down and returns every location whose area_id is anywhere in that subtree. With recursive=false it returns only the area's own directly-assigned locations.
GET /api/master-data/areas/resolve?warehouseId=&ref= Resolve an area by reference (code) for handheld scan/verify → {found, areaId, areaCode, …}. By design a 200 found:false on a miss (never 404). Backs the process-designer area verify kind.

The location-ids?recursive=true call is the primitive everything else builds on: an operator picks an area, and a service reserves only work whose location is inside that subtree.

Area-scoped mobile work (per-operator reservation)

Two services consume the area subtree to hand operators only their area's work, one piece at a time, with a per-operator reservation so two people in the same area never grab the same item.

Counting (count lines)

  • POST /api/counting/lines/claim-next {warehouseId, areaId, instanceId} atomically reserves the next PENDING count line whose location is inside the area subtree (resolved via GET /api/master-data/areas/{id}/location-ids?recursive=true, identity forwarded), using Postgres FOR UPDATE SKIP LOCKED so concurrent operators never claim the same line. Returns {found, countTaskId, lineId, locationId, skuId, expectedQty, uomCode, countType} or {found:false}.
  • POST /api/counting/lines/release {instanceId} frees every not-yet-started reservation held by the instance (idempotent; returns {freed}).
  • The first recorded count stamps started_at, making the line immune to release and to the sweeper.
  • Schema: count_line gains reserved_by / reserved_at / reserved_by_instance / started_at (V6). A ShedLock sweeper reclaims stale reservations (TTL openwcs.counting.reservation-ttl, default 15m).

Slotting (replenishment tasks)

  • POST /api/slotting/replenishment/claim-next {warehouseId, areaId, instanceId} reserves the next PLANNED replenishment (collection) task whose destination pick face (to_location_id) is inside the area subtree, EMERGENCY priority first, same FOR UPDATE SKIP LOCKED guarantee. Returns {found, taskId, skuId, fromLocationId, toLocationId, qty, uomId, priority}.
  • POST /api/slotting/replenishment/release {instanceId} frees not-yet-started reservations.
  • started_at is stamped on dispatch; the sweeper TTL is openwcs.slotting.reservation-ttl (default 15m). Schema: replenishment_task gains the same four reservation columns (V8).

Reservation lifecycle

  1. Reserved: claim-next hands the operator the next line/task for their instance.
  2. Firm claim: once the first count or move is posted, started_at is set; a started item is never released or swept.
  3. Freed: by an explicit release (a work.release step on the flow's exit branch, see the Mobile Process Designer) or by the TTL sweeper if the app is closed / crashes, so an abandoned reservation never wedges an item forever.

The handheld side of this (the stockcheck.next, replenishment.next and work.release curated tasks, the area verify kind, and the seeded stock-check-by-area reference process) is documented on the Mobile Process Designer page.

Frontend

In the Master data section the UI gains an Areas tab with an AreaDialog for creating and editing nested areas (the parent select excludes the area itself and its descendants, so the tree stays acyclic), and the Locations screen gains an Area select so a location can be filed under an area.

Data ownership

area and location.area_id live in the master_data schema. Counting and slotting reference the resolved location UUIDs by value (no cross-schema foreign keys); they call master-data over HTTP with the operator's forwarded identity to resolve an area to its location-ids. See Architecture and Services.

Clone this wiki locally