+ Start a Discussion
TLFTLF 

Problem with auto-convert trigger on Lead

I have a requirement to automatically convert a Lead if the Lead status is a specific value (the Lead is to be converted when Lead.Status = 'Complete Request - Convert'). I wrote an insert/update trigger that performs the conversion process. It seems to be working fine when I test it by creating a Lead thru the Salesforce UI. If I create it with the specified status value, it is immediately converted upon saving my new record. So, I then wrote a unit test to test it programmatically. My unit test code looks like this:

 

 

	// Tests successful conversion of a single Lead inserted directly in the 'Complete Request - Convert' status
public static testMethod void testSingleInsert() {

RecordType clientLeadRT = [SELECT Id FROM RecordType WHERE DeveloperName = 'Client' AND SObjectType = 'Lead'];

Lead l = new Lead();
l.FirstName = 'Test';
l.LastName = 'Lead1';
l.Company = 'Test Company';
l.Phone = '3335552233';
l.Email = 'tlead1@test.com';
l.Status = 'Complete Request - Convert';
l.Street = '123 Test Street';
l.City = 'Test Town';
l.State = 'TN';
l.PostalCode = '12345';
l.RecordTypeId = clientLeadRT.Id;
insert l;

// Retrieve the Lead... it should have been converted
Lead convertedLead = [SELECT Id, IsConverted, ConvertedContactId, ConvertedAccountId, Status FROM Lead WHERE Id = :l.Id];
System.assert(convertedLead.IsConverted);
System.assertEquals('Qualified', convertedLead.Status);

// Retrieve the converted Contact
Contact convertedContact = [SELECT Id, FirstName, LastName, CompanyName__c, AccountId, RecordType.Name,
Phone, Email, MailingStreet, MailingCity, MailingState, MailingPostalCode
FROM Contact WHERE Id = :convertedLead.ConvertedContactId];
System.assertEquals(l.FirstName, convertedContact.FirstName);
System.assertEquals(l.LastName, convertedContact.LastName);
System.assertEquals(l.Company, convertedContact.CompanyName__c);
System.assertEquals(convertedLead.ConvertedAccountId, convertedContact.AccountId);
System.assertEquals(l.Phone, convertedContact.Phone);
System.assertEquals(l.Street, convertedContact.MailingStreet);
System.assertEquals(l.City, convertedContact.MailingCity);
System.assertEquals(l.State, convertedContact.MailingState);
System.assertEquals(l.PostalCode, convertedContact.MailingPostalCode);
System.assertEquals('Client', convertedContact.RecordType.Name);
}

 

 

The status value 'Complete Request - Convert' is the value that triggers the auto-conversion. When I run this test, I get an exception at the "insert l;" statement (highlighted in red). The error is:

 

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_UPDATE_CONVERTED_LEAD, cannot reference converted lead: []

 

I get the same error if I try to create a Lead in this state from the "Execute Anonymous" window.

 

If I create the Lead in a different state, and then update it to "Complete Request - Convert", it works fine. Is there any workaround for this, or can I not insert a Lead that is converted by a trigger in the same transaction?

TLFTLF

By the way, the trigger is an "after insert, after update" trigger.

jkucerajkucera

Please post your trigger code.  Is the status you're using a "converted status"?

http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_leadstatus.htm?SearchType=Stem&Highlight=LeadStatus

 

TLFTLF

The status value I am using is not a "converted" status value. It is merely a value that I am using to trigger the conversion operation. Here is the trigger code:

 

 

trigger AutoConvertLeads on Lead (after insert, after update) {
	
	final String CONVERT = 'Complete Request - Convert';
	 
	RecordType clientLeadRT = [SELECT Id FROM RecordType WHERE SObjectType='Lead' AND DeveloperName='Client' LIMIT 1];
	
	// Only consider unconverted "Client" Leads for auto-conversion
	Set<Id> leadIds = new Set<Id>();
	for (Lead l : Trigger.new) {
		if ((l.RecordTypeId == clientLeadRT.Id) && (!l.IsConverted)) {
			// For inserts, convert if status is 'Convert'
			if ((Trigger.isInsert) && (CONVERT.equals(l.Status))) {
				leadIds.add(l.Id);
				
			// For updates, only convert when status transitions to 'Convert'
			} else if ((Trigger.isUpdate) && 
				((CONVERT.equals(l.Status)) && (!CONVERT.equals(Trigger.oldMap.get(l.Id).Status)))){
				leadIds.add(l.Id);
			}
		}
	}
	
	// Do it!
	if (leadIds.size() > 0) {
		LeadConversionUtils.convertClientLeads(leadIds);
	}
}

An here is the LeadConversionUtils class that contains the "converClientLeads" method:

 

 

public class LeadConversionUtils {

