top of page
Writer's pictureAmol Wagh

Bulk Approval or Rejection using Custom Lightning Component in Salesforce

Updated: Mar 1, 2023

In this post, we are going to see an example for Bulk Approval or Rejection using Lightning Component in Salesforce.


Follow the below steps for developing Lightning Component:


Step1: Create Lightning Component- “MassApprovalRejectionComponent.cmp”


<aura:component controller="MassApprovalRejectionController" implements="flexiPage:availableForAllPageTypes,force:appHostable">
    
    <!-- Call Javescript Method on Load -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> 
    
    <!-- Functional Attributes -->
    <aura:attribute name="columns" type="list" description="Captures the value of column names of the data table"/>
    <aura:attribute name="data" type="object" description="Captures the records displayed in the data table"/>
    <aura:attribute name="sortedBy" type="string" description="Captures the column name used for sorting"/>
    <aura:attribute name="sortedDirection" type="string" description="Captures the direction of sorting(ascending or descending)"/>
 
    <!-- Spinner -->
    <lightning:spinner aura:id="spinnerId" variant="brand" size="medium"/>
    <!-- Spinner -->
    
    <!-- Page Header -->
    <div class="slds-page-header" role="banner">
    	<span class="slds-page-header__title">Approval page</span>
    </div>
    <!-- Page Header -->
    
    <!--Approve and Reject buttons are disabled by default.Whenever a record is selected, buttons will be enabled-->
    <div class="slds-m-vertical_medium">
    	<lightning:button aura:id="approvalButtonId" label="Approve" variant="success" disabled="true"
                          onclick="{!c.handleApproveAction}"/>
        <lightning:button aura:id="rejectButtonId" label="Reject" variant="destructive" disabled="true"
                          onclick="{!c.handleRejectAction}"/>
    </div>
     <!--Approve and Reject buttons are disabled by default.Whenever a record is selected, buttons will be enabled-->
    
    <!-- Page Body -->
    <div>
    	<lightning:datatable aura:id="approvalRecordsTableId"
                             keyField="workItemId"
                             columns="{!v.columns}"
                             data="{!v.data}"
                             sortedBy="{!v.sortedBy}"
                             sortedDirection="{!v.sortedDirection}"
                             onsort="{!c.handleSortingOfRows}"
                             onrowselection="{!c.handleRowSelection}"/>
 
    </div>
    <!-- Page Body -->

Step2: Create Javascript Controller- “MassApprovalRejectionComponent.js”


({
    //Method call on load of Lightning Component
    doInit : function(component,event,helper){
        helper.doInitHelper(component,event,helper);
    },
    
    //Method to handle sorting of records
    handleSortingOfRows : function(component,event,helper){
        helper.handleSortingOfRows(component,event);
    },
    
    //Method to enable or disable Approve and Reject button
    handleRowSelection : function(component,event,helper){
        helper.handleRowSelection(component,event,helper);
    },
    
    //Method to Approve the selected records
    handleApproveAction : function(component,event,helper){
        helper.processSelectedRecords(component,event,helper,'Approve');
    },
    
    //Method to Reject the selected records
    handleRejectAction : function(component,event,helper){
        helper.processSelectedRecords(component,event,helper,'Reject');
    }
})

Step3: Helper Class: “MassApprovalRejectionComponent.helper”


