January 19, 2016
Mastering the Recall of an Approval ProcessBrenda Finn
At one time or another, most Apex developers have had the need to interact with Approval Processes programmatically.Salesforce does make this possible, but the sequence of steps to do so is not as straightforward or intuitive as one might like.
There are plenty of blog articles out there that already describe how to create and subsequently handle an Approval Request in Apex. However, this article explains how to update the Status for an existing Approval Process Request through Apex.
We will explore the following topics:
- Object Model of Approval Process Objects
- Querying the Approval Process Objects
- Updating the Approval Process Objects
- Bonus : How to Imitate Recall of an Approved Instance
1. Approval Process Object Model
First lets begin with a few definitions and the object model of the different Objects in the database that support the Approval Process.
2. Querying Approval Process Objects
Lets assume you have a pending approval for a
Quote and you want the user to be able to remove this Approval Process Request as you realize that you need to do further updates to the
Quote before submitting it for Approval. You create a button that invokes a call to the server updating the
Status field and the record type on the
Quote record directly and the Approval Process Request is removed. However, this does not create an Approval History record which would be quite useful. So how can we do this and have it be part of the official Approval History?
We are going to use the same approach. We will have a button on the
Quote detail page that will contact the SF server to recall the
Quote. The difference is we will update the actual Approval Process Request record, thereby creating a corresponding Approval History record.
First we need to get the existing Approval Process Request. This is done through DML operations on the set of
// First we get the instance of the Approval Process to update.
// TargetObjectId is the Quote Id that is sent to our server method.
// Status = ‘Pending’ for a request that has not been handled yet.
ProcessInstance instance = [SELECT Id
WHERE TargetObjectId='0Q0R0000000A1Bq' AND Status='Pending' limit 1];
// Now that we have our Approval Request instance, lets get the child WorkItem
ProcessInstanceWorkitem workItem = [SELECT Id FROM ProcessInstanceWorkitem
where ProcessInstanceId=:instance.Id limit 1];
Now we have the Pending Approval Request and we need to actually update it.
3. Updating the Approval Process Objects
To retrieve the Approval Process Request and its Work Item records, we needed to perform queries against the database accessible via the Apex SOAP API. To actually update the Approval Process Request, we use the standard Apex API and all of its classes exist in the
Approval namespace. This is what got me – I had to connect the dots myself. Switching between using the Apex SOAP API and the standard API. But once I had the trail of breadcrumbs, I was back on track.
First we create an
Approval.ProcessWorkItemRequest that corresponds to the
ProcessInstanceWorkItem we found earlier. We associate it with that database record by setting the
WorkItemId attribute. Finally we set the status of the
WorkItem to our desired status. In this case, we wish to recall the request so the
Action property to value of ‘
Removed’. (SIDE NOTE: This is confusing to me in two ways. First, the field name for
Action in the UI is
Status. Second, the
Status field value for
. As you can see – I am easily confused.) But I digress…
Finally, we update the Approval Request by sending our request object to the Approval Process. This is the same action that would be executed if the user were to select the “Recall Approval Request” in the Approval History Related List.
Lets take a look at the code.
Approval.ProcessWorkItemRequest req = new Approval.ProcessWorkItemRequest(); req.setWorkItemId(workItem.Id); req.setAction('Removed'); // This means to remove/recall Approval Request req.setComments('Apex Recall Approval.');</code> <code> Approval.ProcessResult result = Approval.process(req); // handle the result... </code>
You may run into a few hiccups along the way as I did. Look here for some tips.
- While we can query the
ProcessInstanceobject from the database, we cannot update it. Specifically, we cannot update the
Statusfield. You will get the following error: Field is not writeable: ProcessInstance.Status. Instead, you need to get the
ProcessIinstanceWorkItemrecord and set its
Statusvalue as shown in the code above.
- You cannot update an Approval Process Request that has already been either
Approved. The above only works on
PendingRequests. Good news is there is a way to mimic this behavior – see the Bonus below.
5. Bonus : How to Imitate Recall of an Approved Instance
So you want to reject or recall a record that has already been approved. This is not possible in Salesforce as all approval process instance records are gone so there is no way to update the
WorkItems. However, if you have a requirement to create an entry in the Approval History list when recalling an object after it has been approved, there is a workaround that might be of use. Follow these steps
- Create a new custom field on your object such as
Recall_Approval__cthat is unchecked by default.
- Add a new Approval Step #1 in your Approval Process that only moves the record to the next step if
false. Otherwise, it auto-rejects the record. It is important that it is the first step so that none of the approval actions are fired such as emails or field updates.
- Add a button to the Object detail page for “Recall Approval”. The button sets the checkbox field to
true, and resubmits the record for Approval. This will cause the Approval Process to auto-reject the record and you get a Rejected entry in your Approval History List.
Not perfect by any means…but feasible.
I hope that by writing this blog article I have been able to help others struggling with the same issue and saved them some time when it comes to dealing with Approval Processes and Apex code.