EOQ in Business Central: From the Wilson Formula to a Free Extension That Writes the Number Into Your Planning Worksheet

Reading Time: 9 minutes

After 15 years of doing BC implementations (and 30 in ERP overall), I’ll tell you the second inventory pattern that frustrates me most: Reorder Quantity values that came from nowhere. (The first one is Safety Stock, and that’s a different post with a different free extension.)

Buyers order in round numbers because nobody told them otherwise. Lot sizes get copied from the legacy system. Item cards have Reorder Quantity set to 100, or 500, or some other suspicious round figure that nobody can explain. Last quarter I audited a manufacturing tenant where the most-ordered raw material was being purchased in batches of 1,000 units. The actual economic quantity, computed from their own demand and cost data, was 287 units. They were sitting on three months of unnecessary inventory because someone typed 1,000 into the system in 2019 and nobody has touched it since.

This is exactly what the Economic Order Quantity formula was invented to fix, in 1913, by Ford W. Harris. The math has been in every operations textbook for over a century. BC respects the result through its Planning Worksheet. The only thing missing is the bit that actually does the calculation and writes the number into the field BC reads. That’s what this free extension fills.

📦 Get the Code

👉 github.com/GmsoftLtd/bc-eoq-calculator

Free. MIT licensed. About 600 lines of AL. Object range 50200 to 50299 (no conflict with bc-safety-stock which uses 50100 to 50199). Runtime 14.0, BC v27+.

What EOQ Actually Is

EOQ answers one question: for an item that I order repeatedly, what quantity per order minimises my total cost per year?

Two costs pull against each other.

Ordering cost is everything you spend each time you place an order, regardless of size. Purchase order admin, vendor setup, freight, inbound inspection, putaway, AP processing the invoice. If ordering costs you 50 EUR per order, you can place one order a year for 1,200 units and spend 50 EUR on ordering, or 12 orders for 100 units each and spend 600 EUR. Larger orders means fewer orders means less ordering cost.

Holding cost is everything you spend to keep stock on hand. Capital tied up, warehouse rent, insurance, obsolescence risk, internal handling, inventory taxes. Holding cost typically runs 20 to 30 percent of unit cost per year. Larger orders means more average inventory means more holding cost.

Order too little, ordering cost dominates. Order too much, holding cost dominates. EOQ is the single quantity where the two curves cross. At that point your total annual cost of replenishment is at a minimum.

EOQ cost curves chart: annual ordering cost falling, annual holding cost rising, and the U-shaped total cost curve with its minimum at the economic order quantity
Ordering cost falls and holding cost rises as order quantity grows. Total cost is the sum of the two, and its minimum is the EOQ.

This is not a rule of thumb. It is the result of taking the derivative of total cost with respect to order quantity and setting it equal to zero. High-school calculus. The result has been in every operations textbook since the First World War.

🧮 The Wilson Formula

EOQ = √( (2 × D × S) / H )

Where:

  • D = annual demand in units
  • S = ordering cost per order
  • H = annual holding cost per unit (typically Unit Cost × Holding Rate)

Worked example. An item with annual demand of 4,800 units, ordering cost of 50 EUR per order, unit cost of 20 EUR, and a holding rate of 25 percent (so H = 20 × 0.25 = 5 EUR per unit per year):

EOQ = √( (2 × 4,800 × 50) / 5 )
    = √( 480,000 / 5 )
    = √( 96,000 )
    ≈ 310 units

At 310 units per order:

  • Annual ordering cost: (4,800 / 310) × 50 = 774 EUR
  • Annual holding cost: (310 / 2) × 5 = 775 EUR

The two come out almost equal. That is not a coincidence. At the EOQ they are mathematically equal. The curve is shallow near the minimum, which means you can round to a sensible pack size (300, 312, whatever your pallet holds) and lose almost nothing.

That shallowness is why EOQ is so robust. You do not need perfect estimates for D, S, and H. Order-of-magnitude is enough to land within 10 percent of optimal, which is well inside the noise floor of real demand variability.

📊 Where the Three Inputs Come From in BC

This is where most EOQ attempts quietly fail. The math is easy. The data hygiene is hard.

Annual demand (D). Pull from Item Ledger Entries of type Sale over the last 365 days. Not Sales Lines. Sales Lines include open and cancelled orders that may never ship. ILE Sale entries are posted history, which is what actually happened. If the item has fewer than around 60 observations across the window, EOQ is meaningless. Fall back to a manual quantity or category default.

Ordering cost (S). BC has no out-of-the-box ordering cost field. The Setup table in this extension defaults to 50 (local currency). Typical values:

  • 25 EUR for a digital order to a low-cost local supplier
  • 50 EUR for a standard domestic order with normal admin
  • 150 to 200 EUR for international with customs and inspection

