Showing posts with label APEX class. Show all posts
Showing posts with label APEX class. Show all posts

Thursday, 15 April 2021

Mass\Bulk Operations in Salesforce using Batch Apex

INTRODUCTION/SCENARIO

There was a requirement for Mass/bulk Update of  more than thousands of records on the particular object based on certain action on object for one of our clients - consulting firm based out of Atlanta, GA, USA.

We, all are familiar with updating records using Apex class but updating more than a thousand records or fire DML on thousands of rows on particular objects is very complex in Salesforce and it does not allow you to operate on more than a certain number of records which satisfy the Governor limits.

But for medium to large enterprises, it is essential to manage thousands of records every day. Adding/Updating/Deleting them when needed. Salesforce has come up with a powerful concept called Batch Apex. It allows you to handle a thousand number of records and manipulates them by using a specific syntax.

APPROACH
For updating the thousand of records we need to develop the Batch Apex class which can mass update the records on the particular object. As Batch Apex operates over small batches of records, covering your entire record set and breaking the processing down to manageable chunks of data.

PROCESS
We have developed a custom global apex class that extends Database.Batchable interface because Salesforce compiler will come to know, this class incorporates batch jobs. Below is a sample class that is designed to update all the records of Account object (Let's say your organization contains more than 50 thousand records and you want to mass update all of them). 

batch class
After using the code, we have to go to Developer Console and click on Debug and then Open Execute Anonymous Window & Enter the following code in the box and click on Execute to see the result.
summary
Using Batch Apex, we can perform Adding/Updating/Deleting of thousands number of records for a particular object. As Batch Apex is asynchronous execution of Apex code, specially designed for processing a large number of records and has greater flexibility in governor limits than the synchronous code.

If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 8 April 2021

Salesforce Lightning TreeGrid with pagination

INTRODUCTION/SCENARIO

While working on one of the user stories of Pharma sector for a client based on Chicago, USA; there was a requirement to display Contact records under their parent account record. Also, pagination was needed with a picklist to choose number of records to be displayed on a page.

CHALLENGE

Lightning:treeGrid is useful component for displaying structured data such as hierarchy or forecasting data while dealing with the same object. But there is no functionality available to display records of 2 different objects (In our case, Account and Contact) in the same table while using Lightning:treeGrid.

APPROACH / SOLUTION

To overcome this limitation, I've implemented custom Javascript in the lightning component containing the mechanism to have customized list of Accounts and Contacts with pagination.

Note: This component requires API version 42.0 and later.

treeGridController is a controller class of the lightning components treeGrid utilized to fetch Account object data which has having child Contact records.

treeGridController.apxc
public class treeGridController {
    
    @AuraEnabled
    public static List <Account> getAccountList() {
        return [Select Id, Name,
                    (SELECT Name, Phone, Email FROM Contacts) 
                    From Account
                    Where Id IN (Select AccountId From Contact)
                    ORDER BY Name ASC];
    }
}

Below are Component & JavaScript files for the reference which i've used to meet the requirement..

treeGrid.cmp
<aura:component controller="treeGridController" implements="flexipage:availableForAllPageTypes" >
    <aura:attribute name="resultData" type="Object" access="private"/>
    <aura:attribute name="gridColumns" type="List" />
    <aura:attribute name="gridData" type="Object" />
    <aura:attribute name="gridExpandedRows" type="Object" />
    <aura:attribute name="PageNumber" type="Integer" />
    <aura:attribute name="TotalPages" type="Integer"/>
    <aura:attribute name="currentPage" type="Integer" default="0" />
    <aura:attribute name="limit" type="Integer" default="5" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <div class="slds-page-header" role="banner">
        <ui:inputSelect aura:id="pageSize" label="Display Records Per Page: " change="{!c.onSelectChange}">
            <ui:inputSelectOption label="5" text="5" value="true"/>
            <ui:inputSelectOption label="10" text="10"/>
            <ui:inputSelectOption label="50" text="50"/>
        </ui:inputSelect>
    </div>
    <lightning:treeGrid aura:id="accTree"
                        columns="{!v.gridColumns}"
                        data="{!v.gridData}"
                        expandedRows="{!v.gridExpandedRows}"
                        keyField="Id"
                        hideCheckboxColumn = "true"
                        />
    <div class="slds-clearfix">
        <div class="slds-page-header" role="banner">
            <div class="slds-float_right">            
                <lightning:button disabled="{!v.PageNumber == 1}" variant="brand" aura:id="prevPage" label="Prev" onclick="{!c.handlePrev}" />            
                <lightning:button disabled="{!v.PageNumber == v.TotalPages}" aura:id="nextPage" variant="brand" label="Next" onclick="{!c.handleNext}"/>
            </div>
            <p class="slds-page-header__title">Page {!v.PageNumber} of {!v.TotalPages}</p>
        </div>
    </div>
</aura:component>

treeGridController.js
({
    doInit : function(component, event, helper) {
        var columns = [
            {
                type: 'url',
                fieldName: 'AccountURL',
                label: 'Account Name',
                typeAttributes: {
                    label: { fieldName: 'accountName' }
                }
            },
            {
                type: 'text',
                fieldName: 'Name',
                label: 'Contact Name'
            },
            {
                type: 'phone',
                fieldName: 'Phone',
                label: 'Phone Number'
            },
            {
                type: 'email',
                fieldName: 'Email',
                label: 'Email'
            }
        ];
        component.set('v.gridColumns', columns);
        var action = component.get("c.getAccountList");
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS" ) {
                var resultData = response.getReturnValue();
                component.set('v.resultData', resultData);
                helper.bindTableData(component, event);
            }
        });
        $A.enqueueAction(action);
    },
    handleNext : function(component, event, helper){
        component.set('v.currentPage',component.get('v.currentPage')+1);
        helper.buildTable(component, event);
    },
    handlePrev : function(component, event, helper){
        component.set('v.currentPage',component.get('v.currentPage')-1);
        helper.buildTable(component, event);
    },
    onSelectChange : function(component, event, helper){
        component.set('v.currentPage',0);
        var pageSize = component.find('pageSize').get('v.value');
        component.set('v.limit',pageSize);
        helper.buildTable(component, event);
    }
})