({
    //Method call on load of Lightning Component
    doInitHelper : function(component,event){
        //Initialize the columns for data table
        component.set('v.columns',[
            {
                label : 'Name',
                fieldName : 'recordId',
                type : 'url',
                typeAttributes : {label:{fieldName:'recordName'},target:'_blank'}
            },
            {
                label : 'Related to',
                fieldName : 'relatedTo',
                type : 'text',
                sortable : true
            },
            {
                label : 'Submitted by',
                fieldName : 'submittedBy',
                type : 'text',
                sortable : true
            },
            {
                label : 'Submitted date',
                fieldName : 'submittedDate',
                type : 'date',
                typeAttributes : {year:"2-digit",month:"short",day:"2-digit"},
                sortable : true
            }
        ]);
        this.getData(component,event);
    },
    
    //Method to fetch data
    getData : function(component,event){
        //show spinner till data is loaded from server
        var spinner = component.find("spinnerId");
        $A.util.toggleClass(spinner, "slds-hide");
        var toastRef = $A.get('e.force:showToast');
        var action = component.get('c.getSubmittedRecords');
        action.setCallback(this,function(response){
            var state = response.getState();
            if(state == 'SUCCESS'){
                var records = response.getReturnValue();
                records.forEach(function(record){
                   record.recordId = '/'+record.recordId;
                });
                $A.util.toggleClass(spinner, "slds-hide");
                if(records.length == 0){
                    toastRef.setParams({
                        'type' : 'error',
                        'title' : 'Error',
                        'message' : 'No records found for approve/reject',
                        'mode' : 'sticky'
                    });
					toastRef.fire();
                }
                component.set('v.data',records);
            }
        });
        $A.enqueueAction(action);
    },
    
    //Method to handle sorting of records
    handleSortingOfRows : function(component,event){
        //Set field name and direction of sorting
        var sortedBy = event.getParam('fieldName');
        var sortedDirection = event.getParam('sortDirection');
        component.set('v.sortedBy',sortedBy);
        component.set('v.sortedDirection',sortedDirection);
        this.sortRecords(component,event,helper,sortedBy,sortedDirection);
    },
    
    //Method to handle sorting of records
    sortRecords : function(component,event,helper,sortedBy,sortedDirection){
        var records = component.get('v.data');
        var direction = sortedDirection == 'asc' ? 1 : -1;
        var fieldValue = function(record){ return record[sortedBy]; }//returns the field value(field used for sorting) for each record
        records.sort(function(record1,record2){
            var fieldValue1 = fieldValue(record1);
            var fieldValue2 = fieldValue(record2);
            return direction * (fieldValue1 > fieldValue2) - (fieldValue2 > fieldValue1);//For asc,return value of -1 sorts the record,1 or 0 keeps the order intact.
        });
        component.set('v.data',records);
    },
    
    //Method to enable or disable Approve and Reject button
    handleRowSelection : function(component,event,helper){
        //To enable or disable Approve, Reject button based on row selection
        var rowsSelected = event.getParam('selectedRows');
        if(rowsSelected.length > 0){
            component.find('approvalButtonId').set('v.disabled',false);
            component.find('rejectButtonId').set('v.disabled',false);
        }
        else{
            component.find('approvalButtonId').set('v.disabled',true);
            component.find('rejectButtonId').set('v.disabled',true);
        }
    },
    
    //Method to Approve or Reject the selected records
    processSelectedRecords : function(component,event,helper,processType){
        //To approve, reject selected records based on 'processType' variable
        component.find('approvalButtonId').set('v.disabled',true);
        component.find('rejectButtonId').set('v.disabled',true);
        var selectedRows = component.find('approvalRecordsTableId').get('v.selectedRows');
        var action = component.get('c.processRecords');//Calling server side method with selected records
        action.setParams({
            lstWorkItemIds : selectedRows,
            processType : processType
        });
        action.setCallback(this,function(response){
            var state = response.getState();
            var toastRef = $A.get('e.force:showToast');
            if(state == 'SUCCESS'){
                var message = response.getReturnValue();
                if(message.includes('success')){
                    toastRef.setParams({
                        'type' : 'success',
                        'title' : 'Success',
                        'message' : message,
                        'mode' : 'dismissible'
                    });
                }
                else{
                   toastRef.setParams({
                        'type' : 'error',
                        'title' : 'Error',
                        'message' : message,
                        'mode' : 'sticky'
                    });
                }
                toastRef.fire();
                $A.get('e.force:refreshView').fire();
            }
        });
        $A.enqueueAction(action);
    }
})



Step4: Create Custom Apex Controller- “MassApprovalRejectionController”


public class MassApprovalRejectionController {

