Showing posts with label Custom Object. Show all posts
Showing posts with label Custom Object. Show all posts

Thursday, 26 May 2022

Setting up merge fields and sending emails using email template on the custom object through apex


SCENARIO

While working on one of the requirements for a Service sector project for a client based out of Atlanta, GA, there was a requirement to send an email using an email template where the related entity type was the custom object.

CHALLENGE

In SingleEmailMessage, if we set template-id (using setTemplateId), then target object id is required. setTargetObjectId() can accept contact, lead, user or person account id only. We cannot pass the custom object id in the parameter of setTargetObjectId() - reference link. Also, the custom object is not having any relationship to any of these standard objects.

APPROACH

With a little magical Apex hand-waving, we can indeed send emails using custom email templates.

The key thing we used here is Salesforce doesn't send an email immediately when the sendEmail() method is executed, instead, Salesforce waits for the very end of the transaction. If we roll back the transaction, Salesforce doesn't send the email at all.

Below is the text value of the email template having some merge fields that we want to populate automatically,

Hi {{{Sourcing__c.Full_Name__c}}},

Good afternoon, I hope you are having a great day, and that this email finds you well. 

I just wanted to reach out to follow up with you about your application for one of
our Entry-Level IT Career Opportunities. We are very interested in speaking with you.

Our initial phone call takes less than 10 minutes. You can reach me on my direct line,
{{{Sender.Phone}}} If for some reason I do not answer please leave a voice mail
and reply to this email. 

Regards,
{{{Sender.Signature}}}

Below is a code we used for sending emails,

public class SendEmailSourcing{
    
    public void sendEmailMessage(){
	// Fetching email template
        EmailTemplate emailTemplate = [SELECT Id, DeveloperName, Subject, HtmlValue, Body
                                       FROM EmailTemplate WHERE Name = '1st Call - Email' LIMIT 1];
									   
	// Picking a dummy contact where email is not null
        Contact con = [SELECT Id, Email FROM Contact WHERE email <> NULL LIMIT 1];    
        
        List<Messaging.SingleEmailmessage> emailMessages = new List<Messaging.SingleEmailMessage>();
        List<Messaging.SingleEmailmessage> emailMessagesToSend = new List<Messaging.SingleEmailMessage>();
        Messaging.SingleEmailmessage email = new Messaging.SingleEmailmessage();  
        
	// For every sourcing record, creating email message and adding it to the list of email messages
        for (Sourcing__c sourcing :  sourcingList) {                
		email = new Messaging.SingleEmailmessage();
		email.setTemplateId(emailTemplate.Id);
		email.setTargetObjectId(con.Id);
		email.setWhatId(sourcing.Id);
		email.setToAddresses(new List<String>{sourcing.Email__c});
		email.setTreatTargetObjectAsRecipient(false);
		email.setUseSignature(false);
		emailMessages.add(email);
	}       
        
	// Setting save point to rollback the transaction after sending email message
        Savepoint sp = Database.setSavepoint();
        Messaging.sendEmail(emailMessages);
        Database.rollback(sp);
        
	// Copying content of the each email message that we just sent using sendEmail() and rolled back
	// and sending these new messages
        for (Messaging.SingleEmailMessage singleEmail : emailMessages) {
		email = new Messaging.SingleEmailMessage();
		email.setToAddresses(singleEmail.getToAddresses());
		email.setPlainTextBody(singleEmail.getPlainTextBody());
		email.setHTMLBody(singleEmail.getHTMLBody());
		email.setSubject(singleEmail.getSubject());
		email.setWhatId(singleEmail.getWhatId());
		email.setUseSignature(false);
		email.setSaveAsActivity(true);
		emailMessagesToSend.add(email);
        }
		
        Messaging.sendEmail(emailMessagesToSend);  
    }
	
}

In the above code, first, we created email message for each sourcing record and added it to the list of email messages. Then trying to send email messages in a transaction that can be rolled back.

After rolling back the transaction, we are iterating through all the emails we just sent and are copying the content of those emails into another list of emails.

That newly created list is then used to send the emails.

CONCLUSION

Using Apex effectively and rolling back the transaction, we are able to set merge fields and send email messages using email templates where a related entity type is a custom object.

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

Thursday, 17 June 2021

Salesforce Custom Metadata Type

In Salesforce Custom Metadata type is similar to creating custom object or custom setting. A custom metadata type's records are metadata, not data, in and of themselves. In contrast to Custom Settings, where only metadata is eligible for migration, Custom Metadata Type and its records can be migrated from one org to another during deployment. Custom Metadata is usually deployable, package-able, customizable, and upgradeable. The key benefit of using Custom Metadata is that it does not count against the SOQL query limit for each APEX transaction.

