Approval Workflow Tutorial
Build a complete approval workflow with amount-based routing and approve/reject buttons.
Scenario
Business Requirement:
- Purchase Orders over $5,000 require manager approval
- POs over $25,000 require director approval
- Approvers should see Approve/Reject buttons
- Email notifications at each step
Final Workflow Diagram
PURCHASE ORDER APPROVAL WORKFLOW
═══════════════════════════════════════════════════════════════════════════════
TRIGGER: On Create (Amount ≥ $5,000)
┌───────────────────┐
│ DRAFT │
│ │
│ Entry: Set Status │
┌─────────────────└─────────┬─────────┘─────────────────┐
│ │ │
│ [Submit Button] │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ PENDING MANAGER │ │
│ │ │ │
│ │ Entry: │ │
│ │ • Lock Record │ │
│ │ • Email Manager │ │
│ └──────────┬─────────┬────────────┘ │
│ │ │ │
│ [Approve] [Reject] │
│ │ │ │
│ ┌──────────────┘ └────────────────┐ │
│ │ │ │
│ ▼ ▼ │
┌───────────────────────┐ ┌─────────────────────┐
│ PENDING DIRECTOR │ │ REJECTED │
│ (if amount > $25,000) │ │ │
│ │ │ Entry: │
│ Entry: │ │ • Set Status │
│ • Email Director │ │ • Unlock Record │
└───────────┬───────────┘ │ • Email Submitter │
│ │ │
[Approve] │ [Resubmit] → DRAFT │
│ └─────────────────────┘
▼
┌─────────────────────┐
│ APPROVED │
│ │
│ Entry: │
│ • Set Status │
│ • Set Approved By │
│ • Unlock Record │
│ • Email Submitter │
│ │
│ (Final State) │
└─────────────────────┘
Step 1: Create the Workflow
Navigation: Customization → Workflow → Workflows → New
WORKFLOW BASIC INFO
═══════════════════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────────────────────┐
│ Name: Purchase Order Approval │
│ ID: custworkflow_po_approval │
│ │
│ Record Type: Purchase Order │
│ Sub Types: (leave blank for all) │
│ │
│ Owner: Procurement Team │
│ Description: Routes POs for approval based on amount thresholds │
│ │
│ Release Status: Testing (change to Released when ready) │
└─────────────────────────────────────────────────────────────────────────────┘
INITIATION:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Initiate On: ☑ Create │
│ ☐ Update │
│ ☐ View/Update │
│ │
│ Initiation Condition: │
│ Formula: {total} >= 5000 │
│ │
│ (Only POs $5,000 and above enter this workflow) │
└─────────────────────────────────────────────────────────────────────────────┘
Step 2: Create Workflow States
State 1: Draft
STATE: DRAFT
═══════════════════════════════════════════════════════════════════════════════
Name: Draft
Description: Initial state for new POs
Entry Actions:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Set Field Value │
│ Field: custbody_approval_status │
│ Value: Draft │
└─────────────────────────────────────────────────────────────────────────────┘
State 2: Pending Manager Approval
STATE: PENDING MANAGER APPROVAL
═══════════════════════════════════════════════════════════════════════════════
Name: Pending Manager Approval
Description: Awaiting manager review
Entry Actions:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Set Field Value │
│ Field: custbody_approval_status │
│ Value: Pending Manager Approval │
│ │
│ 2. Lock Record │
│ (Prevents edits while pending) │
│ │
│ 3. Send Email │
│ Recipient: custbody_approver (or role-based) │
│ Template: PO Approval Request │
│ Subject: PO ${record.tranid} requires your approval │
└─────────────────────────────────────────────────────────────────────────────┘
State 3: Pending Director Approval
STATE: PENDING DIRECTOR APPROVAL
═══════════════════════════════════════════════════════════════════════════════
Name: Pending Director Approval
Description: High-value POs awaiting director review
Entry Actions:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Set Field Value │
│ Field: custbody_approval_status │
│ Value: Pending Director Approval │
│ │
│ 2. Send Email │
│ Recipient: (Director role or specific employee) │
│ Template: High Value PO Approval Request │
│ Subject: HIGH VALUE: PO ${record.tranid} ($${record.total}) │
└─────────────────────────────────────────────────────────────────────────────┘
State 4: Approved (Final)
STATE: APPROVED
═══════════════════════════════════════════════════════════════════════════════
Name: Approved
Description: PO has been approved
Entry Actions:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Set Field Value │
│ Field: custbody_approval_status │
│ Value: Approved │
│ │
│ 2. Set Field Value │
│ Field: custbody_approved_by │
│ Value: {user} (Current User) │
│ │
│ 3. Set Field Value │
│ Field: custbody_approved_date │
│ Value: {today} │
│ │
│ 4. Unlock Record │
│ │
│ 5. Send Email │
│ Recipient: {employee} (Submitter) │
│ Subject: PO ${record.tranid} has been APPROVED │
└─────────────────────────────────────────────────────────────────────────────┘
State 5: Rejected
STATE: REJECTED
═══════════════════════════════════════════════════════════════════════════════
Name: Rejected
Description: PO was rejected
Entry Actions:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Set Field Value │
│ Field: custbody_approval_status │
│ Value: Rejected │
│ │
│ 2. Unlock Record │
│ (Allow changes for resubmission) │
│ │
│ 3. Send Email │
│ Recipient: {employee} (Submitter) │
│ Subject: PO ${record.tranid} has been REJECTED │
│ Body: Include rejection reason: ${record.custbody_reject_reason} │
└─────────────────────────────────────────────────────────────────────────────┘
Step 3: Create Transitions
Submit for Approval (Draft → Pending Manager)
TRANSITION: SUBMIT FOR APPROVAL
═══════════════════════════════════════════════════════════════════════════════
From State: Draft
To State: Pending Manager Approval
Trigger: Button
Button Label: Submit for Approval
Condition: (none - always available in Draft)
Manager Approves (Pending Manager → Approved or Director)
TRANSITION: MANAGER APPROVE
═══════════════════════════════════════════════════════════════════════════════
From State: Pending Manager Approval
To State: *** CONDITIONAL ***
Trigger: Button
Button Label: Approve
CONDITION-BASED ROUTING:
┌─────────────────────────────────────────────────────────────────────────────┐
│ If {total} > 25000: │
│ → Go to "Pending Director Approval" │
│ │
│ Else: │
│ → Go to "Approved" │
│ │
│ This requires TWO separate transitions with conditions: │
│ │
│ Transition 1: "Approve (High Value)" │
│ To State: Pending Director Approval │
│ Condition: {total} > 25000 │
│ │
│ Transition 2: "Approve (Standard)" │
│ To State: Approved │
│ Condition: {total} <= 25000 │
└─────────────────────────────────────────────────────────────────────────────┘
Manager Rejects
TRANSITION: MANAGER REJECT
═══════════════════════════════════════════════════════════════════════════════
From State: Pending Manager Approval
To State: Rejected
Trigger: Button
Button Label: Reject
Transition Actions:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Show Message │
│ Type: Information │
│ Text: "Please enter a rejection reason in the notes field." │
└─────────────────────────────────────────────────────────────────────────────┘
Director Approves
TRANSITION: DIRECTOR APPROVE
═══════════════════════════════════════════════════════════════════════════════
From State: Pending Director Approval
To State: Approved
Trigger: Button
Button Label: Director Approve
Resubmit (Rejected → Draft)
TRANSITION: RESUBMIT
═══════════════════════════════════════════════════════════════════════════════
From State: Rejected
To State: Draft
Trigger: Button
Button Label: Resubmit
Step 4: Required Custom Fields
Create these fields before deploying the workflow:
CUSTOM FIELDS FOR APPROVAL WORKFLOW
═══════════════════════════════════════════════════════════════════════════════
1. Approval Status (custbody_approval_status)
Type: List (Custom List)
List Values: Draft, Pending Manager, Pending Director, Approved, Rejected
Display: Inline Text (read-only)
2. Approved By (custbody_approved_by)
Type: List/Record (Employee)
Display: Inline Text
3. Approved Date (custbody_approved_date)
Type: Date
Display: Inline Text
4. Rejection Reason (custbody_reject_reason)
Type: Text Area
Display: Normal (editable when rejected)
5. Approver (custbody_approver) - Optional
Type: List/Record (Employee)
Purpose: Allow submitter to specify approver
Step 5: SDF Object Definition
<!-- File: src/Objects/Workflows/custworkflow_po_approval.xml -->
<workflow scriptid="custworkflow_po_approval">
<name>Purchase Order Approval</name>
<recordtypes>
<recordtype>PURCHORD</recordtype>
</recordtypes>
<initiateon>CREATE</initiateon>
<initiateconditionformula>{total} >= 5000</initiateconditionformula>
<releasestatus>RELEASED</releasestatus>
<!-- State: Draft -->
<workflowstates>
<workflowstate scriptid="workflowstate_draft">
<name>Draft</name>
<workflowactions triggertype="ENTRY">
<setfieldvalueaction>
<field>custbody_approval_status</field>
<valuetext>Draft</valuetext>
</setfieldvalueaction>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="workflowtransition_submit">
<targetstate>workflowstate_pending_mgr</targetstate>
<buttonlabel>Submit for Approval</buttonlabel>
<triggertype>BUTTON</triggertype>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: Pending Manager -->
<workflowstate scriptid="workflowstate_pending_mgr">
<name>Pending Manager Approval</name>
<workflowactions triggertype="ENTRY">
<setfieldvalueaction>
<field>custbody_approval_status</field>
<valuetext>Pending Manager</valuetext>
</setfieldvalueaction>
<lockrecordaction/>
<sendemailaction>
<recipientfield>custbody_approver</recipientfield>
<subject>PO Requires Approval: ${tranid}</subject>
</sendemailaction>
</workflowactions>
<workflowtransitions>
<!-- Approve (routes to director if high value) -->
<workflowtransition scriptid="workflowtransition_approve_high">
<targetstate>workflowstate_pending_dir</targetstate>
<buttonlabel>Approve</buttonlabel>
<triggertype>BUTTON</triggertype>
<conditionformula>{total} > 25000</conditionformula>
</workflowtransition>
<workflowtransition scriptid="workflowtransition_approve_std">
<targetstate>workflowstate_approved</targetstate>
<buttonlabel>Approve</buttonlabel>
<triggertype>BUTTON</triggertype>
<conditionformula>{total} <= 25000</conditionformula>
</workflowtransition>
<workflowtransition scriptid="workflowtransition_reject">
<targetstate>workflowstate_rejected</targetstate>
<buttonlabel>Reject</buttonlabel>
<triggertype>BUTTON</triggertype>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: Pending Director -->
<workflowstate scriptid="workflowstate_pending_dir">
<name>Pending Director Approval</name>
<workflowactions triggertype="ENTRY">
<setfieldvalueaction>
<field>custbody_approval_status</field>
<valuetext>Pending Director</valuetext>
</setfieldvalueaction>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="workflowtransition_dir_approve">
<targetstate>workflowstate_approved</targetstate>
<buttonlabel>Director Approve</buttonlabel>
<triggertype>BUTTON</triggertype>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: Approved (Final) -->
<workflowstate scriptid="workflowstate_approved">
<name>Approved</name>
<workflowactions triggertype="ENTRY">
<setfieldvalueaction>
<field>custbody_approval_status</field>
<valuetext>Approved</valuetext>
</setfieldvalueaction>
<setfieldvalueaction>
<field>custbody_approved_by</field>
<valuetype>DYNAMIC</valuetype>
<valuebuiltin>USER</valuebuiltin>
</setfieldvalueaction>
<setfieldvalueaction>
<field>custbody_approved_date</field>
<valuetype>DYNAMIC</valuetype>
<valuebuiltin>TODAY</valuebuiltin>
</setfieldvalueaction>
<unlockrecordaction/>
</workflowactions>
</workflowstate>
<!-- State: Rejected -->
<workflowstate scriptid="workflowstate_rejected">
<name>Rejected</name>
<workflowactions triggertype="ENTRY">
<setfieldvalueaction>
<field>custbody_approval_status</field>
<valuetext>Rejected</valuetext>
</setfieldvalueaction>
<unlockrecordaction/>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="workflowtransition_resubmit">
<targetstate>workflowstate_draft</targetstate>
<buttonlabel>Resubmit</buttonlabel>
<triggertype>BUTTON</triggertype>
</workflowtransition>
</workflowtransitions>
</workflowstate>
</workflowstates>
</workflow>
Testing Your Workflow
WORKFLOW TESTING CHECKLIST
═══════════════════════════════════════════════════════════════════════════════
1. Set workflow to "Testing" status
2. Test Standard Approval ($5,000 - $25,000):
□ Create PO with total = $10,000
□ Verify workflow initiates
□ Click "Submit for Approval"
□ Verify email sent to manager
□ Click "Approve"
□ Verify PO goes directly to Approved state
3. Test High-Value Approval (> $25,000):
□ Create PO with total = $30,000
□ Submit → Manager Approve
□ Verify routes to Director Approval
□ Director Approve → Approved
4. Test Rejection Flow:
□ Create PO → Submit
□ Click "Reject"
□ Verify record unlocked
□ Add changes → Click "Resubmit"
□ Verify returns to Draft
5. Verify Buttons:
□ Only Submit button in Draft
□ Approve/Reject in Pending states
□ Resubmit only in Rejected
6. When all tests pass → Change to "Released"
Next Steps
| Goal | Go To |
|---|---|
| Learn about workflow actions | Actions → |
| Add custom action scripts | Workflow Action Script → |
| Schedule workflows | Scheduled Workflows → |