Inventory and food costing
Ingredients, recipes, purchase orders, receiving, lots, counts, transfers, waste, and stock depletion.
Openfront Restaurant links menu sales to ingredient cost and stock. Every stock change is written as a StockMovement.
Screens
| Route | What it does |
|---|---|
/dashboard/platform/ingredients | Ingredient list by category, stock counts, transfers between locations, and operations recovery |
/dashboard/platform/inventory/purchase-orders | Purchase orders, status changes, receiving, invoices, credits, and lots |
/dashboard/platform/inventory/recipes | Recipes and food cost. Not in the sidebar |
/dashboard/platform/inventory/waste | Waste log and reversals |
Vendors and inventory locations are managed through the generic list screens (/dashboard/vendors, /dashboard/inventory-locations).
What is in the inventory layer
Ingredients
- current stock and unit (g, ml, kg, lb, oz, liter, gallon, each, case, box)
- category (produce, meat, dairy, dry goods, beverages, spices, seafood, other)
- par level, reorder point, and reorder quantity
- cost per unit
- vendor and storage location
- expiration date and SKU
Recipes
- the linked menu item
- ingredients as JSON:
{ ingredientId, quantity, unit }per row - yield, prep time, and instructions
- calculated total cost, cost per serving, and food cost percentage
Purchasing and waste
Vendor,InventoryLocation,PurchaseOrder,StockMovement,WasteLog
Stock operations
All of these need canManageInventory and take an idempotency key, so a retry does not double-post.
- Receive a purchase order with
receivePurchaseOrder: line quantities, a reference, and optional lot code and expiry date per line. - Match invoices and credits with
recordRestaurantPurchaseInvoiceandrecordRestaurantPurchaseCredit.restaurantPurchaseEvidenceshows received, billed, and credited amounts. - Lots:
restaurantIngredientLotslists lots for an ingredient.setRestaurantLotHoldputs a lot on hold or releases it. - Count with
countIngredientStock: counted vs expected stock with a reason. - Adjust with
adjustInventory. - Transfer with
transferRestaurantStockbetween two ingredient records with the same SKU in active locations. - Waste with
recordWaste(spoilage, preparation error, overproduction, plate waste, expired, damaged, other) and undo withreverseWaste.
Purchase order statuses: draft, sent, confirmed, shipped, partially_received, received, cancelled. transitionPurchaseOrder moves draft to sent, sent to confirmed, confirmed to shipped, and any open status to cancelled. Received states are only set by posting a receipt.
The practical workflow
Create ingredients
Add raw materials with unit, cost, vendor, location, and starting stock.
Build recipes
Create a recipe, link it to a menu item, and add ingredient rows.
Review food cost
The recipe page shows total cost, cost per serving, and food cost percentage.
Close real orders
When an item is ordered, its recipe is frozen as a snapshot. When the order is completed, or cancelled after the kitchen started the item, the app writes sale stock movements from that snapshot.
Purchase and log waste
Send and receive purchase orders, match invoices, and log waste.
Recovery
If stock depletion fails after an order closes, it is logged as needing recovery. The Ingredients page shows these (restaurantOperationsRecovery). Staff can retry (retryRestaurantOperations) or record a review for items from older versions without a recipe snapshot (reviewLegacyRestaurantRecipes). reconcileOrderInventory re-runs depletion for one closed order.
Current gaps
- Recipe ingredients are stored as JSON, not as related records.
- Depletion fails if a recipe ingredient's unit no longer matches the ingredient's stocking unit. There is no general unit conversion.
- Recipe food cost does not feed the reports.
- There is no automatic reorder based on par levels.
Very small recipe quantities can round to zero. Use grams or milliliters as stocking units when recipes use small amounts.