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:
| Record | Purpose |
|---|---|
| Ingredient Inventory | The current stock for one ingredient at one outlet. |
| Ingredient Transaction | The 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
User action
-> API route
-> Ingredient stock movement service
-> validate movement type and reason
-> create Ingredient Transaction
-> update Ingredient Inventory current stockAdmin 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 action | Movement written |
|---|---|
| Create ingredient inventory with a starting count | ADJUSTMENT / inventory_count from 0 to the starting stock. |
| Edit ingredient inventory current stock | ADJUSTMENT / inventory_count from the previous count to the new count. |
| Delete ingredient inventory with non-zero stock | ADJUSTMENT / inventory_count to bring stock to 0, then the inventory row is removed. |
| Create a manual ingredient transaction | IN or OUT, with a reason that matches the type. Adjustments come only from editing a count. |
| Receive an ingredient stock order | IN / purchase for each received item. |
| Sell a product, modifier option, or combo option with Track ingredients and a recipe | OUT / 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 sale | Follows 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.
| Type | Valid examples |
|---|---|
| IN | purchase, delivery, return, transfer in, production |
| OUT | consumption, waste, expiry, damage, transfer out, theft |
| ADJUSTMENT | inventory 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 / purchasetransaction. - 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 action | Blocked when |
|---|---|
| Delete supplier | An ingredient or stock order still references the supplier. |
| Delete ingredient bundle | A 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.