treeGridHelper.js
({
    bindTableData : function(component, event) {
        var resultData = component.get('v.resultData');
        for (var i=0; i<resultData.length; i++ ) {
            resultData[i].accountName = resultData[i]['Name'];
            delete resultData[i]['Name'];
            resultData[i]._children = resultData[i]['Contacts'];
            delete resultData[i].Contacts;
            resultData[i].AccountURL = '/'+resultData[i].Id;                
        }
        component.set('v.resultData',resultData);
        this.buildTable(component, event);
    },
    buildTable : function(component, event){
        var resultData = component.get('v.resultData');
        var limit = component.get('v.limit');
        var currentPage = component.get('v.currentPage');
        var totalPage = Math.ceil(resultData.length / limit);
        var startIndex = currentPage * limit;
        var row = [];
        var expandedRows = [];        
        for (var i = startIndex; i < parseInt(startIndex)+parseInt(limit); i++) {
            if(resultData[i]){
                expandedRows.push(resultData[i].Id);
                row.push(resultData[i]);
            }
        }
        component.set('v.gridData', row);
        component.set('v.PageNumber',currentPage+1);
        component.set('v.TotalPages',totalPage);
        component.set('v.gridExpandedRows', expandedRows);
    }
})

OUTPUT: 


If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 18 March 2021

Display Picklist values dynamically in lightning component


Requirement:
While working on one of the user stories of Engineering sector project for a client based out of Tampa, FL, USA;  there was a requirement to provide an interface that would display selected Industry (Pick-list field) values for Accounts on the fly while performing Mass Edit.

Challenge: 

Mass Edit feature was implemented using the Custom Lightning component. And, t
here is no out-of-the-box (OOTB) feature available to display selected pick-list values in the in lightning component, while bulk edit is being performed.

Solution:
To overcome this limitation, we have created an Apex class that contains the mechanism to retrieve the list of Industry (pick-list) field values from the Account object.

This Apex class is then used by Custom Lightning Component (BulkEdit.Cmp) which displays the list of Accounts. Using component.get ("v.pickvalues") in DynamicPickController component, we are retrieving selected pick-list values from the Parent Component (BulkEdit.cmp). 

Then Industry pick-list values are being compared with the values that have been retrieved from Parent Component. If field values are matched, it will set the selected Industry field value.

If values are not matched, then it will set the first value from the pick list options.

