---
name: evolution-picking-staging-ledger
description: How picking-to-staging bin moves are stored in the ledger and the qty-delta diff model in pickingsave.php
metadata: 
  node_type: memory
  type: project
  originSessionId: b474a39d-712a-4358-91b8-f8ebb052849b
---

Picking-to-staging bin assignments (manufacturingBinLocationsForStaging) store each pick as a **SOH-neutral in/out `stockmovement` pair** — both legs created with the same `source_bin`/`target_bin` and linked via `smsrc_id`/`smtgt_id`; the out leg removes from the stock bin, the in leg adds to the staging bin, netting zero at item level (so `qty_soh`/`inventory_soh` never change for a pick). Bin location lives only in `stockmovement.source_bin/target_bin` + `inventory_bin_map` qty. `inventory_bin_map` is moved via `inventoryUpdate->addBinMapping($vars, true)` (ON DUPLICATE KEY `qty = qty + ?`).

`pickingsave.php` (~line 385-478) reconciles edits by diffing the original hidden fields (`oinvItemPicked*`) against the current ones, **keyed by stockmovement record id** in `invItemPickedBinsID[]`, and models every change as a **quantity delta** (`$qtydiff`). Gotchas: a pure in-place bin change has `qtydiff == 0` and the qty-delta branches never touch `source_bin`/`target_bin` — so before the 2026-06-25 fix it was silently dropped at `if ($qtydiff == 0) continue;`. The handler was built for remove-row + add-row (the `!in_array($obinid,$binid)` branch reverses a dropped assignment; the `$obinid==''` branch creates a new one), NOT for editing a bin dropdown in place. Fix added a "relocate in place" block: repoint both movement legs' bins + shift bin_map qty old→new, leaving qty_soh alone.

The JS `invItemPickChanged[]` / `data-changed` tracking in [[pickingedit-fragility]] is dead data — `pickingsave.php` never reads it; it diffs the `o*` originals instead. See [[evolution_float_money_columns]] for the related stockmovement precision context.
