Workflows

Workflow definition syntax — initiation, steps, approvals, conditional transitions, and automated actions.

Workflow Definition

P9 workflows define multi-step approval processes with conditional routing, dynamic approvals, and automated actions. Workflows are defined in .workflow files.

Structure

@workflow WorkflowName
@application ApplicationName
@module "Module Name"
@company CompanyName

initiate {
	@model ObjectName
	@trigger Manual

	on (submitted) {
		start "First Step"
	}
}

step "Step Name" {
	@model ObjectName

	approvals {
		from "role_name"
	}

	on (approved) {
		next "Next Step"
	}

	on (rejected) {
		finish
	}
}

Workflow Declaration

Every workflow starts with metadata:

@workflow ExpenseApproval
@application Finance
@module "Finance"
@company AlFahim
Directive Required Description
@workflow Yes Unique workflow name (PascalCase)
@application No Application this workflow belongs to
@module No Human-readable module name
@company No Company this workflow applies to (omit for generic)

Generic Workflows:

For workflows that apply across all companies, use the generic:true attribute:

@workflow GenericApprovalFlow generic:true

initiate {
	on (submitted) {
		start "Request for Approval"
	}
}

Initiate Block

The initiate block defines how the workflow starts:

initiate {
	@model Expense
	@trigger Manual

	on (submitted) {
		start "Manager Approval"
	}
}

Conditional Start:

Use when conditions to start different steps based on record data:

initiate {
	@model PurchaseOrder

	on (submitted) {
		start "Budget Check"
	} when ({Amount} > 5000)

	on (submitted) {
		start "Manager Approval"
	} when ({Amount} <= 5000)
}

Step Blocks

Each step represents a stage in the workflow:

step "Manager Approval" {
	@model Expense

	approvals {
		from "Department_Manager"
	}

	on (submitted) {
		status "InProgress"
	}

	on (approved) {
		status "Approved"
		finish
	}

	on (rejected) {
		status "Rejected"
		finish
	}
}

Workflow Events

Event Description
submitted Record submitted to workflow
submitting Before submission
created Record created in database
creating Before creation
updated Record updated
updating Before update
approved Approver approved the record
rejected Approver rejected the record
validated Custom validation passed
notvalidated Custom validation failed
validating Before validation runs
finalized Record marked final
void Record voided

Approval Configurations

Simple Role Approval:

approvals {
	from "Finance_Controller"
}

Dynamic Approval Map:

approvals {
	from {ApprovalMap}
}

Approval with Label:

approvals {
	from "Department_Manager" label "Manager Approval"
}

Custom Approval Buttons:

approvals {
	from {ApprovalMap} buttons:[approved,rejected]
}

Workflow Actions

Navigation Actions:

start "Step Name"     // Start a workflow step
next "Step Name"      // Move to next step
finish                // Complete workflow

Status Actions:

Update record status:

on (approved) {
	status "Approved"
	finish
}

on (rejected) {
	status "Rejected"
	finish
}

Assignment Actions:

on (validating) {
	assign "procurement_officer"
}

on (creating) {
	assign type:User field:"Manager"
}

Edit Actions:

on (updating) {
	edit "sales_team" label "Upload Document"
}

Notification Actions:

on (created) {
	notifications {
		channel:"Email"
		pipelines:"Workflow"
		to: "{Tenant.Email}"
		cc: "manager@company.com"
		subject:"{Tenant.Welcome}"
		message:"{Tenant.Welcome}"
	}
	finish
}

Complete Example: Expense Approval

@workflow ExpenseFlow
@application Finance
@module "Finance"
@company AlFahim

initiate {
	@model Expense

	on (submitted) {
		start "Approval from Manager"
	}
}

step "Approval from Manager" {
	@model Expense

	approvals {
		from "Payable_Controller"
	}

	on (submitted) {
		status "InProgress"
	}

	on (approved) {
		status "Approved"
		finish
	}

	on (rejected) {
		status "Rejected"
		finish
	}
}

