Dynamic Approval Process in Salesforce using Apex and Trigger

Although this is very common approach and lots of articles are around on this topic, still I want to delineate the topic in other way. This topic covers complete scenarios for the approval process based on the Apex class.

Agenda of this article:

  1. Automatically submit the record for approval on the basis of field value.
  2. Automatically select the next Approver.
  3. Approve / Reject the record on the basis of field.

Assumptions:

  • Opportunity Object is used.
  • Approval Process is already set on the Opportunity.
  • Field “Next_Approver” will decide that who is going to approve the record.
  • There are three steps in the approval process.
  • There is no test class written and no check for mandatory fields needed for the trigger, as I have considered positive scenarios only.

Important URLS:

API of Approval Process classes:

  1. Apex process
  2. Apex ProcessRequest
  3. Apex_ProcessResult
  4. Apex_ProcessSubmitRequest
  5. Apex_ProcessWorkitemRequest

Steps of Standard approval process defined:

Approval Process Steps
Approval Process Steps

To achieve this, I am going to create the trigger named “AutomateApprove”.

Automatically submit the approval process using trigger – Apex:

Below method is used to automatically submit the approval process using trigger.

public void submitForApproval(Opportunity opp)
    {
        // Create an approval request for the Opportunity
        Approval.ProcessSubmitRequest req1 = new Approval.ProcessSubmitRequest();
        req1.setComments('Submitting request for approval automatically using Trigger');
        req1.setObjectId(opp.id);
        req1.setNextApproverIds(new Id[] {opp.Next_Approver__c});

        // Submit the approval request for the Opportunity
        Approval.ProcessResult result = Approval.process(req1);

    }

Class “ProcessSubmitRequest“ is used to automatically submit the approval process. We need to set following items while submitting the approval process using trigger:

  • Comment
  • TargetObjectId
  • NextApproverIds – if needed. Here Custom logic can be written to dynamically set approver for approval process. In this case I am using the custom field present on the Opportunity.

Automatically approve the approval process using trigger – Apex:

Below method is used to automatically approve the approval process using trigger.

/*
    * This method will Approve the opportunity
    */
    public void approveRecord(Opportunity opp)
    {
        Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
        req.setComments('Approving request using Trigger');
        req.setAction('Approve');
        req.setNextApproverIds(new Id[] {opp.Next_Approver__c});
        Id workItemId = getWorkItemId(opp.id); 

        if(workItemId == null)
        {
            opp.addError('Error Occured in Trigger');
        }
        else
        {
            req.setWorkitemId(workItemId);
            // Submit the request for approval
            Approval.ProcessResult result =  Approval.process(req);
        }
    }

Class “ProcessWorkitemRequest“ is used to automatically approve the approval process. We need to set following items while submitting the approval process using trigger:

  • Comment
  • TargetObjectId
  • NextApproverIds – if needed
  • WorkItemId – Custom code required to get this

Get the WorkItemId for the pending approval process of the Object:
This is the tricky part, if the Submission and approval of the record is done in single code block then it’s very easy to get the WorkItemId of the needed process.

Here the standard code snap provided:

After Submission the approval process using Apex we get the object of class “ProcessResult“.

Approval.ProcessResult result = Approval.process(req1);

And from the class we can get workitemid as :

List<Id> newWorkItemIds = result.getNewWorkitemIds();

And set the id like:

req2.setWorkitemId(newWorkItemIds.get(0));

Other method to get the “WorkItemId” :
The above code was not usable in our scenario as the submission and approval or rejection was done at different level. So I have created following utility method to get the WorkitemId of the supplied Object’s id. Here I have considered that only one workitem will present.

public Id getWorkItemId(Id targetObjectId)
    {
        Id retVal = null;

        for(ProcessInstanceWorkitem workItem  : [Select p.Id from ProcessInstanceWorkitem p
            where p.ProcessInstance.TargetObjectId =: targetObjectId])
        {
            retVal  =  workItem.Id;
        }

        return retVal;
    }

As you can see, we need to query the object “ProcessInstanceWorkitem“ to get workitemId of the object.

