0

Is there a way that I can create a visualforce page that will show me the approval steps that will be required if I submit a certain record for approval?

This could be useful for end users, helping them in deciding whether to submit for approval or maybe change the data first (e.g. give less discount so that management approval is not necessary).

There is an idea for this as well: https://success.salesforce.com/ideaView?id=08730000000kjduAAA

(posting my own current best answer below, as I think it might be useful for others)

1

Thanks so much for this! I was looking for a way to provide my users with a way to see exactly who will be approving their records, before actually submitting for approval.

I needed to be able to show the name of the step in addition to the number and name of approver (since sometimes a particular user can approve in different steps). So I made some significant changes, but I just wanted to say thank you and provide you with my alternate version.

I didn't need to be able to show multiple approval processes that met the entry criteria, just the one that will actually run, so I removed some of the repeat logic and just did a simulated submission and recorded the approval steps.By doing that, I was able to solve the first of your possible improvements.

The second, if you're using this in a managed package, my understanding is that as long as the test class has full coverage in your org, it doesn't matter if it has full coverage in every customer org. Generally speaking, of course, most people wouldn't need this code if they didn't have any approval processes. But that's not really a concern for me.

The third improvement was very important for me, so rather than querying the ProcessInstanceWorkItem object for the approval steps, I did a query of the ProcessInstance and a subquery to StepsAndWorkitems (which is the relationship name of ProcessInstanceHistory) to get both the name of the approver and the name of the step.

Here's my code: https://gist.github.com/AndersonKatharineTNC/6b04db1f68540c729b9fbd15ac92ec13

Here's a screenshot of how the visualforce page looks in classic: Review Approver List Page Screenshot

And in Lightning Experience: enter image description here

  • Excellent! Tnx for the additions and for publicizing the code. Didn't know about the StepsAndWorkitems object, great addition! – Guy Feb 7 at 10:27
0

Providing my own current answer, but would be really interested in any improvements and/or alternative solutions.

I implemented the following principles:

  • I didn't want to evaluate the approval triggers and criteria myself, so making use of SFDC's built-in evaluation.
  • In order to trigger the evaluation by SFDC I submit the record through all available approval processes, using a try-catch block for if it fails the entry criteria.
  • In APEX I approve any of the steps that are generated by SFDC.
  • Along the way I store the approval processes and steps that are generated by SFDC to be able to show them on the VF page. And at the end I rollback the entire transaction to make sure nothing is really saved to the database.

This is the APEX controller:

