function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Teja SattoorTeja Sattoor 

Bulkification of Lead Conversion Trigger

Hi, I am new to SF coding and I have written a lead conversion trigger that should convert a lead when it meets the criteria. My code works fine when I convert one lead at a time, but runs into "CANNOT_CONVERT_UPDATE_LEAD" when I run to convert multiple leads at once. Please find my trigger and apex class below.

Apex Trigger:
trigger AutoConvertLeads on Lead (after update) {

    Set<Id> leadIds = new Set<Id>();
    
    for(Lead myLead : Trigger.new){
    
        if((myLead.isconverted == false) && (myLead.Convert_to_Contact__c == true)){
            
            leadIds.add(myLead.Id);
            
        }
        
    if(leadIds.size() > 0) {
        
        LeadConversionUtils.convertLeads(leadIds);
    }
    }
}

Apex Class:
public class LeadConversionUtils {

    public static void convertLeads(Set<Id> leadIds) {
    
        // Retrieve all the necessary fields for the Leads to be converted
        List<Lead> leadList = new List<Lead>([SELECT Id, Company, Zlien_User_ID__c FROM Lead WHERE Id IN :leadIds]);
              
             
         //Convert the lead
       for(lead myLead : leadList){ 
        
            List<Account> AccountList1 = [Select Account.Id, Account.Name from Account where Account.User_ID__c =: myLead.Zlien_User_ID__c];
            if(!AccountList1.isEmpty()){
                List<Contact> ContactList = new List<Contact>();
                List<Account> AccountList = new List<Account>();   
        
                for(Account acc1 : AccountList1){
                    Database.LeadConvert lc = new database.LeadConvert();
                    lc.setLeadId(myLead.Id);
                    lc.setAccountId(acc1.Id);
                    lc.setConvertedStatus('Converted to Opportunity');
        
                    lc.setDoNotCreateOpportunity(true);
                    Database.LeadConvertResult lcr = Database.convertLead(lc);
                    System.assert(lcr.isSuccess());
                    
                    Contact Con = [Select OwnerId, Lead_Status__c, Contact_Stage__c from Contact where Contact.Id =: lcr.getContactId()];
                    Account Acc = [Select OwnerId from Account where Account.Id =: lcr.getAccountId()];
                    Con.Lead_Status__c = 'Converted to Account/Contact';
                    Con.Contact_Stage__c = 'Current Customer';
                    Con.OwnerId = Acc.OwnerId;
                    ContactList.add(Con);
                    AccountList.add(Acc);
    
                }   
                
                update ContactList;
                update AccountList;     
            }
        }   
    }
}


Could you please help me bulkify the trigger and also let me know the mistake I have been doing? Thanks
Nikhil Verma 6Nikhil Verma 6
Hi Teja,

Your apex trigger is fine but there are a few issues in your class code.
  1. Since you have queried Accounts (AccountList1) inside the for loop (a big no in apex), your code tries to convert a lead more than once in case there are multiple records returned by your query, hence the error.
  2. There are 2 more queries on Account (Acc) and Contact (Con) inside a for loop that is further nested in another for loop. This will definitely run into issues in case when you update more than 100 leads at once.
  3. I do not see any update on the Account fields, but you are still executing an update on AccountList. I am guessing that is not required at all.
I have made a few modifications to your class, below is the code:
public class LeadConversionUtils {

    public static void convertLeads(Set<Id> leadIds) {
    
        Set<Id> setUserIds = new Set<Id>();
        // Retrieve all the necessary fields for the Leads to be converted
        List<Lead> leadList = new List<Lead>([SELECT Id, Company, Zlien_User_ID__c FROM Lead WHERE Id IN :leadIds]);
        for(Lead varL : leadList){
            if(varL.Zlien_User_ID__c != null)
                setUserIds.add(varL.Zlien_User_ID__c);
        }
        
        Map<Id, Account> mapUserId_Account = new Map<Id, Account>();
        for(Account varA : [Select Account.Id, Account.Name from Account where Account.User_ID__c IN : setUserIds])
            mapUserId_Account.put(varA.User_ID__c, varA);

        List<Contact> ContactList = new List<Contact>();
         //Convert the lead
        for(lead myLead : leadList){         
            //List<Account> AccountList1 = [Select Account.Id, Account.Name from Account where Account.User_ID__c =: myLead.Zlien_User_ID__c];
            if(myLead.Zlien_User_ID__c != null && mapUserId_Account.containsKey(myLead.Zlien_User_ID__c)){
                Database.LeadConvert lc = new database.LeadConvert();
                lc.setLeadId(myLead.Id);
                lc.setAccountId(mapUserId_Account.get(myLead.Zlien_User_ID__c).Id);
                lc.setConvertedStatus('Converted to Opportunity');
    
                lc.setDoNotCreateOpportunity(true);
                Database.LeadConvertResult lcr = Database.convertLead(lc);
                System.assert(lcr.isSuccess());
                
                Contact con = new Contact(Id = lcr.getContactId());
                con.Lead_Status__c = 'Converted to Account/Contact';
                con.Contact_Stage__c = 'Current Customer';
                con.OwnerId = mapUserId_Account.get(myLead.Zlien_User_ID__c).OwnerId;
                ContactList.add(con);
            }
        }

        if(ContactList.size() > 0)
            update ContactList;
    }
}

