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
EMHDevEMHDev 

Help needed with recursion in trigger - lead conversion

I have a use case where, when a lead is converted, I need to convert all other leads from the same company.  I have a custom object (Lead Company) that the leads are linked to via Lookup.  The Lead Company has a flag Converted__c which is set when a lead is converted, from a lead <before update> trigger and the Lead Company record also then records the Account Id of the converted lead.  There is then a trigger on the Lead Company which detects that the flag has been set and then converts all other leads on that account to contacts.  Or at least, that is what is intended, but I get an error saying that it is recursive, as the conversion process then calls my lead trigger again.

 

So, I understand where the error is coming from, but what I can't work out is how to achieve this objective without the recursion.

 

Can anyone help me with a better solution?

 

Thanks

Best Answer chosen by Admin (Salesforce Developers) 
EMHDevEMHDev

I have fixed this. For reference for anyone hitting the same issue, I'm explaining the solution here.

 

It was caused by my picking up the current lead, which was in the process of being converted, in the list of leads related to the lead company that needed to be converted.  This was because the IsConverted flag had not yet been committed to the database so wasn't being filtered out of my query.  I solved it by putting a static set of Ids into my outer class.  When converted a lead I added its id to the set and then excluded this set from the query of leads to be converted, thus solving the recursion.

 

List <Lead> LeadLst = [Select Id, FirstName, LastName, Lead_Company__c, Email, Country, Company, IsConverted From Lead
                                    where Lead_Company__c in :LCSet and IsConverted = false and Id not in :LeadCompanyUtl.LeadBeingConverted];

All Answers

SargeSarge

Hi,

 

   Can you post your trigger code?

EMHDevEMHDev

I can, but I don't think the code is the issue, it is the design that is the problem, i.e:

 

1. A lead trigger updates a checkbox in a related object

2. Trigger in related object then tries to update other leads (to convert them)

3. This causes the trigger in (1) to fire again, which throws a fatal recursion exception.

 

I'm asking if there is any other way to do this.  The only way the leads in (3) would know that the lead in (1) was converted is via the related Lead Company record in (2).  However, this appears to be forbidden in salesforce because it chains all the triggers.

 

 

