Wednesday 27 November 2013

Salesforce Account Record Merging With Deleting the Duplicate Records

Whenever we are working with bulk data, its a hard thing to check the duplicate records and then delete them without loosing data.

So I thought why shouldn't we go for a bulk process that helps us in Merging the duplicate records and them clearing them from database without any loss of data.

So here I have done something for my Account object, I have written a batch, whenever we execute it, it will start processing on Account records created in "LAST 15 MINUTES", takes those Account's Name and Billing Address, and then it will check in database that is there any record having same Name AND Billing Address or not.

This batch won't work if there is any slight change in between name or billing address.

for example I have created two Account records having same Name and Billing Address only the first record Phone was an extra field filled.


1st Account created



2nd Account without Phone but same Billing Address and Name




Now when I execute my batch in console




Now my batch deletes duplicate Account that have empty phone


Now the best thing is, no matter how many field you have in Account it will save your field values in the unique record, that means your data will never loose your data.

Here is the batch


/**
* Description : Batch class to merge duplicate transaction Account records.
*
* Created Date : 11-26-2013 
* 
* Revision Logs : V1.0 - Created
*
**/
global class Batch_MergeDuplicateTransactionAccounts implements Database.Batchable<sObject>, Database.Stateful {
    
    //Set to hold the account records name with billing address field values
    global Set<String> setAccountNameWithBillingAddress;
    
    //String to hold the Account object fields names
    String accountFieldsNamesString = '';
    
    //Set to hold the Account fields API name strings
    Set<String> setAccountFieldsNameString;
    
    //Calling Constructor 
    global Batch_MergeDuplicateTransactionAccounts() {
        
        //Memory Allocation to collections
        setAccountFieldsNameString = new Set<String>();
        setAccountNameWithBillingAddress = new Set<String>();
        
        //Describe Account object and get all the fields
        Map<String, Schema.SObjectField> accountFieldsMap = Account.sObjectType.getDescribe().fields.getMap();
        
        //Loop through Account fields Names through Schema Methods
        for(String fieldName : accountFieldsMap.keySet()) {
            
            //Describe field
            Schema.DescribeFieldResult field = accountFieldsMap.get(fieldName).getDescribe();
            
            //Filtering out the fields for getting only updatable non system fields
            if(!field.isCalculated() && field.isCreateable() && field.isUpdateable()
               && !field.getLocalName().equalsIgnoreCase(Constants.PARENTID)) {
                   
                   //Populate set with the Fields Names string values
                   setAccountFieldsNameString.add(field.getLocalName()); 
                   
                   //Account object fields name string
                   if(accountFieldsNamesString == '')
                       accountFieldsNamesString = field.getLocalName(); 
                   else
                       accountFieldsNamesString += ',' + field.getLocalName();
               }
        }
    }
    
    //Start method
    global Database.QueryLocator start(Database.BatchableContext BC) {
        
        //Varibale to hold the current date time value
        DateTime currentDateTime = DateTime.now();
        
        //Varibale to hold the 15 minute ago date time value
        DateTime fifteenMinuteAgoDateTime = DateTime.now().addMinutes(-15);
        
        //String variable to hold the Account records those were created in last 15 minute and so.
        String sOQLQuery = 'SELECT ID, Name FROM Account WHERE'
            + ' CreatedDate >: fifteenMinuteAgoDateTime AND CreatedDate <=: currentDateTime'
            + ' AND Name != null ORDER By CreatedDate ASC';
        
        //Fetching all the Account records from the database
        return Database.getQueryLocator(sOQLQuery);
    }
    
    //Exectue Method having logic for duplicate accounts finding's
    global void execute(Database.BatchableContext BC, List<Account> scope) {
        
        //Set to hold the account name with billing address strings
        Set<String> setAccountNameWithBillingAddressStrings = new Set<String>();
        
        //Map to hold the Account records corresponding the account name and billing address value as key
        Map<String, List<Account>> mapNameBillingAddressKeyWithAccounts = new Map<String, List<Account>>();
        
        //This map is to hold the Parent Id and List of List of Accounts. One list will have maximum 2 child records.
        //Standard merge statement allows to merge 3 records at a time, so List of list will hold list of 2 child records
        Map<Id, List<List<Id>>> mapParentAccountWithListOfChildrenAccounts = new Map<Id, List<List<Id>>>();
        
        //This mapis to hold the updated Parent data always
        Map<Id, Account> mapParentAccount = new Map<Id, Account>();
        
        //Loop through account records in scope
        for(Account acc : scope) {
            
            //Checking value in set
            if(!(setAccountNameWithBillingAddress.contains(acc.Name.trim().toLowerCase()))) {
                
                //Populate set with values
                setAccountNameWithBillingAddress.add(acc.Name.trim().toLowerCase());
                setAccountNameWithBillingAddressStrings.add(acc.Name.trim().toLowerCase());
            }
        }
        
        //Check set for size
        if(setAccountNameWithBillingAddressStrings != null) {
            
            //Loop through eligible account records
            for(Account account : Database.query('SELECT ' + accountFieldsNamesString + ' FROM Account WHERE Name IN : setAccountNameWithBillingAddressStrings AND Name != null ORDER By CreatedDate ASC')) {
                
                //Key String
                String keyString = '';
                String billingAddressString = '';
                
                //Appending account billing address field values in key string after performing validation on them
                if(account.BillingStreet != null)
                    billingAddressString += account.BillingStreet.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingCity != null)
                    billingAddressString += account.BillingCity.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingState != null)
                    billingAddressString += account.BillingState.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingCountry != null)
                    billingAddressString += account.BillingCountry.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingPostalCode != null)
                    billingAddressString += account.BillingPostalCode.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                
                //Formation of key string with the help of account name and billing address string
                keyString = account.Name.trim().toLowerCase() + Constants.SEPERATOR + billingAddressString;
                System.debug('@@@@@ keyString ' + keyString);
                
                //Check for key value in map
                if(mapNameBillingAddressKeyWithAccounts.containsKey(keyString)) {
                    
                    //Get the Values of the Map and add Id to it.
                    mapNameBillingAddressKeyWithAccounts.get(keyString).add(account);
                    
                } else {
                    
                    //Creat a new Set at values and add Id to it.
                    mapNameBillingAddressKeyWithAccounts.put(keyString, new List<Account>{account}); 
                }
                
                System.debug('@@@@@ mapNameBillingAddressKeyWithAccounts ' + mapNameBillingAddressKeyWithAccounts);
                
                //Loop through map keys
                for(String key : mapNameBillingAddressKeyWithAccounts.keySet()) {
                    
                    //Checking if we have more than one account record in the list corresponding to the account name, billingaddress combined string key
                    if(mapNameBillingAddressKeyWithAccounts.get(key) != null && mapNameBillingAddressKeyWithAccounts.get(key).size() >= 1) {
                        
                        //Account record having oldest created date stamped on it will become parent of other dup recods
                        Account parentAccount = mapNameBillingAddressKeyWithAccounts.get(key)[0];
                        
                        //Set Parent in Map with latest Values
                        mapParentAccount.put(parentAccount.Id, parentAccount);
                        
                        //Add a default list
                        List<List<Id>> lstOfLst = new List<List<Id>>();
                        lstOfLst.add(new List<Id>());
                        mapParentAccountWithListOfChildrenAccounts.put(parentAccount.Id, lstOfLst);
                        
                        //Lopp through the child records
                        //Set all the null field in Parent with child data if child have not null value
                        for(Integer i=1; i<mapNameBillingAddressKeyWithAccounts.get(key).size(); i++) {
                            
                            //Dup Child Account record
                            Account childAccount = mapNameBillingAddressKeyWithAccounts.get(key)[i];
                            
                            //Loop through set having Account object fields API Name with it
                            for(String accountFieldAPIName : setAccountFieldsNameString) {
                                
                                //Checking for value in child with respect to Parent
                                if(parentAccount.get(accountFieldAPIName) == null && childAccount.get(accountFieldAPIName) != null) {
                                    
                                    //Populating Instance with value
                                    parentAccount.put(accountFieldAPIName, childAccount.get(accountFieldAPIName)); 
                                }
                            }
                            
                            //Put the latest innstance of Parent Account in Map
                            mapParentAccount.put(parentAccount.Id, parentAccount);
                            
                            //Get List from Marging Map
                            List<List<Id>> mergingAccounts = mapParentAccountWithListOfChildrenAccounts.get(parentAccount.Id);
                            
                            //Chekcif list size has been reached to 2, add a new List and add account in that
                            if(mergingAccounts[mergingAccounts.size() - 1].size() == 2) {
                                
                                //Add a new List
                                mergingAccounts.add(new List<Id>()); 
                            }
                            
                            //Add Child record in List
                            mergingAccounts[mergingAccounts.size() - 1].add(childAccount.Id);
                            
                            //Put this list back in original map
                            mapParentAccountWithListOfChildrenAccounts.put(parentAccount.Id, mergingAccounts);
                        }
                    }
                }
            }
            
            System.debug('@@@@@@ value in mapParentAccount ' + mapParentAccount);
            System.debug('@@@@@@ value in mapParentAccountWithListOfChildrenAccounts ' + mapParentAccountWithListOfChildrenAccounts);
            
            //Start Merging Process
            for(Account pAccount : mapParentAccount.values()) {
                
                //Get merging list and start merging process
                if(mapParentAccountWithListOfChildrenAccounts.containsKey(pAccount.Id)) {
                    
                    //Loop through the merging list
                    for(List<Id> accounts : mapParentAccountWithListOfChildrenAccounts.get(pAccount.Id)) {
                        
                        if(accounts != null && accounts.size() > 0) {
                            System.debug('###### accounts ' + accounts);
                            //Merge statement for merging of the child records with respect to Parent Account record
                            merge pAccount accounts;
                        }
                    } 
                }
            }
        }
    }
    
    //Finish Method
    global void finish(Database.BatchableContext BC) {
        
    }
}


