Replica Of Approval Process History Using Apex And Visualforce Page


We can create the replica of standard salesforce Approval Process History. So, I have created a generic code that can be used as in-line VF page to visualize the customized view of approval process. Feel free to use the below code for fulling your client requirement & comment or ask anything related to below code. Don’t forget to appreciate the blog if you really like my this blog post.

~ Apex Class Code (Controller) ~

/*
    Copyright (c) ajay-gupta.com
    All rights reserved.
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.
    3. The name of the author may not be used to endorse or promote products
       derived from this software without specific prior written permission.
    
    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
 * Author : Ajay Gupta
 * ClassName : CustomApprovalProcessController
 * Description : This class generates the customized view of Approval Process 
 *               History.
 **/
public with sharing class CustomApprovalProcessController {


    //Properties Declaration
    transient public ApprovalList aSW {get; private set;}
    private Id recordId;
    private Id retURLRecordId;
    private String ORG_LNK;
    
   //Properties Construnctor
    public CustomApprovalProcessController(ApexPages.StandardController controller) {
        retURLRecordId = recordId = ApexPages.currentPage().getParameters().get('id');
        ORG_LNK = 'https://'+URL.getSalesforceBaseUrl().getHost();
        if(!String.isBlank(recordID)){
            aSW = new ApprovalList();
            aSW = generateData();
        }            
    }  
    ApprovalList generateData(){
        ApprovalList approvalResultForObject = new ApprovalList();
        List<ApprovalStepWrapper> aSW = new List<ApprovalStepWrapper>();
        String recallApprovalProcessLink;
        Boolean isSubmitForApproval = true;
        for(ProcessInstance pI:getProcessHistory(recordId).values()){
            Map<Id,List<ProcessInstanceHistory>> mapOfProcessNodeIdAndProcessInstanceHistory = new Map<Id,List<ProcessInstanceHistory>>();
            Set<Id> processNodeId= new Set<Id>(); 
            
            for(ProcessInstanceHistory sWI:pI.StepsAndWorkitems){
                if(processNodeId.size() ==0)
                    processNodeId.add(sWI.ProcessNodeId);
                else if(processNodeId.size()>0 && processNodeId.contains(sWI.ProcessNodeId)!= NULL)
                    processNodeId.add(sWI.ProcessNodeId);
            }
            
            for(Id pNId: processNodeId){
                ApprovalStepWrapper aSWr = new ApprovalStepWrapper();
                for(ProcessInstanceHistory sWI:pI.StepsAndWorkitems){
                    if(sWI.processNodeId == pNID){
                        aSWr.listOfSteps.add(sWI);
                    }
                    if(sWI.StepStatus == 'Pending'){
                        aSWr.workItemApproveOrRejectLink = ORG_LNK+'/p/process/ProcessInstanceWorkitemWizardStageManager?id='+sWI.Id;
                        aSWr.workItemReassignLink =  ORG_LNK+'/'+sWI.Id+'/e?et=REASSIGN&retURL=/'+retURLRecordId;
                        recallApprovalProcessLink =  ORG_LNK +'/'+sWI.Id+'/e?et=REMOVE&retURL=/'+retURLRecordId;
                        isSubmitForApproval = false;
                    }
                    
                }
                aSW.add(aSWr);
            }
        }
        approvalResultForObject.approvals = aSW;
        approvalResultForObject.recordId = recordId;
        approvalResultForObject.isSubmitForApproval = isSubmitForApproval;
        approvalResultForObject.recallApprovalProcessLink = recallApprovalProcessLink;
        return approvalResultForObject;
    }
    private Map<Id,ProcessInstance> getProcessHistory(Id objectId){
       return new Map<Id,ProcessInstance>([SELECT Id, (SELECT ID, ProcessNodeId,
 StepStatus,Comments,TargetObjectId,ActorId,CreatedById,IsDeleted,IsPending
,OriginalActorId,ProcessInstanceId,RemindersSent,CreatedDate, Actor.Name,
OriginalActor.Name , ProcessNode.Name FROM StepsAndWorkitems order by CreatedDate DESC ) 
FROM ProcessInstance where TargetObjectId =:objectId order by CreatedDate DESC]);
    }
    public class ApprovalStepWrapper{
        public String workItemApproveOrRejectLink {get;set;}
        public String workItemReassignLink {get;set;}
        public List<ProcessInstanceHistory> listOfSteps {get;set;}
        public ApprovalStepWrapper(){
            listOfSteps = new  List<ProcessInstanceHistory>();
        }
    }
    public class ApprovalList{
        public List<ApprovalStepWrapper> approvals {get;set;}
        public String recallApprovalProcessLink {get;set;}
        public Boolean isSubmitForApproval {get;set;}
        public Id recordId {get;set;}
        public ApprovalList(){
            approvals = new List<ApprovalStepWrapper>();
            isSubmitForApproval = true;
        }
    }
}

 

