A recipe tells Bohari what one sale takes off the shelf and what it costs to make, and every theoretical usage figure on the Variance report starts here.
What a recipe is
A recipe is the ingredient list behind something you sell or prepare: a plate of chicken curry, a 25ml tot poured from a 750ml bottle, a 20-litre batch of stock. Each one has a code that's unique within your company (something like REC-0001), a name, and a "Makes" figure that says what the ingredient list produces: 1 plate, 4,000 ml, 18 litres. Everything downstream divides by that figure, so it matters that it's honest.
Recipes are company-wide. There's no per-outlet copy of a recipe; what changes per outlet is the cost, because each outlet has its own purchase history.
You'll find them under Inventory, on the Recipes page. Anyone with access can read them; creating, editing and retiring them needs the manage_recipes permission, which also gates menu-item mapping because both decide what a sale depletes.
Writing one
Press New recipe. The code is filled in for you: Bohari reads the recipe codes you already have and offers the next free number in the same family, so a company already numbering RCP-0180 is offered RCP-0181 rather than starting a second series beside it. Nothing is reserved, so the field stays yours to overwrite if you have your own scheme; if two people open the form at the same moment, the second one to save is told which recipe already holds the code. Editing an existing recipe never re-suggests, because its code is what reports and imports already call it by.
Fill in the name, what it makes and in which unit. Then add ingredient lines. Each line names exactly one thing from a single picker: either a stock item (in that item's usage unit, so grams or millilitres rather than cartons) or another recipe as a sub-recipe (in that recipe's own yield unit). A line can't name both, and it can't name neither; the server refuses the save if a line tries.
A few other things the save checks for you:
- Quantities must be above zero, and the recipe needs at least one line. The refusal reads "A recipe must have at least one ingredient."
- The same ingredient can't appear on two lines. You'll see "The same ingredient appears twice on this recipe." Combine the lines instead, because two quantities for one ingredient is an ambiguity, not a total.
- A line can't point at an item that is discontinuing, pending approval or retired. Use its active successor instead. A line also can't point at a retired sub-recipe.
Saving an edit replaces the whole line set. If you remove a line and save, it's gone; there's no separate delete step to forget.
Importing a whole recipe book
If you arrive with recipes already written down, Import on the Recipes page takes them as a spreadsheet. Download the template first; it has the columns filled in with a worked example.
One row per ingredient. The recipe's own columns (name, what it makes, the unit) go on its first row, and you can either fill them down or leave them blank on the rows below. Blank means "same as above". A row that contradicts what an earlier row said is refused rather than one of them silently winning.
Each row names exactly one ingredient, in either item_code or child_recipe_code, never both.
A sub-recipe may be pointed at before it is defined further down the file, so you don't have to
sort your spreadsheet into any particular order: Bohari works out which batches to create first.
Two things it will not do:
- It never creates items. An ingredient code that doesn't exist is a typo far more often than it's a new product, and an item invented here would have no unit, no category and no cost. Load your items first.
- It never imports part of a recipe. If one line of a dish is wrong, that whole dish is rejected and the rest of the file still imports. A dish loaded without one ingredient would cost too little and under-report its usage, and nothing would look broken. That's the kind of wrong that shows up months later as a variance nobody can explain.
Check before you import. The Check file button writes nothing and reports every row: which recipe it belongs to, what that recipe would create or update, and what's wrong with the ones that can't. Fix the sheet and check again as many times as you need.
Importing the same file twice is safe. The second run updates rather than duplicating, so if you're unsure whether the first attempt worked, just run it again. Re-importing replaces a recipe's whole ingredient list, so a line you deleted from the spreadsheet is genuinely gone.
One thing to watch: a quantity is read in the item's usage unit. If your recipe says 40 g of prawns, the prawns item has to be used in grams. An item used in pieces with a recipe written in grams isn't a rounding difference, it's a different number.
So check the usage units on your items before loading a book written in grams and millilitres, and fix them before your first stocktake. How an item is measured can be corrected freely until something uses it, and is fixed the moment anything does. A stock movement, a recipe line, a sales explosion or a supplier price all count.
That isn't the system being awkward. Every quantity Bohari stores is a plain number that means
what it means because of that unit: a count of 40 is forty bottles or forty grams depending
entirely on the item. Changing the unit afterwards would re-label all of it and convert none of
it, and the stock ledger can never be rewritten. If you hit this after trading has started, the
way out is Items → Replace. Choose measurement correction, define how many new stock
units one old stock unit becomes, and migrate every affected recipe. An independent Admin then
approves one transaction that transfers every location balance at the same total value, moves
the recipe references and retires the old item. See Replacing & Retiring Items.
Sub-recipes and batches
Sub-recipes are how batch cooking works. Your pepper sauce is its own recipe that makes, say, 4,000 ml; the steak plate then has a line for 60 ml of pepper sauce. When a steak sells, Bohari walks down through the sauce into its tomatoes and cream, dividing by each batch yield along the way. That division happens at every level, which is what makes a batch a batch.
Cooking loss belongs in the "Makes" figure. If a 20-litre boil reliably gives you 18 usable litres, enter 18. The form says as much in its hint.
Two guard rails keep the graph sane:
- A recipe can't contain itself, directly or through a chain. The editor won't offer a recipe as its own ingredient, and the server walks the whole graph before saving. A loop is refused with the path named, for example "This would make a recipe contain itself: Pepper Sauce → Brown Stock → Pepper Sauce." That tells you which line to remove.
- Recipes may nest at most 10 deep. Anything deeper is refused rather than left to recurse forever inside a sales import.
The two yields are different yields
This trips people up, so it gets its own section.
The recipe's Makes figure affects quantity. Ingredients divided by what the batch produces is what each portion takes off the shelf.
An item's yield factor affects cost and never quantity. Five kilos of chicken at KES 500 per kilo with a 0.64 yield costs KES 781.25 per usable kilo, because you paid for the trim and the trim never reached the shelf as usable product. But the recipe still takes exactly its stated quantity of chicken out of stock. The yield factor lives on the item, not on this form, precisely so the two don't get confused.
If costing applied the trim to quantity as well, every theoretical usage figure would inflate and every variance would shrink, which is the flattering kind of wrong. Bohari deliberately doesn't do it.
The cost card
Open any recipe and the right-hand Cost card shows what one unit of its yield costs at a chosen outlet, priced from that outlet's live weighted-average purchase costs. Sub-recipe lines expand into their own breakdown, because "why does the sauce cost that" is always the next question.
The cost is per outlet rather than per storage location. Transfers inside an outlet carry the same value on both halves, so the outlet-wide average is exact wherever the crates happen to sit today, and it answers the question a manager actually asks: what does this dish cost at Westlands?
One rule to know: the card refuses partial answers. If any ingredient has never arrived on a priced receipt at that outlet, the cost per yield unit shows a dash, not a sum of the lines that happen to have prices. A partial cost looks like a cost, and it's the kind of number that ends up on a menu. The card names the ingredients that need pricing, and a receipt with a price on it is what turns the dash into a figure. See Stock on Hand for how weighted averages are built.
What recipes feed
When a day's sales are imported, each mapped sale explodes through its recipe into the leaf items it should have consumed, and those quantities become the "recipes say" side of the Variance report. Two things about that are worth knowing here:
- Sales never post to the stock ledger. The explosion is a comparison, not a movement.
- The explosion is stored at import time, snapshot and all. Editing a recipe today changes future imports only; last month's figures don't move, because a report that shifts under you after the fact is a report nobody can argue from.
Retiring, not deleting
There's no delete. Retire takes a recipe off the pickers for new mappings and new lines, while everything that already went through it keeps its numbers. A retired recipe still shows on the list when you tick Show retired.
Retiring is refused while another recipe still uses this one as an ingredient. The refusal names the parents: "This recipe is an ingredient of: Brown Stock, Pepper Sauce. Remove it from those first."
FAQ
I fixed a recipe that was wrong all month. Will the variance report change? No. Sales that already exploded keep their stored quantities and the recipe version they went through. From the next import onward, the corrected recipe applies. If a whole day needs restating, void that day's sales import and re-import it.
Why is the cost card showing a dash when most ingredients have prices? Because one or more ingredients have never been valued at that outlet, and a total that quietly skipped them would be wrong with confidence. The warning box lists the culprits. Receive them on a priced GRN or cash purchase at that outlet and the figure appears.
Why can't I just delete a test recipe? If nothing references it, retire it; that's the supported way out, and it keeps the audit trail intact. If something does reference it, the refusal tells you which recipes to edit first.
Where do I put wastage like peeling loss? Predictable preparation loss goes in the item's yield factor (cost only). Batch cooking loss goes in the recipe's "Makes" figure (enter what you actually get out). One-off spillage or spoilage is a waste note, not a recipe change, and it lands on the explained side of the variance equation.
The save was refused with an arrow path I don't recognise. What happened? You created a loop: some sub-recipe down the chain points back at the recipe you're editing. The message spells out the path one step at a time. Remove the line that closes the circle.