Server-side
var ApprovalChainCalculator = Class.create();
ApprovalChainCalculator.prototype = {
initialize: function() {
this.debug = true;
},
// Calculate approval chain for a given record
calculateApprovalChain: function(tableName, recordSysId) {
var approvalChain = [];
var record = new GlideRecord(tableName);
if (!record.get(recordSysId)) {
gs.error('ApprovalChainCalculator: Record not found - ' + tableName + ':' + recordSysId);
return approvalChain;
}
// Example: Purchase Request approval chain based on amount
if (tableName == 'sc_req_item') {
approvalChain = this._calculatePurchaseApprovalChain(record);
} else if (tableName == 'change_request') {
approvalChain = this._calculateChangeApprovalChain(record);
} else {
// Generic approval chain based on configuration
approvalChain = this._calculateGenericApprovalChain(record);
}
if (this.debug) {
gs.info('ApprovalChainCalculator: Calculated chain for ' + recordSysId + ': ' + JSON.stringify(approvalChain));
}
return approvalChain;
},
_calculatePurchaseApprovalChain: function(record) {
var chain = [];
var amount = parseFloat(record.getValue('price')) || 0;
var department = record.requested_for.department.toString();
// Stage 1: Manager approval (always required)
var managerStage = {
stage: 1,
name: 'Manager Approval',
type: 'sequential', // or 'parallel'
approvers: [],
required: true,
timeout_hours: 24,
escalation_approvers: []
};
// Get requesting user's manager
var requestedFor = record.requested_for.toString();
var manager = this._getManagerForUser(requestedFor);
if (manager) {
managerStage.approvers.push(manager);
// Add manager's manager as escalation
var seniorManager = this._getManagerForUser(manager);
if (seniorManager) {
managerStage.escalation_approvers.push(seniorManager);
}
}
chain.push(managerStage);
// Stage 2: Department head approval (if amount > $1000)
if (amount > 1000) {
var deptStage = {
stage: 2,
name: 'Department Head Approval',
type: 'sequential',
approvers: [],
required: true,
timeout_hours: 48,
escalation_approvers: []
};
var deptHead = this._getDepartmentHead(department);
if (deptHead) {
deptStage.approvers.push(deptHead);
}
chain.push(deptStage);
}
// Stage 3: Finance approval (if amount > $5000)
if (amount > 5000) {
var financeStage = {
stage: 3,
name: 'Finance Approval',
type: 'parallel', // Multiple finance approvers can approve in parallel
approvers: this._getFinanceApprovers(),
required: true,
timeout_hours: 72,
escalation_approvers: [this._getFinanceDirector()]
};
chain.push(financeStage);
}
return chain;
},
_calculateChangeApprovalChain: function(record) {
var chain = [];
var risk = record.getValue('risk');
var category = record.getValue('category');
// Always require CAB approval for high risk changes
if (risk == '1' || risk == '2') { // High or Medium risk
chain.push({
stage: 1,
name: 'CAB Approval',
type: 'sequential',
approvers: this._getCABMembers(),
required: true,
timeout_hours: 168, // 1 week
escalation_approvers: [this._getChangeManager()]
});
}
return chain;
},
_calculateGenericApprovalChain: function(record) {
// Fallback to simple manager approval
var requestedBy = record.getValue('sys_created_by');
var manager = this._getManagerForUser(requestedBy);
return [{
stage: 1,
name: 'Manager Approval',
type: 'sequential',
approvers: manager ? [manager] : [],
required: true,
timeout_hours: 48,
escalation_approvers: []
}];
},
_getManagerForUser: function(userSysId) {
var user = new GlideRecord('sys_user');
if (user.get(userSysId) && user.manager) {
return user.manager.toString();
}
return null;
},
_getDepartmentHead: function(departmentSysId) {
var dept = new GlideRecord('cmn_department');
if (dept.get(departmentSysId) && dept.dept_head) {
return dept.dept_head.toString();
}
return null;
},
_getFinanceApprovers: function() {
var approvers = [];
var gr = new GlideRecord('sys_user');
gr.addQuery('department.name', 'Finance');
gr.addQuery('u_finance_approver', true);
gr.addQuery('active', true);
gr.query();
while (gr.next()) {
approvers.push(gr.sys_id.toString());
}
return approvers;
},
_getFinanceDirector: function() {
var gr = new GlideRecord('sys_user');
gr.addQuery('title', 'CONTAINS', 'Finance Director');
gr.addQuery('active', true);
gr.query();
if (gr.next()) {
return gr.sys_id.toString();
}
return null;
},
_getCABMembers: function() {
var members = [];
var gr = new GlideRecord('sys_user');
gr.addQuery('u_cab_member', true);
gr.addQuery('active', true);
gr.query();
while (gr.next()) {
members.push(gr.sys_id.toString());
}
return members;
},
_getChangeManager: function() {
var gr = new GlideRecord('sys_user');
gr.addQuery('title', 'CONTAINS', 'Change Manager');
gr.addQuery('active', true);
gr.query();
if (gr.next()) {
return gr.sys_id.toString();
}
return null;
},
type: 'ApprovalChainCalculator'
};
This Script Include demonstrates dynamic approval chain calculation with configurable stages supporting both parallel and sequential approvals. The calculateApprovalChain method returns a structured array defining approval stages with approvers, timeouts, and escalation rules that Flow Designer can consume. Each stage specifies whether approvals should be processed in parallel or sequentially, includes timeout periods for escalation, and provides fallback approvers when primary approvers don't respond.