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

RouteWhat it does
/dashboard/platform/ingredientsIngredient list by category, stock counts, transfers between locations, and operations recovery
/dashboard/platform/inventory/purchase-ordersPurchase orders, status changes, receiving, invoices, credits, and lots
/dashboard/platform/inventory/recipesRecipes and food cost. Not in the sidebar
/dashboard/platform/inventory/wasteWaste 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 recordRestaurantPurchaseInvoice and recordRestaurantPurchaseCredit. restaurantPurchaseEvidence shows received, billed, and credited amounts.
  • Lots: restaurantIngredientLots lists lots for an ingredient. setRestaurantLotHold puts a lot on hold or releases it.
  • Count with countIngredientStock: counted vs expected stock with a reason.
  • Adjust with adjustInventory.
  • Transfer with transferRestaurantStock between two ingredient records with the same SKU in active locations.
  • Waste with recordWaste (spoilage, preparation error, overproduction, plate waste, expired, damaged, other) and undo with reverseWaste.

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.

On this page