Approval Delegation
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-31-2023 02:39 PM
I am trying to make a business rule to allow an assignment group the ability to add and remove delegates from a manager user record. Once the delegate is inserted, updated, or deleted it should either show the newly inserted/updated delegate as the approver, and resend the needed approval emails. Or if delegate(s) are deleted it should revert to the 'requested_for' user's manager. The manager is also the original approver. The script below is what I have so far, and while it seems to change the approver to the new delegate, it doesn't seem to work on the updated delegate, and more importantly does not revert to the manager/original approver once the delegate(s) are deleted.
A few things to note, would be that I have created a dictionary entry (column) on the 'sysapproval_approver' table named 'u_original_approver' as a means to store the user's manager as the way to revert it back if delegates are deleted. Furthermore, I have the business rule working on the 'sys_user_delegate' table, queueing up an event of 'approval.delegate.changed' which fires off a notification to approve the request which runs off the 'sysapproval_approver' table.
Any help would be appreciated, and if you need any clarification please let me know. Thank you.
(function executeRule(current, previous /*null when async*/) {
//Checks if the current user has the required roles
if (!gs.hasRole('advocate_tier_1') && !gs.hasRole('admin')) {
return;
}
//Defines a variable to store the delegate
var delegate = '';
if (current.operation() != 'delete') {
delegate = current.delegate;
} else {
delegate = previous.delegate;
}
//Queries the active approvals where the approver is the user whose delegate has been updated
var approvalGR = new GlideRecord('sysapproval_approver');
approvalGR.addQuery('state', 'requested');
approvalGR.addQuery('approver', current.user);
approvalGR.query();
//Loops through the active approvals
while (approvalGR.next()) {
//Gets the related RITM record
var ritmGR = new GlideRecord('sc_req_item');
if (ritmGR.get(approvalGR.document_id)) {
//Gets the 'requested_for' user and user's manager
var requestedForUserGR = new GlideRecord('sys_user');
if (requestedForUserGR.get(ritmGR.requested_for)) {
var manager = requestedForUserGR.manager;
}
}
//If a new delegate is added or the delegate is updated, it reassigns the approval to the new delegate and stores the original approver
if (current.operation() != 'delete' && delegate != '') {
approvalGR.u_original_approver = manager;
approvalGR.approver = delegate;
//If the delegate is deleted or the delegate is updated, it reverts the approver back to the original approver
} else {
approvalGR.approver = manager;
approvalGR.u_original_approver = ''; // Clear the original approver field after reverting
}
approvalGR.update();
//Sends a notification about the approval task
gs.eventQueue('approval.delegate.changed', approvalGR, delegate, current.user);
}
})(current, previous);

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-31-2023 03:37 PM
Then I would just trigger the approval notification to go out again when a delegate is added. I would not mess with the approval records themselves. That may cause your auditors to freak out, assuming you have to deal with auditors.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-15-2024 12:47 PM
There are really two paths here:
1) Assign the single Approval to a new user (and cancel the old approval)
2) Retroactively assign a delegate if one was not created, so that the delegate gets sent emails on any outstanding approval
Item one can be completed with the following UI Action:
table: sysapproval_approver
Condition: (current.state == 'requested' || current.state == 'not requested') && !current.approver.isNil() && canModApprovals(gs.getUserID(),current)
onClick: reassignApproval();
action name: reassign_approval
//Client-side 'onclick' function
function reassignApproval() {
var refRecord = g_form.getValue('document_id');
var comments = g_form.getValue('comments');
var refApprover = g_form.getReference('approver');
var gTask = new GlideRecord('task');
gTask.get(refRecord);
if (g_form.getValue('comments') == '') {
//Remove any existing field message, set comments mandatory, and show a new field message
try {
g_form.hideFieldMsg('comments');
} catch (e) {}
g_form.setMandatory('comments', true);
g_form.showFieldMsg('comments', 'Comments are mandatory when reassigning an approval.', 'error');
return false;
}
var answer = confirm("This action is to create a one-time approval for " + gTask.number + ". If instead you need to create a longer-term delegate for " + refApprover.name + ", click 'Cancel' and choose the 'Assign Delegate' Action. \n\nDo you wish to continue?");
if (answer == false) {
return false; //Abort submission
}
//Call the UI Action and skip the 'onclick' function
//gsftSubmit(null, g_form.getFormElement(), 'reassign_approval'); //MUST call the 'Action name' set in this UI Action
_uiAssignPageCall(gTask.number);
}
//Code that runs without 'onclick'
//Ensure call to server-side function with no browser errors
if (typeof window == 'undefined')
runBusRuleCode();
//Server-side function
function runBusRuleCode() {
//Now this is all handled by the UI Page
}
function _uiAssignPageCall(recordIn) {
var record_sysid = g_form.getValue('document_id').toString();
var record = recordIn;
var appr_sysid = g_form.getUniqueValue();
var oldApprover = g_form.getValue('approver');
var oldApproverName = $('sys_display.sysapproval_approver.approver').value;
var dialog = new GlideDialogWindow('re-assign_approval'); //Render the dialog containing the UI Page 'reassign_approval'
dialog.setTitle("Reassign Approval"); //Set the dialog title
dialog.setPreference("record_sysid", record_sysid); //Pass in comments in case we want to update or require them on the UI Page
dialog.setPreference("record", record); //Pass in comments in case we want to update or require them on the UI Page
dialog.setPreference("appr_sysid", appr_sysid); //Pass in current record's sys_id for use in the dialog
dialog.setPreference("oldApprover", oldApprover); //Pass in Current Approver for use in the dialog
dialog.setPreference("oldApproverName", oldApproverName); //Pass in Current Approver Name for use in the dialog
dialog.render(); //Open the dialog
}
and UI Page is attached (with identifying values removed, so you might not be able to straight import the xml record):
The second item, to Retroactively assign a delegate also has two parts. The UI Action:
table: sysapproval_approver
Condition: (current.state == 'requested' || current.state == 'not requested') && !current.approver.isNil() && (hasDelegate(current.approver) == '' || hasDelegate(current.approver).approvals == false) && canModApprovals(gs.getUserID(),current)
onClick: assignDelegate();
action name: assign_delegate
//Client-side 'onclick' function
function assignDelegate() {
var refRecord = g_form.getValue('document_id');
var comments = g_form.getValue('comments');
var refApprover = g_form.getReference('approver');
var gTask = new GlideRecord('task');
gTask.get(refRecord);
if (g_form.getValue('comments') == '') {
//Remove any existing field message, set comments mandatory, and show a new field message
try {
g_form.hideFieldMsg('comments');
} catch (e) {}
g_form.setMandatory('comments', true);
g_form.showFieldMsg('comments', 'Comments are mandatory when assigning a delegate.', 'error');
return false;
}
var answer = confirm("This action does not create a one-time approval for " + gTask.number + ", but instead creates a delegate for " + refApprover.name + ". If you want to create a one-time approval for " + gTask.number + ", click 'Cancel' and choose the 'Reassign Approval' Action. \n\nDo you wish to continue?");
if (answer == false) {
return false; //Abort submission
}
//Call the UI Action and skip the 'onclick' function
//gsftSubmit(null, g_form.getFormElement(), 'assign_delegate'); //MUST call the 'Action name' set in this UI Action
//Open a UI page to emulate the delegate screen.
_uiPageCall();
}
//Code that runs without 'onclick'
//Ensure call to server-side function with no browser errors
if (typeof window == 'undefined')
runBusRuleCode();
//Server-side function
function runBusRuleCode() {
}
function _uiPageCall() {
var comments = g_form.getValue('comments');
var appr_sysid = g_form.getUniqueValue();
var oldApprover = g_form.getValue('approver');
var oldApproverName = $('sys_display.sysapproval_approver.approver').value;
var dialog = new GlideDialogWindow('add_delegate'); //Render the dialog containing the UI Page 'add_delegate'
dialog.setTitle("Add Approval Delegate"); //Set the dialog title
dialog.setPreference("comments", comments); //Pass in comments in case we want to update or require them on the UI Page
dialog.setPreference("appr_sysid", appr_sysid); //Pass in current record's sys_id for use in the dialog
dialog.setPreference("oldApprover", oldApprover); //Pass in Current Approver for use in the dialog
dialog.setPreference("oldApproverName", oldApproverName); //Pass in Current Approver Name for use in the dialog
dialog.render(); //Open the dialog
}
UI Page is also attached (with identifying values removed, so you might not be able to straight import the xml record):
And then finally, a Business Rule to refire all the new delegate emails:
Action: insert
When: After
//This business rule is designed to fire upon insertion of a new Delegate AFTER approvals
//exist for the current user. The regular email notifications will remain in place to handle
//approvals sent to users who have set up delegates before those approvals were created.
//Because the only approvals that can be worked are 'requested', those are the only ones covered here.
var delegate = current.delegate;
var user = current.user;
var event = 'approval.delegate.inserted';
var gProcess = new GlideRecord("sysapproval_approver");
gProcess.addQuery('approver',user);
gProcess.addQuery('state','requested');
gProcess.query();
while (gProcess.next()) {
gs.log('processing ' + gProcess.sysapproval.number + ' delegate = ' + delegate.u_name_id + ' user = ' + user.u_name_id);
gs.eventQueue(event, gProcess, delegate, user.u_name_id);
}
You'll still have to create the event (approval.delegate.inserted) and the corresponding email, but this should cover everything else.
This does work in workflows - reassigning an approval still halts the workflow at the approval step without proceeding prematurely. There are a couple of fields on the sysapproval_approver table you will have to change with UI policies instead of them being hidden from the dictionary, but that is pretty easy.