1

I have two triggers on the Lead object. One (before insert) checks to see if the Lead matches any existing Contacts (through a custom email object) and the other (after insert, after update) converts the Lead if the source and status match our criteria.

My issue is that it's very inconsistent with actually doing the conversion. Most leads are not converted, even though there isn't any difference in the leads that are compared to those that aren't. When I observed it wasn't usually working, I added an else that changes the status to "Conversion Pending" if it does meet the criteria, and this is in fact what happens.

Trigger 1

trigger CheckLead on Lead (before insert) {

// make a list of email addresses for the leads we are adding
List<String> leadEmails = new List<String>();
for (Lead lead:Trigger.new) {
    leadEmails.add(lead.Email);
}

// find all email objects associated with a matching email address for any of the leads
List<Email__c> allMatchingEmails = [SELECT Id, Email_Address__c FROM Email__c WHERE Email_Address__c IN :leadEmails];

// for each new lead
for (Lead lead:Trigger.new) {

    // set the company value to the lead name
    if (lead.Company != null && lead.Company != '[not provided]') {
        lead.PersonCompany__c = lead.Company;
    }
    lead.Company = lead.FirstName + ' ' + lead.LastName;

    // get the name of the lead we want to add
    String leadName = '';

    if (lead.FirstName == null || lead.FirstName == 'not provided' || lead.FirstName == '[not provided]' || lead.FirstName.contains('not provided') == true || lead.FirstName.contains('null') == true || lead.FirstName.contains('not') == true || lead.FirstName.contains('provided') == true) {
        lead.FirstName = '[unknown]';
    }
    if (lead.LastName == null || lead.LastName.contains('not provided') == true || lead.LastName.contains('null') == true || lead.LastName.contains('not') == true || lead.LastName.contains('provided') == true) {
        lead.LastName = '[unknown]';
    }

    if (lead.Salutation != '' && lead.Salutation != 'not provided' && lead.Salutation != null) {
        leadName += lead.Salutation + ' '; 
    }
    if (lead.FirstName != '') {
        leadName += lead.FirstName + ' '; 
    }
    if (lead.MiddleName != '' && lead.MiddleName != null) {
        leadName += lead.MiddleName; 
    }
    if (lead.LastName != '') {
        leadName += lead.LastName; 
    }
    if (lead.Suffix != '' && lead.Suffix != null) {
        leadName += lead.Suffix; 
    }

    // the email address we want to add exists
    if (allMatchingEmails.size() > 0) {

        // are any of the email addresses we want to add already associated with another user?
        List<Contact_Email__c> primaryEmailMatches = [SELECT Id, Email__c, Contact__c FROM Contact_Email__c WHERE Email__c IN :allMatchingEmails AND Primary_contact_for_email__c = true];
        List<Contact_Email__c> noPrimaryEmail = [SELECT Id, Email__c, Contact__c FROM Contact_Email__c WHERE Email__c IN :allMatchingEmails AND Primary_contact_for_email__c != true];

        if (!primaryEmailMatches.isEmpty()) {
            // there is a contact who is primary for this email
            // for each lead, if there is another primary user with the email, get that contact's information
            // don't convert it automatically if the names don't match 

            String contactID = primaryEmailMatches[0].Contact__c;
            Contact contact = [Select Id, Salutation, FirstName, MiddleName, LastName, Suffix, Name FROM Contact WHERE Id = :contactId];

            // the name and email are the same; assume this is the same person
            if (leadName == contact.Name) {
                lead.Status = 'Matched With Contact';
                lead.Lead_Status_Reason__c = 'Matches primary email address and name';
            } else {
                // there is a matching primary here, but the names do not match
                // change the status
                lead.Status = 'Contact Check Pending';
                lead.Lead_Status_Reason__c = 'Primary Contact Name Mismatch';
            }

        } else if (!noPrimaryEmail.isEmpty()) {
            // no contact has this email as primary but other contacts do have it
            // don't convert it automatically if the names don't match

            // for each lead, if there is another user with the email, get that contact's information
            String contactID = noPrimaryEmail[0].Contact__c;
            Contact contact = [Select Id, Salutation, FirstName, MiddleName, LastName, Suffix, Name FROM Contact WHERE Id = :contactId];

            // the name and email are the same; assume this is the same person
            if (leadName == contact.Name) {
                lead.Status = 'Matched With Contact';
                lead.Lead_Status_Reason__c = 'Matches non-primary email address and name';
            } else {
                // there is a matching email here, but it is not primary and the names do not match
                // change the status
                lead.Status = 'Contact Check Pending';
                lead.Lead_Status_Reason__c = 'No Primary Contact';
            }

        } else {
            lead.Status = 'Conversion Pending';
            lead.Lead_Status_Reason__c = 'No Contact For Email';
        }

    } else {
        lead.Status = 'Conversion Pending';
        lead.Lead_Status_Reason__c = 'Email Does Not Exist';
    }

}

}