Automatically reject the approval process using trigger – Apex:
Following code is used to reject the approval process using code.

public void rejectRecord(Opportunity opp)
    {
        Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
        req.setComments('Rejected request using Trigger');
        req.setAction('Reject');
        //req.setNextApproverIds(new Id[] {UserInfo.getUserId()});
        Id workItemId = getWorkItemId(opp.id); 

        if(workItemId == null)
        {
            opp.addError('Error Occured in Trigger');
        }
        else
        {
            req.setWorkitemId(workItemId);
            // Submit the request for approval
            Approval.ProcessResult result =  Approval.process(req);
        }
    }

Execution of Approval process using Apex and trigger:

Approval Process Log After Execution
Approval Process Log After Execution

Complete code:

trigger AutomateApprove on Opportunity(After insert, After update)
{

    for (Integer i = 0; i < Trigger.new.size(); i++)
    {
     try
     {
        if( Trigger.isInsert || (Trigger.new[i].Next_Step__c == 'Submit' && Trigger.old[i].Next_Step__c != 'Submit'))
        {
           submitForApproval(Trigger.new[i]);
        }
        else if(Trigger.isInsert || (Trigger.new[i].Next_Step__c == 'Approve' && Trigger.old[i].Next_Step__c != 'Approve'))
        {
             approveRecord(Trigger.new[i]);
        }
        else if(Trigger.isInsert || (Trigger.new[i].Next_Step__c == 'Reject' && Trigger.old[i].Next_Step__c != 'Reject'))
        {
             rejectRecord(Trigger.new[i]);
        }
     }catch(Exception e)
     {
         Trigger.new[i].addError(e.getMessage());
     }
    }

    /**
    * This method will submit the opportunity automatically
    **/
    public void submitForApproval(Opportunity opp)
    {
        // Create an approval request for the Opportunity
        Approval.ProcessSubmitRequest req1 = new Approval.ProcessSubmitRequest();
        req1.setComments('Submitting request for approval automatically using Trigger');
        req1.setObjectId(opp.id); 

        req1.setNextApproverIds(new Id[] {opp.Next_Approver__c});

        // Submit the approval request for the Opportunity
        Approval.ProcessResult result = Approval.process(req1);
    }

	/**
	* Get ProcessInstanceWorkItemId using SOQL
	**/
    public Id getWorkItemId(Id targetObjectId)
    {
        Id retVal = null;

        for(ProcessInstanceWorkitem workItem  : [Select p.Id from ProcessInstanceWorkitem p
            where p.ProcessInstance.TargetObjectId =: targetObjectId])
        {
            retVal  =  workItem.Id;
        }

        return retVal;
    }

    /**
    * This method will Approve the opportunity
    **/
    public void approveRecord(Opportunity opp)
    {
        Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
        req.setComments('Approving request using Trigger');
        req.setAction('Approve');
        req.setNextApproverIds(new Id[] {opp.Next_Approver__c});
        Id workItemId = getWorkItemId(opp.id); 

        if(workItemId == null)
        {
            opp.addError('Error Occured in Trigger');
        }
        else
        {
            req.setWorkitemId(workItemId);
            // Submit the request for approval
            Approval.ProcessResult result =  Approval.process(req);
        }
    }

    /**
    * This method will Reject the opportunity
    **/
    public void rejectRecord(Opportunity opp)
    {
        Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
        req.setComments('Rejected request using Trigger');
        req.setAction('Reject');
        Id workItemId = getWorkItemId(opp.id);   

        if(workItemId == null)
        {
            opp.addError('Error Occured in Trigger');
        }
        else
        {
            req.setWorkitemId(workItemId);
            // Submit the request for approval
            Approval.ProcessResult result =  Approval.process(req);
        }
    }
}

Note on possible errors:

1.If you have the “manual Selection of approver” enabled for your approval process/steps then you must specify the approver in the trigger, else you will get an error something like:

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

2.If you set the wrong WorkitemId then may get following error:

Process failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, invalid cross reference id: []

updated on 25-March-2015