Let me know if this works for you
Teja SattoorTeja Sattoor
Hi Nikhil,

Thank you so much for your quick response. I now get a better idea of the mistake I have done, though I incur the same error again. Upon further investigation by looking into the logs, I realize that an other trigger is trying to update the converted lead. This is a "before update" trigger which is being called for the second time. I have no idea why this is being called again. I have only two triggers on my leads, one of which is a "before update"(the above one that is being called twice), and the lead conversion trigger which is an "after update" trigger.

I was hoping if you have any idea why this before update trigger was called twice and the best way to resolve this.

Thank you again,
Teja.
Nikhil Verma 6Nikhil Verma 6
Hi Teja,
This is possibly a case of recursive trigger calls. This happens when your record gets updated by some other configuration (trigger/workflow/process builder, etc) which causes the before and after triggers to fire once again. 
In order to prevent this, you can use a static variable and use it as a flag to only execute the lead conversion piece once. Here is how you can do this (I have modified the code mentioned in previous reply to include the static flag):
 
public class LeadConversionUtils {

	public static boolean flag = false;
    public static void convertLeads(Set<Id> leadIds) {
    
		if(!flag)
		{
			Set<Id> setUserIds = new Set<Id>();
			// Retrieve all the necessary fields for the Leads to be converted
			List<Lead> leadList = new List<Lead>([SELECT Id, Company, Zlien_User_ID__c FROM Lead WHERE Id IN :leadIds]);
			for(Lead varL : leadList){
				if(varL.Zlien_User_ID__c != null)
					setUserIds.add(varL.Zlien_User_ID__c);
			}
			
			Map<Id, Account> mapUserId_Account = new Map<Id, Account>();
			for(Account varA : [Select Account.Id, Account.Name from Account where Account.User_ID__c IN : setUserIds])
				mapUserId_Account.put(varA.User_ID__c, varA);

			List<Contact> ContactList = new List<Contact>();
			 //Convert the lead
			for(lead myLead : leadList){         
				//List<Account> AccountList1 = [Select Account.Id, Account.Name from Account where Account.User_ID__c =: myLead.Zlien_User_ID__c];
				if(myLead.Zlien_User_ID__c != null && mapUserId_Account.containsKey(myLead.Zlien_User_ID__c)){
					Database.LeadConvert lc = new database.LeadConvert();
					lc.setLeadId(myLead.Id);
					lc.setAccountId(mapUserId_Account.get(myLead.Zlien_User_ID__c).Id);
					lc.setConvertedStatus('Converted to Opportunity');
		
					lc.setDoNotCreateOpportunity(true);
					Database.LeadConvertResult lcr = Database.convertLead(lc);
					System.assert(lcr.isSuccess());
					
					Contact con = new Contact(Id = lcr.getContactId());
					con.Lead_Status__c = 'Converted to Account/Contact';
					con.Contact_Stage__c = 'Current Customer';
					con.OwnerId = mapUserId_Account.get(myLead.Zlien_User_ID__c).OwnerId;
					ContactList.add(con);
				}
			}

			if(ContactList.size() > 0)
				update ContactList;
			
			flag = true;		//if the trigger executes this method again in the same context, flag will bypass this since it is set to true because of the previous call
		}
    }
}

 
Teja SattoorTeja Sattoor
Hi Nikhil,

Thanks again. I have implemented the changes you have suggested. This time the code is no longer throwing an error but is converting only one of the leads. I looked at the logs and looks like the first lead that has been converted is turning the flag "true" thereby letting the second lead to bypass the conversion. I was hoping to see if I have to make changes to the trigger to send both(or more) leadIds at once to the conversion class.

I cannot thank you enough for your help. 

Teja.
Nikhil Verma 6Nikhil Verma 6
Yes Teja, you need to send all the updated records at once to the conversion class. The trigger that you have posted above does that correctly. If you have however, made changes to it, update your trigger to the below:
trigger AutoConvertLeads on Lead (after update) {

    Set<Id> leadIds = new Set<Id>();    
    for(Lead myLead : Trigger.new){
        if(myLead.isConverted == false && myLead.Convert_to_Contact__c == true)
            leadIds.add(myLead.Id);

    if(leadIds.size() > 0)
        LeadConversionUtils.convertLeads(leadIds);
    }
}

If this resolves your issue, please mark this as the best answer.
Teja SattoorTeja Sattoor
Hi Nikhil,

I did not change the trigger and I have even tried your code too. It still converts only one lead but doesn't through any error. 
Thanks,
Teja.