Acumatica Workflow — Credit Memo Approval is one of the most common workflow customisations in Acumatica. Every business has a process that does not fit the standard approval map, and the workflow engine is the right place to model it. The trap is that the engine has a lot of moving parts — states, transitions, conditions, handlers, business events, approval groups — and the patterns are not always obvious. This guide walks through the canonical pattern for the scenario, then the variations you will see in the wild.
What the workflow needs to do
The workflow exists to enforce a process. The process has three things: a trigger (something happens), a decision (some condition is checked), and an action (a state change or a notification). The workflow engine implements all three.
Before you write any code, draw the state diagram. Each state is a node, each transition is an arrow, each arrow has a label (the condition) and a side effect (the action). If you cannot draw it on a single page, the workflow is too complex and should be split.
The states
For most approval workflows, the states are:
| State | Code | Meaning |
|---|---|---|
| Draft | D | Created but not submitted |
| Pending Approval | P | Waiting for the approver |
| Approved | A | Approval received |
| Rejected | R | Approval denied |
| Released | E | Posted to the sub-ledger |
Some workflows add a Held state (waiting for additional information) or split the approval into multiple sequential or parallel groups. The pattern is the same; just add the state and the transitions.
The transitions
Each transition has a source state, a target state, a condition, and a handler. The condition is the gate (who can do this); the handler is the work (what happens).
[PXWorkflowDependsOnType(typeof(APInvoice))]
public class APInvoiceEntryWF : PXGraphExtension<APInvoiceEntry>
{
public sealed class States : PX.Data.PXWorkflowStatusAttribute
{
public const string Draft = "D";
public const string PendingApproval = "P";
public const string Approved = "A";
public const string Rejected = "R";
public const string Released = "E";
}
public static class Conditions
{
public static bool RequiresManagerApproval(APInvoice doc)
=> doc?.CuryOrderTotal > 10000m;
}
public sealed class Handlers
{
[PXWorkflowHandler]
public static IEnumerable<APInvoice> SubmitForApproval(
PXGraph graph, APInvoice entity)
{
entity.Status = States.PendingApproval;
yield return entity;
}
}
}
For the broader workflow patterns, see the workflow engine definitive guide.
Approval groups
For multi-approver scenarios, configure approval groups on the Employee screen first, then reference them in the workflow. The patterns:
| Pattern | When | Configuration |
|---|---|---|
| Single approver | Manager signs off | One group, one approver |
| Sequential | Manager, then director | Two groups, ordered |
| Parallel | All departments must sign off | Multiple groups, AND logic |
| Threshold-based | Under $5k auto, over $5k manager | Two workflows, condition-gated |
A workflow that goes through a sequential step and then branches into parallel groups is the most common source of "stuck approvals" tickets. Pick one pattern per workflow; do not mix.
For the parallel approval pattern specifically, see the parallel approvals guide.
Email notifications
A workflow that does not notify the next approver stalls. The pattern is Acumatica Business Events: configure the trigger (state change), the filter (only fire for this transition), and the subscriber (email, push, webhook).
// Business Event setup
// Screen: APInvoice
// Event: Status changes to PendingApproval
// Filter: CuryOrderTotal > 10000
// Subscriber: Email to employee with the approver role
For the full notification pattern, see the automated actions and emails guide.
Conditional assignment
Static approval maps are fine until your business has more than one rule. Dynamic assignment — "if amount > X, route to the CFO; if customer class is VIP, route to the relationship manager" — is what you actually need.
protected void _(Events.RowPersisting<APInvoice> e)
{
var row = e.Row;
if (row == null) return;
if (row.CuryOrderTotal > 100000m)
row.ApproverID = CfoEmployeeID;
else if (row.CustomerClassID == "VIP")
row.ApproverID = RelationshipManagerID;
else
row.ApproverID = DefaultManagerID;
}
For the full pattern, see the conditional assignment guide.
Testing the workflow
Workflows are deceptively hard to test because they involve time (the approver's response) and human judgment (the rejection reason). The minimum coverage:
- Submit a document that meets the condition. Verify it transitions to Pending Approval.
- Approve as the assigned approver. Verify it transitions to Approved.
- Reject as the assigned approver. Verify it transitions to Rejected and the reason is captured.
- Submit a document that does not meet the condition. Verify it auto-approves (or whatever the alternative is).
- Verify the email notifications fire.
[Test]
public void SubmitForApproval_TriggersTransition()
{
var graph = PXGraph.CreateInstance<APInvoiceEntry>();
var doc = new APInvoice { CuryOrderTotal = 50000m };
graph.Document.Insert(doc);
graph.Actions.PressSave();
PXAutomation.GetStep<APInvoice>(graph, doc, "Submit", "PendingApproval").Invoke();
var refreshed = (APInvoice)graph.Document.Search<APInvoice.refNbr>(doc.RefNbr);
Assert.AreEqual("P", refreshed.Status);
}
For the full unit testing pattern, see the unit test framework guide.
Common pitfalls
The pitfalls I see most often, in order of frequency:
- Forgetting the initial state. A record that enters the workflow without a state gets stuck. Always set the status field to the initial state on insert.
- Mixing the condition and the handler. The condition is a one-liner; the handler is the work. If the condition does more than a boolean check, refactor.
- Not testing with restricted users. A workflow that exposes "approve" to a user with no approval rights is a leak. Test with restricted users.
- Hard-coded approver IDs. Approver IDs are tenant-specific. Use roles, not literal IDs.
- Forgetting to publish both the workflow and the graph. The workflow is part of the customisation package. Publish both as a unit.
For the upgrade survival playbook, see the customisation upgrade survival guide.
Wrapping up
The pattern is: state diagram on paper, states in code, transitions with conditions and handlers, approval groups configured, business events wired, tests written, restricted users verified. Do all six and the workflow is solid. Skip any one and you are debugging a stuck-approval ticket six months from now.