Question :
1. Can we add multiple users (Parallel Approval process) as a aprrover automated using above code?
Ans : No. Logic in above code is that we need to select next approver option as “manual”. Currently we cannot use multiple users manually in approval process, you can vote this idea for this feature support. Only solution is to have multiple steps for each approver.

Posted

in

, ,

by


Related Posts

Comments

40 responses to “Dynamic Approval Process in Salesforce using Apex and Trigger”

  1. Royampatuan Avatar
    Royampatuan

    Can we have hide-when formula on custom buttons?

    1. JitendraZaa Avatar
      JitendraZaa

      We cannot hide / unhide custom buttons on Standard Page layout

  2. Ramarajur26 Avatar
    Ramarajur26

    Thank you Very much . I used this code  working fine and Errors which you have given helped me allot. thank you

  3. Solees Avatar
    Solees

    This really helped me a lot, thank you so much my friend !!!

  4. aman Avatar
    aman

    My requirement is to add multiple approvers when i am going to set multiple approvers for a particular code. It showing me a error of Required Field missing.
    List userIds=new List();

    userIds.add(‘xxxxxxxxxxxxxxx’));
    userIds.add(‘xxxxxxxxxxxxxxx));
    Approval.ProcessSubmitRequest req1 = new Approval.ProcessSubmitRequest();
    req1.setComments(‘Submitting request for approval automatically using Trigger’);
    req1.setObjectId(objId);
    req1.setNextApproverIds(userIds);
    // Submit the approval request for the Opportunity
    Approval.ProcessResult result = Approval.process(req1);

    Getting this Error…
    “System.DmlException: Process failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, missing required field: []”

  5. shaambo Avatar
    shaambo

    Dude..this is shaambo….how are you??..:)
    .was wondering is there no way apart from writing a trigger to automatically submit a approval process apart from writing a trigger?..

    1. JitendraZaa Avatar
      JitendraZaa

      Hi Shaamboo,

      Yar, I dont think that there is any other way. We have to call this method “Approval.ProcessSubmitRequest();”

    2. Abhishek Avatar
      Abhishek

      Hi this may be a comment too late and also may be known to you by now. Just a solution that would help other folks.
      We can create a button and set execute Javascript and call apex class through soap sforce.execute()

  6. Jaspreet Singh Avatar
    Jaspreet Singh

    but this will work only for three step approval. What if there are more approvers. Is there a way to create approval steps dynamically?

  7. madhu Avatar
    madhu

    hi Jit.this is madhu.i have small dought.
    how to we can attach pdf’s in lead object.can u suggest me pls.

    1. JitendraZaa Avatar
      JitendraZaa

      Hi Madhu,
      You can attach PDF to Lead in “Notes and Attachment” section if its less than 5MB.

  8. Beginer Avatar
    Beginer

    public Id getWorkItemId(Id targetObjectId)
    {
    Id retVal =null;
    for(ProcessInstanceWorkitem workItem : [Select p.Id from ProcessInstanceWorkitem
    where p.ProcessInstance.TargetObjectId =: targetObjectId])
    {
    retVal = workItem.Id;
    }
    return retVal;
    }

    I am getting null value(for loop) and i am using this—req.setNextApproverIds(new Id[] {UserInfo.getUserId()});
    please help me

  9. Dinesh Singla Avatar
    Dinesh Singla

    hi ,
    can we get the approval Status in trigger.
    means approval process is in pending,rejected or approved state

    1. JitendraZaa Avatar
      JitendraZaa

      Simplest solution – create hidden field of type text. On approval process update this field properly on stages. Use this field inside trigger for any post processing

      1. Dinesh Singla Avatar
        Dinesh Singla

        Thanks

      2. Deep S Avatar
        Deep S

        Can we use custom Assigned to field(which contains queue and other 3 users to reassign it to themselves) ?

  10. Dinesh Singla Avatar
    Dinesh Singla

    hi ,
    in Campaign there is a look up of parent Campaign. in this look up its showing all the active Campaign,All Campaign can we hide that drop down and can we use our filter criteria ?

  11. Lee Avatar
    Lee

    I’m new to the approval process, so this was probably obvious to others but it was not to me and/or I’m getting something wrong.

    In order for the step to work where you explicitly set the approver, you have to set the approval process approval step to “manual Selection of approver”, anything else and it won’t work.

    thanks for the great code!

  12. Dipak Nikam Avatar
    Dipak Nikam

    How do we write a test method for the above code. I tried added above code and its working fine in my case. But I am not getting expected test coverage for above piece of code. I know the issue, in test method ProcessInstanceWorkitem id coming as null so I am facing validation error given in else loop. I tried adding seeallData = true so that test method will look into organization configuration, but I am still facing issues.
    Do I have to use test.isRunningTest attribute to ignore piece of code in test method.

    Thanks in advance and nice post !!!!

  13. Dinesh Avatar
    Dinesh

    Hi, I have a query about this… an object like opportunity may have multiple approval processes set up. How does apex pick the correct approval process when the process name is not mentioned anywhere in the code?

  14. Atikrant Avatar
    Atikrant

    How to get Approvals or Approval Process using REST API?

  15. Deepti V Avatar
    Deepti V

    i have copied same code but i am getting this error:

    Process failed. First exception on row 0; first error: NO_APPLICABLE_PROCESS, No applicable approval process was found.: []

    1. Jitendra Zaa Avatar

      There may be 2 reasons for this issue :

      1. Approval process is not activated

      2. Entry criteria of record doesnt match.

      Check this thread also – http://salesforce.stackexchange.com/questions/16214/no-applicable-process-no-applicable-process-found

  16. sujesh Avatar
    sujesh

    Hi, is there any way I can pull who is the next approver(s) into the record field. I need to bring a report of list of count of records need to be approved by the approvers. Thank you

  17. raghavendra Avatar
    raghavendra

    Hi, Can we customize for one step because based on other field i have select approvers it is for third step

  18. srinivas Avatar
    srinivas

    Hi Jithendra,
    I am having two custom objects A and B.These two are in Lookup Relationship. I am creating a approval process on Object B ,I need the approver to be record owner of Object A. To achieve this i created a field on object B with lookup to User Object.with trigger i am updating the owner on Object B and submitting for approval .if the owner of the object A record is user then my scenario is working fine if it owner is ‘Queue’ then how can i achieve this ..
    please help me out…

  19. Sumeet Avatar
    Sumeet

    Hi Jitendra,

    Whilre replicating the above blog in my env as practice, I observed that TargetObjectId was not required while providing approval via Apex. Just want to bring it to your attention as you have mentioned that its needed. It might have become optional after few release. Very helpful though 🙂 Thanks !

  20. ss Avatar
    ss

    Will Submitting an approval process automatially through Apex Trigger , causes it the record to be unlocked? As when i am implementing it , unlike the standard behaviour- record trigger for Approval remenained unlocked… What will you advice?

  21. Vinay Naidu Avatar
    Vinay Naidu

    Hi Jeetu Sir,

    While creating an approval process, in a test class i am unable to save my code as it is giving me error –
    Compile Error: Invalid type: Approval.ProcessSubmitRequest at line 10 column 50

    My code it –
    public class TestApproval {
    void submitAndProcessApprovalRequest() {
    // Insert an account
    Account a = new Account(Name=’Test’,annualRevenue=100.0);
    insert a;
    system.debug(‘Account a = ‘+ a);
    User user1 = [SELECT Id FROM User WHERE Alias=’SomeStandardUser’];

    // Create an approval request for the account
    Approval.ProcessSubmitRequest req1 = new Approval.ProcessSubmitRequest();
    req1.setComments(‘Submitting request for approval.’);
    req1.setObjectId(a.id);

    // Submit on behalf of a specific submitter
    req1.setSubmitterId(user1.Id);

    // Submit the record to specific process and skip the criteria evaluation
    req1.setProcessDefinitionNameOrId(‘PTO_Request_Process’);
    req1.setSkipEntryCriteria(true);

    // Submit the approval request for the account
    Approval.ProcessResult result = Approval.process(req1);

    // Verify the result
    System.assert(result.isSuccess());

    System.assertEquals(
    ‘Pending’, result.getInstanceStatus(),
    ‘Instance Status’+result.getInstanceStatus());

    // Approve the submitted request
    // First, get the ID of the newly created item
    List newWorkItemIds = result.getNewWorkitemIds();

    // Instantiate the new ProcessWorkitemRequest object and populate it
    Approval.ProcessWorkitemRequest req2 =
    new Approval.ProcessWorkitemRequest();
    req2.setComments(‘Approving request.’);
    req2.setAction(‘Approve’);
    req2.setNextApproverIds(new Id[] {UserInfo.getUserId()});

    // Use the ID from the newly created item to specify the item to be worked
    req2.setWorkitemId(newWorkItemIds.get(0));

    // Submit the request for approval
    Approval.ProcessResult result2 = Approval.process(req2);

    // Verify the results
    System.assert(result2.isSuccess(), ‘Result Status:’+result2.isSuccess());

    System.assertEquals(
    ‘Approved’, result2.getInstanceStatus(),
    ‘Instance Status’+result2.getInstanceStatus());
    }
    }

  22. DevelopmentFreak Avatar
    DevelopmentFreak

    Hi Jitendra,

    Can you please help me to understand the difference between ProcessResult and ProcessRequest Class?

  23. Thayalan Avatar
    Thayalan

    HI Jitendra,

    Can I set multiple approvers Approval.ProcessSubmitRequest ? I was able to do that that I am getting multiple approval submissions.

    how to get a single submission with multi apporvers? code below:

    List requests = new List();
    List approverIds = AutoSubmit.getMap(l.Approvers__c);

    for (Id approverId : approverIds) {
    Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
    // req.setComments(‘Auto submission’);
    req.setObjectId(l.id);
    //req.setSkipEntryCriteria(true);
    req.setNextApproverIds(new List{approverId } );
    requests.add(req);

    }

    List results = Approval.process(requests);

  24. Praveen Avatar
    Praveen

    Hi ,My requirement is to customize reject button to show picklist field,provision to select user a reason of rejection and save the record.How we can do only for rejection.

  25. Shubham Sharma Avatar

    it is not working without giving modify all permission on object level

  26. Vineet Avatar
    Vineet

    Hi Jitendra

    I have one scenario, pls check below

    On Account, I have an approval process. and it’s multistep approval.
    Let’s assume its a 2 step approval.
    but in the approval steps, No approvers are mentioned.
    for the approvers, we are not having user lookups on Account but
    We have an Account Role object (Child of Account) from where approvers will be picked up dynamically based on the role (field on the object level) of the user.

    Now suppose in the Account Role we have 3 users (3 records which contains user with the role) with the role Manager so in that case, approvers in the “first step” will be these 3 users and any of them can approve the record, and once approved the record will go in the next step to be approved.

    Now I want to assign approvers dynamically in each step of the approval as explained in the above scenario.

    Can I call the same defined approval in apex and then in apex set the approvers for different approval steps and fire the approval??

    Any suggestions would be appreciated.

  27. Deep S Avatar
    Deep S

    Hii, please reply, I really need a reply on this. My requirement is, I need to update record status field to “In Process” whenever record submitted for approval is reassigned from Queue to any specific person. I have written an apex class on this but I need to call it using trigger on ProcessInstance object which is impossible. So, seeing your post, I am thinking, is it possible ?

  28. Ihor Avatar
    Ihor

    I am not sure that someone didn’t mention it earlier, but submitting for approval is a DML statement and you will exceed DML statements limit in case there will be 150+ records in a trigger.

  29. Santhiya Durai Avatar
    Santhiya Durai

    How can I use Advanced Approvals in Process Builder?

  30. KSkumar Avatar
    KSkumar

    We are sending an mail for the opp is approved or rejected
    In Approval history,
    Assigned to is == actual approver,
    the both feilds should be same name How will write the code using apex ?

  31. Lata Mahore Avatar
    Lata Mahore

    Hi Jitendra,
    I want to Customize Reassign Action. Please help me how do I do that?

  32. Milos Avatar
    Milos

    Hi, can I send approval process to external user. If I can, which ID I need to enter?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Jitendra Zaa

Subscribe now to keep reading and get access to the full archive.

Continue Reading