Amount-based approval is the single most common workflow request I get, and also the one people most reliably get wrong on the first attempt — not because the Workflow engine can't express it, but because the amount that should gate approval is rarely the field people first reach for. This post is the pattern I actually use, with the currency and rounding traps that show up once real transactions start flowing.
Pick the right amount field first
Every AP/AR/SO document carries several amount-shaped fields, and they are not interchangeable for approval logic: CuryDocBal (outstanding balance, moves as payments apply — wrong for approval, since an already-approved invoice would re-trigger as it gets paid down), CuryOrigDocAmt (the original document total in document currency — usually right), and DocTotal or OrderTotal in the base/company currency (right when you need one threshold across multi-currency documents, since a KES 500,000 order and a USD 5,000 order should probably hit the same approval bar in a multi-branch, multi-currency tenant).
public static void Configure(WorkflowContext<APInvoiceEntry, APInvoice> context)
{
var graph = context.Graph;
graph.WithTransitions(transitions => transitions
.AddGroupFrom<APInvoice.status.Balanced>(g => g
.Add(t => t
.To<APInvoice.status.PendingApproval>()
.IsTriggeredOn(a => a.Actions.Release)
// base-currency total, NOT CuryDocBal
.When(APInvoice.docTotal.IsGreaterEqual(500000m)))
.Add(t => t
.To<APInvoice.status.Open>()
.IsTriggeredOn(a => a.Actions.Release)
.When(APInvoice.docTotal.IsLess(500000m)))));
}
Tiered thresholds, not a single cutoff
"Approval above $5,000" is the requirement as first stated and almost never the requirement as actually needed — real approval matrices are tiered: a supervisor approves up to $5,000, a controller up to $25,000, the CFO above that. Model this as a chain of transitions into different pending-approval states, each carrying its own approval map assignment, rather than one state with a single approver list and a mental "well actually check the amount again" step for the human approving it:
.Add(t => t.To<APInvoice.status.PendingApprovalTier1>()
.IsTriggeredOn(a => a.Actions.Release)
.When(APInvoice.docTotal.IsGreaterEqual(5000m)
& APInvoice.docTotal.IsLess(25000m)))
.Add(t => t.To<APInvoice.status.PendingApprovalTier2>()
.IsTriggeredOn(a => a.Actions.Release)
.When(APInvoice.docTotal.IsGreaterEqual(25000m)))
Each tier state then gets its own row on the Assignment/Approval Maps screen (EP503010) with a distinct approver — a workgroup, a role, or a rule keyed off the vendor's owner. Keeping the states distinct also means your reporting on "what's stuck in tier-2 approval" is a plain status filter instead of a computed field.
Boundary rounding: decide it explicitly
Is an invoice for exactly $5,000.00 tier 1 or tier 2? IsGreaterEqual versus IsGreater on the boundary condition is a business decision, not a technical one — write it down in the requirements doc and pick consistently, because I have seen two adjacent tier conditions both use strict IsGreater at the same boundary, leaving a document at exactly the threshold matching neither transition and getting stuck with no eligible outbound transition at all. That failure mode is silent: the document just sits there with a Release button that does nothing, because no transition's condition matched.
Whenever you build adjacent numeric tiers, verify the boundaries are complementary — one tier's upper bound should be exactly the next tier's lower bound, using consistent >= / < pairing. Test the exact boundary value, not just values comfortably inside each tier; that's where gaps hide.
Wiring the approval map itself
The transition gets you into a pending-approval state; the Assignment and Approval Maps screen decides who has to act. A map row references the graph, the specific status, and an assignment rule — by employee, by role, or (the one clients actually want) by a condition on the document itself, such as "the vendor's assigned buyer" resolved through a relation to EPEmployee. Multiple approvers on one map row can be sequential or require-all; that's a parallel-approval concern I cover separately, but for a straight amount ladder, one approver per tier is usually the whole requirement.
Wrapping up
Amount-based approval done well is three decisions made deliberately: which amount field (base currency total, not a balance that moves), how many tiers (distinct states, not one state with fuzzy logic), and boundary inclusivity (verified with an exact-value test, not eyeballed). Get those three right and the approval map configuration on EP503010 is almost mechanical.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.