Here is the Test Class

/**
* Description : Test Class for Batch_MergeDuplicateTransactionAccounts.
*
* Created Date : 11-27-2013
*
* Revisiion Logs : V_1.0 - Created
*
* Code Coverage : 100%
**/
@isTest
private class Test_Batch_MergeDuplicateTxnAccounts {
    
    //Test method
    static testMethod void myUnitTest() {
        
        //List to hold account records
        List listAccounts = new List();
        
        //Create Account with iteration of count
        for(integer i = 1 ; i <= 100 ; i++) { //Populating the list of Account records listAccounts.add(new Account(Name = 'Test1' , BillingCity = 'TestCity' , BillingState = 'TestState' , BillingPostalCode = '85004' , BillingStreet = 'TestStreet' , BillingCountry = 'US')); } listAccounts.add(new Account(Name = 'Test2' , BillingCity = 'TestCity' , BillingState = 'TestState' , BillingPostalCode = '85005' , BillingStreet = 'TestStreet' , BillingCountry = 'US')); listAccounts.add(new Account(Name = 'Test2'));                  //Insert accounts insert listAccounts; //List to hold contact records List listContacts = new List();
            
            //Populate the list with contact records
            listContacts.add(new Contact(FirstName = 'Test' , LastName = 'Contact' , AccountId = listAccounts[1].Id));
            listContacts.add(new Contact(FirstName = 'Test1' , LastName = 'Contact1' , AccountId = listAccounts[2].Id));
            
            //Insert contacts
            insert listContacts;
            
            //Test start from here
            Test.startTest();
            
            //Batch Initializing
            Batch_MergeDuplicateTransactionAccounts controller = new Batch_MergeDuplicateTransactionAccounts();
            
            //Execute Batch
            Database.executeBatch(controller , 200);
            
            //Test stop here
            Test.stopTest();
            
            //Query to get account records
            listAccounts = [SELECT ID , (SELECT ID From Contacts) FROM Account];
            
            //Assert for results
            System.assertEquals(listAccounts.size() , 3);
            System.assertEquals(listAccounts[0].contacts.size() , 2);
        }
    }   
}