** Trigger 2**

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

// make a leadconvert list
list<Database.LeadConvert> leadConverts = new list<Database.LeadConvert>();

// for each lead, change its status
for (Lead myLead : Trigger.new) {
    if (myLead.LeadSource == 'MailChimp' && (myLead.Status == 'Open' || myLead.Status == 'Conversion Pending') && myLead.Status != 'Contact Check Pending') {
        Database.LeadConvert lc = new database.LeadConvert();
        lc.setLeadId(myLead.Id);
        lc.convertedStatus = 'Qualified';
        lc.setDoNotCreateOpportunity(true);
        leadConverts.add(lc);
    }
}

// run the conversion on those leads
if (!leadConverts.isEmpty()) {
    for (Database.LeadConvert myConvert : leadConverts) {
        Database.LeadConvertResult lcr = Database.convertLead(myConvert,false);
    }
}

}

So again, the vast majority of the Leads that I'm seeing get imported (via manual import, in this case) are being assigned the status of "Conversion Pending" but they're not actually getting converted.

I did notice that I can go and manually update a Lead - for example change a field's value to "1" where it was empty, and then it gets converted. It does not do this if I use something to mass update the values (Apsona, for example).

Any help would be most appreciated.

Thanks.

  • have you read: salesforce.stackexchange.com/questions/43917/… ? – cropredy Dec 5 '14 at 18:31
  • @crop1645 I have now, but it does seem very different. In my case, I don't need to convert the lead on update (I just added after update to it as a curiosity); I need to insert it (changing the status accordingly) and then after it gets inserted I need to convert it (if it meets the criteria). – Jonathan Stegall Dec 5 '14 at 18:35
  • When I had this use case, I did all the lead conversion via @future – cropredy Dec 5 '14 at 18:43
  • @crop1645 that does look promising. Are there any good resources for understanding how to use future in a trigger? – Jonathan Stegall Dec 5 '14 at 18:58
  • I'd suggest you add an error class which creates a code or message to add to a custom field in your lead object that gets written to which tells you where in your trigger the lead failed to meet your criteria. That may help you discover the real cause of why some are passing and some you think "should" aren't. You'll then know where in your trigger they were "cut" from the list of trigger.new. At the end of your trigger, do an upsert on any leads in the error message class. – crmprogdev Dec 5 '14 at 18:59
1

Here was a class I used to do LeadConversion with error checking / sysad notification

List<Database.LeadConvert>  leadConvertList    = new List<Database.Leadconvert> ();
String            leadConvertErrLog  = '';

