Automating the closure of outstanding requested item approvals and sending approval reminders
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-17-2023 09:41 PM
Foreword: This is a solution that worked for our company. It may not work in your environment. As always, develop and test before going live. Also, we link our approvals to our requested items, if your company uses a different method, adjustments will be required.
Background: Our company requested a solution to the growing number of requested items (RITM) that were piling up unapproved and not able to be progressed or closed out without notifying our customers and approvers.
Old Work Around
We already had a very simple workaround for re-sending outstanding email notifications: A UI Action called Resend Approval Email, which allows users who can see the approval record and it is in the state of requested, to fire an event, in turn triggering an email notification (code sample below). The email notification is a simple variation of the original approval email with our company's branding. The email is also logged against the original approval request, which is great for keeping track of how many times we've sent out reminders.
resendApprovalEmail();
function resendApprovalEmail() {
gs.log("Sending Resend Approval Event");
gs.eventQueue("approval.resend", current, "", "");
gs.addInfoMessage("Resending approval email to: " + current.approver.email);
}
However, this cannot address the issue where we have the entire company submitting requests, and each day they age out further. So @David House and myself worked on a solution to perform two functions:
- Provide our IT Service Desk with a button to gracefully close out a RITM without notifying the customer or approver
- Automate an approval reminder and cancellation flow that:
- Reminds approvers of pending request item approvals as they age to 3, 7, 14 and 29 days
- and cancel the request item out without notifying the customer or approver on the 30th day.
IT Service Desk Button
The button is visible on requested items, visible only to roles 'admin' and 'Service Desk Team', and when the requested item’s approval state is Requested. The code in the UI Action works around the mandatory fields on the requested item and references the flow script action that I will go into detail about later on.
//Client-side 'onclick' function
function runCancelonClient() {
if (confirm("Are you sure you want to cancel this request?")) {
g_form.addInfoMessage("Cancelling the request..."); // Optional: Show an info message
g_form.setMandatory('assignment_group', false);
//Call the UI Action and skip the 'onclick' function
gsftSubmit(null, g_form.getFormElement(), 'tcc_ritm_cancel'); //MUST call the 'Action name' set in this UI Action
}
}
//Code that runs without 'onclick'
//Ensure call to server-side function with no browser errors
if (typeof window == 'undefined')
runCancelOnServer();
//Server-side function
function runCancelOnServer() {
(function() {
try {
var inputs = {};
inputs['requested_item'] = current; // GlideRecord of table: sc_req_item
// Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.
// sn_fd.FlowAPI.getRunner().action('global.tcc_cancel_request_item_without_notification').inBackground().withInputs(inputs).run();
// Execute Synchronously: Run in foreground. Code snippet has access to outputs.
var result = sn_fd.FlowAPI.getRunner().action('global.tcc_cancel_request_item_without_notification').inForeground().withInputs(inputs).run();
var outputs = result.getOutputs();
// Current action has no outputs defined.
} catch (ex) {
var message = ex.getMessage();
gs.error(message);
}
})();
action.setRedirectURL(current);
}
Automated solution
Base items
- Flow - (TCC) Request Items awaiting approval - Daily
- Subflow - (TCC) Request Item Approval Reminder
- Email Notifications
- (TCC) Approval Resend - Flow 3 day
- (TCC) Approval Resend - Flow 7 day
- (TCC) Approval Resend - Flow 14 day
- (TCC) Approval Resend - Flow 29 day
- Flow Script Action - (TCC) Cancel Request Item without notification
Flow - (TCC) Request Items awaiting approval - Daily
Runs daily at 04:00
Performs a look up for all approval records where:
- State = Requested
- Approval for.Task type = Requested item
- Created relative before 2 days ago
- Created relative after 4 days ago
The relative days created is isolating the approval records to created 3 days ago.
It then passes each request item to the subflow as an input as well as the day value. In order to send the appropriate email notification (see the subflow below).
This filter and subflow process is repeated for 7 days, 14 days and 29 days.
For the final lookup it locates all requested items that are at or beyond 30 days of age and passes those requested item records to the script action.
3 day filter example
Subflow - (TCC) Request Item Approval Reminder
Our emails for each day iteration simply advise the Approver and Requested by that this is the Nth day reminder, there will be however many more remaining days of reminders, until the 30th day when it will be automatically closed.
Flow Script Action - (TCC) Cancel Request Item without notification
The most complicated part of the entire process is this flow script action.
Taking in the requested item it delicately cancels any related flows and workflows. Noting that if a flow is found there is an extensive wait included to cater for delayed subflows cancelling.
It then cancels approval workflows and sets the approval request to 'No longer required'. Noting that this does nothing for catalogue items running on workflows, their approvals will still show as cancelled.
It does the same for group approvals.
Then it adjusts the values of the requested item and finally the request.
This script will prevent any tasks from being created, emails from being fired, or request items from being assigned.
And an additional reminder, the UI Action for 'Cancel (No notification)' from earlier references this flow script action. Without it it will not function.
Input: Request Item
(function execute(inputs, outputs) {
var requestSysID = "";
// Request Item canceller - Cancelling occurs at the end
// While not strictly required it does error check for if the record is manipulated after the button press or the automated schedule has started
var grRequestItem = new GlideRecord("sc_req_item");
grRequestItem.addQuery("sys_id", inputs.requested_item.sys_id);
grRequestItem.query();
if (grRequestItem.next()) {
// Flow canceller - only runs if flow contexts are present
var flowFound = false; // Boolean variable used to if a flow is found and a 15 second wait is needed
var flowContext = new GlideRecord("sys_flow_context");
flowContext.addQuery("source_record", grRequestItem.sys_id);
flowContext.query();
while (flowContext.next()) {
sn_fd.FlowAPI.cancel(flowContext.sys_id,""); // sys_id of the current record's flow (view this from the flow execution list)
flowFound = true;
}
if (flowFound == true) {
gs.sleep(15000); // Required lengthy 15 second wait for flows with subflows that fail to close out quickly
}
// Workflow canceller - only runs if workflow contexts are present
var workflowContext = new GlideRecord("wf_context");
workflowContext.addQuery("id", grRequestItem.request.sys_id.toString());
workflowContext.query();
while (workflowContext.next()) {
new Workflow().cancelContext(workflowContext);
}
// Approval canceller
// Locate every approval and set to - No Longer Required
// Note: Less relevant for Workflows, as cancelling them sets their approvals to Cancelled
var grApprovalRecord = new GlideRecord("sysapproval_approver");
grApprovalRecord.addQuery("document_id", grRequestItem.sys_id);
grApprovalRecord.query();
while (grApprovalRecord.next()) {
if (grApprovalRecord.state == "requested") {
new Workflow().cancel(grApprovalRecord); // Cancel approval workflow
grApprovalRecord.setWorkflow(false); // Prevents sending of emails
grApprovalRecord.state = "not_required";
grApprovalRecord.update();
}
}
// Group approval canceller
// Locate every Group approval and set to - No Longer Required
var grGroupApprovalRecord = new GlideRecord("sysapproval_group");
grGroupApprovalRecord.addQuery("parent", grRequestItem.sys_id);
grGroupApprovalRecord.query();
while (grGroupApprovalRecord.next()) {
if (grGroupApprovalRecord.approval == "requested") {
new Workflow().cancel(grGroupApprovalRecord); // Cancel group approval workflow
grGroupApprovalRecord.setWorkflow(false); // Prevents sending of emails
grGroupApprovalRecord.approval = "not_required";
grGroupApprovalRecord.update();
}
}
// Request Item cancellation
requestSysID = grRequestItem.request.toString();
grRequestItem.approval = "Cancelled";
grRequestItem.stage = "Request Cancelled";
grRequestItem.state = "7";
grRequestItem.active = "false";
grRequestItem.update();
}
// Request canceller
var grRequest = new GlideRecord("sc_request");
grRequest.addQuery("sys_id", requestSysID);
grRequest.query();
if (grRequest.next()) {
grRequest.setWorkflow(false); // Cancel request workflow
grRequest.approval = "rejected";
grRequest.request_state = "closed_cancelled";
grRequest.update();
}
})(inputs, outputs);
Conclusion: There are likely better ways to do this but this has worked for us.
- 745 Views