~ Visualforce Page Code (View) ~

<apex:page extensions="CustomApprovalProcessController" standardController="Quote" showHeader="false" showChat="false" sidebar="true">
   <style>
       /* 
            @author Ajay Gupta
            Description Custom Overided CSS in Minified Form
       */ 
      .hoverTable tr.overAllStatusRow{font-weight:700;background-color:#ddb929}.whiteHead,td.whiteHead{color:#fff!important;padding-left:10px}.hoverTable tr.dataRow:hover{background-color:#e3f3ff}.hoverTable #overAllStatusRow:hover{background-color:#ddb929}.Approved{background-color:#a1f78d}.Rejected{background-color:#fb8a8c}.Removed{background-color: #c0bebc}.Pending{background-color:#ffd74b}.approval_btn{-webkit-border-radius:4;-moz-border-radius:4;border-radius:4px;font-family:Arial;color:#fff!important;font-size:10.8px;background:#ddb929;padding:5px 10px;margin-left:5px;text-decoration:none}.approval_btn:hover{background:#ebca44;background-image:-webkit-linear-gradient(top,#ebca44,#d1ad1b);background-image:-moz-linear-gradient(top,#ebca44,#d1ad1b);background-image:-ms-linear-gradient(top,#ebca44,#d1ad1b);background-image:-o-linear-gradient(top,#ebca44,#d1ad1b);background-image:linear-gradient(to bottom,#ebca44,#d1ad1b);text-decoration:none}.hoverTable tr#headerRow,.hoverTable tr#headerRow:hover{background:#f2f3f3;border-color:#e0e3e5}.hoverTable tr#headerRow td{padding:11px 0}
   </style>
       <apex:pageBlock title="Quote Approvals Details">
        
       <div class="pbBody">
           <table class="list hoverTable" border="0" cellpadding="0" cellspacing="0">
               <tbody>
                   <tr class="dataRow" id="headerRow">
                   <td class="dataCell"></td>
                   <td class="dataCell"></td>                  
                   <td class="dataCell" colspan="3">
                       <apex:outputLink rendered="{!aSW.isSubmitForApproval}" styleClass="approval_btn" title="Submit For Quote Approval" target="_parent" value="{!URLFOR($Action.Quote.Submit,$CurrentPage.Parameters.ID)}">
                           Submit For Quote Approval
                       </apex:outputLink>
                       <apex:outputLink rendered="{! !aSW.isSubmitForApproval}" styleClass="approval_btn" title="Recall Quote Approval Request" target="_parent" value="{!aSW.recallApprovalProcessLink}">
                           Recall Quote Approval Request
                       </apex:outputLink>
                   </td>

                   <td class="dataCell"></td>
                   <td class="dataCell"></td>                                                        
                   </tr>
                   <tr class="headerRow" style="display:{!IF(aSW.isSubmitForApproval,'none;','')}">
                       <th>Action</th>
                       <th>Date</th>
                       <th>Status</th>
                       <th>Assigned To</th>
                       <th>Comments</th>
                       <th>Actual Approver</th>
                       <th>Overall Status</th>
                   </tr>                   
                   <apex:repeat value="{!aSW.approvals}" var="s">
                       <tr class="overAllStatusRow dataRow" id="overAllStatusRow">
                           <td colspan="6" class="dataCell whiteHead">
                               {!IF(AND(s['listOfSteps'][0].stepstatus != 'Started', s['listOfSteps'][0].ProcessNode.Name != NULL),IF( s['listOfSteps'][0].stepstatus !='Removed','Step : '+s['listOfSteps'][0].ProcessNode.Name + IF(s['listOfSteps'][0].stepstatus=='Pending',' (Pending for first approval) ',''),'Approval Request Recalled'), 'Approval Request Submitted')}
                           </td>
                           <td class="dataCell {!s['listOfSteps'][0].stepstatus}">
                                {!IF(AND(s['listOfSteps'][0].stepstatus != 'Started',s['listOfSteps'][0].stepstatus != 'NoResponse'),IF( s['listOfSteps'][0].stepstatus !='Removed',s['listOfSteps'][0].stepstatus,'Recalled'), '')}
                           </td>
                       </tr>
                           <apex:repeat var="step" value="{!s.listOfSteps}">
                               <tr class="dataRow">
                                   <td class="dataCell">
                                       <apex:outputText rendered="{!step.stepstatus == 'Pending'}">
                                               <a href="{!s.workItemReassignLink}" target="_parent">Reassign</a> | <a href="{!s.workItemApproveOrRejectLink}" target="_parent"> Approve / Reject</a>
                                       </apex:outputText>
                                   </td>
                                   <td class="dataCell">
                                       <apex:outputText value="{0,date,dd/MM/YYYY HH:mm:ss}">
                                           <apex:param value="{!step.CreatedDate}"/>
                                       </apex:outputText> 
                                   </td>
                                   <td class="dataCell">{!IF(step.Stepstatus!='NoResponse',step.Stepstatus,'')}</td>
                                   <td class="dataCell"><a href="/{!step.OriginalActorID}" target='_parent'>{!step.OriginalActor.Name}</a></td>
                                   <td class="dataCell">{!step.comments}</td>
                                   <td class="dataCell"><a href="/{!step.ActorId}" target='_parent'>{!step.Actor.Name}</a></td>
                                   <td></td>
                               </tr>
                           </apex:repeat>
                </apex:repeat>
               </tbody>
           </table>
       </div>
       </apex:pageBlock>
</apex:page>

 

17 Comment

  1. Hi Ajay – Thank you for your post. I need one help. I have approval history object and I need to add one more step to that history object through apex. Please let me know how can I do that.

    Thanks,
    Indrasen

    1. Thanks Indrasen, If you could send me code & requirement in detail. Then I can give you the best direction to make the appropriate changes in code.

  2. hello Ajay,

    Thanks for your Post.
    According to your code when you go for Reassign ,the Status changed to Reassigned and Pending comes in 2nd .is there any way to put pending as current status then after that below Reassigned.

    1. Yes, you can do that use same wrapper to keep records which has status as pending & display that wrapper first on your VF Page.

      I hope this information will help you!

  3. Krishan Gopal says: Reply

    Hi Ajay,
    Thank you for your great post. I need your help. I need to creat Approval Assignment Email Template with complete approval history for next approver .
    Please let me know how can I do that.
    Thanks,
    Krishan

  4. Krishan Gopal says: Reply

    Hi Ajay,
    Please suggest the steps to show pending as current status in approval history, it shown at second line in approval history.
    Thanks,
    Krishan

  5. Hi Ajay, how are you ?
    I’m with this error to create your class: “No such column ‘ProcessNodeId’ on entity ‘ProcessInstanceHistory'”

    Please, can you helpme ?
    Thanks,
    Léo.

  6. Hi Ajay, how are you ?
    I’m with this error to create your class: “No such column ‘ProcessNodeId’ on entity ‘ProcessInstanceHistory’”

    Please, can you helpme ?
    Thanks,
    Léo.

    1. Sure Leo, please send me error screenshot & code you are trying. It should work so many people are using same code.

    2. Hi Leo,
      You should use API 35 or above. Probably SF didn’t allow to access this field before.

  7. Very Simple to implement..

    1. How do we proceed to override the Items to Approve component in home page to have similar looks

    2. Thanks for appreciating & using my code.

  8. Hi, I’ve got the same problem with “No such column ProcessNodeId…” Can you tell me how to fix it?

    1. Matt, Please look at my another post. You just need to change Apex API version to above V35 or later.

  9. Hi Ajay,
    Thank you for your great post.

  10. Hi , Thank you so much for the valuable post.. It really helpful for me. Thank you so much

Leave a Reply