Thanks & Cheers,
Hope helped someone.


Sunday 24 November 2013

Salesforce launches the internet of Customers "Salesforce1"

Salesforce1 is meant to allow the rapid creation of apps that can work across Salesforce's sales, service, and marketing apps, as well as on top of its Force.com, Heroku, and ExactTarget Fuel platforms, all at the same time. Salesforce1 is a free, automatic upgrade for existing Salesforce customers.


Salesforce1 is a new social, mobile and cloud customer platform built to transform sales, service and marketing apps for the Internet of Customers. As the pioneer of enterprise cloud computing, salesforce.com is launching the next generation of the world’s #1 cloud platform, Salesforce1, for the new connected world. Now every company can connect with customers in a whole new way.

New Salesforce1 is the first CRM platform for developers, ISVs, end users, admins and customers moving to the new social, mobile and connected cloud.
Developers can create next generation apps. Salesforce1 was built API-first to enable developers to build the next generation of connected apps.

Built for next generation applications, Salesforce1 has 10 times more APIs and services built-in, for developers to build quickly, and easily create personalized experiences for connect smartphones and wearable smart devices. With Salesforce1, every ISV can accelerate their growth by building, selling and distributing apps for the connected customer.

ISVs such as Evernote and Kenandy are building mobile-ready apps on the platform and leveraging the power of the Salesforce1 AppExchange to market and sell these apps. A new mobile app built on the Salesforce1 Customer Platform allows users to access and experience Salesforce everywhere on any form factor.