public without sharing class ApprovalPreviewController {
    public ApprovalPreviewController() {
        objectId = ApexPages.currentPage().getParameters().get('id');
        showInActive = false;
        system.debug(ApexPages.currentPage().getParameters().get('showInactive'));
        if(ApexPages.currentPage().getParameters().get('showInactive') != null){
            if(ApexPages.currentPage().getParameters().get('showInactive') == '1') showInactive = true;
        }
    }

    public Id objectId {get;set;}
    public User theApprovalUser {get;set;}
    public Boolean showInActive {get;set;}

    public List<approvalProcess> applicableProcesses {
        get{
            if(applicableProcesses == null) applicableProcesses = new List<approvalProcess>();
            return applicableProcesses;
        }
        set;
    }

    public class ApprovalProcess{
        public Integer processNumber {get;set;}
        public String processName {get;set;}
        public String processDescription {get;set;}
        public List<approvalStep> approvalSteps {
            get{
                if(approvalSteps == null) approvalSteps = new List<ApprovalStep>();
                return approvalSteps;
            }
            set;
        }
    }
    public class ApprovalStep{
        public Integer stepNumber {get;set;}
        public String approver {get;set;}
    }

    public void runPreview(){
        system.savepoint sp1 = Database.setSavepoint();

        String theObjectType = objectId.getSObjectType().getDescribe().getName();
        theApprovalUser = [SELECT Id FROM User WHERE Alias='gclai'];

        Set<String> statesToShow = new Set<String>{'Active'};
        if(showInActive) {
            statesToShow.add('Inactive');
            statesToShow.add('Obsolete');
        }
        system.debug(statesToShow);

        // fetch all approval processes for this object
        List<ProcessDefinition> processesForThisObject = [Select Id, Name, Developername,Type, Description 
                                                          From ProcessDefinition 
                                                          Where 
                                                            Type = 'Approval'
                                                            AND State in :statesToShow
                                                            AND TableEnumOrId = :theObjectType ];

        for (ProcessDefinition pd : processesForThisObject){
            system.debug(pd.Name);
            try{
                ApprovalProcess ap = createApproval(pd.Id,pd.Developername);
                ap.processNumber = applicableProcesses.size() + 1;
                ap.processName = pd.Name;
                ap.processDescription = pd.Description;
                applicableProcesses.add(ap);
                system.debug(applicableProcesses);

            } catch (Exception e)
            {
                system.debug(pd.Name+' This process is not applicable.');
                // process not applicable to this record. No problemo.   
            }
        }
        Database.rollback(sp1);
    }

    public ApprovalProcess createApproval(Id processId, String procesName){

        // Create an approval request for the account
        Approval.ProcessSubmitRequest req1 = new Approval.ProcessSubmitRequest();
        req1.setComments('Submitting request for approval.');
        req1.setObjectId(objectId);

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

        // Submit the record to specific process and skip the criteria evaluation
        req1.setProcessDefinitionNameOrId(procesName);
        // for testing, skip entry criteria to make sure at least 1 process is ran
        if(Test.isRunningTest()) req1.setSkipEntryCriteria(true);

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

        system.debug(procesName+ ' START');

        // create the data for viewing
        ApprovalProcess ap = new ApprovalProcess();

        // Verify the result
        System.debug(result.getInstanceStatus());

        goThroughSteps(result,ap.approvalSteps);

        system.debug(procesName+ ' END');

        return ap;
    }    


    public void goThroughSteps(Approval.ProcessResult result,List<ApprovalStep> approvalSteps){
        // First, get the ID of the newly created item
        List<Id> newWorkItemIds = result.getNewWorkitemIds();

        ProcessInstanceWorkitem piw = [Select Id, ActorId, Actor.Name,ProcessInstanceId From ProcessInstanceWorkitem Where Id = :newWorkItemIds[0] ];
        system.debug(piw.actorId + ' '+piw.actor.Name);

        // Instantiate the new ProcessWorkitemRequest object and populate it
        Approval.ProcessWorkitemRequest req2 = new Approval.ProcessWorkitemRequest();
        req2.setComments('Approving request.');
        req2.setAction('Approve');

        // 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.debug(result2.getInstanceStatus());

        ApprovalStep ap = new ApprovalStep();
        ap.stepNumber = approvalSteps.size() + 1;
        ap.approver = piw.actor.Name;
        approvalSteps.add(ap);

        if(!result2.getNewWorkitemIds().isEmpty()){
            goThroughSteps(result2,approvalSteps);
        }

    }


}

And the corresponding VisualForce page:

<apex:page showHeader="true" sidebar="true" controller="ApprovalPreviewController" title="Preview Approvals" action="{!runPreview}" tabStyle="Account">

    <apex:pageBlock title="Preview of Applicable Approvals">
        <apex:repeat value="{!applicableProcesses}" var="ap">
            <apex:pageBlock title="Approval Process #{!ap.processNumber} - {!ap.processName}">
                <apex:pageBlockSection columns="1">     
                    <apex:pageBlockSectionItem>
                        <apex:outputLabel value="Description"/>
                        <apex:outputText value="{!ap.processDescription}"/>
                    </apex:pageBlockSectionItem>
                    <apex:pageBlockSectionItem>
                    </apex:pageBlockSectionItem>
                </apex:pageBlockSection>        
                <apex:pageBlockSection columns="2">     

                    <apex:repeat value="{!ap.approvalSteps}" var="as">
                        <apex:pageBlockSectionItem>
                                <apex:outputText value="Step number" />

                                <apex:outputText value="{!as.stepNumber}" />
                        </apex:pageBlockSectionItem>
                        <apex:pageBlockSectionItem>
                                <apex:outputText value="Approver" />

                                <apex:outputText value="{!as.approver}" />
                        </apex:pageBlockSectionItem>
                    </apex:repeat>

                </apex:pageBlockSection>
            </apex:pageBlock>
        </apex:repeat>
    </apex:pageBlock>
</apex:page>

Screenshot:enter image description here

Possible improvements:

  • run the approval processes in the order specified in the setup
  • test classes? How do I make sure there's an approval process set up in the org?
  • show the approval step descriptions..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.