SOP: Packing
Document ID: WMS-PACK-001 Version: 1.0 Effective date: 04/30/2026 Owner: Warehouse Operations Manager Next review: [six months from effective date] Applies to: Packing-station staff turning picked goods into shipping-ready containers
1. Purpose
This procedure governs how a PICKED order moves through the packing station and becomes PACKED — physically sealed, weighed, measured, photographed, and ready for the shipping label. The system supports two packing modes (per the codebase): direct mode for orders that come out of pick without staged bins, and bin mode for orders pre-staged into one or more pick bins per WMS-PICK-002. Both end at the same place — Order.status: PACKED, weight + dimensions captured on the WorkTask, and the order ready for shipping per WMS-SHIP-001.
The pack station is the last quality-control checkpoint before the order leaves the warehouse. A packing scan that doesn't match what's in the box is the cheapest place to catch a wrong-SKU or short-pick error — once the carrier label prints, the cost of a mistake jumps by an order of magnitude.
2. Scope
In scope:
- The
/packqueue page — orders ready to pack, in-progress, and the bin-scan entry point - Direct mode: generate pack list via
POST /fulfillment/:orderId/pack, verify each item by scan, complete with weight + dimensions - Bin mode: scan a bin barcode, verify items inside the bin (single bin or multi-bin), complete with weight + dimensions
- The transition
PICKED → PACKING → PACKEDand theWorkTaskof typePACKING - Capturing packing photos (
PackingImagerows linked to order, task, and bin) - The two completion endpoints (
/pack/complete-from-bin,/pack/complete-from-bins) - Pack failure modes and recovery
Out of scope:
- Picking and pick-bin creation — see WMS-PICK-001 and WMS-PICK-002
- Shipping label creation, ShipEngine, multi-carrier — see WMS-SHIP-001
- Carrier label printing on a thermal label printer — see WMS-SHIP-002 (when written) — different printer (4×6 thermal), different ZPL flow
- Customer-return packing — see WMS-RET-002 (separate flow)
- Box recommendation / which box to use — surfaced upstream by
OrderPackageService.recommendAndSaveper WMS-INV-006 §4.4
3. Roles & permissions
API enforcement: all packing endpoints (
/fulfillment/:orderId/pack,/fulfillment/:orderId/pack/:taskItemId/verify,/fulfillment/:orderId/pack/complete-from-bin,/fulfillment/:orderId/pack/complete-from-bins,/fulfillment/bin/:barcode) check authentication only — no inline role gate. The router-level/packroute gates onSTAFF_ROLES(perrouter.tsx:296), same as/pick.
| Role | View pack queue | Pack an order | Capture weight + dimensions | Upload packing photos |
|---|---|---|---|---|
| READONLY | — | — | — | — |
| STAFF | ✓ | ✓ | ✓ | ✓ |
| MANAGER | ✓ | ✓ | ✓ | ✓ |
| ADMIN | ✓ | ✓ | ✓ | ✓ |
| SUPER_ADMIN | ✓ | ✓ | ✓ | ✓ |
Operational expectations:
- A pack-station packer typically works one order at a time. Multiple packers running in parallel is fine — each on their own order via their own bin scan.
- Photos are recommended for high-value orders, returns-prone customers, and orders flagged in the customer profile. The system doesn't enforce photo capture today — see §8.
- The packer does not print the carrier label themselves in this SOP. Label generation is a separate step in WMS-SHIP-001 — typically auto-triggered by
Order.status: PACKEDor a separate "Print Label" CTA on the fulfillment page.
4. Procedures
4.1 The pack queue
Use when: Starting your shift, looking for the next order to work, or arriving back at the pack station.
Steps:
- Open
/pack. The page lists orders withstatus IN ('PICKED', 'PACKING'), sorted with priority and age. - The page also has a bin-scan input — a focused field that accepts any barcode and routes accordingly:
- Barcodes starting with
BIN-(the convention from WMS-PICK-002 §5.3) → looked up viaGET /fulfillment/bin/:barcode. On match, navigate to/fulfillment/:orderId?fromBin=true(bin mode). - Order numbers (matching
o.orderNumberor#{orderNumber}) → navigate to/fulfillment/:orderId(direct mode). - Anything else → fall back to bin-lookup attempt (some custom bin barcodes may not start with
BIN-).
- Barcodes starting with
- Tap an order in the queue, or scan a bin/order barcode to enter the pack flow.
Auto-refresh: the queue refreshes every 30 seconds via setInterval, so newly-picked orders surface within 30s of becoming PICKED without the packer manually refreshing.
What's surfaced:
- Order number, customer name, item count, time since order placed
- Status badge —
Ready(statusPICKED) orPacking(statusPACKING) - Priority badge for
URGENT/HIGH(red border on the card)
What's not surfaced:
- The packer who's actively working an in-progress order. Tapping a
PACKINGorder will silently make you the active packer. - The bin count for multi-bin orders. The page shows item count, not bin count — which means multi-bin orders look the same as single-bin until you open them.
4.2 Direct mode — generate the pack list
Use when: The picker did not stage into bins (single-line orders, small orders, ad-hoc no-bin workflow). The order arrives at packing as PICKED with no PickBin rows or with a single PickBin whose contents you want to verify item-by-item.
Prerequisites (enforced by PackingService.generatePackList per packing.service.ts:112-145):
Order.status === 'PICKED'. Otherwise: HTTP 400Cannot start packing: order is {status}, expected PICKED.- No active pack task already exists. Otherwise:
Active pack task {taskNumber} already exists for this order. - A completed pick task exists with
taskItems. Otherwise:No completed pick items found for packing.
Steps:
- From the queue, tap the order. The fulfillment page detects no bin-mode flag and calls
POST /fulfillment/:orderId/pack. - The service creates a
WorkTaskof typePACKING,status: PENDING,taskNumber: PACK-{orderNumber}-{shortId}, with oneTaskItemper completed pick item. - The page shows the first pack item with: SKU, image, product name, required quantity. Order status auto-advances to
PACKINGon first verify.
What this writes:
- One
WorkTaskrow, oneTaskItemper pick item, onefulfillment_eventsrow of typepacking:started.
4.3 Direct mode — verify each item
Use when: You're walking the pack list one item at a time, scanning each unit before placing it in the box.
Steps:
- The page shows the current pack item.
- Scan the unit's barcode (UPC, vendor barcode, or SKU — same priority as receiving and picking).
- The page calls
POST /fulfillment/:orderId/pack/:taskItemId/verify. - The service runs
verifyPackItem(perpacking.service.ts:178-221):- Loads the task item; throws if
task.type !== 'PACKING'. - If
status === 'COMPLETED'already, returns success without re-incrementing. - Otherwise marks the task item
COMPLETED, incrementsWorkTask.completedItems. - Emits
packing:item_verifiedevent.
- Loads the task item; throws if
- The page advances to the next item. Repeat.
⚠ Direct-mode pack-verify does not validate the scanned barcode against the variant. Looking at
verifyPackItem, the scanned barcode is not passed to the API at all — the route only receives thetaskItemId. The verification is purely "did the packer tap the verify button on this task item." Same gap as picking (per WMS-PICK-001 §4.3) but slightly worse — the barcode isn't even captured for audit. To wire scan-validation server-side, addscannedItemBarcodeto the request body, compare against the variant, and reject mismatches. See §8.
4.4 Direct mode — complete with weight & dimensions
Use when: All pack items are COMPLETED and the box is sealed.
Prerequisites (enforced by completePacking per packing.service.ts:227-310):
- All
taskItemshavestatus === 'COMPLETED'. Otherwise:{n} items still pending verification. Complete all items before finishing packing. - Task is type
PACKING.
Steps:
- Place the sealed box on the scale. Weigh.
- Measure with the dimension tool (or use the box's known dimensions if a recommended box was applied per WMS-INV-006 §4.4).
- Enter weight (default unit
ounce) and optional dimensions (length × width × height + unit) in thePackingCompleteFormUI block. - (Optional) Capture packing photos — see §4.7.
- Tap Complete Packing. The page calls the route that wraps
completePacking(taskId, { weight, weightUnit, dimensions, userId, orderId }). - Order status:
PACKED. Two events emitted:packing:completed(with weight + dimensions) andorder:packed.
Result:
WorkTask.status: COMPLETED(and the same fields populated on the task:packedWeight,packedWeightUnit,packedDimensions).Order.status: PACKED.- The order is now ready for shipping per WMS-SHIP-001.
4.5 Bin mode — scan a bin
Use when: The picker staged the order into one or more pick bins per WMS-PICK-002. The bin label is on the tote/box; you scan it at the pack station to start.
Steps:
- Bring the bin to the pack station.
- From
/pack, scan the bin barcode (or type it into the bin-scan input). - The page calls
GET /fulfillment/bin/:barcode. The service runsPickingService.getOrderByBinBarcode(barcode)to look up the bin and return the order id. - Page navigates to
/fulfillment/:orderId?fromBin=true. ThefromBin=truequery param tells the page to render bin mode. - The page shows the bin contents — each
PickBinItemwithsku,quantityordered into the bin,verifiedQty(starting at 0), and the variant's image.
What's shown for multi-bin orders:
- All bins for the order, with their statuses (
STAGED/PACKING/COMPLETED). - The bin you scanned is the active bin — you verify its contents before moving to the next.
- A "next bin" affordance once the current bin is fully verified (or the multi-bin completion endpoint per §4.6).
⚠ Bin lookup is by barcode, not by order. If the same barcode somehow exists for two bins (it shouldn't —
PickBin.barcodehas a unique constraint), the lookup is ambiguous. The unique constraint protects this, so it shouldn't happen. If it does, escalate to IT.
4.6 Bin mode — verify items in the bin
Use when: A bin is on the pack station and you're scanning items inside it before sealing.
Steps:
- The page shows the bin's contents — list of items with
verifiedQty / quantityper row. - Scan an item's barcode.
- The page calls a verify endpoint (route mounted at the fulfillment plugin, calling
verifyBinItemperpacking.service.ts:317-378):- Service finds an item in the bin where
pv.upc === barcode || pv.barcode === barcode || pv.sku === barcode || pv.sku.toUpperCase() === barcode.toUpperCase(). - If no match: HTTP 400
Item with barcode "{barcode}" not in this bin. - If already fully verified (
verifiedQty >= quantity): no-op success, returns the current state. - Otherwise increments
verifiedQtyby 1 viaincrementBinItemVerifiedQty.
- Service finds an item in the bin where
- The row updates to
{verifiedQty}/{quantity}. Repeat for each scan. - When
verifiedQty >= quantityfor every bin item, the page enables the Complete CTA.
⚠ Bin-mode scan validation is real. Unlike direct-mode (per §4.3 callout), bin-mode actually compares the scanned barcode against
pv.upc,pv.barcode, andpv.skuand rejects mismatches. The audit story for bin-mode is materially better than direct-mode. If your warehouse cares about pack-time scan integrity, prefer bin mode wherever possible. Picker-side discipline drives this — pickers should always stage to bins, not pick directly into the order box.
To complete a single bin (bin's contents fully verified):
- Capture weight + dimensions (and optional photos).
- Tap Complete Bin. The page calls
POST /fulfillment/:orderId/pack/complete-from-binwith{ binId, weight, weightUnit, dimensions }. - The service runs
completePackingFromBin(perpacking.service.ts:403-490andpacking.repo.ts:453-525) in a transaction:- Re-checks all bin items are verified. Throws if not.
- Updates all
Allocationrows on the order fromALLOCATEDtoPICKED. - For each bin item, overwrites
OrderItem.quantityPickedwith the bin's quantity (see callout below). - Sets
PickBin.status: COMPLETED,packedBy: userId,packedAt: now. - Sets
Order.status: PACKED. - Retroactively creates a
WorkTaskrow of typePACKING,status: COMPLETED, with the weight/dimensions captured. (Bin mode never has a task inPENDINGorIN_PROGRESS.) - Writes a
task_eventsrow of typeTASK_COMPLETEDwithtype: PACKING_FROM_BIN.
⚠ Bin-mode
completePackingFromBinis single-bin only —Order.status: PACKEDis set on the first bin completion. Perpacking.repo.ts:486-489, the order's status is updated toPACKEDregardless of how many bins remain. For a true multi-bin order, the second and third bins still need to be packed (they have statusSTAGEDand items to verify), but the order is already labeledPACKED. Use/pack/complete-from-bins(plural — see below) for multi-bin orders. The single-bin endpoint should only be called when there's only one bin on the order.
⚠ The OrderItem.quantityPicked overwrite. Per
packing.repo.ts:478-480,quantityPickedis set to the bin's quantity, not incremented. For multi-bin orders where the same SKU appears in multiple bins, this is wrong — the second bin's call would overwrite the first bin's recorded quantity. Use the multi-bin endpoint (below) which handles this correctly. Single-bin endpoint is safe only when each SKU appears in exactly one bin.
To complete multiple bins (all bins for the order verified):
- Each bin has been verified per §4.6 step 1-5.
- The packer collects weight + dimensions for each bin.
- The page calls
POST /fulfillment/:orderId/pack/complete-from-binswith{ bins: [{ binId, weight, weightUnit, dimensions }, ...] }. - The service runs
completePackingFromBins(plural — handles multi-bin correctly). - All bins flip to
COMPLETED. Order flips toPACKEDafter the last bin is processed.
4.7 Capturing packing photos
Use when: High-value orders, fragile items, customer-flagged accounts, or any time visual proof of what was packed has operational value (chargeback defense, customer disputes, regulatory).
Steps:
- From the pack page (direct or bin mode), tap the Camera icon to open the
PackingImageUploadblock. - Capture or upload one or more images (camera or file picker).
- Each image is uploaded to GCS and a
PackingImagerow is created with:orderId(required), optionaltaskId, optionalpickBinIdurl,filename,size,contentType(defaultimage/jpeg)uploadedBy: <user>,createdAt: <now>- Optional
reference(for cross-referencing — e.g., RMA number) andnotes
- The image is now linked to the order. It surfaces on the order detail page and in customer-service tooling.
Result:
- Persistent photographic record of what shipped.
- Linked to the order, the task (if direct mode), and the bin (if bin mode).
- Cascades: deleting an order cascades-deletes its packing images (per the schema).
⚠ Photos are optional and unenforced. No code path requires a photo before pack completion. If your operation needs photos for high-value orders, that's manager-discipline today. A "photo required" toggle on the variant or order would close this gap as a small engineering ticket.
5. Reference
5.1 Two packing modes — quick comparison
| Direct mode | Bin mode | |
|---|---|---|
| Trigger | POST /:orderId/pack from queue | Scan bin barcode at pack station |
| Pre-requisite | Order is PICKED, no active pack task, completed pick task exists | Bin exists with verified pick items |
| Task created | WorkTask in PENDING, walked through item-by-item | WorkTask retroactively created in COMPLETED after bin completion |
| Per-item scan validation | Not performed — taskItemId only, no barcode in payload | Real — barcode compared to pv.upc || pv.barcode || pv.sku |
Per-item verifiedQty tracking | No — items go directly PENDING → COMPLETED | Yes — increments per scan, full visibility |
| Multi-bin support | N/A (single task) | Yes — one task per completion, but multi-bin endpoint preferred |
| Audit trail | WorkTask.taskItems, task_events ITEM_COMPLETED | PickBinItem.verifiedQty per scan, task_events TASK_COMPLETED only |
Recommendation: prefer bin mode. Better scan validation, better audit trail, supports multi-bin. Direct mode exists for compatibility with single-bin or no-bin workflows.
5.2 The WorkTask of type PACKING — fields used
Captured at completePacking or completePackingFromBin/s:
| Field | Value |
|---|---|
taskNumber | PACK-{orderNumber}-{shortId} |
type | PACKING |
status | PENDING → COMPLETED (direct) / always COMPLETED (bin retroactive) |
totalItems | Number of pack items |
completedItems | Increments on verify; equals totalItems at complete |
packedWeight, packedWeightUnit | Captured at complete |
packedDimensions | JSON: { length, width, height, unit } if provided |
verifiedAt, verifiedBy | Set at complete (bin mode) |
startedAt, completedAt | Lifecycle timestamps |
events | One-to-many task_events |
5.3 Pack-related events
| Event type | When |
|---|---|
PACKING_STARTED | generatePackList (direct mode) |
PACKING_ITEM_VERIFIED | Each verifyPackItem (direct mode) |
PICKBIN_COMPLETED | completePackingFromBin (bin mode) |
PACKING_COMPLETED | completePacking and completePackingFromBins |
ORDER_PACKED | After both pack endpoints — the universal "this order is now packed" signal |
The shipping flow (per WMS-SHIP-001) listens for ORDER_PACKED to determine which orders are ready for label generation.
5.4 Related SOPs
- WMS-PICK-001 — Picking (the upstream — produces the
PICKEDorder this SOP starts with) - WMS-PICK-002 — Pick bin labels (the bin barcode this SOP scans in §4.5)
- WMS-PICK-003 — Short-pick recovery (pre-pack disposition decisions)
- WMS-INV-006 §4.4 — Box recommendation (where
boxLabeland dimensions come from) - WMS-SHIP-001 — Shipping label creation (the immediate downstream — fires on
ORDER_PACKED) - WMS-AUD-002 — Pack-time variance investigation (when written)
6. Audit & compliance
The packing flow has strong audit data overall, with one significant scan-validation gap on the direct-mode path:
- Bin-mode is fully auditable per item:
PickBinItem.verifiedQtyincrements per scan,task_eventsrecords the bin's completion, the scan-validation rejects bad barcodes server-side. A regulator asking "show me proof every unit in this bin was scanned at pack" can reconstruct fromverifiedQtyand bin contents. - Direct-mode is auditable as "verify happened on this task item" but not as "the scanned barcode matched." The
taskItemIdis captured; the scanned barcode is not. A direct-mode order can be packed with the wrong contents and the audit trail won't catch it — it'll show "all items verified" with no record of what was actually scanned. - Weight + dimensions captured on
WorkTask.packedWeight,packedWeightUnit,packedDimensions. Carried forward to shipping for carrier label generation. - Photos captured in
packing_imagestable, with FKs to order, task, and bin. Cascade-deletes with the order.
Manager weekly review:
- Pull
WorkTask WHERE type = 'PACKING' AND status = 'COMPLETED' AND createdAt > now() - 7 days. Cross-reference with shipping events — should be a 1:1 correspondence between packed and (eventually) shipped orders. - Pull
WorkTask WHERE type = 'PACKING' AND status = 'IN_PROGRESS' AND startedAt < now() - 4 hours— stale pack tasks. Each one is an order someone walked away from mid-pack. - Pull
Order WHERE status = 'PACKED' AND updatedAt < now() - 24 hours AND status != 'SHIPPED'— packed orders that haven't shipped within a day. Either the shipping queue is backed up or labels are failing.
Quarterly governance:
- Direct-mode rate vs. bin-mode rate. Lower direct-mode % is better (better audit).
- Photos-attached rate per high-value order class. Should trend up if photo discipline is enforced.
- Pack-time per order (mean, median, p95). Useful for staffing and throughput planning.
- Wrong-pack rate (returns coded as "wrong item received" / packed orders, lagged). Cross-reference with WMS-RET-002.
7. Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
Cannot start packing: order is {status}, expected PICKED (HTTP 400) | Order isn't PICKED yet | Check the order's actual status. If PICKING, the picker isn't done. If PACKED, refresh — the order's already packed. If PARTIALLY_SHIPPED, the order has been split via WMS-PICK-003 §4.4. |
Active pack task {taskNumber} already exists (HTTP 400) | Someone else (or you, in another tab) started packing this order | Refresh the queue, the order should now show PACKING. Tap to resume. |
No completed pick items found for packing (HTTP 400) | The pick task didn't complete cleanly, or all pick items went SHORT (no COMPLETED items) | Investigate the pick task. If all items are SHORT, the order has no shipable contents — see WMS-PICK-003 for the recovery decision. |
| Bin lookup returns 404 | Wrong barcode, bin was cancelled, or bin belongs to a different order | Verify the physical bin label. Check the PickBin row for the barcode. If barcode is right but bin is CANCELLED, the order may have been re-binned — find the active bin. |
Item with barcode "{barcode}" not in this bin (HTTP 400) | Scanning the wrong item, or the picker put a wrong unit in the bin | Verify physically. If a wrong unit, do not complete the bin — return the item to inventory (per WMS-INV-001 §4.1) and request the correct unit from the pick area. |
{n} items still pending verification (HTTP 400) on completePacking | Direct-mode: not all task items are COMPLETED | Find the unverified items and verify them. The page should show progress; refresh if it doesn't. |
{n} item(s) not fully verified: {skus} (HTTP 400) on completeBin | Bin-mode: bin items have verifiedQty < quantity | Continue scanning. Each bin item must reach verifiedQty === quantity. |
Order showed PACKED after first bin even though there are more | Per §4.6 callout, single-bin endpoint sets Order.status: PACKED immediately | Multi-bin orders should use /pack/complete-from-bins (plural). If first bin used the single endpoint by mistake, the second and third bins still need packing — use the same endpoint per remaining bin (it'll log Order.status as PACKED again, no-op). The packed/shipped audit will be slightly noisy but the result is correct. |
Bin {binNumber} already completed (HTTP 400) | Trying to re-complete an already-completed bin | The bin is done. Move to the next bin or the next order. |
| Direct-mode pack completed but customer complains about wrong item | Per §4.3 callout, direct-mode doesn't validate the scanned barcode server-side. The task item said "verified" but the scan may have been ignored | Process the customer return per WMS-RET-001. To prevent recurrence, switch to bin mode (always pre-stage). Engineering ticket: wire scan validation into direct-mode verifyPackItem. |
| Photos didn't upload | GCS upload failed, network drop | Retry. The upload is independent of pack completion; you can pack first and add photos after. |
Multi-bin order: completed all bins but order still shows PACKING | The plural endpoint may have failed partway, or some bins were completed via the singular endpoint and miscount | Manually update Order.status: PACKED via DB if the bins are all COMPLETED and the items are all packed. Or reach to IT for transaction recovery. |
| Pack queue shows an order that's already shipped | Cache hasn't refreshed (30s auto-refresh, but stale cache possible if recently shipped) | Wait 30s, refresh manually. If still showing, escalate to IT. |
| Pack-station bin scan keeps trying bin lookup, but the barcode is an order number | Custom barcode that doesn't start with BIN- will fall through to bin-lookup. Order-number match runs first | The page falls back to bin lookup for unknown barcodes. If you wanted order-by-number, type it explicitly into the search field rather than scanning. |
8. Escalation
- Wire scan validation into direct-mode
verifyPackItem. Today, the scanned barcode isn't even passed to the API. AddingscannedItemBarcodeto the request body and comparing against the variant inverifyPackItemis a small ticket with significant audit benefit. Mirrors the bin-mode behavior. Highest-leverage packing fix. - Always create the
WorkTaskinPENDINGstate, never retroactively inCOMPLETED. Bin-modecompletePackingFromBincreates the task already-completed (perpacking.repo.ts:490-523). This makes "show me a list of pack tasks in flight" impossible for bin-mode orders. Refactor to create the task at bin-scan time and progress it normally. - Multi-bin order status semantics. The single-bin endpoint sets
Order.status: PACKEDon the first call regardless of remaining bins. Either deprecate the single-bin endpoint, or make it checkbins[].statusfirst and only markPACKEDon last completion. - Photo enforcement. Add a
requirePackingPhotoflag on the variant or order class, and havecompletePacking/completePackingFromBin/sreject completion if the order has the flag and noPackingImage. - Bin reassignment for items in the wrong bin. If a packer scans an item that's not in the current bin but is in another bin on the order, the system rejects with
Item with barcode "{barcode}" not in this bin. A friendlier flow would say "this item is in Bin 2, switch?" — small UX ticket, big impact for multi-bin packers. - Stuck pack task investigation. WMS-AUD-002 (when written) — pull stuck tasks weekly, walk them physically, decide reassign / cancel / complete. Until that SOP exists, the warehouse manager handles per
Manager weekly reviewqueries above. - Suspected pack-time fraud or repeated wrong-pack patterns. Manager spot-checks + cross-reference with returns. Without server-side scan validation in direct mode, audit trail is weak; bin mode + photos is stronger evidence.
9. Revision history
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | [DATE] | [NAME] | Initial release. Documents the two packing modes — direct (generatePackList → verifyPackItem × N → completePacking with weight + dimensions) and bin (getOrderByBinBarcode → verifyBinItem × N per bin → completePackingFromBin or completePackingFromBins). Documents the pack queue at /pack with the dual-purpose bin/order scan input. Documents the PackingImage model for optional photo capture. Documents three real findings: (a) direct-mode does not validate scanned barcodes server-side — the taskItemId is the only thing on the verify request, no barcode is captured; bin-mode does validate (compares against pv.upc || pv.barcode || pv.sku). (b) completePackingFromBin (singular) sets Order.status: PACKED on first call regardless of remaining bins (per packing.repo.ts:486-489); multi-bin orders should use /pack/complete-from-bins (plural). (c) Bin-mode completePackingFromBin retroactively creates the WorkTask in COMPLETED status (per packing.repo.ts:490-523), which makes "in-flight pack tasks" queries miss bin-mode orders. (d) Direct-mode overwrites OrderItem.quantityPicked from the bin's quantity rather than summing — wrong for multi-bin orders. Cross-references WMS-PICK-001 (upstream), WMS-PICK-002 (bin barcodes), WMS-PICK-003 (pre-pack disposition), WMS-INV-006 (box recommendation), WMS-SHIP-001 (downstream). Code references: packing.service.ts:112-490, packing.repo.ts:323-525, fulfillment.routes.ts:103-141, 169-213, 694-728, pages/pack/index.tsx, pages/fulfillment/PackStation.tsx, pages/fulfillment/[id].tsx (the multi-mode fulfillment page), orders.prisma:228-260 (PickBin), orders.prisma:105-128 (PackingImage). |