v1.14.0: picking integrity
"Picking integrity" release. The pick path gains a layered defense against silently under-fulfilled orders: a batch can no longer be created for sales orders that pickable inventory cannot cover, and a line that was never picked can no longer ride the SO header through PICKED, PACKED, and SHIPPED. The pick-batch lifecycle becomes deterministic -- a new scan auto-cancels the operator's prior in-progress batch with a full revert, so there is no half-stored "Resume" state -- and admins get a Picking Batches page to release batches that strand their orders. Wave-validate now resolves transfer orders alongside sales orders from the same scan.
Mobile. First mobile/ change since v1.10.3: the picker surfaces an unpickable-orders modal on under-allocation, the home screen drops the Resume affordance, and the pick screen accepts transfer-order scans. APK rebuilt at versionCode 8 (version 1.14.0).
Added
- Under-allocation pre-flight at batch creation (#364):
create_pick_batchandwave_createrun a coverage check inside the same transaction as theFOR UPDATEinventory locks; if any SO cannot be fully covered by pickable inventory, the helper walks contributors FIFO, rolls back any partial state, and raisesInsufficientCoverageError-> HTTP 409insufficient_coveragewith anunpickable[]payload of{so_id, so_number, lines:[{sku, item_name, ordered, available}]}. Both endpoints accept anexclude_so_idslist so the client's second call commits the pickable subset, replacing the historical "log a warning and proceed" path that let SOs ship under-allocated. - Line-level fulfillment guards across batch / pack / ship (#364):
complete_batch,complete_packing, andrecord_shipeach refuse to promote an SO whose line hasquantity_picked < quantity_orderedwithout an operator-confirmed shortfall marker (pick_tasks.status = SHORT, orwave_pick_breakdown.short_quantity > 0for wave picks that fan one task across many lines). A refused ship rolls back before the fulfillment INSERT, so no fulfillment row is written; legitimate shorts via/api/picking/shortstill complete. - Admin pick-batch list and release (#365):
GET /api/admin/pick-batcheslists the active batches that hold the cross-pick lock;POST /api/admin/pick-batches/<id>/deletereusesfull_revert_batchto cancel one and unwind every effect, returning its orders to a clean pickable state. Transfer-order batches list read-only and refuse delete with 409. Surfaced as the Outbound > Picking Batches admin page and gated by a newpicking-batchespage permission. - Wave-validate accepts transfer orders (#366): the pick-screen scan resolves
sales_ordersfirst, thentransfer_orders, returning akinddiscriminator ('SO' or 'TO') so the caller routes by type. A TO is scannable only when OPEN / PARTIALLY_PICKED, provisioned with apick_batchvia admin start-picking, and assigned to the caller; other states return 404 / 409 with the reason in the body.WaveValidateRequestrenamesso_barcode->barcodewith apopulate_by_namealias so older mobile builds keep working.
Changed
- No-Resume pick-batch lifecycle (#365):
create_pick_batchandwave_createruncancel_prior_user_batchesas their first step, reverting every OPEN / IN_PROGRESS batch the scanning user owns in the target warehouse before the new batch is created -- inventory restored, line counters reset, tasks RELEASED, batch CANCELLED. The mobile home screen no longer fetches or renders the active-batch banner, since a scan is always a fresh session. Cross-user picking is unchanged: a different user scanning an SO already in someone else's active batch still hits the per-SO active-batch rejection.
Docs
- Stuck-pick-after-revert recovery runbook (#365): cleanup procedure for SOs left carrying stale
quantity_allocatedresidue by the pre-fix revert path -- diagnose the residue against livepick_tasks, cancel any active batches via the API sofull_revert_batchrestores inventory, clamp the residue down to the live-task sum in a logged transaction, and re-scan to validate.
See CHANGELOG.md for the full notes.