“It’s not about thousands of new computers, it’s about 50 billion connected things,” said Benioff. “Everything is on the net. The airplane engine is on the net. The Caterpillar tractor is on the net. The Coca-Cola vending machine is on the net.”

Salesforce1 is a new home base for Salesforce mobile, providing access to core customer relationship management (CRM) features as well as custom and partner apps. It’s also a platform for developers, who have access to 10 times more Salesforce APIs than they did before.

Wednesday 20 November 2013

Salesforce Integration With Klout




Recently from the client side got a requirement of getting Twiitter Account rating in Salesforce using Klout.
Go to Klout
Here what we are doing is just creating a visualforce page which is having custom fields of Account on the click of a button on detail page, our visualforce page will open, where user will get its Twitter Account rating.
It can be easily understand by the snaps below

Write your twitter Screen Name and click on the button "Get Rating"


After clicking that button user will get its Twitter rating as shown


Now the question rises how to do this thing?

So first of all, create an Account in Klout(Link provided above) then on the dashboard Click on "REGISTER AN APP", fill all the formalities and then you'll find this, marked in red is the key which we'll use to get Twitter score.


Here is the apex class

/**     Description    :    Ths class will Integrate Salesforce with Klout. 

  *

  *    Created By     :    Abhi Tripathi

  *

  *    Created Date   :    07/30/2013

  *

  *    Revisison Log  :    v1.0 - Created

  *

  *    Version        :    V1.0

**/



