PhoenixKitWarehouse.Deficits (PhoenixKitWarehouse v0.2.2)

Copy Markdown View Source

Context for computing per-item stock deficits (§5, full variant).

Combines three existing signals — current on-hand stock (StockLedger.stock_map/0), how much of that stock is reserved by posted internal orders (reserved_by_item/0), and the configured per-item minimum (MinStockSettings.min_stock_map/0) — into "how much is actually available" and "which items have fallen below their minimum".

Wave-1 limitations (see dev_docs/DEVELOPMENT_PLAN.md):

  • The minimum stock threshold is global per item, not per {item_uuid, location_uuid} pair — available_by_item/0 sums on-hand quantity across every warehouse (via StockLedger.stock_map/0).
  • Neither reserved_by_item/0 nor available_by_item/0 see stock "in transit" on an unfinished (in_transit) Transfer — it has already left the source warehouse's Stock row but hasn't yet landed on the destination's, so it's invisible to both. Not compensated for here — the same limitation is noted on PhoenixKitWarehouse.Turnover and Web.StockLive.

Summary

Functions

Returns %{item_uuid => Decimal} — on-hand quantity (summed across every warehouse via StockLedger.stock_map/0) minus reserved_by_item/0, for the union of items appearing in either source. An item missing from one side is treated as 0 there: an item with stock but no open reservation is simply its full on-hand quantity; an item reserved but with no Stock row at all yields a negative available quantity, surfacing the over-commitment rather than hiding it.

Returns a list of %{item_uuid:, min_quantity:, available:, deficit:} — one entry per item with a configured minimum (MinStockSettings.min_stock_map/0, which already excludes unset or zero minimums) whose available_by_item/0 quantity has fallen below that minimum. Items at or above their minimum are omitted.

Returns %{item_uuid => Decimal} — the quantity currently reserved against each item by posted internal orders.

Functions

available_by_item()

Returns %{item_uuid => Decimal} — on-hand quantity (summed across every warehouse via StockLedger.stock_map/0) minus reserved_by_item/0, for the union of items appearing in either source. An item missing from one side is treated as 0 there: an item with stock but no open reservation is simply its full on-hand quantity; an item reserved but with no Stock row at all yields a negative available quantity, surfacing the over-commitment rather than hiding it.

list_deficits()

Returns a list of %{item_uuid:, min_quantity:, available:, deficit:} — one entry per item with a configured minimum (MinStockSettings.min_stock_map/0, which already excludes unset or zero minimums) whose available_by_item/0 quantity has fallen below that minimum. Items at or above their minimum are omitted.

reserved_by_item()

Returns %{item_uuid => Decimal} — the quantity currently reserved against each item by posted internal orders.

Only status == "posted" internal orders reserve stock (InternalOrders.list_posted_internal_orders/0 already filters for this in SQL) — drafts are unconfirmed proposals and reserve nothing, otherwise every draft would manufacture a false deficit.

For each posted order, each line's reservation is max(0, required_quantity - already_issued), where already_issued is how much of that order's own required_quantity has already shipped out via a posted Goods Issue referencing it (via CommittedQuantities.compute/5, status: "posted" — a draft Goods Issue hasn't decremented stock yet, so it must not shrink the reservation early; doing so would let available_by_item/0 overstate what's actually free). This is computed per line, per order, then summed by item_uuid across every order — deliberately not as a single global Σrequired - Σissued per item. The per-order clamp to zero matters: without it, fully (or over-) issuing one internal order could drive that order's own line negative, which would then wrongly cancel out a different internal order's still-open reservation for the same item once summed globally.