Complete Example: Multi-Step Procurement

@workflow Procure2PayFlow
@application Finance
@module "Procure to Pay"
@company AlFahim

initiate {
	@model MaterialRequest

	on (submitted) {
		start "Material Requisition"
	}
}

step "Material Requisition" {
	@model MaterialRequest

	approvals {
		from {ApprovalMap}
	}

	on (approved) {
		status "Approved"
		next "Check Stock"
	}

	on (rejected) {
		status "Rejected"
		finish
	}
}

step "Check Stock" {
	@model MaterialRequest

	on (validating) {
		assign "procurement_warehousing"
	}

	on (validated) {
		next "Stock Transfer"
	}

	on (notvalidated) {
		next "Request for Quote"
	}
}

step "Stock Transfer" {
	@model StockTransfer

	on (creating) {
		assign "procurement_warehousing"
	}

	on (created) {
		status "Closed"
		finish
	}
}

step "Request for Quote" {
	@model RequestForQuotation

	on (creating) {
		assign "procurement_purchasing"
	}

	on (created) {
		next "Purchase Order"
	}
}

step "Purchase Order" {
	@model PurchaseOrder

	approvals {
		from {ApprovalMap}
	}

	on (approved) {
		status "POApproved"
		next "Goods Receipt"
	}

	on (rejected) {
		status "Rejected"
		finish
	}
}

step "Goods Receipt" {
	@model GoodsReceiptNote

	on (created) {
		status "Received"
		next "Invoice"
	}
}

step "Invoice" {
	@model PurchaseInvoice

	on (finalized) {
		status "Closed"
		finish
	}
}

Complete Example: Leasing Flow with Notifications

@workflow LeasingFlow
@application Leasing
@module "Leasing"
@company AlFahim

initiate {
	@model Contract

	on (submitted) {
		start "Create Tenancy Contract"
	} when ({ContractInitiationType} = 'New')
}

step "Create Tenancy Contract" {
	@model Contract

	on (submitted) {
		status "Open"
		next "Upload Sign Copy"
	}
}

step "Upload Sign Copy" {
	@model Contract

	on (updating) {
		edit sales_team label "Upload"
	}

	on (updated) {
		next "Supervisor Approve"
	}
}

step "Supervisor Approve" {
	@model Contract

	approvals {
		from "leasing_executive" label "Supervisor Approve"
	}

	on (approved) {
		status "ContractApproved"
		next "Payments"
	}

	on (rejected) {
		status "Open"
		finish
	}
}

step "Payments" {
	@model PaymentReceipt

	on (creating) {
		assign "cash_officer"
	}

	on (created) {
		status "Payment"
		notifications {
			channel:"Email"
			pipelines:"Workflow"
			to: "{Tenant.Email}"
			cc: "manager@company.com"
			subject:"Welcome to {Building.Name}"
			message:"{Tenant.WelcomeMessage}"
		}
		finish
	}
}

Best Practices

  1. Use descriptive step names — "Approval from Manager" instead of "Step 1"
  2. Always handle approval outcomes — Include both approved and rejected events
  3. Execute status updates — Keep workflow status in sync with business logic
  4. Use conditional starts — Apply when conditions for complex initiation logic
  5. Leverage approval maps — Use {ApprovalMap} for flexible, configurable approvals
  6. Send notifications — Keep users informed at critical steps
  7. Assign appropriately — Use role-based or field-based assignment
  8. Handle validation — Include validated and notvalidated events for conditional logic
  9. Document with labels — Use label attribute in approvals for clarity
  10. Finish workflows — Always include finish at terminal steps

Experience the Platform

See how P9 and the Tuli platform work together

Ready to Build with P9?

Get hands-on with the platform. See how P9 accelerates your development workflow and integrates seamlessly with your existing systems.