public with sharing class KloutWithSalesforceTwitterRatingUpdate {



  //Wrapper Class for First Response

  public class firstResponseParsingWrapper {

   

    //Response variables

    public String id;

    public String network;

   

    //Constructor

    public firstResponseParsingWrapper(String id, String network) {

      this.id = id;

      this.network = network;

    }

  }

 

  //Wrapper for second response

  public class KloutFinalResponseWrapper {

   

    //Score from the response

    public String score;

   

    //Constructor

    public KloutFinalResponseWrapper(String score) {

      this.score = score;

    }

  }

 



  //account

  public Account account { get; set; }

  public String twitterScore { get; set; }

 

  //constructor

  public KloutWithSalesforceTwitterRatingUpdate(ApexPages.StandardController stdController){

   

    //Initiallize

    twitterScore = '';



    //account record

    this.account = (Account)stdController.getRecord(); 

  }

   

    //Method for making callout and populating values retrieved from response is going to be diplayed on Visualforce Page

    public void kloutTwitterRating() { 



    try {

      //Http

      Http http = new Http();

     

      //Request

      HttpRequest req = new HttpRequest();

      req.setEndpoint('http://api.klout.com/v2/identity.json/twitter?screenName='+ twitterScore +'&key=4j6pe8zamj4dmh2by9tzv5sc');

      req.setMethod('GET');

     

      //Send request

      HTTPResponse firstResponse = http.send(req);

      System.debug('res::::::' + firstResponse.getBody());

     

      //Body

      String body = firstResponse.getBody();

     

      //Deserializing response

      KloutWithSalesforceTwitterRatingUpdate.firstResponseParsingWrapper parsedResponse = (KloutWithSalesforceTwitterRatingUpdate.firstResponseParsingWrapper)JSON.deserialize(body, firstResponseParsingWrapper.class);

      System.debug('parsedResponse:::::::' + parsedResponse);

     

      //String for id in response

      String responseId = parsedResponse.id;

      System.debug('responseId:::::::' + responseId);

   

      //Second request for score

      HttpRequest finalReq = new HttpRequest();

      finalReq.setEndpoint('http://api.klout.com/v2/user.json/'+ responseId +'/score?key=4j6pe8zamj4dmh2by9tzv5sc'); 

      finalReq.setMethod('GET');

     

      //Send request

      HTTPResponse lastResponse = http.send(finalReq);

      System.debug('lastResponse::::::' + lastResponse.getBody());

     

      //Body

      String finalBody = lastResponse.getBody(); 

     

      //Deserializing response

      KloutWithSalesforceTwitterRatingUpdate.KloutFinalResponseWrapper parseFinalResponse = (KloutWithSalesforceTwitterRatingUpdate.KloutFinalResponseWrapper)JSON.deserialize(finalBody, KloutFinalResponseWrapper.class);

      System.debug('parseFinalResponse::::::' + parseFinalResponse);

     

      //Assigning value

      account.Twitter_Rating__c = parseFinalResponse.score;

      account.Validate_Score_Successfully__c = true;

      account.Validate_Score_Last_Attempt__c = date.today();   

   



    }catch (exception e) {

     

      //Error messages

      ApexPages.Message errormsg = new ApexPages.Message(ApexPages.severity.ERROR,'UserName Not Found, Sorry Try Again');

            ApexPages.addMessage(errormsg);

      System.debug('e:::::::' + e); 

    }   

   

  }

}


Here is the Visualforce Page

 <apex:page standardController="Account" extensions="KloutWithSalesforceTwitterRatingUpdate">  
    <!-- heading-->    
   <apex:sectionHeader title="Screen Name" subtitle="Twitter Rating"/>  
   <!-- form -->  
   <apex:form >  
     <!-- page messages-->  
     <apex:pageMessages />  
     <!-- page block -->  
     <apex:pageBlock mode="edit">  
         <!-- button -->  
         <apex:pageBlockButtons >   
           <apex:commandButton value="Get Rating" action="{!KloutTwitterRating}" />  
           <apex:commandButton value="Cancel" action="{!cancel}" />  
         </apex:pageBlockButtons>  
       <!-- block section -->  
       <apex:pageBlockSection title="Twitter Screen Name">  
         <!--Input Text>-->  
         <apex:pageBlockSectionItem >  
           Your Twitter Screen Name  
           <apex:inputText value="{!twitterScore}"/>  
         </apex:pageBlockSectionItem>  
       </apex:pageBlockSection>  
       <apex:pageBlockSection title="Rating With Related Values">  
         <!--Score-->  
         <apex:outputField value="{!account.Twitter_Rating__c}" />  
         <apex:outputField value="{!account.Validate_Score_Successfully__c}" />  
         <apex:outputField value="{!account.Validate_Score_Last_Attempt__c}" />   
       </apex:pageBlockSection>  
     </apex:pageBlock>   
   </apex:form>  
 </apex:page>  


Now get your Twitter Rating in your salesforce.
Cheers.......!!!!!