trigger LeadBeforeInsertUpdateTrigger on Lead (before insert, before update, after insert) {
    //Checks whether the lead company record exists.  If not, it creates it, sets the owner of the lead company to the owner of the lead, 
    //and links the lead to it.  
    //If it does exist, it links the new lead to the existing company.
    
    // 1.	Do a simple dedupe before creating/updating lead
    Map<String, Lead> leadMap = new Map<String, Lead>();
    Map<String, Lead> leadCoMap = new Map<String, Lead>();		    
    
	List <Lead> LeadLst = new List <Lead>(); // for after trigger to update leads
	
    for (Lead lead : Trigger.new) {
        if (Trigger.isInsert || (Trigger.isUpdate && (lead.Email != Trigger.oldMap.get(lead.Id).Email))) {
                   
            // Make sure another new lead isn't also a duplicate
            if (Trigger.isBefore && leadMap.containsKey(lead.Email)) {
                if ((leadMap.get(lead.Email).LastName == lead.LastName) && (leadMap.get(lead.Email).FirstName == lead.FirstName) && (leadMap.get(lead.Email).Company == lead.Company)) 
                    lead.addError('Another new lead has the same name, company and email address.');
            } else {
                leadMap.put(lead.Email, lead);
                if (lead.Lead_Company__c == null) leadCoMap.put(lead.Company, lead);		// Don't add it if it was already mapped
            }
       }
    }
    
    // Using a single database query, find all the leads in the database that have the same name and email address as any
    // of the leads being inserted or updated.
    if (Trigger.isBefore) {
    	for (Lead lead : [SELECT Email, LastName, FirstName, Company FROM Lead WHERE Email IN :leadMap.KeySet()]) {
        	Lead newLead = leadMap.get(lead.Email);
        	if ((newLead.LastName == lead.LastName) && (newLead.FirstName == lead.FirstName) && (newLead.Company == lead.Company))
            	newLead.addError('A lead with the same name, company and email address already exists.');
    	}
    }

	List <LeadCompany__c> LCInsLst = new List <LeadCompany__c>();   
	
    // 2.	Get all Lead Company records that match the trigger company names.  
    List <LeadCompany__c> LClst = [Select Id, Postcode__c, OwnerId, Name, Country__c, Converted__c, Account__c From LeadCompany__c where Name IN :leadCoMap.KeySet()];
    Boolean linked = false;
	   
   	
	//3.	For existing lead company records, link our lead to existing lead company record	   
    if (trigger.isBefore) {
    	for (Lead lead : Trigger.new){
	    	if (lead.Lead_Company__c == null){
		        for (Integer i=0; i<LClst.size(); i++) {
		            if (lead.Company == LClst[i].Name) {
		        	// Check for same country as well	            	
		            	if (lead.Country == LClst[i].Country__c)  {
		            		if (LClst[i].Converted__c == false) {
			            		lead.Lead_Company__c = LClst[i].Id;
	//			            		lead.OwnerId = LClst[i].OwnerId;                	
system.debug('### lead company (existing) is '+lead.Lead_Company__c);	                
			                	linked = true;
		    	            	break;
		            		} else {
		            			lead.addError('This lead company has already been converted. Create this as a contact on the account.');
		            		} 
		            	} 
		            }
		        }
		        if (!linked) {
		        	/* Because we don't have the lead id, we can't link the lead to the about-to-be-created lead company, 
				so we will do that in the LeadCompany trigger */
			    	LeadCompany__c lc = new LeadCompany__c();
		            // Add the Company to the Lead Company list to be inserted
		            lc.Name = lead.Company;
		            lc.Postcode__c = lead.PostalCode;
		            lc.Country__c = lead.Country;
		            lc.OwnerId = lead.OwnerId;   
					LCInsLst.add(lc);
		    	}
	        	linked=false;
   			}
    	}
    	if (LCInsLst.size() > 0){		// Insert the list of lead companies to create
	    	try {	               
	       		insert LCInsLst;
			} catch (DmlException e){
            	system.debug('### DML Exception:'+e);
		   	}
system.debug('### lead companies (new) is '+LCInsLst);
	    }
    } else {
    	if (!LeadCompanyUtl.DoneLink) {
    		LeadCompanyUtl.linkLead(Trigger.newMap.keySet());
    	}
    }
	    
  	if (trigger.isUpdate && !LeadCompanyUtl.RelatedConvert) {   	
	// 4.	Check each trigger lead for the lead company.  If the lead has been converted and there are additional leads on the lead company, 
	// we want to convert them. Rather complicated as we don't want it to be recursive.  So, best to update a flag on the Lead Company and 
	// let a Lead Company trigger convert the remaining leads.
		List <LeadCompany__c> LCConv = new List <LeadCompany__c> ();
		for (Lead lead : Trigger.new){
			if ((lead.Lead_Company__c != null)&& (lead.IsConverted == true)) {
				// should be safe as we don't convert leads in bulk
				LeadCompany__c lc = [Select Id, Converted__c, Account__c From LeadCompany__c where Id = :lead.Lead_Company__c LIMIT 1];
				lc.Converted__c = true;
				lc.Account__c = lead.ConvertedAccountId;
	system.debug('### lead is being converted');				
				LCConv.add(lc);				
			}
		}
		if (LCConv.size()>0){
			try {
				update LCConv;
			} catch (DmlException e){
		        system.debug('### DML Exception:'+e);
		    }
		}    	 
 	}     
}

 

 

Lead Company trigger (had to comment it out for now so I can test the rest of the functionality):

 

trigger LeadCompanyConverted on LeadCompany__c (after insert, after update) {
        
	/* If the Converted__c flag is set, then we need to convert all other leads that are linked
		to this company to the linked account */
		Set <Id> LCSet = new Set <Id>();
		Map <Id, Id> AccMap = new Map<Id, Id>();
		for (LeadCompany__c lc:trigger.new){
			if (lc.Converted__c == true) {
system.debug('### Lead company has been converted '+lc);
				LCSet.add(lc.Id);	
				AccMap.put(lc.Id, lc.Account__c);	// Keep a map of converted LC to Account
			}
		}
/*		if (LCSet.size()>0) {
			List <Lead> LeadLst = [Select Lead_Company__c, LastName, Id, FirstName, Email, Country, Company From Lead
									where Lead_Company__c in :LCSet];
			LeadCompanyUtl.RelatedConvert=true;
			LeadCompanyUtl.convertLead(LeadLst, AccMap);
		}*/
		
}

Outer class:

 

