TLDR
The order of workflow transitions matters. The transition with the higher idx (later in the list) has higher priority and will always win when multiple transitions match.
Use Case
For our Purchase Order workflow, we had two requirements:
- If a Purchase User submits a PO, the state should become
Pending Approval - If a Purchase Manager submits a PO, the state should go directly to
Submitted
On our first try, we added transitions in this order:
- Draft -> Submitted (Allowed: Purchase Manager)
- Draft -> Pending Approval (Allowed: Purchase User)
We then noticed a problem: when a user with both the Purchase User and Purchase Manager roles submitted a PO, the document transitioned to Pending Approval instead of Submitted. That was wrong - anyone with the Purchase Manager role should bypass the approval step entirely.
So we dug a little deeper to understand why.
The Cause
It's not really a bug - it's just the way Frappe works.
The code lives in frappe/model/workflow.py, inside apply_workflow. After collecting all transitions the current user is eligible for, it picks the one matching the clicked action like this:
# find the transition
transition = None
for t in transitions:
if t.action == action:
transition = t # ← last match overwritesThere's no break in the loop. Every matching transition overwrites the previous one, so the last eligible transition in the list wins.
get_transitions builds that list by iterating workflow.transitions in idx order:
for transition in workflow.transitions:
if transition.state == current_state and transition.allowed in roles:
if not is_transition_condition_satisfied(transition, doc):
continue
transitions.append(transition.as_dict())So whichever transition appears later in the list (higher idx) is the one that gets applied - regardless of role hierarchy.
The Fix
Reorder your transitions so that the more specific or higher-priority one comes last. In our case, we simply swapped the order:
- Draft -> Pending Approval (Allowed: Purchase User)
- Draft -> Submitted (Allowed: Purchase Manager)
Now when a Purchase Manager submits a PO, both transitions match - but since "Draft -> Submitted" is last in the list, it wins. The general rule: put your most privileged or specific transitions at the bottom.