# ERP Approval Matrix Design: Stop Hardcoding Approval Chains

> How to design a configurable ERP approval matrix — amount bands, roles, departments — so finance can change approvers without a deployment. Lessons from a real build.

URL: https://www.matthewswong.com/en/blog/erp-approval-matrix-configuration/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2026-04-16
- Updated: 2026-04-16
- Category: ERP

The first approval feature I shipped for ANCoraPRO, the ERP I built for a real client on React, NestJS, and PostgreSQL, had the approval chain written straight into the service code: supervisor, then finance manager, then director. It worked perfectly for exactly four months — until the client restructured, added a procurement department, and wanted invoices above a certain amount to need two finance signatures. Every one of those changes was a code deployment.

The rewrite taught me the lesson this post is about: approval logic is business configuration, not application code. The deliverable is an approval matrix — rules stored as data, keyed on amount bands, document types, roles, and departments — that an admin can edit from a screen. Here is how I design that now, schema included, plus the edge cases that will find you in production whether you design for them or not.

## Why Hardcoded Chains Always Lose

Hardcoding feels faster on week one, and for a two-step chain it genuinely is. But organizations change approvers far more often than developers expect — promotions, resignations, restructures, audit findings, new spending policies. Compare the two approaches honestly and the matrix wins everywhere except initial effort:

Aspect

Hardcoded chain

Configurable matrix

Changing an approver or threshold

Code change, review, deployment — days, and a developer in the loop forever.

Admin edits a rule row on a screen — minutes, finance owns it.

Organizational restructure

Rewrite of every affected branch; high regression risk in untouched flows.

Deactivate old rules, insert new ones with a validity date; old documents keep their history.

Audit answering — who could approve what, when

Read git history and pray the deploy dates line up with the question.

Query rule rows with valid_from and valid_until; the answer is data.

Initial build effort

Low — an if-chain and two role checks.

Higher — rule tables, resolution engine, admin UI. Pays back on the first reorg.

## The Four Dimensions of a Real Approval Matrix

Every approval requirement I have gathered from finance teams decomposes into four dimensions. Model these explicitly and almost any policy fits without schema changes:

## Document type

Purchase orders, AP invoices, expense claims, leave requests. Each type gets its own rule set — a PO matrix and an expense matrix rarely share thresholds.

## Amount band

Half-open ranges in IDR: zero up to 10 million, 10 to 100 million, 100 million and above. Half-open intervals (min inclusive, max exclusive) prevent the classic boundary double-match.

## Department or cost center

Procurement approves differently than marketing. A NULL department on a rule means it applies company-wide, and specific-department rules override it.

## Approver step definition

Each matching rule yields ordered steps. A step points to a role, a named user, or a relationship like manager-of-requester, plus a quorum for N-of-M approvals.

Microsoft's Dynamics 365 workflow documentation describes exactly this shape in its expense report example: a 7,000 dollar report routes through two approvers, an 11,000 dollar one adds a third. The thresholds and participants are workflow configuration, not compiled logic — that is the standard the big systems set, and a custom ERP should not do worse.

## A Schema That Holds Up: Rules, Steps, and Resolution

Two tables carry the whole design: approval_rule holds the matching dimensions, approval_rule_step holds the ordered approvers for each rule. Resolution picks the most specific active rule — department-specific beats company-wide, then explicit priority breaks ties:

The resolution query runs once, at submission time. That moment matters: the engine snapshots the resolved steps into an approval_instance table attached to the document. The document then works through its own frozen copy of the chain. If an admin edits the matrix tomorrow, in-flight documents are untouched — only new submissions see the new rules.

Pro tip: snapshot-at-submission is the single most important behavior to get right. The alternative — re-resolving rules on every approval click — means an admin edit can silently re-route documents that are mid-chain, and your audit trail can no longer explain why a document went to whoever it went to.

## The Edge Cases That Will Find You

These five scenarios are not exotic. Each one appeared within the first year of my ERP running in production, and each needs an explicit design decision:

Approver is also the requester — a manager submitting their own purchase request must not self-approve. Skip to the next step or escalate one level up; pick one policy and enforce it in the engine.

Absent approvers — people go on leave. You need delegation with an expiry date, set by the approver or by an admin, and the audit trail must record acted-as-delegate, not impersonation.

Overlapping rules — two rules match the same document. Deterministic ordering (specificity, then priority, then lowest id) plus an admin-screen warning when a new rule overlaps an existing one.

