Mastering the Recall of an Approval Process

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:

  1. Object Model of Approval Process Objects
  2. Querying the Approval Process Objects
  3. Updating the Approval Process Objects
  4. Gotchas
  5. 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 Process objects.

// 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
FROM   ProcessInstance
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 Removed is Recalled. 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.


[code language=”javascript”]
Approval.ProcessWorkItemRequest req = new Approval.ProcessWorkItemRequest();
req.setAction(‘Removed’); // This means to remove/recall Approval Request
req.setComments(‘Apex Recall Approval.’);</code>
Approval.ProcessResult result = Approval.process(req);
// handle the result…


4. Gotchas

You may run into a few hiccups along the way as I did. Look here for some tips.

  1. While we can query the ProcessInstance object from the database, we cannot update it. Specifically, we cannot update the Status field. You will get the following error: Field is not writeable: ProcessInstance.Status. Instead, you need to get the ProcessIinstanceWorkItem record and set its Status value as shown in the code above.
  2. You cannot update an Approval Process Request that has already been either Rejected or Approved. The above only works on Pending Requests. 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

  1. Create a new custom field on your object such as Recall_Approval__c that is unchecked by default.
  2. Add a new Approval Step #1 in your Approval Process that only moves the record to the next step if Recall_Approval__c is 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.
  3. 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.

Happy Recalling!