Approval Process with dynamic multiple approvers

Introduction

According to Salesforce Help: An approval process automates how records are approved in Salesforce. It specifies each step of approval, including from whom to request approval and what to do at each point of the process.

Assumptions

  • Position Custom Picklist field on the User object, with values: “Employee”, “Manager”, “Director”,
  • Apex class that dynamically sets multiple approvers based on the Position value from the User object and submits Approval Process,
  • 2-step Approval Process on Opportunity Object – for Manager and Director Approval, with manually chosen approver.
Approval Process

Problem

According to Salesforce Help, ProcessRequest class provides setNextApproverIds(ID[] nextApproverIds)method. Unfortunately, the List passed to this method must have only one element.

Screen from documentation

If we pass more than one User Id to this method, we will receive an error message:

System.DmlException: Process failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, missing required field: [nextApproverIds]: [nextApproverIds]

Solution

The workaround is to create a Process Request for each Approver:

public static void initApprovalProcess(List<Id> sObjectIdList) {
    List<Approval.ProcessSubmitRequest> approvalRequestList = new List<Approval.ProcessSubmitRequest>();

    List<User> managerUserList = [
            SELECT
                    Id
            FROM
                    User
            WHERE
                    Position__c = 'Manager'
    ];

    for (Id sObjectId : sObjectIdList) {
        for (User managerUser : managerUserList) {
            //Class to submit a record for approval.
            Approval.ProcessSubmitRequest approvalRequest = new Approval.ProcessSubmitRequest();
            //Sets the comments to be added to the approval request
            approvalRequest.setComments('Submitting request for approval.');
            //Sets the ID of the record to be submitted for approval. For example, it can specify
            // an account, contact, or custom object record.
            approvalRequest.setObjectId(sObjectId);
            //If the next step in your approval process is another Apex approval process, you specify
            // exactly one user ID as the next approver. If not, you cannot specify a user ID and this
            // method must be null
            approvalRequest.setNextApproverIds(new List<Id>{managerUser.Id});
            
            approvalRequestList.add(approvalRequest);
        }
    }

    Approval.process(approvalRequestList);
}

After submitting the Approval Process, we will set next approvers and proceed to Manager Approval step:

Approval related list

To proceed to the next step, we need to get all Process Work Items and for each record, we will create a new Process Work Item for every step:

//Helper method get Process Instance Work Items which represents a user’s pending approval request.
private static List<ProcessInstanceWorkitem> getProcessInstanceWorkItems(Id objectId) {
    return [
            SELECT
                    Id
            FROM
                    ProcessInstanceWorkitem
            WHERE
                    ProcessInstance.TargetObjectId =: objectId
    ];
}

//Helper method to create new Process Work Item Request record to process an approval request after it is submitted.
private static Approval.ProcessWorkitemRequest createProcessWorkItemRequest(Id approvalStepId, Id approverId, String approvalAction, String commentFromApprover){
    Approval.ProcessWorkitemRequest result = new Approval.ProcessWorkitemRequest();
    result.setComments(commentFromApprover);
    result.setAction(approvalAction);
    result.setWorkitemId(approvalStepId);
    if (approverId != null) {
        result.setNextApproverIds(new List<Id>{approverId});
    }
    return result;
}

To Approve, Reject or Recall Manager step, we will use this method:

public static void approveRecordForManager(Id sObjectId, String action, String commentFromApprover) {
    List<Approval.ProcessWorkitemRequest> approvalRequestList = new List<Approval.ProcessWorkitemRequest>();

    if (action == 'Approve') {
        List<User> directorUserList = [
                SELECT
                        Id
                FROM
                        User
                WHERE
                        Position__c = 'Director'
        ];

        for (ProcessInstanceWorkitem workItem : getProcessInstanceWorkItems(sObjectId)) {
            for (User directorUser : directorUserList) {
                approvalRequestList.add(createProcessWorkItemRequest(workItem.Id, directorUser.Id, action, commentFromApprover));
            }
        }
    } else if (action == 'Reject' || action == 'Removed') {
        for (ProcessInstanceWorkitem workItem : getProcessInstanceWorkItems(sObjectId)) {
            approvalRequestList.add(createProcessWorkItemRequest(workItem.Id, null, action, commentFromApprover));
        }
    }

    Approval.process(approvalRequestList);
}

After approving Manager Approval step, we will proceed to the Director Approval step:

Approval related list

As Director Approval is the last step, we won’t set next approvers and the method will be like this:

public static void approveRecordForDirector(Id sObjectId, String action, String comment) {
    List<Approval.ProcessWorkitemRequest> approvalRequestList = new List<Approval.ProcessWorkitemRequest>();

    for(ProcessInstanceWorkitem approvalProcessWorkingItem : getProcessInstanceWorkItems(sObjectId)) {
        approvalRequestList.add(createProcessWorkitemRequest(approvalProcessWorkingItem.Id, null, action, comment));
    }
    Approval.process(approvalRequestList);
}

And the whole process will be approved:

Approval related list

Important Notes

When we look at the Approval History related list, we will see that there are duplicated Approval Steps and approvers.

Approval related list

This is because each Approver has a separate Process Work Item Request record. It’s necessary to dynamically assign multiple approvers. To adjust this to the standard-like view, we will need to implement a custom Lightning Component or Visualforce page.

To Recall the Approval step, the Action value will be “Removed”.

ApprovalProcessService.approveRecordForManager('0061r00001Co2MoAAJ', 'Removed', 'Recalled by Manager');

Resources

5 7 votes
Article Rating
Subscribe
Notify of
guest
18 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Shakhawat
Shakhawat
9 months ago

It worked. Thanks.

Akanksha
Akanksha
1 year ago

For some reason it is not working for me. I have two step approval process where each level has multiple approvers. I am trying to approve one of the work items (Level 1) via apex but it gives me this error – “System.DmlException: Process failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, missing required field: [nextApproverIds]: [nextApproverIds]” x as it doesn’t allow me to proceed without nextApproverId. But I don’t want to trigger second Level (like Director) unless first Level (Manager) is fully approved. It works fine if I have single approver in level 1. What am I missing… Read more »

Akanksha
Akanksha
1 year ago

Hi Pawel
Another question – how have you designed the actually approval process? Does it have one or two approval steps? For some reason createProcessWorkItemRequest is not working for me to trigger the next level of approvers. It is automatically executing the final approval action once all level one approvers (like in you case – manager) approves the record.

Ben
Ben
1 year ago

Hi Pawel, how is the approveRecordForManager method called?

Akanksha
Akanksha
1 year ago

Hi..How is this method ‘approveRecordForManager’ triggered like how is it decided when to trigger then next level of approval?

Eran
Eran
1 year ago

Hi, I’m dealing with a similar requirement. Please verify – the above implementation would necessarily submit the approval to the multiple approvers at once, and they’ll be pending approval simultaneously and not sequentially. Is that correct?

yamini
yamini
1 year ago

hi Paweł Burzak m getting error in setnextapproverid ,this variable not accepting the list

Paul
Paul
1 year ago

When is the last time this was tested? I am using the code above, but I get an error: NO_Applicable_Process, No applicable approval process was found. I can create a single approval process in APEX successfully, but when I use the above code I get the error. I’m assuming that the error is because you can’t create multiple approval processes concurrently on the same object. I have seen other posts that suggest the same solution as above, but they are from 2013. I’m wondering if SF has made changes that make the above solution longer viable. Which is why I… Read more »

Close Menu
18
0
Would love your thoughts, please comment.x
()
x