Once comparison is done,  value is set to the attribute IndustryPick that we have defined in the Custom DynamicPickController.Cmp component.

BulkEdit.cmp
<aura:iteration items="{!v.selectedlst}" var="ac" >
                                    <c:DynamicPickController selectedOptions = '{!v.selectedlst}'
                                                  Name = '{!ac.lstTelLineAccLocUserobject2.Name}'
                                                  Industry = '{!ac.lstTelLineAccLocUserobject2.Industry}'
                                                  pickvalues = '{!v.pickvalues}'
                                                  />
</aura:iteration>

DynamicPickController.Js
doInit : function(component, event, helper) {
        var IndustryPick = [];
        for(var i = 0 ; i < component.get("v.pickvalues").length ; i++)
        {
            if(component.get("v.pickvalues")[i] == component.get("v.Industry"))  
            {
                IndustryPick.push({"label":component.get("v.pickvalues")[i], "value":component.get("v.pickvalues")[i], "selected":true})
            }
            else
            {
                IndustryPick.push({"label":component.get("v.pickvalues")[i], "value":component.get("v.pickvalues")[i]})
            }
        }
        component.set("v.IndustryPick", IndustryPick);
    }

DynamicPickController.Cmp
<aura:attribute name="pickvalues" type="list"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<lightning:select name="Industry" label="Select a Industry:" aura:id="Industry" value="{!v.Industry}">
                <aura:iteration items="{!v.IndustryPick}" var="option">
                    <option text="{!option.label}" value="{!option.label}" selected="{!option.selected}"/>
                </aura:iteration>
</lightning:select>

Output:

If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 11 March 2021

Hyperlink a Record in lightning:datatable

Requirement:

While working on one of the user stories of healthcare sector project for a client based out of Atlanta, GA, USA, there was a need of displaying a list of Contacts in lightning:datatable having 2 columns with hyperlinks. One is contact name and other is account name. And clicking on it, it should redirect to contact record and account record respectively.

Challenge: 

The challenging part was, there is no standard functionality available in the lightning:datatable for a hyperlink field to redirect to record detail page.

Solution:

To overcome this limitation, we'd to make changes in column format for a Contact name and Account name fields with type:'url' and also need to add typeAttributes as display below.

{ label: 'Name', fieldName: 'contacturl', type: 'url', typeAttributes: { label: { fieldName: 'Name' }, target: '_blank' } }, { label: 'Account Name', fieldName: 'accounturl', type: 'url', typeAttributes: { label: { fieldName: 'AccountName' }, target: '_blank' } }




See below code files - ApexController, Component files, and output to have a clear understanding.

ContactController
public class ContactController {
    @AuraEnabled
    public static List < Contact > fetchContacts() {
        return [ SELECT Id, Name, Account.Id,
                Account.Name,Phone,Department,LeadSource FROM Contact];        
    }
}
The contact controller class is referenced by the HyperLinkDatable component to display retrieved contacts as a data table.

HyperLinkDatable.cmp
<aura:component implements="force:appHostable" controller="ContactController">
               
    <aura:attribute type="Contact[]" name="conList"/>
    <aura:attribute name="columns" type="List"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
   
    <lightning:datatable data="{!v.conList}"
                         columns="{!v.columns}"
                         keyField="Id"
                         hideCheckboxColumn="true"/>
   
</aura:component>

HyperLinkDatableController.js
({
    
    doInit : function( component, event, helper ) {        
        component.set('v.columns', [
            { label: 'Name', fieldName: 'contacturl', type: 'url',
             typeAttributes: { label: { fieldName: 'Name' }, target: '_blank' }},
            {label: 'Department',fieldName: 'Department', type: 'Text' },
            {label: 'Lead Source',fieldName: 'LeadSource', type: 'Phone' },
            {label: 'Phone',fieldName: 'Phone', type: 'Phone' },
            { label: 'Account Name', fieldName: 'accounturl', type: 'url',
             typeAttributes: { label: { fieldName: 'AccountName' }, target: '_blank' } }
        ]);
        //getting contact records
        var action = component.get( "c.fetchContacts" );
        action.setCallback(this, function( response ) {            
            var state = response.getState();
            if ( state === "SUCCESS" ) {
                var records = response.getReturnValue();
                //setting value for url
                records.forEach( function( record ) {
                    record.AccountName = record.Account.Name;
                    record.accounturl = '/' + record.Account.Id;
                    record.Name = record.Name;
                    record.contacturl = '/' + record.Id; 
                });
                component.set( "v.conList", records );                
            }            
        });
        $A.enqueueAction( action );        
    }    
})