	// Performs bulk conversion of the passed in "Client" Leads
	// This will perform the following:
	// - Convert Lead, creating Account and Contact
        // - 
	// - Create Project, assigned to Account just created
	public static void convertClientLeads(Set<Id> leadIds) {
		
		try {
			// Retrieve all the necessary fields for the Leads to be converted
			Map<Id, Lead> leadMap = new Map<Id, Lead>([SELECT Id, Name, FirstName, LastName, Company, Email,
				Status, OwnerId, Subject__c, Description, Scope__c, BudgetConstraints__c, DoneBy__c, Coupon__c, 
				Talent__c, TalentId__c, SearchLink__c,
	            CCName__c, CCType__c, CCNumber__c, CC_CSC__c, CCExpirationMonth__c, CCExpirationYear__c
	            FROM Lead WHERE Id IN :leadIds]);
			
			// Retrieve record types for Contact, Project and Account
			List<RecordType> recordTypes = [SELECT Id, SObjectType, DeveloperName FROM RecordType WHERE 
				(DeveloperName = 'Client' AND SObjectType = 'Contact') OR
				(DeveloperName = 'ClientAccount' AND SObjectType = 'Account') OR 
				(DeveloperName = 'Project' AND SObjectType = 'Case')];
			// Map them by SObject type
			Map<String, RecordType> rtMap = new Map<String, RecordType>();
			for (RecordType rt : recordTypes) {
				rtMap.put(rt.SObjectType, rt);
			}
			
	        // Convert the lead
	        List<Database.LeadConvert> lcList = new List<Database.LeadConvert>();
	        for (Lead l : leadMap.values()) {
	        	Database.LeadConvert lc = new Database.LeadConvert();
	        	lc.setLeadId(l.Id);
	        	// TODO - Who is the owner?
	        	// lc.setOwnerId(ownerId);
	        	lc.setConvertedStatus('Qualified');
	        	lc.setDoNotCreateOpportunity(true);
	        	// TODO - Email notification?
	        	lcList.add(lc);
	        }
	        
	        // Convert!
	        List<String> convertMsgs = new List<String>();		// List of Strings containing status messages about each conversion operation
	        Map<Id, Lead> convertedLeadMap = new Map<Id, Lead>();
	        Map<Id, Id> accountIdMap = new Map<Id, Id>();
	        Map<Id, Id> contactIdMap = new Map<Id, Id>();
	        Database.LeadConvertResult[] lcrList = Database.convertLead(lcList, false);
	        for (Database.LeadConvertResult lcr : lcrList) {
	        	if (lcr.isSuccess()) {
	        		// Build maps of converted leads, contact Ids and account Ids
	        		Id accountId = lcr.getAccountId();
	        		Id contactId = lcr.getContactId();
	        		Id leadId = lcr.getLeadId();
	        		convertedLeadMap.put(leadId, leadMap.get(leadId));
	        		accountIdMap.put(leadId, accountId);
	        		contactIdMap.put(leadId, contactId);
	        		String convertMsg = 'CONVERSION SUCCEEDED: Converted Lead: ' + leadId + ' into Account: ' + accountId + ' and Contact: ' + contactId + '\n';
	        		convertMsgs.add(convertMsg);
	        	} else {
					Database.Error[] errors = lcr.getErrors();
					String convertMsg = 'CONVERSION FAILED: ';
					for (Database.Error err : errors) {
						convertMsg += '\tERROR: ' + err.getMessage() + '\n';
					}       		
	        	}
	        }
	        
	        // Fetch the converted Contacts and Accounts and map them by their Ids
	        Map<Id, Contact> contactMap = new Map<Id, Contact>([SELECT Id, Name, AccountId FROM Contact WHERE Id IN :contactIdMap.values()]);
	        Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Id, Name, EnableNameChange__c FROM Account WHERE Id IN :accountIdMap.values()]);
	        
	        // Map of Projects being created, by Lead Id
	        Map<Id, Case> projectMap = new Map<Id, Case>();
	        
