Skip to content

Ingredient Stock Architecture

Overview

Every change to ingredient stock writes the same two records, whether it comes from the admin console, the store app, or a sale:

RecordPurpose
Ingredient InventoryThe current stock for one ingredient at one outlet.
Ingredient TransactionThe append-only ledger entry showing what changed, why, and the stock before and after.

The ledger is the history. The inventory row is the latest count.

Stock movement flow

text
User action
  -> API route
    -> Ingredient stock movement service
      -> validate movement type and reason
      -> create Ingredient Transaction
      -> update Ingredient Inventory current stock

Admin and store-app changes all go through this flow. Sales and voids write the same two records from the order itself.

What creates stock movements

User actionMovement written
Create ingredient inventory with a starting countADJUSTMENT / inventory_count from 0 to the starting stock.
Edit ingredient inventory current stockADJUSTMENT / inventory_count from the previous count to the new count.
Delete ingredient inventory with non-zero stockADJUSTMENT / inventory_count to bring stock to 0, then the inventory row is removed.
Create a manual ingredient transactionIN or OUT, with a reason that matches the type. Adjustments come only from editing a count.
Receive an ingredient stock orderIN / purchase for each received item.
Sell a product, modifier option, or combo option with Track ingredients and a recipeOUT / consumption, linked to the order. If the outlet has no inventory row for the ingredient, it is created at a negative count.
Void items, cancel, or refund a saleFollows the ingredient's On Void: IN / return; IN / return then OUT / waste; or nothing.

Movement rules

Each transaction type only accepts reasons that match that type.

TypeValid examples
INpurchase, delivery, return, transfer in, production
OUTconsumption, waste, expiry, damage, transfer out, theft
ADJUSTMENTinventory count, correction, system adjustment, quality check

For example, an OUT movement cannot use the reason purchase, and an IN movement cannot use the reason consumption.

Inventory edit rules

Once an ingredient inventory row exists, its outlet and ingredient are locked. Only the current stock count can be changed.

This prevents an old inventory row from being reused for a different outlet or ingredient, which would make old ledger entries point at the wrong stock record.

Order receiving rules

When a stock order is received:

  • Each received line creates an IN / purchase transaction.
  • The inventory count for that outlet and ingredient increases.
  • A line cannot receive more than the remaining ordered quantity.
  • Receiving an already finalized order is blocked.
  • If only part of the order is received, the order stays partially received until the remaining quantities are received.

Delete guards

Rewardly blocks deletes that would create broken references:

Delete actionBlocked when
Delete supplierAn ingredient or stock order still references the supplier.
Delete ingredient bundleA product or product variant still references the bundle.

These guards prevent new missing-reference data. They do not repair old records that were already missing references before the guard existed.