After this, we need to create a Lightning App to integrate all of the files as a solution.

<aura:application
access="GLOBAL" extends="force:slds"> <c:HyperLinkDatatable/> </aura:application>

Output:


If you have any questions you can reach out our Salesforce Consulting team here.

Friday, 13 November 2020

Salesforce Releases its Top Features Of Winter '21

Salesforce Release Winter'21

As a result of the three Salesforce updates every year; Administrators, Developers, and Consultants are getting a lot more features and resources to please their customers and consumers. At the moment, we are moving towards the release of Winter'21, which will add new looks and features to the organization. We get new Features related to Lightning Experience, Lightning Flow, Lightning Web Component, Apex, Communities, Pardot, Quip, Einstein Analytics, and APIs. Let’s dive into our top features of this release.


Scheduled List Email 

Now we can plan when to send a list email. Choose the right time and date to ensure the best time to receive emails. We determine which emails we would like to send — and on which days. By scheduling, we can build emails ahead of time and schedule when they're out. By planning all of our email choices ahead, we will make our marketing activities even more efficient. For example, schedule a list email to arrive on a working day. Previously, scheduling was only available for individual emails, not for list emails. 





Analyze the performance of the Lightning Page 

When it comes to growth, organizational performance is the ruler. If users experience sluggish performance or long page loading times when using Lightning Experience, they no longer need to worry. Find suggestions for a better performance of the record page based on an analysis of the page right inside the Lightning App Builder. Performance Analysis in the App Builder tests the fields, instances of the Relevant Lists component, and the metadata of the record page. By clicking the Analyze button, get an analysis of the page with suggestions for improving the page performance.




Display survey pages that are focused on your data 


With each update, Salesforce Surveys seem to be getting more and more strong. Now, we can use our data in Salesforce org to decide which survey page the user will see next. Users will move to the relevant page on the basis of the answer. We can use the variables to specify the conditions in Page branching logic. Now the user will experience a new journey. 



Choose the Position of your utility bar


The utility bar gives easy access to common productivity tools, such as Notes and Recent items. Now, we can choose the position of our Salesforce org utility bar. Customize where our utility bar appears. We may move the utility bar to the bottom right or to the bottom left of the screen. 



Trigger a flow before the record is deleted 


Previously, we always need a trigger to add any validation or functionality before a record is deleted. We don't need to write an Apex code anymore. We can now use a flow to run before the record is deleted. This auto launched flow will execute in the background and update relevant records when a record is deleted. 





Use Safe Navigation Operator to Remove NullPointerException 


Winter'21 is bringing us a new safe navigation operator (?.). We can use the safe navigation operator (?.) to remove the consecutive condition that checks for null references. We may prevent  NullPointerException with the aid of this new operator. This operator handles expressions that try to run on a null value and returns null instead of raising a NullPointerException. The right-hand side evaluates only if the left-hand side is not evaluated to null. In the variable as well as in the function, we can use a safe navigation operator. 


This expression will first test x and return null if x is null. Otherwise, the return value will be x.y



// code checking for nulls 
x?.y // test to: x == null? Null : x.y 

This example demonstrates a SOQL query using a safe navigation operator. 

ContactRec = [SELECT FirstName FROM Contact WHERE Id = :contactId]; 
if (ContactRec.size() == 0)  // Contact was deleted
{     
    return null;
} 
return ContactRec[0].FirstName; 

//New Code 
return [SELECT FirstName FROM Contact WHERE Id = : contactId]?.FirstName; 


Enhance Apex Testing with New SObject Error Methods 


New Release gives us new SObject Error methods that minimize our efforts. Previously, we needed to perform DML operations to check the result for errors. Now we can monitor errors with the new  SObject.hasErrors() method and SObject.getErrors() method without running a DML operation. Use  SObject.addError() method to add errors to the specific fields. The hasErrors() method checks if an instance of SObject contains errors. We can get a list of errors for a specific instance of SObject using the getErrors() method. 


The following example will help you to understand:

//Base code sample for use with addError, getErrors

Contact con = new Contact();    

String message = 'New error in SObject';

con.addError('FirstName', message);  //New overload that dynamically embraces the field at runtime   

List<Database.Error> errors = con.getErrors();     

System.debug( errors.size());  //print 1 in debug 

Database.Error error = errors.get(0);    

System.debug(error.getStatusCode()); //print FIELD_CUSTOM_VALIDATION_EXCEPTION in debug 

String[] fields = error.getFields();   

System.debug(fields);   // print FirstName in debug 

System.debug(fields.size()); // print 1 in debug 


So in Salesforce Winter 21 update, we've got some fantastic features. Here you can read the full Salesforce Winter'21 Release Notes. 

If you have any questions you can reach out our Salesforce Consulting team here.

Tuesday, 15 September 2020

[SOLVED]: Issue on getting Parent record-Id (whoid) while retrieving Task details generated through Email-To-Task

Requirement:
There was a requirement to track Sales person interaction with Customer which was being done via various Email Platforms such as Outlook, Gmail, Yahoo. We have used Email-To-Task to create task for such interactions. We need to track last interaction with customer by Sales\Support Person and get Due Date of Completed Task and auto populate it in "Last Sales Activity Date" (custom field) of related Contact/Lead record.

Challenge: 
The major challenge – When the Email is received to Salesforce, the task is generated and automatically linked to the relevant Contact/Lead record using Salesforce Email Configuration OOTB feature. For such Tasks, if Apex trigger is fired on the task object and at that time, we were unable to obtain the parent Id of Task generated via Email.

Solution:
To overcome this limitation, we developed a custom solution that involves creating an apex class named EmailTask having future method. Using the future method we can get parent recordId(Contact/Lead record), as it runs after some minor delay. The class contains the mechanism for retrieving Tasks List through Set of IDs that we need to pass as parameter from the Insert/Update Trigger. And after that, we need to get the Due Date (Activity Date) value from the Task and update to Last Sales Activity Date to the relevant parent record.

EmailTask Class


global class EmailTask {
    @future
    public static void getUpdate(set<Id> taskIds){
        //store WhoId and Task Record to the Map 
        Map<Id,Task> taskWhoMap = new map<Id,Task>();
        List<Task> TaskList = [Select Id,whoId,ActivityDate from task where Id IN :taskIds AND WhoId!= null];
        if(TaskList.size()>0){
            for(Task t : TaskList){
                taskWhoMap.put(t.WhoId,t); 
            }
        }
        
        if(taskwhomap.keySet().size()>0){
            List<Contact> contactList =[select Id, Last_Sales_Activity_Date__c from Contact where 
                                        Id =: taskwhomap.keySet()];
            
            if(contactList.size()>0){
                for(Contact c : contactList)
                {
                    if(taskwhomap.containsKey(c.Id)){
                        // store due date of task in Contact date field
                        c.Last_Sales_Activity_Date__c= taskwhomap.get(c.Id).ActivityDate;
                    }
                }
                // updating contact
                update contactList; 
            }
        }
        
        List<Lead> leadList =[select Id, Last_Sales_Activity_Date__c from Lead where 
Id =: taskwhomap.keySet()]; if(leadList.size()>0){ for(Lead l : leadList) { if(taskwhomap.containsKey(l.Id)){ // store due date of task in Lead date field l.Last_Sales_Activity_Date__c= taskwhomap.get(l.Id).ActivityDate; } } // updating Lead update leadList; } } }

Now, on the Task object, we need to configure an Apex Trigger which will initialize the EmailTask apex class on task creation/update.  

Email Trigger

trigger Email on Task (After insert,After update) {   
    if(trigger.Isafter){
        if(trigger.Isinsert || trigger.Isupdate){
            set<Id> TaskId = new set<Id>(); 
            for(Task t : trigger.new){
                TaskId.add(t.Id);
            }
            EmailTask.getUpdate(taskIds);
        }
    } 
}

Conclusion:

Using the above solution, we can achieve the requirement to generate the task for the relevant parent record, whenever the sales/support person communicates with the customer via E-mail and stores the interaction between them through generating Tasks and update the Last Sales Activity Date (custom field) to the relevant parent record. 

If you have any questions you can reach out our Salesforce Consulting team here.