Recipe Profitability
Recipe Profitability turns every recipe's ingredient cost and selling price into a single margin report, so you can see at a glance which items are actually making money and which ones still need their costs filled in.
Overview
The page loads one row per recipe from GET /bakery/recipes/profitability — each row carries the recipe's total ingredient cost, its cost per yield unit, its configured selling price, profit per unit, and a margin percentage.
Three summary cards sit above the table: Recipes Costed (how many of your recipes have both an ingredient cost and a selling price set, out of the total), Avg Margin (the average margin percentage across costed recipes only), and Top Recipe (the first costed, margin-bearing recipe returned by the API).
Recipes that are missing a cost or a selling price are not silently hidden — they still appear in the table with dashes in place of numbers, plus a small "Add costs in Recipes" warning tag, so nothing that needs attention falls out of view.
This report is read-only: it never changes a recipe's cost or price. To fix a low or missing margin you go to the Recipes page and update the ingredient list, yield, or selling price there.
Before You Start
- Requires the Advanced plan tier or above; below Advanced, Recipe Profitability is hidden from the Bakery sidebar entirely.
- Recipes must already exist on the Recipes page. A recipe only counts as "costed" once it has ingredients with unit costs entered and a Selling Price Per Unit set.
Step-by-Step Guide
1 Read the profitability snapshot
- Look at the three summary cards: Recipes Costed (x / total), Avg Margin (average across costed recipes, shown in green at 40%+ and amber below that), and Top Recipe with its margin %.
- Scan the Margin column's coloured bar on each row — green means 50%+ margin, amber means 25–49%, red means under 25%.
2 Search, filter and sort the recipe list
- Type into the Search recipe… box to narrow the table to matching recipe names.
- Use the All / Costed / Not costed toggle to show every recipe, only fully costed recipes, or only recipes still missing a cost/price.
- Click the sort button to flip between "Highest margin" and "Lowest margin" ordering; recipes with no margin value are always pushed to the bottom of the highest-margin sort (or the top of the lowest-margin sort).
3 Find and fix recipes with missing costing
- Switch the filter to Not costed to see only recipes without a full cost/price picture.
- Note the recipe name(s) shown with the amber "Add costs in Recipes" tag.
- Go to the Recipes page, open that recipe, and fill in ingredient quantities/costs and a Selling Price Per Unit — the next time you open Recipe Profitability it will be included in the costed count and margin average.
Every Field & Button, Explained
| Field / Button | What it does |
|---|---|
Recipes Costed | Count of recipes that have both an ingredient cost and a selling price set, shown as "x / total recipes". |
Avg Margin | Average margin percentage across only the costed recipes; shown in green at 40% or above, amber below that; shows "—" if no recipes are costed yet. |
Top Recipe | Name and margin % of the first costed, margin-bearing recipe returned by the API — see the note above on how this is picked. |
Search recipe… | Filters the table by recipe name as you type. |
All / Costed / Not costed | Filters the table to every recipe, only recipes with full costing, or only recipes still missing costing. |
Highest margin / Lowest margin sort toggle | Flips the table's sort direction by margin percentage. |
Recipe column | Recipe name and category; shows an amber "Add costs in Recipes" warning tag when the recipe has no costing yet. |
Ingredient Cost column | Total cost of the ingredients for one batch, with "for {yield qty} {yield unit}" underneath; shows "—" if zero/not set. |
Cost / Unit column | Ingredient cost divided down to a single yield unit (e.g. per loaf, per piece); shows "—" if not costed. |
Selling Price column | The recipe's configured selling price per unit; shows "—" if not set. |
Profit / Unit column | Selling price minus cost per unit; green if positive, red if negative, grey dash if the recipe is not costed. |
Margin column | A coloured progress bar plus the margin percentage — green (50%+), amber (25–49%), red (under 25%); shows "—" if margin cannot be calculated. |
Missing-costs footer note | Appears only when at least one recipe is not fully costed, telling you how many recipes still need a cost or price set on the Recipes page. |
Tips & Best Practices
- Run this report after any flour/sugar/dairy price change — ingredient cost updates on the Recipes page flow straight into this margin view.
- Use the Not costed filter right after adding several new recipes at once, so none of them are quietly missing a selling price.
- Do not judge a recipe purely on Ingredient Cost — always look at the Margin bar, since a cheap recipe with a very low selling price can still have a poor margin.
Troubleshooting & FAQ
Why does a recipe show dashes instead of numbers?
Avg Margin shows "—".
"Top Recipe" doesn't look like my best-margin item.
A recipe I just costed on the Recipes page still shows "Add costs in Recipes" here.
🧑💻 Developer Notes
Source component(s):
frontend-app/packages/pos-react-lib/src/pages/fnb/RecipeProfitabilityPage.jsx
Backend endpoints used:
GET /bakery/recipes/profitability
Related tables (db-core repositories):
RecipeRecipeIngredientProduct
Feature flag key: recipe_profitability (requires advanced plan tier or above)
All filtering, searching and sorting happens client-side against the single array returned by GET {apiPrefix}/recipes/profitability (component accepts an apiPrefix prop, defaulting to /bakery). costedCount, avgMargin and bestRecipe are derived from the raw report array, not from the currently filtered/sorted displayed list, so the summary cards do not change when you filter or sort the table below them.