    //Method to fetch all the records which are submitted for approval
    @AuraEnabled
    public static List<SubmittedRecordsWrapper> getSubmittedRecords(){
        List<SubmittedRecordsWrapper> lstSubmissionWrapper = new List<SubmittedRecordsWrapper>();
        //Process instance stores the info of records submitted for approval,
        // Process instance work item are the records an approver sees while approving/rejecting, Process instance step stores approved/rejected record including approva;/rejection comments
        for(ProcessInstance ps : [SELECT Id,TargetObjectId,TargetObject.Name,CreatedDate,
                                 (SELECT ID FROM WorkItems WHERE OriginalActorId = : UserInfo.getUserId()),
                                 (SELECT OriginalActor.Name FROM Steps WHERE StepStatus = 'Started') FROM ProcessInstance]){
            if(!ps.WorkItems.isEmpty()){
                SubmittedRecordsWrapper objSubmittedRecordsWrapper = new SubmittedRecordsWrapper();
                objSubmittedRecordsWrapper.workItemId = ps.WorkItems[0].Id;
                objSubmittedRecordsWrapper.recordId = ps.TargetObjectId;
                objSubmittedRecordsWrapper.recordName = ps.TargetObject.Name;
                objSubmittedRecordsWrapper.relatedTo = getObjectName(ps.TargetObjectId);//get the object name using the record id
                objSubmittedRecordsWrapper.submittedDate = Date.newInstance(ps.CreatedDate.year(),ps.CreatedDate.month(),ps.CreatedDate.day());
                if(!ps.steps.isEmpty()){
                    objSubmittedRecordsWrapper.submittedBy = ps.steps[0].OriginalActor.Name;
                	lstSubmissionWrapper.add(objSubmittedRecordsWrapper);
                }
            }
        }
        return lstSubmissionWrapper;
    }
    
    public static String getObjectName(String recordId){
        //To get the label of the object name using Schema methods
        String keyPrefix = recordId.subString(0,3);
        String objectName = '';
        Map<String,Schema.SObjectType> sobjectTypeMap = Schema.getGlobalDescribe();
        for(String obj : sobjectTypeMap.keySet()){
            Schema.DescribeSObjectResult sobjectResult = sobjectTypeMap.get(obj).getDescribe();
            if(sobjectResult.getKeyPrefix() == keyPrefix){
                objectName = sobjectResult.getLabel();
                break;
            }
        }
        return objectName;
    }
    
    //Method to Approve or Reject the selected records
    @AuraEnabled
    public static String processRecords(List<String> lstWorkItemIds,String processType){
        String message = '';
        Integer recordsProcessed = 0;
        String comments = processType == 'Approve' ? 'Approved' : 'Rejected';
        List<Approval.ProcessWorkitemRequest> lstWorkItemRequest = new List<Approval.ProcessWorkitemRequest>();//ProcessWorkitemRequest class has methods to programmatically process submitted records
        for(String workItemId : lstWorkItemIds){
            Approval.ProcessWorkitemRequest objWorkItemRequest = new Approval.ProcessWorkitemRequest();
            objWorkItemRequest.setComments(comments);
            objWorkItemRequest.setAction(processType);//approve or reject
            objWorkItemRequest.setWorkitemId(workItemId);
            lstWorkItemRequest.add(objWorkItemRequest);
        }
        Approval.ProcessResult[] lstProcessResult = Approval.process(lstWorkItemRequest,FALSE);//process method is used for approving/rejecting records depending on setAction attribute
        for(Approval.ProcessResult processResult : lstProcessResult){
            if(processResult.isSuccess()){
                recordsProcessed++;
            }
            else{
                for(Database.Error error : processResult.getErrors()){
                    message += error.getMessage();
                }
            }
        }
        if(recordsProcessed == lstWorkItemIds.size()){
            message = 'All records are '+comments+' successfully';
        }
        return message;
    }
    
    //Wrapper class to store the column values of data table
    public class SubmittedRecordsWrapper{
        @AuraEnabled public Id workItemId;
    	@AuraEnabled public String recordId;
        @AuraEnabled public String relatedTo;
        @AuraEnabled public String recordName;
        @AuraEnabled public String submittedBy;
        @AuraEnabled public Date submittedDate;
    }
}


That's it you have developed a custom lighting component for Bulk approval.,

Create one Tab in your org and link to the above-created Component to see a list of record for Bulk approval




2 comments

2 Comments


mayank jain
mayank jain
Jan 10, 2023

Can you please provide test class for this functionality.

Like

Nicholas Orsmond
Nicholas Orsmond
Jul 08, 2021

This is super cool, I've added it as a component, the only place I'm stuck at (not being a developer, I can really just sort of read code), I am able to add more columns, but how would I go about adding the value from the related record to that column?

Like
bottom of page