The way to estimate it: take the total annual cost of the purchasing function (people, software, item-agnostic freight, inspection labour) and divide by the number of POs placed in the year. Most BC clients have never run this exercise. The first time they do, the number surprises them.

Holding cost (H). Unit Cost × Holding Rate. The Setup defaults to 25 percent. Holding rate has four components:

  • Cost of capital (5 to 10 percent for inventory financed via credit line)
  • Warehouse and handling (5 to 10 percent normal, much higher for refrigerated, hazardous, high-rack)
  • Obsolescence and shrinkage (5 to 15 percent, much higher for fashion, electronics, food)
  • Insurance and taxes (1 to 3 percent)

For most B-class items 25 percent is defensible. For slow movers or perishables, 35 to 40 percent. For commodities with stable demand and long shelf life, 18 to 20 percent.

🔄 How BC Uses EOQ in MPS / MRP

This is the part that matters most for planners.

BC’s Planning Worksheet does not call the Wilson formula. What it does is read fields on the Item card and respect them as constraints when generating supply suggestions. The fields:

  • Reordering Policy is the algorithm the engine uses
  • Reorder Point is when to trigger replenishment
  • Reorder Quantity is how much to order under Fixed Reorder Qty.
  • Order Multiple rounds suggested quantities for Lot-for-Lot
  • Minimum Order Quantity sets a lower bound on suggestions
  • Maximum Inventory sets an upper bound for Maximum Qty. policy

The four standard Reordering Policies and where EOQ fits each:

  • None. No automatic replenishment. EOQ does not apply.
  • Fixed Reorder Qty. Engine suggests Reorder Quantity units whenever projected inventory drops below Reorder Point. This is the classic EOQ home. EOQ goes into Reorder Quantity.
  • Maximum Qty. Engine tops stock back up to Maximum Inventory. EOQ does not map cleanly to this. Use it when you want a stock cap rather than economic batches.
  • Order. One-for-one supply per demand. Used for make-to-order. EOQ does not apply.
  • Lot-for-Lot. Engine suggests exactly what is needed within the time bucket. EOQ can still apply through Order Multiple. Set Order Multiple = EOQ and the engine rounds each Lot-for-Lot suggestion up to the nearest economic batch.

The extension supports both common patterns. By default it writes the EOQ to Reorder Quantity and switches Reordering Policy from None to Fixed Reorder Qty. when the policy is None. Configure it to write to Order Multiple instead if your shop runs Lot-for-Lot.

What MPS and MRP do with the result

When the Planning Worksheet (for purchased items) or MPS/MRP (for manufactured items) runs, it reads these Item fields at calculation time. When projected inventory drops below Reorder Point on a future date, the engine creates a supply suggestion.

For Fixed Reorder Qty., the suggestion is exactly the Reorder Quantity. If you have written the EOQ in there, the suggestion is the economic order quantity. The buyer reviews and carries it into a purchase order.

For Lot-for-Lot with Order Multiple, the engine calculates the net requirement and rounds up to the nearest multiple of Order Multiple. If the multiple is the EOQ, the supply suggestion will be one EOQ, two EOQs, three EOQs, never an awkward 73 or 184 units.

Both patterns are valid. The choice depends on demand volatility:

  • Stable demand → Fixed Reorder Qty. with EOQ
  • Volatile or seasonal demand → Lot-for-Lot with EOQ as Order Multiple

🛠️ What Comes With the Extension

ObjectTypeID
EOQ SetupTable + Page50200
EOQ Calculation LogTable + Page50201
EOQ Result CodeEnum50200
EOQ CalculatorCodeunit50200
EOQ Job Queue RunCodeunit50201
Item Card Page ExtensionPageExt50200
Item List Page ExtensionPageExt50201
EOQ Calculator Permission SetPermission Set50200

Single-item action

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

  1. Pulls Item Ledger Entries of type Sale for the last 365 days
  2. Checks observations against the minimum threshold (default 60)
  3. Sums quantity, annualises to D
  4. Reads Item Last Direct Cost (or Standard Cost) as Unit Cost
  5. Computes H = Unit Cost × Holding Rate
  6. Reads Ordering Cost S from Setup
  7. Applies the Wilson formula
  8. Applies a maximum cap (default 6 months of demand)
  9. Rounds to Item Order Multiple if set
  10. Shows the calculated EOQ and asks “apply?”

If you confirm, it writes to Item."Reorder Quantity" and switches Item."Reordering Policy" to Fixed Reorder Qty. when the policy is None. Every calculation is logged with all inputs, the formula output, and the field that was updated.

Business Central Item Card showing the Calculate EOQ (Wilson) confirmation dialog: EOQ for 1896-S is 11 units, previous value 0, apply to Reorder Quantity?
Real BC environment: item 1896-S (ATHENS Desk), calculated EOQ of 11 units versus a previous Reorder Quantity of 0. The action shows the result and asks before writing to the field.

