Custom Approval Process in Salesforce – Part 1

By | December 8, 2021

In this blog, we will discuss how to Create Custom Approval Process. But, the Purpose of creating Custom Approval Process is when the client wants any addition in Approval UI or any validation on Approval screen then we can’t achieve that because of Standard Functionality, so then we have created our own Approval Process.

So mentioned below are the steps for creating the Lighting Component of Custom Approval Process in Salesforce.

Open Developer Console, then go to File – > New – > Lighting Component

Firstly, we will see how to make UI for the Submitter Screen to Submit their Approval

ApprovalProcess.cmp

<aura:component controller="QuoteApprovalProcessController" 
                implements="force:lightningQuickActionWithoutHeader,force:hasRecordId,force:appHostable,lightning:actionOverride"
                access="global" >
            <!--<aura:handler name="reinit" value="{!this}" action="{!c.reInit}" />-->
            <aura:attribute name="recordId" type="Id" default="0Q0p0000000YySICA0"/>
            <aura:attribute name="comment" type="String" default="" />
            <aura:attribute name="loaded" type="Boolean" default="true" />
            <aura:attribute name="Spinner" type="Boolean" default="false"/>
            <aura:attribute name="LoadMessage" type="string" default="Loading.."/>
            <aura:attribute name="showProgressbar" type="Boolean" default="false"/>
            <aura:attribute name="isOpen" type="Boolean" default="false"/>
            <aura:attribute name="filetype" type="List" default="['.pdf']" />
            <aura:attribute name="multiple" type="Boolean" default="true" />
            <aura:attribute name="disabled" type="Boolean" default="true" />
    		<aura:component controller="QuoteApprovalProcessController" implements="force:lightningQuickActionWithoutHeader,force:hasRecordId,force:appHostable,lightning:actionOverride"
                access="global" >
            <!--<aura:handler name="reinit" value="{!this}" action="{!c.reInit}" />-->
            <aura:attribute name="recordId" type="Id" default="0Q0p0000000YySICA0"/>
            <aura:attribute name="comment" type="String" default="" />
            <aura:attribute name="loaded" type="Boolean" default="true" />
            <aura:attribute name="Spinner" type="Boolean" default="false"/>
            <aura:attribute name="LoadMessage" type="string" default="Loading.."/>
            <aura:attribute name="showProgressbar" type="Boolean" default="false"/>
            <aura:attribute name="isOpen" type="Boolean" default="false"/>
            <aura:attribute name="filetype" type="List" default="['.pdf']" />
            <aura:attribute name="multiple" type="Boolean" default="true" />
            <aura:attribute name="disabled" type="Boolean" default="true" />
    		<aura:attribute name="encryptedToken" type="String" />
            <aura:attribute name="FileList" type="String"/>
            <aura:attribute name="parentId" type="Id" default="00128000002KuXUAA0" />
            <!-- 'fileName' attribute for display the selected file name --> 
            <aura:attribute name="fileName" type="String" default="No File Selected.." />
            <ltng:require scripts="{!$Resource.CommonFunction}" afterScriptsLoaded="{!c.doInit}" />
    		<c:spinner Spinner="{! v.Spinner}" LoadMessage="{! v.LoadMessage}" showProgressbar="{! v.showProgressbar}" progress="{! v.progress}"/>
            <aura:html tag="style"> 
                .cuf-content 
                {
                padding: 0 0rem	!important;
                }
                <!-- MOD -->
                .slds-p-around--medium 
                {
                padding: 0rem !important;
                }        
                
                .slds-modal__content
                {
                //	overflow-y:hidden !important;
                height:unset !important;
                max-height:unset !important;
                }       
                
                .GUMUStyle 
                {
                z-index: 10000;
                position: fixed;
                visibility: visible;
                opacity: 1;
                height: 100%;
                width: 100%;
                max-height: 250px;
                background-color: rgba(255, 255, 255, 0.75) !important;
                }
    
            </aura:html>
    <aura:if isTrue="{!v.isOpen}">
            <section role="dialog" tabindex="-1" aura:id="Modalbox" aria-labelledby="modal-heading-01" aria-modal="true" aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
                <div class="slds-modal__container slds-col slds-size_1-of-1 slds-small-size_1-of-1 slds-medium-size_1-of-1">
                    <header class="slds-modal__header">
                        <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Submit For Approval</h2> 
                    </header> 
                    <div class="slds-modal__content slds-p-around_medium " id="modal-content-id-1">
                        <div class="slds-grid slds-wrap"> 
                    <div class="slds-col slds-size_1-of-1 slds-small-size_1-of-1 slds-medium-size_1-of-1 slds-p-right_xx-small" >
                        <c:customLookup  aura:id="lcUser" lookupFieldId="lcUser" label="Select Approver" objectAPIName="User" descFields="" whereClause="" fieldName="Name" required="true"></c:customLookup>
                    </div> 
                                <div class="slds-col slds-size_1-of-1 slds-small-size_1-of-1 slds-medium-size_1-of-1 inputArea">
                                    <lightning:textarea name="comment" label="Comments" value="{!v.comment}" placeholder="type here..." />
                                </div>
                         </div>
                        <div class="slds-grid slds-m-around_x-small">
                            <div  class="slds-col"> 
                                <lightning:input class=" slds-float_left" aura:id="fuploader" onchange="{!c.handleFilesChange}" type="file" name="file" label="Upload File" multiple="false"/>
                            <div class="slds-m-top_xx-large slds-m-top_medium slds-text-body_small  slds-text-color_error">&nbsp;&nbsp;&nbsp;&nbsp;{!v.fileName} </div>
                            </div> 
                            <div class="slds-col slds-clearfix">
                                <div class="slds-m-top_x-large slds-float_right"> 
                                    
                                    <lightning:button class=" slds-m-right_x-small slds-button slds-button_neutral" label="Cancel" onclick="{!c.closeModal }"/> 
                                    <lightning:button class="slds-button slds-button_neutral" variant="brand" label="Submit" onclick="{!c.handleClick}" />
                                </div> 
                            </div>
                        </div>
                        </div>
                </div>
            </section>
    </aura:if>