Custom metadata types can be used for


  • Mappings— Make connections between objects, such as a custom metadata type that allocates cities, states, or provinces to specific countries & regions. 
  • Business rules— Custom functionality can be combined with configuration records. To route payments to the correct destination, use custom information types and Apex code.
  • Master data— Assume your organization utilizes a basic accounting system. Create a special metadata type for custom charges such as customs and VAT rates. Subscriber orgs can refer to the master data if this type is included as part of an extension package. 
  • Whitelists— Keep track of lists like approved contributors and pre-approved vendors. 
  • Secrets— Protected custom metadata types within a package can be used to store information such as API keys. 

Field Manageability


Field manageability is used to manage custom fields when they are created under the Custom Metadata Type. Custom metadata type supports the following custom field types: 
  • Metadata Relationship 
  • Checkbox
  • Date
  • Date and Time 
  • Email
  • Number
  • Percent
  • Phone
  • Picklist
  • Text
  • Text Area 
  • URL

Access Custom Metadata Type Records


There are new ways to access Custom Metadata Types in the Salesforce Spring-21 pre-release orgs and similar to those for Custom Settings. It is no longer necessary to query them using SOQL, and the contribution to the Query Rows limit is reduced. To access information from custom metadata type records faster, use the Apex getAll(), getInstance(recordId), getInstance(qualifiedApiName), and getInstance(developerName) methods. These methods don't use the SOQL engine and return the sObject information from the call directly.

1.Access the Custom Metadata Type records before Spring 21.

2.Access the Custom Metadata Type records after Spring 21.

A. getAll() to get Custom Metadata Type Records.

The following example uses the getAll() method. The custom metadata type named Ticket has a field called TicketType 

B. getInstance() to get specific Custom Metadata Type Record.

What about if you don’t need to get all records and need to access only a single record? In that case, you can use the getInstance() method. 

Advantages of Custom Metadata:

  • It's possible to distribute metadata! There will be no more time-consuming post-deployment configuration, as there will be with custom settings. To create your default custom setting records, you don't need to develop Apex classes.
  • Change sets or the force.com migration tool can also be used to deploy custom metadata records with metadata type definitions (ANT). The records in custom settings are uploaded after the definition of the custom setting is deployed. 
  • ListViews, Page Layouts, and Validation Rules can be created on the Custom Metadata Types.
  • Metadata Relationships are a thing of beauty! Lookups between Custom Metadata objects are possible. You may also perform an Object Definition lookup.
  • With custom metadata types, you can issue unlimited Salesforce Object Query Language (SOQL) queries for each Apex transaction. 
  • Custom metadata type is visible in test class without using “SeeAllData”. 
  • Custom Settings has the same permissions to edit records and configure the system. The “Configure Application” permission allows you to do both. You can edit records with Custom Metadata's "Configure Application," but you'll need "Author Apex" to update the configuration.

Limitations


The following are the limitations of Custom Metadata Type:
  • Custom metadata records cannot exceed the size of 10MB.
  • It does not support formula field data type.
  • It cannot be updated through Apex. The only way to edit custom metadata types is by leveraging metadata API. 
  • 100 custom metadata types can be created per salesforce org. 
  • We can create only 100 fields per custom metadata type. 
  • Global picklists are not supported. 

Summary


By creating custom metadata, we can create a static set of data and reuse it in our applications, triggers, apex class, test class, Aura components, etc. Click here for more details. 

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

Thursday, 13 May 2021

Integration of Salesforce Chatter with SharePoint Online

INTRODUCTION 

When working on one of the requirements for a manufacturing sector project for a client based in Atlanta, Georgia, USA, we came across a requirement where we had to develop a solution by integrating Salesforce with SharePoint. The basic need was to save feedback/comments from SharePoint to custom object in Salesforce. 

Comment should contain user name same as in Salesforce Chatter. Responses received from either of the platform, Salesforce or SharePoint; should be in sync. 

CHALLENGE

The main challenge was that SharePoint users are not licensed users within Salesforce. And API provided by chatter saves comment or post to chatter for only Salesforce licensed user. These users were not in sync with both Salesforce and SharePoint. 
 
The other challenge was to display comments from Salesforce into SharePoint. Connect API provides very complex response from Salesforce and it was not easy to map the field values in SharePoint. 

APPROACH

To address these issues, we took an approach of configuring a custom object in Salesforce to save comments. With a Master-detail relationship, we linked this custom object (which was created to save comments) to its parent object. 

To get more insight about the technical approach, have a look at the below explanation and screenshots. 

Let’s assume that we have an object (e.g., Account) which we consider as a parent object. We want to add comments related to the each record of that object. 

Now, users from SharePoint are going to add their comments to the records of this object to keep track of approval status. Let’s not forget that these users are not licensed Salesforce users. 

SharePoint users add their comments from SharePoint UI and with use of REST API, it will be stored in custom object in Salesforce, which is related to our parent object - Account. 

To store these comments from SharePoint, Salesforce connected app credentials were used for authentication and authorization.

Configuration of custom object for Chatter is shown in below screenshot:

Data fetched through REST API from SharePoint, would be displayed as shown in below screenshot.

SUMMARY 

By configuring custom object in Salesforce & using REST API, we can easily integrate data from 3rd party applications used by non-Salesforce users by its username or email id.

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.