	        // Iterate over the converted Leads
	        for (Lead l : convertedLeadMap.values()) {
	        	
	        	// Get the corresponding Contact by Lead Id
	        	Id contactId = contactIdMap.get(l.Id);
	        	Contact c = contactMap.get(contactId);
	        	
	        	// Set the Contact record type, company name fields
	            c.CompanyName__c = l.Company;
	            c.RecordTypeId = rtMap.get('Contact').Id;
	            c.EmailVerified__c = true;
	            
	            // Get the corresponding Account by Lead Id
	            Id accountId = accountIdMap.get(l.Id);
	            Account a = accountMap.get(accountId);
	            
	            // By default, Account.Name will be set to Lead.Company. 
	            // If Lead.Company was not provided, then it defaults to '[not provided]'.
	            // In this case, we want to set Account.Name = Contact.Name
	            // In order to do this, we need to set the "Enable Name Change" flag that overrides
	            // the "Lock_Account_Names" validation rule on the Account object         
	            if (('[not provided]'.equalsIgnoreCase(l.Company)) || (l.Company == null)) {
	            	a.Name = c.Name;
	            	a.EnableNameChange__c = true;
	            }
	            
	            // Set the record type and status
	            a.RecordTypeId = rtMap.get('Account').Id;
	            a.Status__c = 'Pay Customer';
	            
	            // Set the encrypted credit card fields since these can't be mapped
	            a.CCNumber__c = l.CCNumber__c;
	            a.CC_CSC__c = l.CC_CSC__c;
	            
		        // Now create Projects for the Leads
		        Case p = new Case();
		        p.ContactId = c.Id;
		        p.AccountId = a.Id;
		        p.Status = 'Staffing';
		        p.Update__c = 'New case created during Client Lead conversion';
		        p.Description = l.Description;
		        p.Subject = l.Subject__c;
		        p.Scope__c = l.Scope__c;
		        p.BudgetConstraints__c = l.BudgetConstraints__c;
		        p.DoneBy__c = l.DoneBy__c;
		        p.Talent__c = l.Talent__c;
		        p.TalentId__c = l.TalentId__c;
		        p.Coupon__c = l.Coupon__c;
		        p.SearchLink__c = l.SearchLink__c;
		        p.CreatedViaLeadConversion__c = true;
		        p.FrictionlessProject__c = true;
		        projectMap.put(l.Id, p);
	        }
	        
	        // Apply the updates to the converted Contacts and Accounts
	        update contactMap.values();
	        update accountMap.values();
	        
	        // Now we must go back and clear the "Enable Name Change" field 
	        // on Accounts to prevent subsequent name changes
	        List<Account> accountList = new List<Account>();
	        for (Account a : accountMap.values()) {
	        	if (a.EnableNameChange__c) {
	        		a.EnableNameChange__c = false;
	        		accountList.add(a);
	        	}
	        }
	        if (accountList.size() > 0) {
	        	update accountList;
	        }
	        
	        // Create the projects
	        insert projectMap.values();
	        
	        // Send notification email
	        String msgBody = 'Automatic Lead conversion results:\n';
	        for (String convertMsg : convertMsgs) {
	        	msgBody += convertMsg;
	        }
	        sendEmail(UserInfo.getUserId(), UserInfo.getName(), 'Automatic Lead Conversion Completed Normally', msgBody, null);
	        
		} catch (Exception ex) {
			sendEmail(UserInfo.getUserId(), UserInfo.getName(), 'Automatic Lead Conversion Failed', ex.getMessage(), null);
			throw ex;
		}
	}
	
    public static Boolean sendEmail(Id targetId, String senderDisplayName, 
        String subject, String textBody, Messaging.EmailFileAttachment[] attachments) {
        
        // Create and send a single email message to the targetId      
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        
        // Use the specified template
        email.setTargetObjectId(targetId);
        email.setSubject(subject);
        email.setPlainTextBody(textBody);
        email.setSenderDisplayName(senderDisplayName);
        email.setSaveAsActivity(false);
        if (attachments != null) {
            email.setFileAttachments(attachments);
        }
        Messaging.SendEmailResult[] results = 
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });
        return results[0].isSuccess();        
    } 
			
	
}

 

Note that it works fine if I first create the Lead in some other status and then update the Lead to set the status to the trigger value of "Complete Request - Convert". In this case, the Lead is converted and all of the additional DML operations are performed.

 

 

jkucerajkucera

I'm not sure what the problem is as you're checking for recursion w/ the !l.IsConverted check, which should prevent any converted leads from popping into your utility.

 

Do you have any other updates that occur upon lead update? 

 

My best suggestion is to add a bunch more logging to understand where the issue starts.

Jeetesh BahugunaJeetesh Bahuguna

Add lead status value (which you are passing to the converted  lead status) to status field picklist on lead.

Mayank_JoshiMayank_Joshi

 

DOC Link : http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_convertLead.htm      http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_leadstatus.htm

 

 trigger ConvertLead on Lead (after insert, after update) {

for (Lead lead : Trigger.new) {

if (lead.isConverted == false) //to prevent recursion {

 Database.LeadConvert lc = new Database.LeadConvert();

 lc.setLeadId(lead.Id); String oppName = lead.Name;

lc.setOpportunityName(oppName);

LeadStatus convertStatus = [SELECT Id, MasterLabel FROM LeadStatus WHERE IsConverted=true LIMIT 1];

 lc.setConvertedStatus(convertStatus.MasterLabel);

 Database.LeadConvertResult lcr = Database.convertLead(lc);

System.assert(lcr.isSuccess());

}

}

 }

 

Regards,

Mayank Joshi