Skip to main content

SOP: Receiving Putaway

Document ID: WMS-REC-004 Version: 1.0 Effective date: 04/30/2026 Owner: Warehouse Operations Manager Next review: [six months from effective date] Applies to: Floor staff moving newly-received inventory from the receiving zone to its pickable bin


1. Purpose

This procedure governs the physical movement of inventory from the receiving location (where approval per WMS-REC-003 placed it) to its final pickable location in the warehouse. Putaway is the step that turns "received and signed off" stock into "findable and pickable" stock. Until putaway is complete, the stock is technically AVAILABLE in the system but sitting in the receiving zone — pickers can be allocated to it (the FEFO/FIFO policy in WMS-PICK-001 §5 doesn't care that it's still in receiving), and they will end up walking to the receiving zone to pick from it. That defeats the purpose of having pick zones.

Approval creates a WorkTask of type PUTAWAY with one TaskItem per inventory line. This SOP covers what should happen with that task next.

⚠ MAJOR CURRENT-STATE GAP. As of WMS app v[current], the receiving-side putaway flow is substantially unimplemented:

  • The WorkTask of type PUTAWAY is created at approval (WMS-REC-003 §4.2).
  • No UI route exists for working a receiving-side putaway task. The only putaway pages in the app are /returns/putaway/:taskId and /returns/customer-putaway/:taskId — both for return-flow putaway, not receiving.
  • No API endpoint specific to receiving putaway exists. The generic WorkTaskService exists but no Fastify route exposes its methods for floor use.
  • Crucially, WorkTaskService.complete() does not move inventory. It updates order status only for PICKING tasks. For a PUTAWAY task, "complete" today means the row's status flips to COMPLETED — and nothing else. No InventoryUnit.locationId updates, no per-unit move events, no audit trail of where the stock physically ended up.

This SOP documents (a) the intended target flow when the system is built, and (b) the current-state workaround that uses WMS-INV-001 §4.1 (Move Location) on each unit to physically log the move. Putaway tasks created at approval are effectively idle today.

2. Scope

In scope:

  • The WorkTask of type PUTAWAY created at receiving-approval time
  • Moving each newly-received InventoryUnit from the receiving zone to its pickable bin
  • Recording the moves so the audit trail is intact
  • The current-state workaround using WMS-INV-001

Out of scope:

  • Customer return putaway — see WMS-RET-003 (separate codepath, separate task type)
  • Bin location creation (when a SKU has no current pickable bin) — see WMS-INV-004
  • Floor-count and SKU-to-location mapping — see WMS-INV-003
  • Pick allocation to pickable bins — see WMS-PICK-001

3. Roles & permissions

API enforcement: Today, none of the receiving putaway endpoints exist, so there is nothing to enforce. The downstream operation that the workaround actually uses — POST /inventory/:id/move — checks authentication only (see WMS-INV-001 §3). Any authenticated user can move any unit.

RoleWork a putaway taskView putaway tasks
READONLY
STAFF
MANAGER
ADMIN
SUPER_ADMIN

Operational expectation: putaway is high-throughput and assigned to whoever's free. The WorkTask is created with status: PENDING and priority: 50; it sits in the queue alongside picking tasks until someone takes it. Pickers and putaway staff are typically the same people on a small floor.

4. Procedures

4.1 The target putaway flow (when the system is built)

This describes the intended state. See §4.2 for the current-state workaround.

Use when: A receiving session has been approved, you're on putaway duty, and the resulting WorkTask is in PENDING.

Prerequisites:

  • The WorkTask exists with type: PUTAWAY, status: PENDING, and a non-zero totalItems.
  • Each linked TaskItem has a productVariantId, a source locationId (the receiving zone), and a quantityRequired matching the approved good quantity.
  • A pickable destination location exists for each variant. (If not, see WMS-INV-003 for the new-SKU mapping flow before starting putaway.)