Bulk action

Open the Item List, filter to the set you want (Item Category Code, Vendor No., a saved view of A-class items), then click Calculate EOQ (Bulk) in Actions. The extension processes every item in the filter, applies the result, and writes a log entry for each. Items that fail the observation threshold are logged as Skipped. No silent failures.

Job Queue

The included EOQ Job Queue Run codeunit (object 50201) accepts a Parameter String. Example for quarterly recalc of finished goods:

  • Object Type: Codeunit
  • Object ID: 50201
  • Parameter String: FILTER=Item Category Code:FERT
  • Recurring: Quarterly

Other supported filters: Vendor No., No., Inventory Posting Group, Gen. Prod. Posting Group.

⚖️ EOQ + Safety Stock + Reorder Point

EOQ is one piece of a three-formula replenishment stack:

  • Safety Stock answers: how much buffer to hold against variability. Goes into Item.Safety Stock Quantity. Use bc-safety-stock.
  • Reorder Point answers: when to trigger the order. Mechanically (Average Daily Demand × Lead Time) + Safety Stock. Goes into Item.Reorder Point.
  • EOQ answers: how much to order. Goes into Item.Reorder Quantity. That is this extension.

When all three are populated and Reordering Policy is Fixed Reorder Qty., the Planning Worksheet has everything it needs. It triggers at Reorder Point, suggests Reorder Quantity. Every number is defensible. Any planner can explain why each value is what it is.

That is the goal. Not perfect numbers (perfect does not exist for inventory). Defensible numbers.

🎯 Practical Tips

Ordering cost. If you have never measured this, start with 50 EUR for routine local orders and 150 EUR for international with customs. Override per Vendor as you learn. The shallow EOQ curve forgives small errors in S, so it does not need to be perfect on day one.

Holding rate. 25 percent is the default. Bump to 35 to 40 percent for items with high obsolescence risk. Drop to 18 to 20 percent for commodities with stable demand and long shelf life.

Demand window. Default 365 days. For seasonal items, keep it at a full year (the seasonality averages out). For growth or decline trends, shorten to 180 days so the recent trend dominates.

Minimum observations. 60 is the default. Anything less and the demand estimate is too noisy.

ABC class differentiation. A-class items: quarterly recalculation. B-class: twice a year. C-class: once a year or whenever the cost or supplier changes. The bulk action plus a Job Queue schedule makes this trivial.

🚫 When EOQ Is Not the Right Answer

The Wilson formula assumes steady demand and a constant unit cost. It is not appropriate for:

  • Intermittent demand (long gaps, occasional bursts). Use Croston’s method or category defaults.
  • Make-to-order items. No inventory to optimise.
  • Heavy seasonality. Run the calculation in the relevant season window, or use a manual quantity.
  • Quantity discount tiers. The formula assumes a single unit cost. Compare total cost at EOQ versus total cost at the discount tier directly.
  • Joint replenishment items that ship together. EOQ treats each item separately; this overestimates ordering cost.
  • Capacity- or capital-constrained items. EOQ does not know your warehouse cap or working capital ceiling. The Max EOQ Months cap helps but is not a substitute for judgement.

For configured or make-to-order items, push the replenishment decision to raw materials. Let the BOM and routing drive the finished-goods supply.

⚙️ Install It

Developer or sandbox:

git clone https://github.com/GmsoftLtd/bc-eoq-calculator

Open in VS Code with the AL extension. Confirm the object ID range (50200 to 50299) does not conflict. Press F5.

Production tenant:

  1. Download a release .app from the Releases page, or build from source
  2. Upload via Extension Management
  3. Open Search → EOQ Setup, configure Ordering Cost, Holding Rate, Write Target
  4. Test on a single item before running bulk

The extension only modifies up to two fields per item (Reorder Quantity, optionally Reordering Policy). Safe to uninstall.

🔮 What’s Next

This is the second piece of the replenishment series. The third is coming.

  • Reorder Point with full demand and lead-time variability handling. Companion repo bc-reorder-point. Article walks through how Safety Stock plus average lead-time demand equals the value that goes into Reorder Point.
  • Combined pillar post. Once all three repos exist, a single piece on how to choose between Fixed Reorder Qty., Lot-for-Lot, and Maximum Qty., with worked examples.

The three extensions together cover the full deterministic-replenishment math for BC. None of them require AppSource subscription, BC modification, or third-party API.

🔗 Resources

If your BC tenant has Reorder Quantity values that came from somewhere nobody remembers, install this in a sandbox, run it against ten real items, compare the calculated EOQ to the current Reorder Quantity. The gap is usually large. The story of where that gap is costing you money writes itself.


Questions, edge cases, or a planning behaviour you cannot 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