Safety Stock in Business Central: From Theory to a Free Extension You Can Install Today

Reading Time: 7 minutes

Most BC implementations have a problem with safety stock. Either the value is zero (so nothing protects against stockouts), or it’s a number someone wrote during a training course three years ago and nobody updates. Neither is the right answer. Safety stock is a statistical quantity — it has a defensible value derived from how variable your demand is, how variable your supplier’s lead times are, and what service level you’ve decided to commit to. This post walks through the theory, then shows you a free, open-source BC extension that does the calculation properly using actual data from your Item Ledger Entries and Purchase Receipts.

If you only want the code: github.com/GmsoftLtd/bc-safety-stock.

What safety stock actually is

Safety Stock Formula Inputs
Safety Stock Formula Inputs

Safety stock is the buffer inventory you hold to absorb variability you can’t predict. It exists because two things wobble:

  1. Demand wobbles — Tuesdays you sell 50 units, Wednesdays you sell 80, sometimes nothing for a week, sometimes a rush of 200
  2. Lead times wobble — your supplier’s quoted 14 days actually arrives between day 10 and day 21 depending on transport, customs, weather, factory load

If both demand and supply were perfectly stable, you’d need zero safety stock — you’d order exactly the right quantity to arrive exactly when stock runs out. In the real world, both wobble. Safety stock is the explicit price you pay (in tied-up capital) to absorb that wobble without losing sales.

The question is: how much wobble is each item subject to, and how much of it do you want to hedge? That’s where the math comes in.

The Z-score method

Safety stock theory borrows heavily from statistical process control. The core insight: if you treat daily demand as a normally distributed random variable (with some mean and some standard deviation), you can compute how many standard deviations of buffer you need to cover any chosen probability of not stocking out.

That number of standard deviations is the Z-score, and it maps directly to a target service level:

Service LevelZ-ScoreMeaning
70%0.5230% chance of stockout per cycle (almost no protection)
80%0.84One stockout in five replenishment cycles
90%1.28One in ten
95%1.65One in twenty (industry standard for non-critical items)
97.5%1.96One in forty
99%2.33One in a hundred
99.9%3.09One in a thousand (mission-critical: pharma, aerospace)

Pick your service level deliberately. Higher = more stock, more tied-up cash, fewer stockouts. There’s no universally right number — for a fast-moving B-class item, 95% is fine. For a finished-goods SKU your top customer is angry about, you’re going to commit to 99%.

Three calculation methods

The Z-score is just one piece. The other piece is what you multiply it by. There are three increasingly sophisticated formulas:

1. Basic (rule-of-thumb)

Safety Stock = Average Daily Demand × Safety Days

Where “safety days” is something like 7 or 14 — a flat buffer. Cheap and wrong. It treats all items identically regardless of how variable they actually are. Useful as a placeholder for new items with no history.

2. Demand variability only

Safety Stock = Z × σdemand × √(Lead Time)

This is the formula in most introductory operations textbooks. It assumes lead time is fixed and only demand varies. Defensible if your supplier is reliable to the day; almost never true.

3. Full variance (demand + lead time)

Safety Stock = Z × √( LT × σ²demand  +  D² × σ²LT )

Where:

  • Z = service level Z-score
  • LT = average lead time in days
  • σdemand = standard deviation of daily demand
  • D = average daily demand
  • σLT = standard deviation of lead time

This is the formula my free extension uses. It accounts for both sources of wobble.

The intuition: if your supplier is also unreliable (some days 10 LT, some days 21 LT), you need additional buffer to cover the demand that happens during the extra lead time when shipments are late. That’s the D² × σ²LT term.

For an item with average daily demand of 20, demand stddev of 8, average lead time of 14 days, lead time stddev of 3 days, at 95% service level:

Safety Stock = 1.65 × √( 14 × 8²  +  20² × 3² )
            = 1.65 × √( 896 + 3600 )
            = 1.65 × √4496
            = 1.65 × 67.05
            ≈ 111 units

That 111 units is the level you should never drop below if you want to honour your 95% service commitment. Reorder point becomes Avg Daily Demand × Lead Time + Safety Stock = 20 × 14 + 111 = 391 units.

What Business Central does out of the box

BC has a field on the Item table called Safety Stock Quantity. It’s used by the Planning Worksheet to trigger replenishment when projected inventory drops below that level. The field is fully respected by MRP/MPS calculations.

There is no out-of-the-box code to compute it. BC trusts you to fill in the number yourself. In practice, what happens:

  • New implementations leave it at zero (no buffer at all)
  • Someone copies a value from the old system, which was probably also wrong
  • A planner manually edits it for “important” items and forgets the rest
  • Nobody recalculates as demand patterns change

This is the gap the free extension fills.

The extension

bc-safety-stock on GitHub — MIT licensed, runtime 14.0 (BC v27+), free forever.

What it adds:

ObjectTypePurpose
Safety Stock SetupTable + PageDefaults: service level, history window, min observations
Safety Stock CalculatorCodeunitCore math (the full-variance formula above)
Safety Stock Calculation LogTable + PageEvery calculation logged for audit
Item Card extensionPage extension“Calculate Safety Stock” action on single item
Item List extensionPage extension“Calculate Safety Stock (Bulk)” action on filtered/selected items
Safety Stock Job Queue RunCodeunitSchedule recurring recalculation via Job Queue