Steps (target):

  1. Open the work-task queue. Filter by type: PUTAWAY.
  2. Tap the task you want to work. Tap Assign to me (or whatever the queue UI calls self-assignment).
  3. Tap Start. Task status flips to IN_PROGRESS.
  4. For each TaskItem in sequence: a. The screen shows: SKU, current location (receiving zone), quantity to put away. b. Walk to the receiving zone. Locate the units. c. Scan the source location barcode to verify (UI confirms). d. Walk to the destination bin (the system suggests one based on prior placement; if no suggestion, see WMS-INV-003). e. Scan the destination location barcode. f. Scan a unit barcode (or +1) for each unit moved. g. Tap Done for the line.
  5. Once every TaskItem is complete, the task auto-completes. Task status flips to COMPLETED.

Result (target):

  • Each InventoryUnit for the moved variants now has locationId: <destination bin>.
  • One inventory:unit_moved event per unit moved is logged in inventory_events with the From/To locations and your user.
  • The WorkTask row is COMPLETED. A task_events row is logged with eventType: TASK_COMPLETED.
  • The receiving zone empties (modulo what's still on the dock).

4.2 Current-state workaround: moves via WMS-INV-001

Use when: §4.1 doesn't work because the UI is unbuilt, but stock has been approved and is sitting in the receiving zone.

The workaround uses the Move Location action documented in WMS-INV-001 §4.1 on each unit individually. This produces the correct audit trail (inventory:unit_moved events with From/To) but does not mark the receiving putaway WorkTask as COMPLETED — that row remains PENDING forever.

Steps:

  1. After approval, note the putaway task number from the success toast (PUTAWAY-{poRef}-{shortId}). Write it down or screenshot it for your putaway handoff log (§5.3).
  2. Walk to the receiving zone and physically locate the new stock by SKU.
  3. For each new InventoryUnit (one per variant + lot, created by the approve transaction): a. From any device, navigate to /inventory/:unitId for that unit. The fastest path: from the approval page, tap the SKU in the line list (links to inventory). On the inventory page, the new unit is at the top, sorted by createdAt. b. Follow WMS-INV-001 §4.1 to move the unit to its pickable bin location. Scan the destination, leave quantity blank for full move. c. The system writes an inventory:unit_moved event. The unit's locationId updates.
  4. Repeat for every unit created at approval.
  5. At the end of the session: in #warehouse-ops, post a message tagging the warehouse manager:
    PUTAWAY DONE — {putawayTaskNumber}
    Approved session: {sessionId}
    Units moved: {count}
    Putaway by: {your name}
    Time: {ISO timestamp}
    This bridges the audit gap left by the un-completed WorkTask row.

Result:

  • All inventory is at its final pickable location, with inventory:unit_moved events giving full traceability.
  • The WorkTask of type PUTAWAY is still PENDING in the database. This is intentional with the current code — completing it via the work-task service would not do anything additional and would mask the fact that the proper flow doesn't exist. Leave it pending; engineering can sweep these once the proper UI ships.

⚠ This workaround leaves PUTAWAY tasks accumulating as PENDING rows in work_tasks. That is the visible signal that putaway needs a proper implementation. If your queue page filters out PUTAWAY tasks (because they're noise), you'll lose the signal. Better to accept the noise and feel the friction.

4.3 What "complete" means today (and what it doesn't)

If you or anyone else calls WorkTaskService.complete(taskId) for a putaway task today, here is exactly what happens:

  • WorkTask.status flips from IN_PROGRESS (or whatever) to COMPLETED.
  • A task_events row is created with eventType: TASK_COMPLETED, your user ID, and the timestamp.

Here is exactly what does not happen (compared to a real putaway implementation):

  • No InventoryUnit.locationId updates.
  • No inventory_events rows of type inventory:unit_moved are written.
  • No verification that the inventory was actually moved physically.

This is why §4.2's workaround uses POST /inventory/:id/move directly — it's the only path that produces the correct inventory side effects today. Calling WorkTaskService.complete() in isolation produces an audit lie ("task was completed") with no inventory truth attached.

⚠ Do not call complete on a receiving putaway task as a shortcut. A COMPLETED task with stock still in the receiving zone is worse than a PENDING one — the next person checking putaway state will think it's done. Leave the task PENDING and complete the moves via §4.2.

4.4 Where putaway exists today (return flow)

For comparison and to avoid confusion: the return flow (/returns/putaway/:taskId and /returns/customer-putaway/:taskId) does have a working putaway UI. That code path:

  • Is a separate route file (return.routes.ts and customer-putaway.routes.ts).
  • Operates on the Return and CustomerReturnPutawayTask tables, not on WorkTask.
  • Updates inventory via inventoryUnit create/update calls inline within the return controllers, not via WorkTaskService.

Lessons from that code path will inform a proper receiving-putaway implementation. See WMS-RET-003 for the customer-side equivalent procedure.

5. Reference

5.1 Putaway task structure

The putaway WorkTask created by approval has these fields (from WorkTaskService and the approve transaction in ReceivingService.approve()):

FieldValue
taskNumberPUTAWAY-{poReference}-{base36-timestamp}
typePUTAWAY
statusPENDING (until/unless completed)
priority50
totalItemsCount of approved lines with goodQuantity > 0
completedItems0
totalOrders0 (putaway is not order-driven)
orderIds[]
notesPut-away for PO {poReference}

Each TaskItem row has:

FieldValue
taskIdThe putaway task ID
productVariantIdThe received variant
locationIdThe receiving location ID (source)
quantityRequiredThe good quantity (counted minus damaged)
quantityCompleted0 (until/unless completed)
sequence1, 2, 3... in the order processed by the approve transaction
statusPENDING

5.2 What a real putaway implementation should write

When the proper flow is built, completing a putaway task should produce:

  • One inventory:unit_moved event per unit moved (From: receivingLocation, To: destinationBin, with userId)
  • InventoryUnit.locationId updated for each moved unit
  • TaskItem.quantityCompleted and TaskItem.locationId updated to reflect destination
  • WorkTask.status: COMPLETED and WorkTask.completedItems: totalItems
  • task_events row with eventType: TASK_COMPLETED

5.3 Putaway handoff log (current-state)

Until the proper flow ships, maintain a putaway handoff log. The Slack post in §4.2 step 5 is the log. Format:

PUTAWAY DONE — PUTAWAY-{poRef}-{shortId}
Approved session: {sessionId}
Units moved: {count}
Putaway by: {name}
Time: {ISO timestamp}

Manager weekly review: scrape #warehouse-ops for PUTAWAY DONE posts and reconcile against the count of PUTAWAY WorkTask rows in status PENDING from the past week. The number of PENDING rows should equal the number of approved sessions in that week. If not, an approval went without a putaway — investigate.

  • WMS-REC-001 — Counting (the start of the inbound flow)
  • WMS-REC-002 — Exceptions (which determine goodQuantity per line)
  • WMS-REC-003 — Approval (which creates the putaway task)
  • WMS-INV-001 §4.1 — Move Location (the workaround procedure)
  • WMS-INV-003 — Floor count & location discovery (when a destination bin must be created)
  • WMS-INV-004 — Location management (creating bins)
  • WMS-PICK-001 — Picking (the immediate downstream consumer of putaway-completed inventory)
  • WMS-RET-003 — Customer return putaway (the code path that does work, for reference)

6. Audit & compliance

The current-state workaround produces correct inventory audit data via inventory_events (inventory:unit_moved per unit). What it lacks:

  • No association between the move events and the source PO. The inventory_events.payload.reason field is null for moves performed via the Move Location button (see WMS-INV-001 §4.1 callout). The PO context is implicit — provable only via timestamp correlation with the audit_logs SESSION_APPROVED row.
  • No completion of the WorkTask. The task row stays PENDING, which is the visible flag that this flow is broken.
  • Reliance on a Slack-message handoff log rather than a structured database record.

When the proper flow ships, the audit story becomes:

  • task_events rows tie each move action to a putaway task to a PO via WorkTask.taskNumber
  • InventoryUnit history is queryable per-unit via the Activity Feed (WMS-INV-001 §4.4)
  • Duration from approval to putaway-complete becomes a meaningful metric

Manager weekly review (current-state):

  • Count of WorkTask WHERE type='PUTAWAY' AND status='PENDING' AND createdAt < now() - 24h — anything older than 24 hours is stale; reconcile via §5.3.
  • #warehouse-ops PUTAWAY DONE posts — verify each one has a matching PUTAWAY-... task in the database.

Manager weekly review (target-state):

  • Mean time (WorkTask.completedAt - WorkTask.createdAt) for putaway tasks. If creeping above ~2 hours, putaway is bottlenecked.
  • Receiving zone unit count. Should trend toward zero by end-of-day; persistent backlog is a putaway-staffing issue.

7. Troubleshooting

SymptomCauseResolution
I approved a session but I don't see a putaway task in any UIPutaway UI doesn't existUse §4.2 workaround. Move each unit via WMS-INV-001 §4.1.
Pickers are walking to the receiving zone to pick ordersStock was approved but never put awayAudit: InventoryUnit WHERE locationId = <receiving location> AND createdAt < now() - 4h. Each one is a missed putaway. Run §4.2 retroactively.
Multiple PUTAWAY tasks accumulating in PENDING statusExpected with current implementationThese are the audit signal that the proper flow is unbuilt. Don't bulk-cancel them; engineering will sweep them when the flow ships.
Tried to call WorkTaskService.complete() on a putaway task — task says complete but stock didn't movePer §4.3, complete() doesn't move inventory for putaway tasksManually move via §4.2. Document in #warehouse-ops.
The destination bin has no barcodeUnmapped locationSee WMS-INV-003 (floor count) to map a SKU to a bin and assign a barcode before completing putaway.
Approved session was for a SKU we don't normally stock; no destination bin existsNew SKU first timeUse WMS-INV-003 to create the bin and barcode it, then run §4.2.
Slack PUTAWAY DONE message has typos or a wrong task numberCurrently the only audit; no validationManager reconciles by hand. Discipline > tooling, until the UI ships.

8. Escalation

  • Inventory has been at the receiving location for >24 hours: warehouse manager. Either putaway staff is overloaded or §4.2 isn't being run. Both are operational issues, not bugs.
  • Engineering priority for the receiving putaway UI: this is the highest-leverage missing receiving feature. Until it ships, every approved session generates manual work and a Slack post. Loop in the warehouse operations manager to advocate.
  • Receiving location is full and no more POs can be received: stop receiving. Run §4.2 on everything in the zone. Resume receiving only when the zone has capacity.
  • Pickers report walking to the receiving zone repeatedly: a serious signal that putaway is failing. Audit per §7 row 2.

9. Revision history

VersionDateAuthorChanges
1.0[DATE][NAME]Initial release. Documents the receiving-putaway flow as designed and the current-state code gap: the WorkTask of type PUTAWAY is created at approval but no UI, no API route, and no WorkTaskService codepath actually moves inventory for it. WorkTaskService.complete() is explicitly documented as a "do nothing for putaway" function — it flips status without moving inventory. The §4.2 workaround uses POST /inventory/:id/move from WMS-INV-001 to produce the correct inventory:unit_moved events. The §5.3 Slack handoff log bridges the audit gap until the flow is built. Cross-references the working returns putaway code paths (/returns/putaway/:taskId, /returns/customer-putaway/:taskId — see WMS-RET-003) as the reference implementation that should inform the receiving build. Field structure of the putaway task pulled from ReceivingService.approve() in packages/domain/src/services/receiving.service.ts.