Putaway & Warehouse Slotting Rules
Rule-based automatic bin/warehouse assignment for incoming stock
1. Requirements
1.1 Functional requirements
- Let a warehouse manager define, per item-and-warehouse pair, a Putaway Rule: a capacity ceiling and a priority, so incoming stock is routed automatically instead of a person typing a destination on every line. Several rules can be layered for one item across warehouses — primary rack, then overflow, then floor stock.
- Capacity may be authored in whatever unit the manager finds natural, but the system resolves it internally to the item’s own stock-keeping unit.
- On save, the system evaluates every candidate rule for a line, orders them, and splits the incoming quantity across as many rule-backed warehouses as it takes, or until no candidate has room. A quantity no rule can accommodate is left out of the document and reported back, not posted into a default location.
- One shared entry point drives both an external receiving document and an internal stock-transfer/receipt document, and a hand-typed destination is still checked against a matching rule’s ceiling at save time.
- Rule utilization should be inspectable in aggregate — per warehouse, how full each slot is as a percentage of capacity.
1.2 Non-functional requirements
- Determinism: re-saving a draft with unchanged rules and stock levels reproduces the same split and row identities.
- Fractional-unit safety: when a unit of measure must be whole, headroom is never exploited fractionally.
- Company scoping: a rule, its warehouse, and the posted transaction must share one legal entity.
1.3 Constraints
- Both the auto-placement pass and the standalone capacity check read “how much is already there” from the live stock ledger balance at evaluation time, not a snapshot — free capacity is only as fresh as the last posted movement.
- Only one rule may exist per item-and-warehouse pair.
- The rule set auto-assigns a destination only from a small, fixed set of receiving document types; others can be blocked by a ceiling but never have it choose a destination.
2. High-Level Design
2.1 Component diagram
2.2 Rule setup and invocation
Saving a rule runs a short ordered check, not a branching flow: reject a duplicate for the item+warehouse; reject priority below 1; confirm the warehouse belongs to the rule’s company; reject a capacity smaller than what’s already stocked there, or zero; then compute stock_capacity = capacity * conversion_factor — the figure every later check reads.
Only two document types invoke the assignment pass automatically: an external receiving document (unless it’s a return), and an internal stock-transfer/receipt document whose purpose is a material receipt or warehouse-to-warehouse transfer. Two more document types — an update-stock purchase invoice and a stock reconciliation — never get an auto-assigned destination but are still subject to the Capacity Guard (§3.4).
3. Deep Dive
3.1 Data model
Putaway Rule
| Field | Purpose |
|---|---|
item_code / warehouse / company |
identity of the rule; unique per item+warehouse |
priority |
lower fires first; must be ≥ 1 |
capacity |
ceiling in the rule’s own chosen unit of measure |
uom / conversion_factor |
the chosen unit, and its ratio to the item’s stock unit |
stock_capacity |
capacity * conversion_factor, stored at save time |
disable |
excludes the rule from the assignment pass |
Receiving Line — the generic name for one row of the receiving document’s item table: requested quantity in its own unit, the corresponding stock-unit quantity, the resolved warehouse, and a back-reference to the rule that placed it. A repeated item across several rows in one document shares one rule’s shrinking headroom rather than each row re-reading the original capacity.
3.2 Rule selection and ordering
Two details matter. The initial fetch orders by priority then declared capacity, but that’s provisional: among same-priority rules, the real tie-break is remaining free space, computed only after the live balance read. And “no candidates” differs from “candidates exist but none has room” — the former leaves the line’s original warehouse untouched; the latter reports the item as unplaceable.
3.3 Splitting a quantity across multiple destinations
for each rule in the ordered list, while pending stock-unit qty > 0:
allocate = min(pending qty, rule.free_space)
convert allocate back to the line's own unit of measure
if that unit must be whole: floor it, recompute stock-unit amount used
if the (possibly floored) amount is zero: stop
write a Receiving Line: this warehouse, this qty, tagged with the rule
pending -= amount placed; rule.free_space -= amount placed
if pending stock-unit qty > 0 after every rule is tried:
do not write a line for it — report the item and the leftover instead
Worked example. 350 kg of an item arrives. Two rules apply, both priority 1 (a tie), both currently empty: Rule A on Warehouse 1, 100 kg capacity; Rule B on Warehouse 2, 200 kg capacity. Free space breaks the tie — Warehouse 2 is tried first:
pending = 350 kg
Warehouse 2 (free 200 kg): allocate 200 kg -> Line: 200 kg -> Warehouse 2; pending = 150 kg
Warehouse 1 (free 100 kg): allocate 100 kg -> Line: 100 kg -> Warehouse 1; pending = 50 kg
No rules left. 50 kg dropped from the document; item + 50 kg reported as unassigned.
Two lines are written (300 kg placed); the remaining 50 kg becomes only a message, never a row in the saved document.
3.4 Error handling
- Capacity Guard: re-validates every line’s destination at save/submit — receiving document, transfer document, update-stock purchase invoice, and stock reconciliation — regardless of whether the assignment pass ran. The first three compare against remaining free space; a reconciliation compares against the rule’s full capacity, since it sets an absolute ending balance rather than adding to what’s there.
- Whole-unit rounding: fractional leftover headroom, converted to a must-be-whole unit, is floored away rather than rounded up — a rule can show stock-unit capacity unusable in the line’s own unit.
- Idempotent resave: reapplying the pass compares the proposed result to existing lines field-by-field; if nothing changed, existing row identities are left alone.
- Batch/serial items: serial numbers already picked for a line split across the resulting rows in the same order as the quantity split.
4. Scale and Reliability
- Evaluation cost scales with candidate-rule count per item, not catalog size — each candidate costs one live balance lookup.
- The balance read is always live, so it’s correct at read time, but nothing locks a rule’s headroom during one pass — two documents for the same item saved concurrently can both see the same “free” space and both claim it. The design relies on low-frequency saves rather than guarding against this race.
- The Capacity Guard aggregates all lines resolving to the same rule within one document before comparing against the ceiling, so it can’t be slipped past line-by-line.
- The per-warehouse utilization view re-runs the same live lookup per rule per request — fine at its paginated page size, not built to scale past it.
5. Trade-off Analysis
| Decision | Trade-off |
|---|---|
| Capacity authored in a configurable unit, converted to the stock unit once at save | Lets staff think in bags/pallets, but the stored conversion factor is a snapshot — a later change to the item’s own conversion leaves rules stale until resaved. |
| Same-priority ties broken by live remaining free space | Intuitive “fill the emptier destination first,” but fill order between close-capacity rules isn’t stable across saves as stock shifts. |
| An unplaceable remainder is dropped, not posted to a fallback location | Keeps the ceiling honest, but the only record of the leftover is a transient message — nothing persists it for follow-up. |
| Live ledger read per rule per save, no reservation or lock | Simple and always fresh, no allocation table to keep in sync — but concurrent receipts against the same rule can race. |
| One shared routine serves both external receipt and internal transfer | A single algorithm to test, but document-specific nuances (excluding a transfer’s own source warehouse) leak into an otherwise generic routine. |
| Capacity enforced twice — auto-assignment and a standalone guard for typed destinations | Closes an obvious bypass, but the two checks compute “available capacity” differently for the reconciliation case, inviting drift. |
6. What to Revisit as the System Grows
- Concurrency: add a lock or reservation scoped to one rule for the duration of an assignment pass once concurrent same-item receiving becomes routine.
- Stale conversion factors: refresh a rule’s stored stock-unit capacity automatically when the item’s own unit-of-measure conversion changes, instead of requiring a manual resave.
- Persisting the unplaced remainder: today it is only a one-time message; a durable record would make follow-up auditable.
- Unifying the two capacity checks: the assignment pass and the standalone guard should share one definition of “available capacity,” particularly for the reconciliation special case.
This document runs shorter than the module’s High-priority siblings by design: the rule-based placement logic is a small, self-contained pass over a handful of files, and the source genuinely supports only this much grounded detail before further length would be padding.