Budgeting, Commitments, and Availability Control
Budget in an ERP is not a spreadsheet. It’s an enforceable control that can stop a purchase order from being created.
The vocabulary
| Term | Meaning |
|---|---|
| Plan | Expected figures, informational, used for variance reporting |
| Budget | Authorised spending limit, potentially enforced |
| Commitment | Money legally promised but not yet spent (an open purchase order) |
| Actual | Money spent (posted expense) |
| Available | Budget − commitments − actuals |
| Encumbrance | US/public sector term for commitment |
| Obligation | A commitment that has become legally binding |
| Release | Portion of an annual budget made available for a period |
| Supplement / return | Increase / decrease of budget in-year |
| Carry-forward | Unused budget moved to next year |
Why commitments matter
Annual budget for cost centre IT: 1,200,000
Actual expenses posted to date: 800,000
Naive "available": 400,000 ← WRONG
Open purchase orders not yet received: 350,000
Purchase requisitions approved: 80,000
─────────────────────────────────────────────────────
Actually available: (30,000) ← already over
Without commitment tracking, budget control is fiction. By the time expenses post, the money is already spent. This is why the commitment is created at requisition or PO time, before any accounting entry exists.
Commitment lifecycle:
Purchase requisition approved → commitment created +80,000
Converted to purchase order → commitment moves to PO (reclassified)
Goods received → commitment reduced, actual created
Invoice posted → actual finalised
PO closed / deleted → residual commitment released
Commitments are stored in a separate line-item table, not in the GL, because they are not accounting transactions.
Availability control
The check that runs before a spending document can be saved.
Tolerance levels on a budget:
80% consumed → warning message, log it
95% consumed → warning + email to budget owner
100% consumed → hard error, document cannot be saved
110% consumed → error, requires budget supplement
Configuration decisions:
- What’s checked — requisitions, POs, invoices, journals, travel requests?
- At what level — cost centre, project, WBS element, fund, GL account group?
- Annual or periodic — can you use Q4’s budget in Q1?
- Who can override — and is the override logged and reported?
Public sector runs this hard. Government and university ERPs (Fund Management / Public Sector Management) treat exceeding budget as legally impermissible, not merely undesirable. Commercial companies usually run warnings only.
Budget dimensions
Budgets are set at some combination of:
Cost centre × GL account × period
Project / WBS element × cost element
Fund × functional area × grant × budget period ← public sector
Profit centre × product group
Sales region × sales rep ← revenue budgets
The finer the dimension, the more accurate the control and the more maintenance pain. Most companies budget at cost centre × account group × year, and release quarterly.
Planning processes
Top-down
Management sets the total; it cascades down with allocation rules. Fast, often unrealistic.
Bottom-up
Cost centre managers submit; finance consolidates. Realistic, slow, and everyone pads their numbers.
Iterative
Top-down targets, bottom-up detail, negotiated over rounds. What actually happens.
Rolling forecast
Instead of a fixed annual budget, re-forecast 12–18 months ahead every quarter. Better in volatile markets; requires much better tooling.
Zero-based budgeting
Every line justified from zero each cycle rather than “last year + 5%”. Effective at cutting cost, exhausting to run.
Driver-based planning
Model the business, not the accounts: headcount × average salary, units × material cost, revenue × commission rate. Changes to a driver flow through automatically. This is what modern planning tools (Anaplan, Board, SAP Analytics Cloud, Oracle EPM Planning) are built for, and it’s far better than typing numbers into account cells.
Budget versions
You always need more than one:
Version 0 — Actual
Version 1 — Original approved budget (frozen, never edited)
Version 2 — Current forecast
Version 3 — Best case
Version 4 — Worst case
Version 5 — Prior year actuals (for comparison)
Version 1 must be immutable after approval. “Budget vs actual” is meaningless if the budget quietly moved. If someone needs to change it, that’s a supplement — a tracked, approved delta, not an edit.
Variance reporting
Cost centre IT, March 2026
Budget Actual Variance % Commitment Available
Salaries 450,000 462,000 (12,000) (2.7%) 0 (12,000)
Software licences 120,000 95,000 25,000 20.8% 40,000 (15,000)
Hardware 80,000 31,000 49,000 61.3% 95,000 (46,000)
Travel 25,000 18,500 6,500 26.0% 2,000 4,500
Consulting 200,000 210,000 (10,000) (5.0%) 60,000 (70,000)
──────────────────────────────────────────────────────────────────────────────────
Total 875,000 816,500 58,500 6.7% 197,000 (138,500)
The “Actual” column looks like a 6.7% underspend. The “Available” column shows a 138,500 overrun. This is the report that justifies the whole commitment mechanism.
Sign convention: pick one (favourable positive, or actual-minus-budget) and apply it everywhere. Mixed conventions in one report are a guaranteed misreading.
Capital vs operating budgets
- OpEx budget — annual, by cost centre, hits P&L
- CapEx budget — per project/asset, multi-year, hits the balance sheet then depreciates
CapEx has its own approval flow (AFE — Authorisation for Expenditure, or capital appropriation request) with thresholds: under 50k the plant manager approves, over 5M the board does. The AFE is linked to an internal order or WBS that collects the cost and settles to an asset. See Fixed Assets.
Data model
budget_version(id, company_id, fiscal_year, code, name, status, locked_at)
budget_line(
version_id, company_id, fiscal_year, period, -- period NULL = annual
cost_center_id, gl_account_id, project_id, fund_id,
amount, currency,
UNIQUE(version_id, fiscal_year, period, cost_center_id, gl_account_id, project_id)
)
budget_change( -- supplements/returns, audited
id, version_id, budget_line_key, delta_amount,
reason, requested_by, approved_by, approved_at
)
commitment_line( -- not in the GL
id, company_id, source_type, -- PR | PO | CONTRACT | TRAVEL
source_document, source_line,
cost_center_id, gl_account_id, project_id,
fiscal_year, period,
amount_original, amount_open, currency,
status, -- OPEN | PARTIAL | CLOSED
created_at, closed_at
)
Availability check implementation
function checkAvailability(costObject, account, period, amount):
budget = sum(budget_line where matching, version = approved)
+ sum(budget_change approved)
actuals = sum(journal_line where matching)
commitments = sum(commitment_line.amount_open where matching)
available = budget - actuals - commitments
consumed_pct = (actuals + commitments + amount) / budget
if consumed_pct > hard_limit: return ERROR
if consumed_pct > warn_limit: return WARNING
return OK
Performance notes:
- This runs on every requisition, PO, and invoice save. It must be fast.
- Aggregating three tables on every check does not scale. Maintain a budget consumption summary per (cost object, account, period), updated transactionally, with a nightly rebuild-and-compare job.
- Watch for the classic race: two users each check “available = 10,000”, both save 8,000. Serialise the check and the write on the budget key, or accept overrun and detect it after the fact — a real design choice, and public sector must take the serialised option.
Practical rules
- Commitments or your budget control is decorative.
- Freeze the approved budget version. Changes go through tracked supplements.
- Warn early (80%), block late (100%) — blocking at 100% with no warning creates month-end chaos.
- Release annual budgets by period if seasonality matters, otherwise Q1 spends the year.
- Every override is logged and reported to the budget owner. An unlogged override is a control failure.
- Reconcile: commitments open in the budget system = open PO value in purchasing. They drift; a weekly check catches it.
- Close stale commitments. POs left open for years hold budget hostage. Run an ageing report and a closure process.