No rule matches — a document type and amount combination nobody configured. Fail closed: block submission with a clear message, and alert the admin. Auto-approving on a configuration gap is how unauthorized spending happens.

Amount edited mid-flight — an invoice corrected upward may now belong in a higher band. The safe policy: any change to the amount voids current approvals and re-resolves from step one.

Whatever you decide for each edge case, write the decision into the engine as code and into the client's SOP as one sentence each. The expensive failure mode is not picking a wrong policy — it is two screens silently implementing different policies.

## Rolling Out a Matrix Without Chaos

Migrating a live ERP from hardcoded chains to a matrix is a data migration plus a behavior change, and the sequencing matters:

Transcribe the current hardcoded behavior into rule rows first, verbatim — same approvers, same thresholds. Ship the engine running the same policy as before.

Run dual-resolution in staging: for two weeks of real documents, log what the old code and the new engine each would have decided, and diff the outputs to zero.

Cut over with feature-flag scope: one document type first (expenses are lowest risk), then POs, then AP invoices.

Hand finance the admin screen only after cutover is stable, with rule changes requiring a second admin to activate — four-eyes on the matrix itself.

Schedule a quarterly matrix review with finance. Stale rules pointing at departed employees are the matrix equivalent of dead code.

## The Takeaway

An approval matrix is the difference between an ERP the client operates and an ERP the developer babysits. Rules as data, snapshot at submission, fail-closed resolution, and delegation with expiry — those four decisions cover ninety percent of what finance will ever ask for, and they turn org-chart churn from a deployment into a form edit.

If you are starting a NestJS build today: resist the if-statement. The two-table schema above costs maybe a week including the admin screen. The hardcoded version costs a week too — just spread over every reorganization for the life of the system.

## Sources and further reading

- Microsoft Learn — Workflow System Overview in Dynamics 365 Finance and Operations
- Camunda Documentation — DMN Decision Table Reference

> **Key takeaway:** The first approval feature shipped for ANCoraPRO had the chain written straight into the service code: supervisor, then manager, then finance. Every new rule then needed a deployment. Moving the chain into a configurable matrix turns an approval change from an engineering task into an administrative one.

## Source links

- https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/organization-administration/overview-workflow-system
- https://docs.camunda.org/manual/latest/reference/dmn/decision-table/

## FAQ

### Why is hardcoding approval chains in service code a problem?

Hardcoded chains require a code change, review, and deployment every time an approver or threshold changes — a process that takes days and keeps developers in the loop indefinitely. Organizations change approvers far more often than developers expect, through promotions, resignations, restructures, and new spending policies. A configurable matrix stores rules as data, letting finance admins make changes in minutes without a deployment.

### What are the four dimensions every approval rule should model?

The four dimensions are document type (POs, AP invoices, expense claims, etc.), amount band (half-open ranges in IDR to prevent boundary double-matches), department or cost center (with a NULL department meaning company-wide), and approver step definition (ordered steps pointing to a role, named user, or relationship like manager-of-requester, with an optional N-of-M quorum). Modelling all four explicitly means almost any finance policy fits without schema changes.

### Why must the approval chain be snapshotted at submission time rather than resolved on each approval click?

Snapshotting at submission freezes each document's approval steps into an approval_instance table so that in-flight documents are never affected by later matrix edits. Re-resolving on every click means an admin change can silently re-route a document mid-chain, and the audit trail can no longer explain which rules were in effect when each step was approved. Snapshot-at-submission is described in the post as the single most important behavior to get right.

### How should a system handle a document whose amount is edited mid-flight?

Any change to the document amount may move it into a different amount band, so the safe policy is to void all current approvals and re-resolve the chain from step one. This ensures the correct approvers for the new amount are always applied and prevents a higher-value document from slipping through a lower-threshold chain. The post recommends picking one explicit policy, coding it into the engine, and documenting it in the client's SOP.

### What is the recommended rollout sequence when migrating a live ERP from hardcoded chains to a configurable matrix?

First, transcribe the existing hardcoded behavior verbatim into rule rows and ship the new engine running the same policy as before. Then run dual-resolution in staging for two weeks of real documents, diffing the old and new outputs to zero before cutting over. Cut over one document type at a time behind a feature flag — starting with expenses as lowest risk — and hand the admin screen to finance only after cutover is stable, requiring a second admin to activate any rule changes.

---

- HTML version: https://www.matthewswong.com/en/blog/erp-approval-matrix-configuration/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/erp-approval-matrix-configuration/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
