Workflows
Workflows automate business processes by defining states, transitions, and actions that execute based on conditions.
When to Use Workflows
| Use Case | Example |
|---|---|
| Approval processes | Invoice approval routing |
| Status management | Order lifecycle tracking |
| Notifications | Alert on record changes |
| Field updates | Auto-populate based on rules |
| Validation | Enforce business rules |
Workflow Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ WORKFLOW STRUCTURE │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ WORKFLOW DEFINITION │
│ custworkflow_invoice_approval │
│ ────────────────────────────────────────────────────────────────│
│ Record Type: Vendor Bill │
│ Trigger: On Create, On Update │
└──────────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ STATE 1 │ │ STATE 2 │ │ STATE 3 │
│ Draft/New │ │ Pending │ │ Approved │
│ │ │ Approval │ │ │
│ Entry Actions: │ │ Entry Actions: │ │ Entry Actions: │
│ • Set status │ │ • Send email │ │ • Update status │
│ │ │ • Lock record │ │ • Unlock record │
│ Exit Actions: │ │ Exit Actions: │ │ │
│ • Validate │ │ • Log action │ │ (Final State) │
└────────┬────────┘ └────────┬────────┘ └─────────────────┘
│ │
│ Transition: │ Transition:
│ "Submit for │ "Approve"
│ Approval" │
│ ───────────── │ ─────────────
│ Condition: │ Condition:
│ amount > 1000 │ Button click
│ │
└─────────────────────┘
Workflow Execution Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ WORKFLOW EXECUTION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ TRIGGER EVENT │
│ ────────────────────────────────────────────────────────────────│
│ • Record Created │
│ • Record Updated │
│ • Record Approved │
│ • Scheduled (time-based) │
└──────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ CHECK WORKFLOW CONDITIONS │
│ ────────────────────────────────────────────────────────────────│
│ Does record match workflow initiation conditions? │
│ Example: Type = Vendor Bill AND Amount > $1,000 │
└──────────────────────────────┬───────────────────────────────────┘
│
┌────────────No───────┴───────Yes────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────────────────────┐
│ No Action │ │ ENTER INITIAL STATE │
│ (Skip) │ │ Execute Entry Actions │
└─────────────────┘ └──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ EVALUATE TRANSITIONS │
│ ────────────────────────────────│
│ Check transition conditions │
│ • Button clicked? │
│ • Condition met? │
│ • Scheduled trigger? │
└──────────────┬───────────────────┘
│
┌───────Condition Met───┴───No Match──────┐
│ │
▼ ▼
┌────────────────────────────────┐ ┌────────────────────┐
│ EXECUTE TRANSITION │ │ WAIT │
│ ──────────────────────────── │ │ (Stay in state │
│ 1. Run exit actions │ │ until condition │
│ 2. Run transition actions │ │ met) │
│ 3. Move to next state │ └────────────────────┘
│ 4. Run entry actions │
└────────────────────────────────┘
Basic Workflow XML
<?xml version="1.0" encoding="UTF-8"?>
<workflow scriptid="custworkflow_invoice_approval">
<name>Invoice Approval Workflow</name>
<description>Routes vendor bills for approval based on amount</description>
<!-- Record Type -->
<recordtype>vendorbill</recordtype>
<!-- Release Status -->
<releasestatus>RELEASED</releasestatus>
<!-- Initiation -->
<initoncreate>T</initoncreate>
<initonupdate>F</initonupdate>
<initonvieworupdate>F</initonvieworupdate>
<!-- Initiation Condition -->
<initcondition>
<formula>{total} > 1000</formula>
</initcondition>
<!-- Keep instance active -->
<keepinstancealivetillcomplete>T</keepinstancealivetillcomplete>
<!-- Workflow States -->
<workflowstates>
<!-- State 1: Pending Approval -->
<workflowstate scriptid="workflowstate_pending">
<name>Pending Approval</name>
<description>Waiting for manager approval</description>
<donotexitalivetillcomplete>T</donotexitalivetillcomplete>
<!-- Entry Actions -->
<workflowactions>
<triggertype>ENTRY</triggertype>
<!-- Set Field Value -->
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Approval</value>
</setfieldvalueaction>
<!-- Send Email -->
<sendemailaction>
<recipientfield>custbody_approver</recipientfield>
<recipientisemail>F</recipientisemail>
<senderfield>currentuser</senderfield>
<subject>Invoice Approval Required: {tranid}</subject>
<body>Please review and approve invoice {tranid} for ${total}</body>
</sendemailaction>
</workflowactions>
<!-- Transitions from this state -->
<workflowtransitions>
<!-- Transition: Approve -->
<workflowtransition scriptid="transition_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<tostate>workflowstate_approved</tostate>
<buttonaction>
<button>Approve</button>
</buttonaction>
</workflowtransition>
<!-- Transition: Reject -->
<workflowtransition scriptid="transition_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<tostate>workflowstate_rejected</tostate>
<buttonaction>
<button>Reject</button>
</buttonaction>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State 2: Approved -->
<workflowstate scriptid="workflowstate_approved">
<name>Approved</name>
<description>Invoice has been approved</description>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Approved</value>
</setfieldvalueaction>
<setfieldvalueaction>
<field>custbody_approved_date</field>
<valuetype>STATIC</valuetype>
<value>NOW</value>
</setfieldvalueaction>
</workflowactions>
</workflowstate>
<!-- State 3: Rejected -->
<workflowstate scriptid="workflowstate_rejected">
<name>Rejected</name>
<description>Invoice has been rejected</description>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Rejected</value>
</setfieldvalueaction>
<sendemailaction>
<recipientfield>createdby</recipientfield>
<subject>Invoice Rejected: {tranid}</subject>
<body>Your invoice {tranid} has been rejected.</body>
</sendemailaction>
</workflowactions>
</workflowstate>
</workflowstates>
</workflow>
Workflow Actions
Set Field Value
<setfieldvalueaction>
<field>custbody_status</field>
<valuetype>STATIC</valuetype>
<value>Pending</value>
</setfieldvalueaction>
<!-- Dynamic value from another field -->
<setfieldvalueaction>
<field>custbody_approved_by</field>
<valuetype>FIELD</valuetype>
<valuefield>currentuser</valuefield>
</setfieldvalueaction>
<!-- Formula value -->
<setfieldvalueaction>
<field>custbody_due_date</field>
<valuetype>FORMULA</valuetype>
<value>{today}+30</value>
</setfieldvalueaction>
Send Email
<sendemailaction>
<recipientfield>custbody_approver</recipientfield>
<recipientisemail>F</recipientisemail>
<senderfield>currentuser</senderfield>
<usetemplate>T</usetemplate>
<template>Email Template Name</template>
</sendemailaction>
Create Record
<createrecordaction>
<recordtype>customrecord_approval_log</recordtype>
<createrecordfield>
<field>custrecord_al_record</field>
<valuefield>id</valuefield>
</createrecordfield>
<createrecordfield>
<field>custrecord_al_action</field>
<value>Submitted for Approval</value>
</createrecordfield>
<createrecordfield>
<field>custrecord_al_user</field>
<valuefield>currentuser</valuefield>
</createrecordfield>
</createrecordaction>
Custom Action (Script)
<customaction>
<scriptid>customscript_approval_action</scriptid>
<scriptdeploymentid>customdeploy_approval_action</scriptdeploymentid>
<parameters>
<parameter>
<name>custscript_action_type</name>
<value>approve</value>
</parameter>
</parameters>
</customaction>
Lock Record
<lockrecordaction>
<locked>T</locked>
</lockrecordaction>
Transition Types
┌─────────────────────────────────────────────────────────────────────────────┐
│ TRANSITION TRIGGER TYPES │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────┬─────────────────────────────────────────────────────────┐
│ BUTTON TRIGGER │ User clicks workflow button │
│ │ <buttonaction> │
│ │ <button>Approve</button> │
│ │ </buttonaction> │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ CONDITION │ Field value meets condition │
│ TRIGGER │ <condition> │
│ │ <formula>{total} > 10000</formula> │
│ │ </condition> │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ SCHEDULED │ Time-based trigger │
│ TRIGGER │ <scheduledaction> │
│ │ <delay>1</delay> │
│ │ <delayunit>HOURS</delayunit> │
│ │ </scheduledaction> │
├────────────────────┼─────────────────────────────────────────────────────────┤
│ EVENT │ Record event (save, submit, etc.) │
│ TRIGGER │ <triggerevent>AFTERSUBMIT</triggerevent> │
└────────────────────┴─────────────────────────────────────────────────────────┘
Trigger Events
| Event | When It Fires |
|---|---|
| BEFORELOAD | Before record loads |
| BEFORESUBMIT | Before record saves |
| AFTERSUBMIT | After record saves |
| ONENTRY | When entering state |
| ONEXIT | When exiting state |
| SCHEDULED | At scheduled time |
Workflow Conditions
Field-Based Conditions
<condition>
<formula>{total} > 5000</formula>
</condition>
<condition>
<formula>{custbody_region} = 'West'</formula>
</condition>
<condition>
<formula>{entity.custentity_credit_rating} = 'A'</formula>
</condition>
Combined Conditions
<condition>
<formula>{total} > 5000 AND {custbody_requires_approval} = 'T'</formula>
</condition>
<condition>
<formula>{total} > 10000 OR {custbody_rush_order} = 'T'</formula>
</condition>
Saved Search Conditions
<condition>
<savedsearch>customsearch_high_risk_customers</savedsearch>
</condition>
Complete Example: Multi-Level Approval
<?xml version="1.0" encoding="UTF-8"?>
<workflow scriptid="custworkflow_po_approval">
<name>Purchase Order Approval</name>
<description>Multi-level approval based on PO amount</description>
<recordtype>purchaseorder</recordtype>
<releasestatus>RELEASED</releasestatus>
<initoncreate>T</initoncreate>
<initonupdate>T</initonupdate>
<initcondition>
<formula>{total} > 500</formula>
</initcondition>
<workflowstates>
<!-- State: Draft -->
<workflowstate scriptid="state_draft">
<name>Draft</name>
<donotexitalivetillcomplete>T</donotexitalivetillcomplete>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Submission</value>
</setfieldvalueaction>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="trans_submit">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Submit for Approval</button>
</buttonaction>
<!-- Route based on amount -->
<tostate>state_manager_review</tostate>
<condition>
<formula>{total} <= 5000</formula>
</condition>
</workflowtransition>
<workflowtransition scriptid="trans_submit_high">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Submit for Approval</button>
</buttonaction>
<tostate>state_director_review</tostate>
<condition>
<formula>{total} > 5000</formula>
</condition>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: Manager Review -->
<workflowstate scriptid="state_manager_review">
<name>Manager Review</name>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Approval</value>
</setfieldvalueaction>
<lockrecordaction>
<locked>T</locked>
</lockrecordaction>
<sendemailaction>
<recipientfield>custbody_manager</recipientfield>
<subject>PO Approval Required: {tranid}</subject>
<body>Please review PO {tranid} for ${total}</body>
</sendemailaction>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="trans_mgr_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Approve</button>
</buttonaction>
<tostate>state_approved</tostate>
</workflowtransition>
<workflowtransition scriptid="trans_mgr_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Reject</button>
</buttonaction>
<tostate>state_rejected</tostate>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: Director Review (for high value) -->
<workflowstate scriptid="state_director_review">
<name>Director Review</name>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending Director Approval</value>
</setfieldvalueaction>
<lockrecordaction>
<locked>T</locked>
</lockrecordaction>
<sendemailaction>
<recipientfield>custbody_director</recipientfield>
<subject>High Value PO Approval: {tranid}</subject>
<body>PO {tranid} for ${total} requires your approval.</body>
</sendemailaction>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="trans_dir_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Approve</button>
</buttonaction>
<tostate>state_approved</tostate>
</workflowtransition>
<workflowtransition scriptid="trans_dir_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Reject</button>
</buttonaction>
<tostate>state_rejected</tostate>
</workflowtransition>
<workflowtransition scriptid="trans_dir_escalate">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Escalate to CFO</button>
</buttonaction>
<tostate>state_cfo_review</tostate>
<condition>
<formula>{total} > 50000</formula>
</condition>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: CFO Review -->
<workflowstate scriptid="state_cfo_review">
<name>CFO Review</name>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Pending CFO Approval</value>
</setfieldvalueaction>
</workflowactions>
<workflowtransitions>
<workflowtransition scriptid="trans_cfo_approve">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Approve</button>
</buttonaction>
<tostate>state_approved</tostate>
</workflowtransition>
<workflowtransition scriptid="trans_cfo_reject">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Reject</button>
</buttonaction>
<tostate>state_rejected</tostate>
</workflowtransition>
</workflowtransitions>
</workflowstate>
<!-- State: Approved -->
<workflowstate scriptid="state_approved">
<name>Approved</name>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Approved</value>
</setfieldvalueaction>
<setfieldvalueaction>
<field>custbody_approved_by</field>
<valuetype>FIELD</valuetype>
<valuefield>currentuser</valuefield>
</setfieldvalueaction>
<setfieldvalueaction>
<field>custbody_approved_date</field>
<valuetype>STATIC</valuetype>
<value>NOW</value>
</setfieldvalueaction>
<lockrecordaction>
<locked>F</locked>
</lockrecordaction>
<sendemailaction>
<recipientfield>createdby</recipientfield>
<subject>PO Approved: {tranid}</subject>
<body>Your PO {tranid} has been approved.</body>
</sendemailaction>
</workflowactions>
</workflowstate>
<!-- State: Rejected -->
<workflowstate scriptid="state_rejected">
<name>Rejected</name>
<workflowactions>
<triggertype>ENTRY</triggertype>
<setfieldvalueaction>
<field>STDBODYAPPROVALSTATUS</field>
<value>Rejected</value>
</setfieldvalueaction>
<lockrecordaction>
<locked>F</locked>
</lockrecordaction>
<sendemailaction>
<recipientfield>createdby</recipientfield>
<subject>PO Rejected: {tranid}</subject>
<body>Your PO {tranid} has been rejected.</body>
</sendemailaction>
</workflowactions>
<workflowtransitions>
<!-- Allow resubmission -->
<workflowtransition scriptid="trans_resubmit">
<triggerevent>AFTERSUBMIT</triggerevent>
<buttonaction>
<button>Resubmit</button>
</buttonaction>
<tostate>state_draft</tostate>
</workflowtransition>
</workflowtransitions>
</workflowstate>
</workflowstates>
</workflow>
Workflow State Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ MULTI-LEVEL APPROVAL FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────┐
│ DRAFT │
│ (Initial) │
└──────┬──────┘
│
Submit for Approval
│
┌──────────────────────┴───────────────────────┐
│ │
Amount <= $5,000 Amount > $5,000
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ MANAGER │ │ DIRECTOR │
│ REVIEW │ │ REVIEW │
└────────┬────────┘ └────────┬────────┘
│ │
┌─────────┴─────────┐ ┌─────────────────┼─────────────────┐
│ │ │ │ │
Approve Reject Approve Reject Escalate
│ │ │ │ (> $50,000)
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ ┌──────────┐
│APPROVED│ │ REJECTED │ │APPROVED│ │ REJECTED │ │ CFO │
└────────┘ └─────┬─────┘ └────────┘ └─────┬─────┘ │ REVIEW │
│ │ └─────┬────┘
│ │ │
Resubmit Resubmit Approve/Reject
│ │ │
└─────────────►DRAFT◄─────────────┘ ▼
┌────────────────┐
│ APPROVED / │
│ REJECTED │
└────────────────┘
Import Existing Workflows
# Import all workflows
suitecloud object:import --type workflow --destinationfolder Objects
# Import specific workflow
suitecloud object:import --type workflow --scriptid custworkflow_invoice_approval --destinationfolder Objects
Best Practices
| Practice | Description |
|---|---|
| Clear naming | Use descriptive state and transition names |
| Document flows | Include descriptions for each state |
| Handle all paths | Account for approve, reject, and edge cases |
| Use conditions | Route based on business rules |
| Test thoroughly | Test all possible paths |
| Log actions | Create audit trail records |
Next Steps
- Workflow Action Script - Add custom logic
- Invoice Approval Scenario - Complete example
- Custom Records - Store workflow data