</aura:component>

Certainly, the above code will create the UI for the Submitter Screen as shown below.

Submitter Screen for Custom Approval
Submitter Screen for Custom Approval

But, there are 2 fields Validation, Submitter should Select Approver & Upload File. On the other hand, for Approval it’s mandatory for Approver to write their comments while they Approve or Cancel.

  • Select Approver: It is a Custom Lookup where you get all users list for selecting approver.
  • Upload File: Where Submitter can upload their files like documents which stored in Attachment.
  • Comment: Where Submitter Can write their comment regarding Document, business logic etc.

QuoteApprovalProcessController.apex

public class QuoteApprovalProcessController 
{
	@auraEnabled
    public static string submitAndProcessApprovalRequest(String recordId, String comment,Id UserChkId) {
        string response;
        Id Userid=UserInfo.getUserId();
        //list<User> SecondApproval= [select id from User];
        try
        {
            recordId = String.escapeSingleQuotes(recordId);
            comment = String.escapeSingleQuotes(comment);
            UserChkId = String.escapeSingleQuotes(UserChkId);
            Id idToProccess = recordId;
            Schema.sObjectType entityType = idToProccess.getSObjectType();
            system.debug('entityType:::'+entityType);
            String entity = String.valueOf(entityType);
            system.debug('entity:::'+entity);
            
            ApprovalInstance__c Ap = new ApprovalInstance__c();
            Ap.Approval_Status__c='Pending';
            Ap.Object_Name__c =entity;
            Ap.Submitter_U__c=Userid;
            //Ap.Approver__c=UserChkId;
            Ap.Submitter_Comment__c=comment;
            //Ap.Submitter__c=Userid;  
            Ap.Approver_U__c=UserChkId;
            Ap.TargetObjectId__c=recordId;
            Ap.Quote_Relation__c=recordId;
            insert Ap;
            response = 'success';
        }
        catch(System.DmlException ex)
        {
            system.debug('Error Message ::: ' + Ex.getMessage() + ' Due to this issues ::: ' + Ex.getLineNumber());   
            response = Ex.getMessage(); 
        }
        return response;
    }
    
    @auraEnabled
    public static string ApprovedOrReject(String recordId, String comment,String State) {
        string response;
        Id Userid=UserInfo.getUserId();
        //list<User> SecondApproval= [select id from User];
        try
        {
            recordId = String.escapeSingleQuotes(recordId);
            comment = String.escapeSingleQuotes(comment);
            Id idToProccess = recordId;
            Schema.sObjectType entityType = idToProccess.getSObjectType();
            system.debug('entityType:::'+entityType);
            String entity = String.valueOf(entityType);
            system.debug('entity:::'+entity);
            List<ApprovalInstance__c> appProcessList = [Select Id,Submitter_Comment__c,Approver_Comment__c, Approval_Status__c,TargetObjectId__c From ApprovalInstance__c WHERE TargetObjectId__c =: recordId AND Approval_Status__c ='Pending' LIMIT 1];
            
            for(Integer i=0;i<appProcessList.size();i++)
            {
                if(State=='Approved')
                {
                	appProcessList[i].Approval_Status__c='Approved';
                }
                else if(State =='Rejected')
                {
                    appProcessList[i].Approval_Status__c='Rejected';
                }
                appProcessList[i].Approver_Comment__c=comment;
             }
            update appProcessList;
            response = 'success';
        }
        catch(System.DmlException ex)
        {
            system.debug('Error Message ::: ' + Ex.getMessage() + ' Due to this issues ::: ' + Ex.getLineNumber());   
            response = Ex.getMessage(); 
        }
        return response;
    }
    