Item Card action — single item

On any Item Card, click Calculate Safety Stock (in the Actions tab). The extension:

  1. Pulls Item Ledger Entries of type Sale for the last 180 days (configurable)
  2. Aggregates them by date to get daily demand series
  3. Computes mean and standard deviation
  4. Pulls Purchase Receipts for the same item, comparing Order Date to Posting Date
  5. Computes mean and stddev of actual realised lead times
  6. Applies the full-variance formula at the configured service level (default 95%)
  7. Shows you the calculated number and asks “apply?”

Calculate Safety Stock action result on BC Item Card showing Z=1.6449, calculated value of 4 units at 95% service level for item 1999-S ATHENS Desk
Real BC environment — item 1999-S (ATHENS Desk), 29 observations of demand history, calculated buffer of 4 units at 95% service level. Z=1.6449, average lead time 1 day (σ=0), average demand 0.41/day (σ=2.42).

If you confirm, it writes to Item."Safety Stock Quantity" and logs the calculation. If you cancel, nothing changes — useful for what-if analysis.

If lead time history is missing (new supplier, recently added item), it falls back to the existing Item."Lead Time Calculation" field on the item.

Item List action — bulk

Open the Item List, filter (e.g. by Item Category Code = FERT, or by Vendor No. = your top supplier), and click Calculate Safety Stock (Bulk) in the Functions menu.

The extension iterates through the filtered set, calculates each, applies the value, and logs every entry to the log table. A progress dialog shows count. Items that fail the minimum-observation threshold or are blocked get a log entry with a “Skipped” result code — no silent failures.

This is the right pattern for periodic recalculation. Once a month, filter by ABC class A items, recalc. Once a quarter, run all inventory items.

Job Queue option — scheduled

For production environments where you want this to happen automatically, the included Safety Stock Job Queue Run codeunit (object ID 50101) can be scheduled like any other Job Queue Entry.

Example: monthly recalc of all FERT items at 99% service level:

  • Object Type: Codeunit
  • Object ID: 50101
  • Parameter String: FILTER=Item Category Code:FERT;SERVICELEVEL=99
  • Recurring: Monthly

Without parameters, all inventory items get recalculated at the setup default service level.

Choosing your service level — practical guide

This is where I see the most consultant-side judgement calls. A framework:

  • A-class items (top 20% by value, ~80% of revenue) — start at 99%. These are the items where stockouts cost you customer trust and revenue.
  • B-class items (next 30%) — 95% is the sweet spot. Defensible buffer, not overcommitted capital.
  • C-class items (bottom 50%, often only 5% of revenue) — 85-90%. Sometimes lower. These are commodities; stockouts annoy but don’t kill.
  • Items with safety regulations (food, pharma, aerospace) — what the regulation says. Override the formula.
  • Make-to-order itemsskip safety stock entirely. Push the buffer onto raw materials, not finished goods.
  • New items, <10 sales observations — the extension skips these (insufficient data). Use a category default until enough history accumulates.

Run the calculation, then sanity-check the result. If safety stock for a slow mover comes out as 800 units, demand is too lumpy for normal-distribution methods — consider Croston’s method instead (out of scope for this extension; planned for a future version).

When NOT to use this

The Z-score method assumes near-normal demand distribution. It breaks down for:

  • Intermittent demand — long gaps between sales, then bursts. Z-score wildly overestimates. Use Croston’s method or category defaults.
  • Heavy seasonality — averaging across summer and winter for a swimsuit item gives garbage. Run the calculation during the relevant season window, not annually.
  • Configurable / make-to-order items — there is no inventory to buffer.
  • New items — no history, no statistics. Apply a category-level default.
  • End-of-life items — declining demand. Statistical methods over-stock these. Phase out manually.

The extension’s Insufficient Demand Data result code catches new-item cases. The others need consultant judgement.

Installation

Two paths:

Developer / sandbox (recommended for first try):

  1. git clone https://github.com/GmsoftLtd/bc-safety-stock
  2. Open in VS Code with the AL Language extension
  3. Confirm app.json ID range (50100–50199) doesn’t conflict with your tenant
  4. Press F5 (publish + launch)

Production tenant:

  1. Wait for the first release on the GitHub Releases page (or build the .app file yourself)
  2. Upload via Extension Management → Manage → Upload Extension
  3. Open Search → Safety Stock Setup, configure defaults
  4. Test on a single item before running bulk

The extension only modifies one BC field (Item.Safety Stock Quantity) and inserts into its own log table. It’s non-invasive and safe to uninstall.

What’s next

This is the first piece of a series. Coming soon:

  • EOQ (Economic Order Quantity) — companion calculation, classic Wilson formula, decides order quantity
  • Reorder Point — when to actually trigger the order
  • Combined pillar post — choosing the right replenishment formula for each item class

Each will get its own deep-dive post and a focused repo.

Resources

If your BC implementation has the “all zeros” safety stock problem, install this in a sandbox, run it against a real item, look at the calculated number. That’s usually enough to convince a planner that the manual approach was leaving real money on the table.


Questions, edge cases, or a costing variance you can’t explain? Get in touch.


Discover more from Inside Business Central

Subscribe to get the latest posts sent to your email.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top