You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: skills/source/core/frappe-core-workflow/SKILL.md
+215-4Lines changed: 215 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,16 +14,227 @@ metadata:
14
14
15
15
# Workflow Engine
16
16
17
-
> **STATUS**: Stub - content pending V2.5 development phase.
17
+
The Frappe Workflow engine is a state machine that controls document lifecycle through configurable states, transitions, and role-based permissions. It governs when and how documents change status, who can perform actions, and what side effects occur on each transition.
18
18
19
19
## Quick Reference
20
20
21
-
_Content to be developed._
21
+
```
22
+
Workflow DocType → Defines the state machine for a specific DocType
23
+
├── states (child table) → Workflow Document State rows
├── next_state → Target state (Link to Workflow State)
34
+
├── allowed → Role that can perform this action
35
+
├── allow_self_approval → Check (default: 1)
36
+
├── condition → Python expression (optional)
37
+
└── transition_tasks → Link to Workflow Transition Tasks
38
+
```
39
+
40
+
### Key Fields on Workflow DocType
41
+
42
+
| Field | Type | Purpose |
43
+
|-------|------|---------|
44
+
|`workflow_name`| Data | Unique identifier |
45
+
|`document_type`| Link → DocType | Target DocType |
46
+
|`is_active`| Check | Only ONE workflow per DocType can be active |
47
+
|`workflow_state_field`| Data | Default: `workflow_state`|
48
+
|`override_status`| Check | Prevent workflow from overriding list view status |
49
+
|`send_email_alert`| Check | Email notifications with next possible actions |
50
+
51
+
## How the Engine Works
52
+
53
+
### 1. Activation and Field Creation
54
+
55
+
When a Workflow is saved with `is_active = 1`:
56
+
- All other workflows for the same DocType are deactivated automatically
57
+
- A hidden Custom Field (`workflow_state_field`, default `workflow_state`) is created on the target DocType if it does not exist
58
+
- The field is type `Link` to `Workflow State`, with `hidden=1`, `allow_on_submit=1`, `no_copy=1`
59
+
- Existing documents with empty workflow state get their state set based on their current `docstatus`
60
+
61
+
### 2. State Resolution
62
+
63
+
Every document under a workflow has a `workflow_state` field. The engine resolves available transitions by:
64
+
65
+
1. Reading current `workflow_state` from the document
66
+
2. Filtering `workflow.transitions` where `transition.state == current_state`
67
+
3. Filtering by user roles: `transition.allowed in frappe.get_roles()`
68
+
4. Evaluating `transition.condition` via `frappe.safe_eval()` (if set)
69
+
5. Returning matching transitions as available actions
70
+
71
+
### 3. Applying a Transition
72
+
73
+
When `apply_workflow(doc, action)` is called:
74
+
75
+
1. Load document from DB (fresh read)
76
+
2. Get available transitions for current user
77
+
3. Find transition matching the requested `action`
78
+
4. Check self-approval: blocked if `allow_self_approval=0` AND user is document owner
79
+
5. Set `workflow_state_field` to `transition.next_state`
80
+
6. If `update_field` is set on the target state, update that field
81
+
7. Execute transition tasks (sync first, then async via `frappe.enqueue`)
82
+
8. Handle docstatus change based on source/target state `doc_status` values
83
+
9. Save/Submit/Cancel document accordingly
84
+
10. Add workflow comment
85
+
86
+
## Workflow and DocStatus Interaction
87
+
88
+
**CRITICAL**: The workflow engine controls docstatus transitions. You NEVER call `doc.submit()` or `doc.cancel()` directly on a workflow-controlled document. The workflow does it.
**ALWAYS** define your states so that docstatus only moves forward: 0→0, 0→1, 1→1, 1→2.
103
+
**NEVER** create a transition from a cancelled state or from submitted back to draft.
104
+
105
+
### Non-Submittable DocTypes
106
+
107
+
If the target DocType is NOT submittable, ALL states MUST have `doc_status = 0`. The engine validates this and throws an error if any state has `doc_status = 1` or `2` on a non-submittable DocType.
108
+
109
+
## Workflow States
110
+
111
+
Workflow State is a separate DocType used as a master list. Each state has:
112
+
113
+
| Field | Purpose |
114
+
|-------|---------|
115
+
|`state`| Display name of the state |
116
+
|`style`| CSS class for badge display (Primary, Success, Warning, Danger, Info, Inverse) |
117
+
|`icon`| Font Awesome icon class |
118
+
119
+
### State Row Fields (Workflow Document State)
120
+
121
+
| Field | Purpose |
122
+
|-------|---------|
123
+
|`state`| Link to Workflow State |
124
+
|`doc_status`| Select: 0, 1, or 2 |
125
+
|`allow_edit`| Link to Role — ONLY this role can edit the document in this state |
126
+
|`update_field`| Field to update when document enters this state |
127
+
|`update_value`| Value to set (string or Python expression if `evaluate_as_expression=1`) |
128
+
|`is_optional_state`| Check — optional states are skipped in `get_next_possible_transitions`|
129
+
|`send_email`| Check (default 1) — send email notification on entering this state |
130
+
|`next_action_email_template`| Link to Email Template |
131
+
|`message`| Text message for the email notification |
132
+
133
+
## Workflow Transitions
134
+
135
+
Each transition row defines one possible action:
136
+
137
+
| Field | Purpose |
138
+
|-------|---------|
139
+
|`state`| Source state (MUST exist in states table) |
140
+
|`action`| Link to Workflow Action Master (e.g., "Approve", "Reject", "Review") |
141
+
|`next_state`| Target state (MUST exist in states table) |
142
+
|`allowed`| Link to Role — ONLY users with this role see this action |
143
+
|`allow_self_approval`| Check (default 1) — if 0, document owner cannot perform this action |
144
+
|`condition`| Python expression evaluated with `frappe.safe_eval()`|
145
+
|`transition_tasks`| Link to Workflow Transition Tasks (v15+) |
146
+
147
+
### Condition Expressions
148
+
149
+
Conditions are Python expressions evaluated in a sandboxed environment. Available globals:
150
+
151
+
```python
152
+
# Available in condition expressions:
153
+
frappe.db.get_value(doctype, name, fieldname)
154
+
frappe.db.get_list(doctype, filters, fields)
155
+
frappe.session.user
156
+
frappe.session.roles # NOT available — use frappe.get_roles() outside conditions
157
+
frappe.utils.now_datetime()
158
+
frappe.utils.add_to_date(date, **kwargs)
159
+
frappe.utils.get_datetime(datetime_str)
160
+
frappe.utils.now()
161
+
doc.fieldname # Access any field on the document (as dict)
Simple DocType with just a `workflow_action_name` field. Common actions: Approve, Reject, Review, Send Back, Cancel. Create these first before defining transitions.
176
+
177
+
### Workflow Action DocType
178
+
179
+
Tracks pending actions for users. Created automatically when a document enters a state with outgoing transitions.
180
+
181
+
| Field | Purpose |
182
+
|-------|---------|
183
+
|`status`| Open or Completed |
184
+
|`reference_doctype`| The DocType of the document |
185
+
|`reference_name`| The document name |
186
+
|`workflow_state`| Current workflow state |
187
+
|`user`| Assigned user |
188
+
|`permitted_roles`| Table MultiSelect of roles that can act |
189
+
|`completed_by`| User who completed the action |
190
+
|`completed_by_role`| Role used to complete |
191
+
192
+
Workflow Actions appear in the user's "Workflow Action" list and can be acted on via email links.
193
+
194
+
## Self-Approval Control
195
+
196
+
```python
197
+
defhas_approval_access(user, doc, transition):
198
+
return (user =="Administrator"
199
+
or transition.get("allow_self_approval")
200
+
or user != doc.get("owner"))
201
+
```
202
+
203
+
-**Administrator** ALWAYS has approval access regardless of settings
204
+
- If `allow_self_approval = 1` (default): document owner CAN approve
205
+
- If `allow_self_approval = 0`: document owner CANNOT approve their own document
22
206
23
207
## Decision Tree
24
208
25
-
_Content to be developed._
209
+
```
210
+
Need workflow on a DocType?
211
+
├── Is DocType submittable?
212
+
│ ├── YES → States can use doc_status 0, 1, 2
213
+
│ └── NO → ALL states MUST have doc_status = 0
214
+
├── Define states → Create Workflow State records first
215
+
├── Define transitions → Need Workflow Action Master records first
216
+
├── Who can edit in each state? → Set allow_edit per state
217
+
├── Need conditional transitions?
218
+
│ └── Use Python expressions with doc.field access
219
+
├── Need to block self-approval?
220
+
│ └── Set allow_self_approval = 0 on specific transitions
221
+
└── Need email notifications?
222
+
└── Set send_email_alert on Workflow + email templates on states
223
+
```
224
+
225
+
## Common Errors
226
+
227
+
| Error | Cause | Fix |
228
+
|-------|-------|-----|
229
+
|`WorkflowStateError`| Document has no workflow_state set | Ensure workflow sets initial state on creation |
230
+
|`WorkflowTransitionError`| Action not valid for current state/role | Verify transitions table covers all needed paths |
231
+
|`WorkflowPermissionError`| User lacks role for transition, or self-approval blocked | Check `allowed` role and `allow_self_approval`|
0 commit comments