    @auraEnabled
    public static string approvalProcessStatus(String recordId) 
    {
        string response;
        recordId = String.escapeSingleQuotes(recordId);
        //Taking out already running Approval Process.
        List<ApprovalInstance__c> appProcessList = [Select Id, Approval_Status__c From ApprovalInstance__c WHERE TargetObjectId__c =: recordId AND (Approval_Status__c ='Pending' OR Approval_Status__c ='Approved') LIMIT 1];
        if(appProcessList.size()==0)
        {
            response = 'success';
        }
        else
        {
            response = 'failure';
        }
        system.debug('response::'+response);
        return response;
    }
    
    @auraEnabled
    public static string CheckDocumentAttatched(String recordId)
    {
        String responce;
        list<Attachment> CheckAttachment = [SELECT Id, ParentId, Name, IsPrivate, BodyLength, Body, OwnerId, Description FROM Attachment Where ParentId =:recordId limit 100];
        if(CheckAttachment.size() > 0 )
        {
            responce='Success';
        }
        else
        {
            responce='failure';
        }
        
        return responce;
    }
    
    @AuraEnabled
    public static Id SaveFile(Id parentId, String fileName, String base64Data, String contentType) 
    {
        base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
        Attachment attach = new Attachment();
        attach.parentId = parentId;
        attach.Body = EncodingUtil.base64Decode(base64Data);
        attach.Name = fileName;
        attach.ContentType = contentType;
        Insert attach;
        return attach.Id;
    }
    
    @auraEnabled
    public static string ApprovalRequestCheck(String recordId)
    {
        string response;
        //Id Userid=UserInfo.getUserId();
        recordId = String.escapeSingleQuotes(recordId);
        //Taking out already running Approval Process.
        List<ApprovalInstance__c> appProcessList = [Select Id,Name, TargetObjectId__c, Approval_Status__c, Object_Name__c, Submitter_Comment__c, Approver_Comment__c, Approver__c, Submitter__c From ApprovalInstance__c WHERE TargetObjectId__c =: recordId AND Approval_Status__c ='Pending' LIMIT 1];
        if(appProcessList.size()==0)
        {
            response = 'failure';
        }
        else
        {
            response = 'success';
        }
        system.debug('response::'+response);
        return response;
    }
    @auraEnabled
    public static string UserCheck(String recordId)
    {
        String responseUser;
        Id Userid=UserInfo.getUserId();
   		recordId = String.escapeSingleQuotes(recordId);
        //Taking out already running Approval Process.
        List<ApprovalInstance__c> appProcessList = [Select Id,Name, TargetObjectId__c, Approval_Status__c, Object_Name__c, Submitter_Comment__c, Approver_Comment__c,Approver_U__c, Submitter_U__c From ApprovalInstance__c WHERE TargetObjectId__c =: recordId AND Approval_Status__c ='Pending' LIMIT 1];
		
        if(appProcessList[0].Approver_U__c != Userid)
        {
            responseUser = 'Userfailure';
        }
        else
        {
            responseUser = 'success';
        }    
        system.debug('responseUser::'+responseUser);
        return responseUser;
    }
}

In conclusion, the above code will get data from custom object Named as “ApprovalInstance__c” where based on Submitter action, record has been generated & other validation has been handled in above code.

Moreover, in the next blog, we will see Approver Side UI & Apex Code.

We hope you may find this blog resourceful and helpful. If you still have concerns and need more help, please contact us at salesforce@greytrix.com.

References – Custom file upload in Salesforce lightning component & Dynamic Approval Process based on Apex and Trigger

About Us
Greytrix – a globally recognized and one of the oldest Sage Development Partner and a Salesforce Product development partner offers a wide variety of integration products and services to the end users as well as to the Partners and Sage PSG across the globe. We offer Consultation, Configuration, Training and support services in out-of-the-box functionality as well as customizations to incorporate custom business rules and functionalities that require apex code incorporation into the Salesforce platform.

Greytrix has some unique solutions for Cloud CRM such as Salesforce Sage integration for Sage X3Sage 100 and Sage 300 (Sage Accpac). We also offer best-in-class Cloud CRM Salesforce customization and development services along with services such as Salesforce Data MigrationIntegrated App developmentCustom App development and Technical Support to business partners and end users.
Salesforce Cloud CRM integration offered by Greytrix works with Lightning web components and supports standard opportunity workflow. Greytrix GUMU™ integration for Sage ERP – Salesforce is a 5-star rated app listed on Salesforce AppExchange.

The GUMU™ Cloud framework by Greytrix forms the backbone of cloud integrations that are managed in real-time for processing and execution of application programs at the click of a button.

For more information on our Salesforce products and services, contact us at salesforce@greytrix.com. We will be glad to assist you.

Related Posts