Skill125 repo starsupdated 2mo ago
frappe-core-workflow
>
Install in Claude Code
Copygit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-core-workflow && cp -r /tmp/frappe-core-workflow/skills/source/core/frappe-core-workflow ~/.claude/skills/frappe-core-workflowThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# Workflow Engine
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.
## Quick Reference
```
Workflow DocType → Defines the state machine for a specific DocType
├── states (child table) → Workflow Document State rows
│ ├── state → Link to Workflow State
│ ├── doc_status → 0 (Draft), 1 (Submitted), 2 (Cancelled)
│ ├── allow_edit → Role that can edit in this state
│ ├── update_field → Field to update when entering state
│ ├── update_value → Value to set (literal or expression)
│ └── next_action_email_template → Email Template link
└── transitions (child table) → Workflow Transition rows
├── state → Source state (Link to Workflow State)
├── action → Link to Workflow Action Master
├── next_state → Target state (Link to Workflow State)
├── allowed → Role that can perform this action
├── allow_self_approval → Check (default: 1)
├── condition → Python expression (optional)
└── transition_tasks → Link to Workflow Transition Tasks
```
### Key Fields on Workflow DocType
| Field | Type | Purpose |
|-------|------|---------|
| `workflow_name` | Data | Unique identifier |
| `document_type` | Link → DocType | Target DocType |
| `is_active` | Check | Only ONE workflow per DocType can be active |
| `workflow_state_field` | Data | Default: `workflow_state` |
| `override_status` | Check | Prevent workflow from overriding list view status |
| `send_email_alert` | Check | Email notifications with next possible actions |
## How the Engine Works
### 1. Activation and Field Creation
When a Workflow is saved with `is_active = 1`:
- All other workflows for the same DocType are deactivated automatically
- A hidden Custom Field (`workflow_state_field`, default `workflow_state`) is created on the target DocType if it does not exist
- The field is type `Link` to `Workflow State`, with `hidden=1`, `allow_on_submit=1`, `no_copy=1`
- Existing documents with empty workflow state get their state set based on their current `docstatus`
### 2. State Resolution
Every document under a workflow has a `workflow_state` field. The engine resolves available transitions by:
1. Reading current `workflow_state` from the document
2. Filtering `workflow.transitions` where `transition.state == current_state`
3. Filtering by user roles: `transition.allowed in frappe.get_roles()`
4. Evaluating `transition.condition` via `frappe.safe_eval()` (if set)
5. Returning matching transitions as available actions
### 3. Applying a Transition
When `apply_workflow(doc, action)` is called:
1. Load document from DB (fresh read)
2. Get available transitions for current user
3. Find transition matching the requested `action`
4. Check self-approval: blocked if `allow_self_approval=0` AND user is document owner
5. Set `workflow_state_field` to `transition.next_state`
6. If `update_field` is set on the target state, update that field
7. Execute transition tasks (sync first, then async via `frappe.enqueue`)
8. Handle docstatus change based on source/target state `doc_status` values
9. Save/Submit/Cancel document accordingly
10. Add workflow comment
## Workflow and DocStatus Interaction
**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.
### DocStatus Transition Rules
| Source doc_status | Target doc_status | Engine Action | Valid? |
|:-:|:-:|---|:-:|
| 0 (Draft) | 0 (Draft) | `doc.save()` | YES |
| 0 (Draft) | 1 (Submitted) | `doc.submit()` | YES |
| 1 (Submitted) | 1 (Submitted) | `doc.save()` | YES |
| 1 (Submitted) | 2 (Cancelled) | `doc.cancel()` | YES |
| 2 (Cancelled) | ANY | BLOCKED | NO |
| 1 (Submitted) | 0 (Draft) | BLOCKED | NO |
| 0 (Draft) | 2 (Cancelled) | BLOCKED | NO |
**ALWAYS** define your states so that docstatus only moves forward: 0→0, 0→1, 1→1, 1→2.
**NEVER** create a transition from a cancelled state or from submitted back to draft.
### Non-Submittable DocTypes
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.
## Workflow States
Workflow State is a separate DocType used as a master list. Each state has:
| Field | Purpose |
|-------|---------|
| `state` | Display name of the state |
| `style` | CSS class for badge display (Primary, Success, Warning, Danger, Info, Inverse) |
| `icon` | Font Awesome icon class |
### State Row Fields (Workflow Document State)
| Field | Purpose |
|-------|---------|
| `state` | Link to Workflow State |
| `doc_status` | Select: 0, 1, or 2 |
| `allow_edit` | Link to Role — ONLY this role can edit the document in this state |
| `update_field` | Field to update when document enters this state |
| `update_value` | Value to set (string or Python expression if `evaluate_as_expression=1`) |
| `is_optional_state` | Check — optional states are skipped in `get_next_possible_transitions` |
| `send_email` | Check (default 1) — send email notification on entering this state |
| `next_action_email_template` | Link to Email Template |
| `message` | Text message for the email notification |
## Workflow Transitions
Each transition row defines one possible action:
| Field | Purpose |
|-------|---------|
| `state` | Source state (MUST exist in states table) |
| `action` | Link to Workflow Action Master (e.g., "Approve", "Reject", "Review") |
| `next_state` | Target state (MUST exist in states table) |
| `allowed` | Link to Role — ONLY users with this role see this action |
| `allow_self_approval` | Check (default 1) — if 0, document owner cannot perform