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.
The Solution…
At least in my mind, in order for this solution to be successful, it needs to meet a few criteria…
- First and foremost, the user should not be notified of an approval request if they’ve already approved a record.
- 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!
- 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.
Name: Duplicate Approval Requests Not Required
Table: Approval [sysapproval_approver]
When: Before
Insert/Update: true
Condition: current.state.changesTo(‘requested’)
Script:
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.
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:
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.
Thanks, this is great, the only issue that I seem to be having is that its changing it to no longer required but doesn’t continue on the workflow and just sits. I tried changing it to change it to approved instead and that didn’t continue either. Any thoughts?
Only thought I have is to check and make sure you’ve correctly added the second business rule in the article above. Without that, your workflow will hang until an update is made to the ticket.
Mark,
I verified that the second business rule was the same as in the post. I checked with ServiceNow and it appears that out of box when you change something to No Longer Required it will not auto continue the workflow. They said I would need to modify the ‘SNC – Run parent workflows (Approval)” I tried adding the condition of “current.state.changesTo(‘No Longer Required’)” and that didn’t seem to do anything. Any thoughts?
I don’t think ServiceNow support is telling you the right thing. Without access to your system it’s almost impossible to see what’s going on or make a suggestion. I know that this same solution has worked for several other people though so I don’t think it’s a conflict with out-of-box behavior. Only suggestion I can make is to try and set it up in a demo environment and see if it works there. If it does that will at least let you know a little better where you might look in your own environment.
Hi Mark, How would this work on an Order Guide ? I want a manager to only approve his Items once and I don’t want him to get multiple emails with an approval for each item. As always, any help would be greatly appreciated. Thanks
Order guides would be a completely different case. If you only want one approval for an order guide, then the approval should probably happen at the request level rather than the individual item level.
How would put all the items on hold until the Request of approved and then propogate them ? Sorry for all the questions, but I am new at creating order guides. Thanks again.
Unless something has been modified in your instance, the workflows for all request items should automatically wait for the parent request record to be approved. That’s the way it is by default. Once the request is approved, all of the item workflows can start. If it’s not behaving that way, you’ll need to take a closer look at the workflows associated to the request table.
I don’t know that. Thank you so much, I will give it a try.
Can we do it via workflow script without using business rules because i want to it for some stages only instead of all.
Thanks
I don’t know of an easy way to do that since all of the code for that is handled within the workflow activity. You could adjust your business rule to look at the ‘wf_activity’ field on the approval table (it’s inactive by default) and selectively apply this logic based on the workflow activity field associated to the approval.
Can you help me how to recognize change work flow stage via your script. For instance there are several stages in change workflow and i want to know at which stage the script is running. Here i want to block not to run for some stages.
Thanks
This breaks in eureka. There is something putting it back to required, looks to be an after busines rule introduced but I cannot find it. Any idea?
Can I use these business rules to only affect one catalog item and not all approvals?
You could. You would need to adjust the query so that it only includes the catalog item or workflow activity you care about. The adjustment would need to be made to this area of the script…
//Optionally restrict to current workflow
//app.addQuery(‘wf_activity.workflow_version’, current.wf_activity.workflow_version);
Hi Mark,
Thank you for the script. Sorry I do not know any scripting and I just copied your business rule and it is working perfect. Now I would like to use your script only for two workflows named “HR generic_workflow” and “it generic_workflow”. How to apply this names into below script line?
//app.addQuery(‘wf_activity.workflow_version’, current.wf_activity.workflow_version);
thank you so much
If you want to restrict to a particular workflow you would probably be best off targeting that workflow by name, like this…
app.addQuery(‘wf_activity.workflow_version.name’, ‘HR generic_workflow’).addOrCondition(‘wf_activity.workflow_version.name’, ‘it generic_workflow’);
Dear Mark, Thank you so much. I really appropriate your help.
It is great. thank you Mark
I’m troubleshooting the same issue. We r on Helsinki already. So not sure if this is still working.
What I see is that the async business rule seems not to have a condition …
ServiceNow: “Note: Asynchronous business rules do not have access to the previous version of a record. Therefore, these GlideElement methods do not work with async rules: changes(), changesTo(), changesFrom().”
… so I’m wondering how the 2nd Business Rule mentioned above will be fired without the condition set. Sorry I’m not the scripting guy 😉
I think you’re probably right about the ‘changesTo’ in the Async script above. You should be safe to change the condition to current.state == ‘not_required’ and it should work fine though. Normally I wouldn’t recommend running a business rule every time a state equals that value just because it’s going to be running way more than is necessary, but in this case an approval really wouldn’t be updated very often (if at all) once it reaches a ‘not required’ state. I’ve updated the article above reflecting this change.
Thanks Marks for the reply. Unfortunately conditions are somehow not possible to be used when set to async even there is a condition field on top of the script … but entering the condition – current.state == ‘not_required’ .. the error occurs: “JavaScript parse error at line (1) column (18) problem = illegal character (; line 1)” …
any idea?
Might be a copy/paste issue. You might try typing it in directly and see if that helps. You could also use the condition builder widget on the business rule to provide the condition.
When u change the “when” to “asncy” the filter conditions disappear .. . Same on my developer instance … there might be a reason for that ?! I could not find any UI Action that is triggering that (on the sys_script table) …
I’m not sure what else you can try then. I can enter that condition without issue into the standard ‘Condition’ field on the business rule in my Helsinki instance. You’re correct that the condition builder is removed, but the standard field should still work. You may try typing it in manually or testing with a completely different condition to see if anything works at all for you.
Thanks! That was the problem (never have seen that before … ). Saving possible.
Unfortunately it is not working anyhow. Need to further investigate. Have 2 approvals sent to to different groups … meaning double mails in case one person is in both groups.
Hi again,
still struggling.
I tried in the developer instance – using the OoB Change Request Workflow and added a 2nd group to the CAB approval approvers and put same person in both groups.
With or without the Business Rules … same is happening.
Approval mail generated twice. After approving one of the approval tasks .. the 2nd approval is still open to be approved (for the same user).
No idea what is going wrong … any idea on that?
I think I see what’s going on now. All of your approvals are being included in the same approval activity. That’s not what this utility handles. This is for different approvals that are added later in an approval workflow. In your situation the workflow activity should handle this for you (or you need to code in logic in the workflow activity to make sure the same person isn’t added twice. I think the reason ServiceNow has left it this way is that you could argue (particularly for a group approval activity) that each user should need to approve on behalf of each group they are a member of.
OOOOOOK. This does make sense now.
I think I was close to find that out myself as I was wondering about the logic of the condition. Now this is clear.
Thanks a lot!