if (leadToConvertList.size() > 0) {
      LeadStatus convertStatus       = [Select Id, MasterLabel from LeadStatus where IsConverted=true limit 1];   

      //  Build a LeadConvert object
      for (Lead l: leadToConvertList) {                
        Database.LeadConvert lc    = new Database.LeadConvert();
        lc.setleadId(l.id);
        lc.setConvertedStatus(convertStatus.MasterLabel);
        lc.setDoNotCreateOpportunity(true);
        lc.setOwnerId(l.ownerId);     //   Rely on this sequence to ensure proper owner:
                                      //  1. When lead is inserted, before/after triggers fire but auto_convert__c is false 
                                      //  2. WF field update auto_convert__c
                                      //  3. WF lead Assignment rules fire, setting owner
                                      //  4. Field update in #2 causes before/after triggers to fire again
                                      //  5. Lead conversion executes here 
        leadConvertList.add(lc);
      }
      //  Convert a list of LeadConvert objects
      Database.LeadConvertResult[] lcrResList  = Database.convertLead(leadConvertList,false);      // allOrNone = false
      for (Database.LeadConvertResult lcr: lcrResList) 
        if (!lcr.isSuccess()) 
          leadConvertErrLog = leadConvertErrLog + '\n' + 'Lead Convert fail: ' + lcr.getLeadId() + ' ' + 
                                       ' ERROR:' + lcr.getErrors();
      //  Inform admin if error
      if (leadConvertErrLog.length() > 0) 
        Util.notifySysad('Automatic Lead Conversion','Lead auto conversion errors' + leadConvertErrLog);
    }

where Util.notifySysad was a utility class that sent an email to the sysad. Note the Database.convertLead allOrNothing argument set to false to allow partial successes.

  • Apparently my trigger was running more than once, for some reason. I've been able to prevent that from happening, I think, but now the test is still running more than once of course. In any case, this is super helpful. – Jonathan Stegall Dec 5 '14 at 21:55
1

Solving data handling issues can be difficult, especially the kind that require human intervention. We can program try-catch blocks and all sorts of exception handlers, but ultimately, at some point we often have to label data for someone to look at when our code doesn't know how to process it.

Here's some code to get you started with doing that in your trigger:

// declare variables: a map  and two strings to hold your error messages
map<id,string>errormap=newmap<id,string>();
string errmssg;
string errmssg1;

// At the start of your your `For loop` on `trigger.new` initialize the errmssg variable:
for (Lead lead:Trigger.new) {
errmssg = ' ';

   // At present, you have a series of nested if conditions and don't know at which point your data fails to pass 

   if(condition1){
       continue;    
   }else{
       errmssg1 = errmssg + ' Err1 on Ln xx at ' + system.now() ;
       errmssg = errmssg1;
       errmap.put(trigger.new.id, errmssg);
   }

       if(condition2{
           continue;
       }else{
           errmssg1 = errmssg + ' Err2 on Ln xx at ' + system.now() ;
           errmssg = errmssg1;
           errmap.put(trigger.new.id, errmssg);
       }

   // ................

} // end of for loop near end of trigger, 

if errmap.keyset.isempty() == false{
list<lead>ulead = new list<lead>();
    for(ID ids:errmap.keyset){
        lead l = new lead();
        l.id = ids;
        l.lead_trigger_error_field__c = errmap.get(ids);  // field you'll use to hold the strings for errors
        ulead.add(l);
    }
    if(l.isEmpty() == false) upsert l;  // upsert, update as appropriate for the trigger. Can also pass this to another class to do this for you


/*** Note: what's above is untested code that's based on other code I've written ***/

This type of code will allow you to see where your errors are occurring in your trigger when looking at your data. If you run something like this as a separate class on all your triggers, you can collect data and email to an admin when data errors occur that need human intervention. Your lead system is one that unfortunately is fraught with that kind of problem. I hope this helps you get a handle on it.

Note that you can insert things like system.now() among other things (text value of variables, variable names, etc) within this kind of text string that you're building. Depending on how your code is laid out, you can also string error messages together as they accumulate. Otherwise, only your last one will appear in the map that gets created. That's why I've appended the message each time and reset it to '' at the beginning of each for loop.

Hope you find this useful in getting your data sorted.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.