Skip to main content

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

GoalGo To
Learn about workflow actionsActions →
Add custom action scriptsWorkflow Action Script →
Schedule workflowsScheduled Workflows →