SOP: Picking
Document ID: WMS-PICK-001 Version: 1.0 Effective date: 04/30/2026 Owner: Warehouse Operations Manager Next review: [six months from effective date] Applies to: Floor staff working pick tasks on a Zebra TC22; managers monitoring the pick queue
1. Purpose
This procedure governs how an allocated order becomes physically picked — generating the pick list, walking the floor in pick-sequence order, scan-verifying the location and item at each stop, confirming the pick (or marking short), and staging the picked goods into a pick bin for handoff to packing. Picking is the first physical-touch step in the outbound flow and the most time-consuming activity in the warehouse on a per-order basis.
Allocation (per WMS-INV-006 §4.1) decides what gets picked from where. This SOP picks up at order status ALLOCATED and walks through to status PICKED (or PARTIALLY_PICKED if any item went short). The downstream packing flow is in WMS-PACK-001.
2. Scope
In scope:
- The pick queue at
/pick— what orders are pickable, who's already on them, priority filtering - Generating a pick list via
POST /fulfillment/:orderId/pick(creates theWorkTaskof typePICKING) - Walking the pick session at
/fulfillment/:orderId— the two-phase scan flow (location → item) - Scan-driven and manual confirm paths
- Denomination mode: scanning multiple units per item to make case-pack picking faster
- Short-picking when physical stock doesn't match allocation
- Staging into a pick bin at task completion
- The events written to
task_eventsand the SSE feed picker UIs subscribe to
Out of scope:
- Order allocation — see WMS-INV-006 §4.1 (this SOP starts after allocation succeeds)
- Pick bin behavior beyond initial creation at task completion — see WMS-PACK-001
- Packing, shipping, label printing for the package — see WMS-PACK-001 onwards
- Multi-bin / multi-package pick orchestration — covered as part of WMS-PACK-001 (the pick bin auto-creates here; bin lifecycle is a packing concern)
- Reassigning a stuck pick task — there is no reassign UI today; see §8
3. Roles & permissions
API enforcement, verified against routes:
POST /fulfillment/:orderId/pick(generate list),POST /fulfillment/:orderId/pick/:taskItemId/confirm(per-item confirm),POST /fulfillment/:orderId/pick/confirm-all(bulk confirm) — auth only, no role gate- The
/pickroute in the React router gates onSTAFF_ROLES— meaning STAFF, MANAGER, ADMIN, SUPER_ADMIN can access the page; READONLY cannot
| Role | View pick queue | Generate pick list | Walk a pick task | Confirm pick / short | Bulk confirm |
|---|---|---|---|---|---|
| READONLY | — (router blocks at /pick) | — | — | — | — |
| STAFF | ✓ | ✓ | ✓ | ✓ | ✓ |
| MANAGER | ✓ | ✓ | ✓ | ✓ | ✓ |
| ADMIN | ✓ | ✓ | ✓ | ✓ | ✓ |
| SUPER_ADMIN | ✓ | ✓ | ✓ | ✓ | ✓ |
Operational expectations (not technically blocked):
- A picker walks one pick task at a time. The system doesn't block parallel picks by the same user, but two simultaneous task confirms from the same person create messy event timelines and confused multi-bin state.
- Self-assignment is the norm — no central dispatcher. Picker taps an order in the queue, becomes the de-facto worker on it, walks the floor.
- Bulk-confirm (
/pick/confirm-all) bypasses scan verification entirely. Reserve it for known-clean pick lists where scan-verify isn't operationally required (e.g., admin bypass on a stuck task that's already 95% complete and the picker walked away). It is not the primary path.
4. Procedures
4.1 The pick queue
Use when: Starting your shift, looking for the next order to work, or checking who's on what.
Steps:
- Open
/pick. The page lists orders withstatus IN ('ALLOCATED', 'PICKING'), sorted by priority then age. - Each row shows:
- Order number
- Status badge —
Ready(statusALLOCATED, no one's on it) orPicking(statusPICKING, someone's working it) - Priority badge for
URGENT/HIGH(red border on the card) - Customer name, item count, time since order placed (
timeAgo) - Progress bar for in-progress orders (
{picked}/{total})
- Tap the order. The page navigates to
/fulfillment/:orderId— the unified pick → pack → ship workflow page. - If the order is
ALLOCATED(no pick task yet), the fulfillment page callsPOST /fulfillment/:orderId/pickto create the pick task before showing the first pick item. If the order is alreadyPICKING(someone's in flight), the page resumes from the next pending item.
What's surfaced and what's not:
- The queue does not show who is currently picking an in-progress order. If you tap an in-progress order, you become the active picker by default — there's no warning that someone else may be on it.
- There is no filter for "tasks assigned to me" because task assignment is implicit — the first user to hit
confirmPickItemon a task becomes the recordeduserIdon those events, but there is noassignedToIdfield on theWorkTaskfor picking tasks.
4.2 Generating the pick list
Use when: An order is ALLOCATED and you tap it from the queue. (You don't normally call this endpoint directly — the fulfillment page handles it.)
Prerequisites (enforced by PickingService.generatePickList):
- Order status is one of
PENDING,CONFIRMED,READY_TO_PICK, orALLOCATED. If the order is in any other status (alreadyPICKING,PICKED,CANCELLED, etc.), the endpoint throwsCannot start picking: order is {status}. Expected one of: PENDING, CONFIRMED, READY_TO_PICK, ALLOCATED. - Order has at least one allocation. Otherwise:
No allocations found for order {orderNumber}. Allocate inventory first. - No active pick task already exists for the order. Otherwise:
Active pick task {taskNumber} already exists for this order.
What this writes (one transaction):
- A new
WorkTaskrow with:taskNumber: PICK-{orderNumber}-{shortId}(pergenerateTaskNumberhelper)type: PICKINGstatus: PENDINGpriority: 2 if order.priority==='EXPRESS' else 1 if order.priority==='RUSH' else 0totalItems: <allocation count>orderIds: [orderId],totalOrders: 1
- One
TaskItemper allocation, sorted bylocation.pickSequence ASC(nulls last via fallback9999):taskId,productVariantId,locationId,inventoryUnitId,allocationId,orderItemIdquantityRequired: <allocation quantity>quantityCompleted: 0sequence: 1, 2, 3...(the walking order — same aspickSequenceascending)status: PENDING
- Two
fulfillment_eventsrows:order:processingandpicklist:generated(with the per-item summary in the payload).
Result:
- The order doesn't transition to
PICKINGimmediately. It transitions when the first item is confirmed (per §4.4) — the repo flipsWorkTask.status: IN_PROGRESSand the order tracks separately. - The fulfillment page now has a
currentPickItemto show the picker.
4.3 The two-phase scan flow
Use when: You're on /fulfillment/:orderId after generating or resuming a pick list.
The flow alternates between two scanPhase states:
scan_location— the page shows the next pick item with its expected location (zone, aisle, rack, shelf, bin from theLocationrow, plus the location's barcode). Walk to that location.scan_item— after the location is scanned, the page accepts the next scan as the item barcode. The picker physically takes the unit from the bin and scans it before moving on.
Steps for a single pick:
- The screen shows the current item: SKU, product image, variant name, location label (e.g.,
A-01-02 / Zone A / Aisle 1), required quantity. - Walk to the location. Scan the location's barcode.
- The page shows feedback:
✓ Location scanned — now scan item. ThescanPhaseflips toscan_item. (If the location has no expected barcode set, the page showsNo location required — scan itemand skips this phase.) - Pick the unit physically. Scan the unit's barcode.
- The page calls
POST /fulfillment/:orderId/pick/:taskItemId/confirmwith{ quantity: <required>, scannedLocationBarcode, scannedItemBarcode, binId }. - The server records the pick (per §4.5). The page advances to the next item, scan phase resets to
scan_location.
Important behaviors:
- The location-scan barcode is just stored on the request. The server records it on the task item (
locationScanned: true) but does not validate it against the expected location's barcode. Same for the item scan. The picker's flow gates verification client-side; the audit trail is "this scan happened" not "this scan matched." - If a barcode looks like a bin barcode (matches a
pickBin.barcodefor this order), the page treats it as a "switch active bin" command rather than a location/item scan. This is for multi-package orders — see §4.7.
⚠ Scan validation is client-only. A picker who knows what they're doing can scan any barcode-shaped thing in either phase and the server will accept it. The
locationScannedanditemScannedflags on the task item are based on whether any scan happened in that phase, not whether the scan was correct. This is an honest limitation; if you suspect a picker is gaming the flow, the audit trail won't catch it directly. Manager spot-checks during walks are the control. See §8.
4.4 Denomination mode (multi-unit per scan)
Use when: The order needs multiple units of a single SKU and you're scanning each unit one at a time (rather than one scan = full quantity).
How it works:
- The page maintains a
scanMultiplierstate. Default is0(one scan = full quantity). - The picker can set
scanMultiplierto a positive number (typically1, meaning each scan = +1 to the running total). The UI exposes this; tap+1,+2, etc., depending on how many units come per scan. - Each item-scan in
scan_itemphase increments apickAccumulatorfor the currenttaskItemId. The page shows{sku}: {n}/{required} scanned. - When the accumulator reaches the required quantity, the page auto-calls
confirmPickand advances to the next item.
Why this matters:
- For a 24-pack of an item where each individual unit has its own barcode, the picker can scan each unit (1+1+1...) and the system accumulates rather than firing 24 separate "pick complete" events.
- The accumulator is local UI state — if the picker refreshes the page, the accumulator resets. The server doesn't know about partial accumulation; it only knows when a full confirm fires.
4.5 Confirming a pick: full quantity vs. short
Full pick (the happy path):
The auto-flow at end of §4.3 step 5. Server-side, confirmPickItem (per picking.service.ts:213 and picking.repo.ts:323) runs in a transaction:
- Updates
TaskItem:status: COMPLETED,quantityCompleted: <quantity>,completedBy: <userId>,completedAt: now,locationScanned,itemScanned. - Updates the linked
Allocation:status: PICKED,pickedAt: now. - Increments
OrderItem.quantityPickedby the picked quantity. - If the underlying
InventoryUnit.quantity <= 0after the pick (i.e., the bin's now empty), updates the unit'sstatus: PICKED. (Otherwise the unit staysAVAILABLEfor future allocations.) - Updates the
WorkTask:completedItems: <count>,status: IN_PROGRESS,startedAt: <existing or now>. - Writes a
task_eventsrow:eventType: ITEM_COMPLETED, with sku/locationName/quantity in the payload. - If this was the last item on the task, also updates
WorkTask.status: COMPLETED,completedAt: now,completedOrders: 1, and writes aTASK_COMPLETEDevent.
Short pick:
Use when: You arrive at the location and there's not as much physical stock as the allocation expected. Maybe 5 of 7 are there, or none.
Steps:
- Pick what's physically available. Don't overstate.
- The UI exposes a
Confirm {n} (Short)button when in denomination mode with a partial count — it sendsquantity: <accumulated>, which is less thanquantityRequired. - Outside denomination mode, the UI shows the
All {required}confirm; for a short, the picker can manually call confirmPick with a smallerquantity(the page exposes this via the confirm-with-accumulator path; if not, see §7 troubleshooting).
Server-side, when quantity < quantityRequired:
TaskItem.status: SHORT,quantityCompleted: <picked qty>,shortReason: "Short pick: {picked}/{required}".- Linked
Allocation.status: PARTIALLY_PICKED(notPICKED). OrderItem.quantityPickedincrements by the picked quantity (not the required).- Same task-level updates as full pick. The
task_eventsrow useseventType: ITEM_SHORT.
Result of a short pick:
- The order does not automatically backorder the missing quantity. The order's
OrderItem.quantitystays at the original, butquantityPicked < quantity. The downstream packing/shipping flow handles the partial — see WMS-PACK-001. - The picker keeps walking. Subsequent items confirm normally.
- When the task completes, it has both
completedItems(everything that was confirmed in any state) andshortItems(subset that went short).
⚠ Short pick does not automatically write an
InventoryAdjustment. The system records the short on the task item and the allocation, but the inventory variance — "the system says 5, you found 3" — needs a separate cycle count (per WMS-INV-002) to formally reconcile. Until then, the system thinks the missing 2 units are still at that location, and may try to allocate them to the next order. The two-step recovery: (a) finish the short pick to release the order, (b) immediately cycle-count the bin per WMS-INV-002 to fix the system stock. Skipping step (b) propagates the short to future orders.
4.6 Bulk confirm (admin shortcut)
Use when: Rare. A pick task is mostly complete and the remaining items can't be physically scanned for some reason — picker walked away mid-task and you need to close it for downstream packing.
Steps:
- (No mounted UI button on the picker page — see §8.) Call
POST /fulfillment/:orderId/pick/confirm-all. - Server-side,
confirmAllPickItemsiterates everyPENDINGtask item and confirms each withquantity: quantityRequired,locationScanned: false,itemScanned: false.
Caveats:
- No barcode verification at all. Both flags record
falseon every item. - No short-pick option. Every remaining item confirms at full required quantity. If physical stock is short, the system records full pick anyway — the resulting allocation says
PICKEDbut the picker hasn't actually picked. This will surface as a packing variance later. - One
task_eventsrow per item. Even though it's bulk, the events are individualITEM_COMPLETEDrows; the audit trail is preserved per item.
⚠ Bulk confirm is dangerous. It writes the same database state as a real pick session, but with no scan verification. Use it only when (a) you can physically verify the units are correctly picked, or (b) you accept that downstream packing/shipping will catch any discrepancy. The audit trail will show
locationScanned: falseanditemScanned: falseon every item — that's the manager's flag that this was a bulk confirm. If you find yourself reaching for it routinely, that's an operational problem, not a feature problem.
4.7 Pick bin staging (handoff to packing)
When confirmPickItem results in taskComplete: true, the service also runs createPickBin(orderId, pickTaskId, userId):
- Generates a bin number via
generateBinNumber()and a printable barcode. - Creates a
PickBinrow associated with the order and task. - Returns the bin (id, binNumber, barcode) in the
PICKLIST_COMPLETEDevent payload.
The picker physically transfers the picked goods into the bin (typically a tote, but can be a bag/box for small orders). The bin sits at the staging shelf for packing pickup.
For multi-package orders (orders that need to ship in multiple boxes — typical for large orders or when ShipEngine's box recommender suggests splitting), the pick bin model expands. The fulfillment page initializes multiple bins via /fulfillment/:orderId/bins/init before picking starts, and each pick scan can target a specific bin via binId in the confirm body. This is covered in WMS-PACK-001 — for the pick procedure, the picker just scans whatever bin the page shows as "active" and continues.
5. Reference
5.1 The WorkTask of type PICKING — fields used
Created per §4.2:
| Field | Value |
|---|---|
taskNumber | PICK-{orderNumber}-{shortId} |
type | PICKING |
status | PENDING → IN_PROGRESS (first pick) → COMPLETED (last pick) |
priority | 2 (EXPRESS) / 1 (RUSH) / 0 (STANDARD) |
totalItems | Number of allocations |
completedItems | Increments on each confirm |
shortItems | Increments on each short-pick |
orderIds | [orderId] |
totalOrders | 1 |
startedAt | First confirm |
completedAt | Last confirm |
5.2 The TaskItem per pick — fields used
| Field | Value |
|---|---|
taskId | The PICK task |
productVariantId | The SKU's variant |
locationId | The bin to pick from |
inventoryUnitId | The specific unit (FK to Allocation.inventoryUnitId) |
allocationId | The reservation made at order-allocation time |
orderItemId | Back-reference to the order line |
quantityRequired | What allocation reserved |
quantityCompleted | What the picker confirmed (≤ required for shorts) |
sequence | 1..n in pick-walk order (by location.pickSequence ASC) |
status | PENDING → COMPLETED or SHORT (or unused: SKIPPED) |
locationScanned | True if any scan happened in scan_location phase |
itemScanned | True if any scan happened in scan_item phase |
shortReason | "Short pick: {qty}/{required}" if short, null otherwise |
completedBy, completedAt | User and timestamp |
5.3 The task_events audit row
Each pick action writes one row:
| Event type | When |
|---|---|
ITEM_COMPLETED | Full pick confirm |
ITEM_SHORT | Short-pick confirm |
TASK_COMPLETED | Last item of the task confirmed |
task_events.data is JSON containing quantity, sku, locationName, isShort. The picker's userId is the row's userId.
The TaskItem.status enum has SKIPPED as a value but no code path writes it — same dead-enum pattern as elsewhere (see WMS-INV-007 §5.1, WMS-INV-005 §5.1). If you see SKIPPED, someone wrote it manually.
5.4 SSE events for live picker UIs
The picking service emits these events through the fulfillment SSE stream (useFulfillmentStream):
| Event | Payload includes | When |
|---|---|---|
ORDER_PROCESSING | orderNumber, taskId | When pick list generated |
PICKLIST_GENERATED | taskId, taskNumber, items[], totalItems | When pick list generated |
PICKLIST_ITEM_PICKED | taskItemId, sku, locationName, quantity, isShort, progress | Each confirm |
PICKLIST_COMPLETED | taskId, completedItems, shortItems, bin | Last confirm |
ORDER_PICKED | orderNumber | Last confirm |
Multiple managers / monitors can connect to the SSE feed and watch picks happen in real time. Useful for live throughput dashboards and supervisor visibility — the events are also queryable from fulfillment_events after the fact.
5.5 Related SOPs
- WMS-INV-006 §4.1 — Order allocation (the upstream that produces the pickable allocations)
- WMS-INV-002 — Cycle counts (the recovery for inventory variance after a short-pick — see §4.5 callout)
- WMS-INV-003 — Floor count (creates
SkuLocationMaprows that allocation uses to choose which location to pick from) - WMS-INV-004 §5.3 — Pick sequence (the field that orders the pick walk within a zone)
- WMS-PACK-001 — Packing (the immediate downstream consumer of the pick bin)
- WMS-PICK-002 — Pick bin labels (when written)
- WMS-PICK-003 — Short-pick recovery (when written — the formal procedure for what to do after a short pick)
- WMS-AUD-002 — Shrinkage investigation (where pick variance trends get analyzed)
6. Audit & compliance
The picking flow has the most complete audit trail of any inventory operation in the WMS:
- Per-item events — every confirm writes a
task_eventsrow withuserId, timestamp, sku, location, quantity, short flag. - Per-order events — every status transition writes a
fulfillment_eventsrow visible across the fulfillment dashboard. - Per-allocation status —
Allocation.statusflipsALLOCATED → PICKED(orPARTIALLY_PICKED) atomically with the pick confirm, withpickedAttimestamp. - Per-task aggregates —
WorkTask.completedItems,shortItems,startedAt,completedAtare the per-task summary.
What's still missing:
- Scan correctness verification. The flags say "a scan happened in this phase," not "the scan matched the expected barcode." For audit-grade scan-verification, the server would need to load the expected barcode at confirm time and compare. Today, that's not implemented.
- Reassignment trail. A pick task can be silently taken over by another user — the latest
userIdon the events wins. There's noassignedToIdor reassignment log. - Pause/resume. No procedure for pausing a pick task in progress. Walking away leaves the task in
IN_PROGRESSindefinitely; the next picker who taps it on/pickbecomes the de-facto worker.
Manager weekly review:
- Pull
WorkTask WHERE type = 'PICKING' AND status = 'IN_PROGRESS' AND startedAt < now() - 4 hours— stale pick tasks. Each one was abandoned mid-pick. - Pull
task_events WHERE eventType = 'ITEM_SHORT' AND createdAt > now() - 7 daysgrouped byproductVariantId— recurring shorts on a SKU indicate either chronic over-allocation or stocking issues. Cross-reference with cycle-count results per WMS-INV-002. - Per-picker:
task_eventsrows byuserId, count ofITEM_COMPLETEDvs.ITEM_SHORT. A picker with high short rate either gets the worst SKUs or has technique issues — coaching material.
Quarterly governance:
- Mean
(completedAt - startedAt) / totalItemsper picker — pick speed. - Bulk-confirm rate (
/pick/confirm-allcalls). Should be near zero. If trending up, investigate. locationScanned: falserate. Should be near zero. If high, scanners are broken or pickers are bypassing.
7. Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
Cannot start picking: order is PICKING (HTTP 400) | Order already has an active pick task | Resume by tapping the order in /pick (it shows Picking badge). Don't try to regenerate. |
No allocations found for order {orderNumber} (HTTP 400) | Allocation was never run, or it returned zero matches | Run allocation from the backorders page (per WMS-INV-006 §4.4) or from the order detail page. |
Active pick task {taskNumber} already exists (HTTP 400) | Same as above — one task per order rule | Resume the existing task. If the task is stuck (picker abandoned), check §7 row 8. |
| Picker scans the location, but the page doesn't advance to scan_item | Scan keyboard input wasn't captured (page wasn't focused), or the barcode looked like a bin barcode | Tap the scan input to refocus. If a bin-barcode collision (rare), see §7 row 5. |
| Picker scans a bin barcode by mistake during scan_location | The page interprets it as "switch active bin" | If a multi-package order, this may be intentional. If not, scan the actual location barcode to advance. |
quantityCompleted was set higher than quantityRequired | Bug — the API does not validate this | Should not happen via the UI, but if it does, escalate to IT. The downstream pack flow may show a discrepancy. |
Order shows PICKED but OrderItem.quantityPicked < quantity | One or more items went short; the order is PARTIALLY_PICKED not PICKED. UI label may be confusing | Check the order's actual status in the DB. Status PICKED means everything fully picked; PARTIALLY_PICKED means at least one short. |
| Picker walked away mid-task; new picker can't see "where to resume" | The task is IN_PROGRESS with some items COMPLETED and some PENDING; the page should show the next PENDING item by sequence | New picker taps the order from /pick; the fulfillment page resumes from the next PENDING item. If it doesn't, the task is corrupted — escalate. |
Bulk confirm wrote PICKED status but stock isn't actually in the bin | Per §4.6 callout, bulk confirm has no scan verification | Cycle-count the affected location per WMS-INV-002. The discrepancy will show in the count and produce a formal InventoryAdjustment per WMS-INV-007. |
| Short-pick recorded but stock variance not corrected | Per §4.5 callout, short pick alone doesn't write an adjustment | Run cycle count immediately. WMS-INV-002 §4.x. |
| Two pickers somehow worked the same order | No mutex on pick task — first to confirm wins | Whichever picker confirmed last is recorded on those events. Manager review the task_events to identify the actual worker. Operationally, coordinate via radio/Slack to prevent. |
8. Escalation
- Add real scan validation in
confirmPickItem. The expected barcodes are loaded into the UI; the server has the data. A 10-line addition to compare scanned vs. expected (and reject mismatches) closes the audit gap. Engineering ticket — high audit value, low effort. - Build pause / pick-task reassignment. No UI for either today. A picker abandoning a task today leaves it stuck
IN_PROGRESSand the next person silently takes over. A/admin/pick-taskspage with reassign and pause CTAs would close that hole. Cross-reference with WMS-AUD-001 (where pick stuckness gets investigated). - Auto-create cycle count on short pick. A short pick is a 100% reliable indicator of inventory variance. Auto-enqueueing a
cycleCountTaskof typeLOCATIONfor the affected bin would force the variance to be reconciled rather than relying on operator discipline. Engineering ticket. - Mount the bulk-confirm UI behind a manager role check. Today the endpoint is auth-only. If you want it to stay (it has legitimate uses), add
MANAGER+ role enforcement so STAFF can't accidentally bypass scan-verify on a whole task. - Use the dead
SKIPPEDenum value. The schema hasTaskItemStatus.SKIPPEDbut no code writes it. A "skip this item" UI for irretrievable shorts (e.g., the bin literally collapsed) would let pickers continue without confirming, and the audit trail would show the skip explicitly. - Stuck pick task investigation. WMS-AUD-001 (when written) — pull stuck tasks weekly, walk them physically, decide reassign / cancel / complete by hand. Until that SOP exists, the warehouse manager's spreadsheet.
- Suspected scan bypass / fraudulent picks. Manager spot-checks during the day are the only control today. Without server-side scan validation, a picker entering bogus barcodes will not be caught by the system. Coaching, cameras, or technical fix per first bullet.
9. Revision history
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | [DATE] | [NAME] | Initial release. Documents the full picker flow grounded in apps/web/src/pages/pick/index.tsx (queue), apps/web/src/pages/fulfillment/[id].tsx (the actual pick session UI, despite the path being under /fulfillment not /pick), apps/api/src/routes/fulfillment.routes.ts (endpoints), packages/domain/src/services/picking.service.ts (generatePickList, confirmPickItem, createPickBin), and packages/db/src/repositories/picking.repo.ts (the transactional state writes). Documents the two-phase scan flow (scan_location → scan_item), denomination mode (scanMultiplier + pickAccumulator), short-pick path (status: SHORT, Allocation.status: PARTIALLY_PICKED), and the auto-created pick bin at task completion. Documents three real audit gaps: (a) scanned barcodes are not server-validated against expected — the flags say "a scan happened" not "it matched"; (b) short-pick alone does not write an InventoryAdjustment — formal reconciliation requires a follow-on cycle count per WMS-INV-002; (c) pick task assignment is implicit (latest user on events wins; no assignedToId, no reassignment log, no pause). Documents two dead schema values: TaskItemStatus.SKIPPED (never written) and the unused assignedToId pattern. Documents bulk confirm (/pick/confirm-all) as the admin-bypass path with explicit warning. Cross-references WMS-INV-002 (cycle count for short-pick recovery), WMS-INV-003 (SkuLocationMap), WMS-INV-004 §5.3 (pick sequence), WMS-INV-006 (allocation), WMS-PACK-001 (downstream), WMS-AUD-001 (pick stuckness — when written). Code references: apps/web/src/pages/fulfillment/[id].tsx:264, 635-720, 882+, 2799-2845, picking.service.ts:103-317, 323+, picking.repo.ts:323-440, fulfillment.routes.ts:103-141, 143-167. |