Home / Help Center / pos-app-bakery / bakery / Recipe Profitability
📊 bakery

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.

📍 Menu path: Bakery → Recipe Profitability
👤 Who uses it: All roles with access to the Bakery menu (the page itself does not add extra permission checks beyond the sidebar's feature/tier gate)
🏷️ Plan tier: advanced+

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

Step-by-Step Guide

1 Read the profitability snapshot

  1. 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 %.
  2. Scan the Margin column's coloured bar on each row — green means 50%+ margin, amber means 25–49%, red means under 25%.
💡 Top Recipe is simply the first recipe in the API response that has both costing and a margin — it is not necessarily your single highest-margin recipe unless the backend already returns recipes ordered by margin.

2 Search, filter and sort the recipe list

  1. Type into the Search recipe… box to narrow the table to matching recipe names.
  2. Use the All / Costed / Not costed toggle to show every recipe, only fully costed recipes, or only recipes still missing a cost/price.
  3. 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

  1. Switch the filter to Not costed to see only recipes without a full cost/price picture.
  2. Note the recipe name(s) shown with the amber "Add costs in Recipes" tag.
  3. 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 / ButtonWhat it does
Recipes CostedCount of recipes that have both an ingredient cost and a selling price set, shown as "x / total recipes".
Avg MarginAverage 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 RecipeName 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 costedFilters the table to every recipe, only recipes with full costing, or only recipes still missing costing.
Highest margin / Lowest margin sort toggleFlips the table's sort direction by margin percentage.
Recipe columnRecipe name and category; shows an amber "Add costs in Recipes" warning tag when the recipe has no costing yet.
Ingredient Cost columnTotal cost of the ingredients for one batch, with "for {yield qty} {yield unit}" underneath; shows "—" if zero/not set.
Cost / Unit columnIngredient cost divided down to a single yield unit (e.g. per loaf, per piece); shows "—" if not costed.
Selling Price columnThe recipe's configured selling price per unit; shows "—" if not set.
Profit / Unit columnSelling price minus cost per unit; green if positive, red if negative, grey dash if the recipe is not costed.
Margin columnA coloured progress bar plus the margin percentage — green (50%+), amber (25–49%), red (under 25%); shows "—" if margin cannot be calculated.
Missing-costs footer noteAppears 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

Troubleshooting & FAQ

Why does a recipe show dashes instead of numbers?
That recipe has not been fully costed yet — it is missing ingredient costs, a yield quantity, or a selling price on the Recipes page. Add those there and the numbers will appear here on next load.
Avg Margin shows "—".
No recipes have a calculable margin yet — every recipe on this list is still missing either its ingredient costs or its selling price.
"Top Recipe" doesn't look like my best-margin item.
Top Recipe is the first costed recipe with a margin value in the API response order, not a calculated "highest margin" pick. Sort the table by Highest Margin to find your actual best performer.
A recipe I just costed on the Recipes page still shows "Add costs in Recipes" here.
Refresh the Recipe Profitability page — it loads its report once when the page opens and does not automatically refresh if you edited the recipe in another tab or window.

🧑‍💻 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):

  • Recipe
  • RecipeIngredient
  • Product

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.