public class LeadCompanyUtl {
	public static Boolean DoneLink=false;
	public static Boolean RelatedConvert=false;
	private static List <LeadStatus> ConvStat = [Select MasterLabel, Id From LeadStatus  where IsConverted=true LIMIT 1];
@future
	public static void linkLead(Set<Id> leadIdSet) {
		
		List <Lead> LeadLst = [Select Id, Company, Country, OwnerId, Lead_Company__c from Lead where Id IN :leadIdSet]; //Get our leads again
		Set <String> CoNameSet = new Set<String> ();
		
		for (Integer i=0; i<LeadLst.size(); i++) {
			if (!CoNameSet.contains(LeadLst[i].Company)) {
				CoNameSet.add(LeadLst[i].Company);
			}
		}
				
		List <LeadCompany__c> LClst = [Select Id, Postcode__c, Name, Country__c, Converted__c, Account__c From LeadCompany__c where Name in :CoNameSet];
		
		for (Lead lead:LeadLst){
			for (Integer i=0; i<LClst.size(); i++) {
				if (lead.Company == LClst[i].Name) {
			     	// Check for same country as well	            	
			       	if (lead.Country == LClst[i].Country__c)  {
		           		lead.Lead_Company__c = LClst[i].Id;           	
system.debug('### lead company (existing) is '+lead.Lead_Company__c);	                
	    	           	break;
		           	} 
			    }
			}
		}
		try {	               
	    	update LeadLst;
		} catch (DmlException e){
           	system.debug('### DML Exception:'+e);
	   	}
	   	DoneLink=true;				
	}
	
	public static void convertLead(List <Lead> LeadLst, Map<Id, Id> AccMap) {
		for (Lead ld:LeadLst){
			Database.LeadConvert lcv = new database.LeadConvert();
	   		lcv.setLeadId(ld.id);
	   		lcv.setAccountId(AccMap.get(ld.Lead_Company__c));
	   		lcv.setDoNotCreateOpportunity(true);
	   		lcv.setConvertedStatus(ConvStat[0].MasterLabel);
	   		Database.LeadConvertResult lcr = Database.convertLead(lcv);
system.debug('### lead conversion result: '+lcr);			       				
		}
	}
}

 

 

 

SargeSarge

Hi,

 

 

   I think you can break the recursion. Do the following:

 

1) Create a Class with static variable (preferably boolean)  and initialize it to true in the class;

2) Check this varaible in the lead trigger before doing any operation.

3) Set the value of the static variable as false in LeadCompany trigger before invoking "LeadCompanyUtl.convertLead(LeadLst, AccMap)".

 

At this point the lead trigger fires upon conversion but it checks the static variable and does nothing. This will break the recursion.

4) Change back the value of the static variable as true, if it is required to run the same lead trigger but does not lead to recursion.

 

 

This is suggested based on the fact that the static variable retains the value, changed anywhere, in one apex transaction. The recursion is a part of the apex trigger transaction.

 

Hope this helps.

EMHDevEMHDev

I already had such a variable and was checking it, but put in an extra check as the first line to be sure.  However, that is not the issue. I wasn't worried about the actual recursion, I was worried about the exception thrown.  The error report is:

 

Apex script unhandled trigger exception by user/organization: 
Source organization:
LeadCompanyConverted: execution of AfterUpdate

caused by: System.DmlException: ConvertLead failed. First exception on row 0; first error: SELF_REFERENCE_FROM_TRIGGER, Object (id = 00QR0000009TxKl) is currently in trigger LeadBeforeInsertUpdateTrigger, therefore it cannot recursively convert itself: []

I don't believe that I am trying to convert the lead that has already been converted - checking the log, that is not the id reported - and I am checking the boolean and exiting if it is being called from the ConvertLead outer class.  So I'm stuck here. :-(

EMHDevEMHDev

I have fixed this. For reference for anyone hitting the same issue, I'm explaining the solution here.

 

It was caused by my picking up the current lead, which was in the process of being converted, in the list of leads related to the lead company that needed to be converted.  This was because the IsConverted flag had not yet been committed to the database so wasn't being filtered out of my query.  I solved it by putting a static set of Ids into my outer class.  When converted a lead I added its id to the set and then excluded this set from the query of leads to be converted, thus solving the recursion.

 

List <Lead> LeadLst = [Select Id, FirstName, LastName, Lead_Company__c, Email, Country, Company, IsConverted From Lead
                                    where Lead_Company__c in :LCSet and IsConverted = false and Id not in :LeadCompanyUtl.LeadBeingConverted];

This was selected as the best answer