I

f you’re like many of the customers I’ve worked with, you may have dealt with the frustration of having excess or redundant approval requests come to you from ServiceNow. This happens very often simply because the same user may be responsible for various different tasks in the system. For example, on a change request, I may be asked to approve as the ‘Requested by’ person’s manager, then again because I own one of the affected CIs, and then again because I’m a member of the Change Advisory Board! While this behavior may be desirable in certain situations, most of the time it’s completely redundant and annoying to end users. If I’ve already indicated my approval on a change as a manager, why should I be asked to approve again later? I’ve come up with what I think is a pretty effective solution to this problem that I’ll share here in this article.

Redundant Approvals

The Solution…

At least in my mind, in order for this solution to be successful, it needs to meet a few criteria…

  1. First and foremost, the user should not be notified of an approval request if they’ve already approved a record.
  2. We need to maintain the history of the approval record for audit purposes and accurately reflect what happened to the redundant approval requests. This means that deleting or aborting record insertion is out of the question!
  3. We cannot negatively impact the approval workflow by interrupting the normal approval process.

The first two criteria need to be met at the same time. Preventing the approval request could be done in a couple of different ways. One way would be to somehow manipulate the workflow activity to check if the user has previously approved. The drawback to this approach is that it requires hacking the workflow approval activities…which would be a significant upgrade risk, or adding custom code to every single approval activity in every single workflow…which would be a maintenance nightmare.

That leaves us with intercepting the creation/update of the approval records in a business rule. Using a ‘before’ insert/update business rule we can evaluate every ‘requested’ approval record, run a script to determine if the approval is for a user that has already approved this item, and then adjust the approval record by setting the approval state to ‘No Longer Required’…all before we trigger any update or notification to the approving user. We can also add some approval comments so that we can accurately reflect why the approval isn’t required anymore. Adding the following business rule to the ‘Approval [sysapproval_approver]’ table accomplishes that for us.

‘Duplicate Approval Requests Not Required’ Business Rule
Name: Duplicate Approval Requests Not Required
Table: Approval [sysapproval_approver]
When: Before
Insert/Update: true
Condition: current.state.changesTo(‘requested’)
Script:

//Check to see if user has previously approved
approveDuplicateApproval();

function approveDuplicateApproval(){
    //Must have link to record being approved
    if(current.document_id || current.sysapproval){
        //Query for approval records for this user/record
        var app = new GlideRecord('sysapproval_approver');
        //Handle empty document_id and sysapproval fields
        if(!current.document_id.nil()){
            app.addQuery('document_id', current.document_id);
        }
        else if(!current.sysapproval.nil()){
            app.addQuery('sysapproval', current.sysapproval);
        }
        app.addQuery('approver', current.approver);
        app.addQuery('state', 'approved');
        //Optionally restrict to current workflow
        //app.addQuery('wf_activity.workflow_version', current.wf_activity.workflow_version);
        app.query();
        if(app.next()){
            //If previous approval is found set this approval to 'approved'
            current.state = 'not_required';
            current.comments = "Approval marked by system as 'Not Longer Required' due to a previous approval on the same record by the same user.";
        }
    }
}

While the above script works great in manipulating the approval records on its own, it fails in the third criteria mentioned above…it can negatively impact the workflow in certain situations. This is because the workflow processing that happens after an approval status is changed isn’t seeing the true value of the approval request to process the workflow approval activity correctly. In my testing this couldn’t be done in a ‘before’ or ‘after’ business rule due to the timing of the updates. What is needed is to run the workflow checks one more time, and ensure that it happens after all of the above manipulation happens. The best way I could find to do this is through a separate ‘async’ business rule. The only downside to this async business rule is that it may not process immediately depending on the load on your system. Generally it should process within a few seconds though so for all practical intents and purposes it’s a non-issue.

‘Run parent workflows (Not Required)’ Business Rule
Name: Run parent workflows (Not Required)
Table: Approval [sysapproval_approver]
When: async
Priority: 200 (This is important to be set to something greater than 100 to ensure that this runs ASAP!)
Insert/Update: true
Condition: current.state == ‘not_required’
Script:

// Run any workflows for our parent so they can check the approval states
runWorkflow_userApprove();

function runWorkflow_userApprove() {
   var id = current.sysapproval.nil() ? current.document_id : current.getValue('sysapproval');
   var table = current.source_table.nil() ? 'task' : current.source_table;
   if (id != null && table != null ) {
      var gr = new GlideRecord(table);
      if (gr.get(id)) {
           new Workflow().runFlows(gr, 'update');
        }
   }
}

With the above solution in place, you should have created a very effective way to prevent the issue of redundant approval requests for the same user against the same record in ServiceNow.