3

I'm trying to create some code to prevent the creation of duplicate leads when there is already an existing contact in the database with the same email address. Not only this, but, if a matching contact is found, I want to update that contact instead of inserting the lead. I can successfully add an error to prevent the lead from being created, but I can't seem to update the contact.

Reading this: http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_bulk_exceptions.htm

It seems that Salesforce rolls back all DML operations when there is an error on the record. I thought if I tried the update contact DML before adding the error to the lead record that this might solve the problem, but I have been unsuccessful. Any ideas on how to accomplish this?

Trigger Handler Class below:

public class LeadTriggerHandler {

    /********************************************************
     * Before Insert
     * *****************************************************/

    public static void handleBeforeInsert(List<Lead> newLeads) {
        //Do some before insert stuff
        checkForDupes(newLeads);

    }//End of method

    /********************************************************
     * 
     * Private Methods
     * *****************************************************/

    private static void checkForDupes(List<Lead> newLeads){
        Map<String,Lead> leadMap = new Map<String,Lead>();
        for(Lead l : newLeads){
            if(l.Email != null && !leadMap.containsKey(l.Email)){
            leadMap.put(l.Email,l);
            }//end of if
            else if(l.Email == null){
                //do nothing
            }//end of else if
            else{
                l.Email.addError('Another lead in this batch has the same Email Address');
            }//end of else
        }//end of for loop
        if(leadMap.size()>0){
            List<Contact> queriedContacts = new List<Contact>([Select Id, Email from Contact Where Email in :leadMap.keySet()]);
            if(queriedContacts.size()>0){
                List<Contact> contactsToUpdate = new List<Contact>();
                Map<String,Contact> matchingContacts = new Map<String,Contact>();
                for(Contact con : queriedContacts){
                    matchingContacts.put(con.Email,con);
                }//end of for loop
                for(Lead l2 : newLeads){
                    //do stuff
                    if(matchingContacts.containsKey(l2.Email)){
                        //handle dupes
                                contactsToUpdate.add(new Contact(
                                Id = matchingContacts.get(l2.Email).Id,
                                Do_Not_Sync_w_Eloqua__c = false
                            ));
                    }//end of containsKey if
                }//end of for loop
                Database.update(contactsToUpdate, false);
                for(Lead l3 : newLeads){
                    if(matchingContacts.containsKey(l3.Email)){
                        l3.Email.addError('A contact with this email already exists in the system with ID of '+matchingContacts.get(l3.Email).Id);
                    }
                }//end of l3 for loop
            }//end of queriedContacts.size()>0 if
        }//end of leadMap.size()>0 if
    }//end of method
}
  • Two questions. Are you purchasing your lead data and do you care whether or not you're notified that you've updated a contact's info based on info obtained from the source data obtained from a new lead? – crmprogdev Mar 5 '15 at 20:49
  • I have two main use-cases for this. 1) Email-to-lead functionality, which, at present, just creates a new lead regardless of if a record already exists. I want to update the existing contact with the body of the email, and yes, I would want to be notified when this update occurs. 2) A contact has been removed from our Marketing Automation due to inactivity, we mark the Salesforce record so that it does not continue to be synced. But if that contact were to come back and re-engage, I'd want to update the existing contact by unchecking the Do_Not_Sync rather than create a new lead. – Todd Sprinkel Mar 5 '15 at 21:13
  • n.b. appexchange product Ringlead can be useful here – cropredy Mar 8 '15 at 1:58
1

Why dont you give a shot at Data.com Duplicate Management, which was recently introduced by Salesforce. You can set your criteria and salesforce would identify duplicates for you.

I think its an apt candidate for such issues. Can save you a lot of time and effort. Data.com Duplicate management

Secondly can you confirm whether the actual DML operation which invoked the trigger is making use of Database.upsert or something like that. If not try that, should work in that case.

  • I'm less concerned about presenting a user with options, but more about preventing Email-to-Lead or our Marketing Automation Platform from creating duplicates, which would not be able to choose. My testing was by using the UI to create a lead, but I had planned to test using Data Loader as well if I could get it to work with a single record. – Todd Sprinkel Mar 5 '15 at 21:21
  • Todd, your understanding here is correct. If you addError to your object, your complete transaction is rolled back. Only way it would try to retry is when intial DML was invoked using Database.method operation. In case there is a single record and that record gives error, then definetely it would not retry what so ever. – Kaw Sumit Mar 5 '15 at 21:27
  • In your case you need to maintain 2 lists one which has duplicates, and other one which is clean and then take the clean ones and validate against contacts. after that even if you add error to duplicates while retrying salesforce would still process the valid ones. In short when you add error he should have final duplicates. I hope it makes sense . ----- However the most important point being, the DML operation that invokes should be database.method because thats where trigger logic will work --- – Kaw Sumit Mar 5 '15 at 21:30
1

Based on your response to my comments, you want to remove the "Add Error" method from your class. Why? Because there is no error to report through the class. Instead, it's doing what you want by updating the contact's information. Now, if another lead in the batch has the same email address, then what you'll want to do is merge those two email records or decide which one should be inserted (or just use an upsert instead).

What you need here is a "data error handler". You'll want to collect a list of the emails with which you had a problem, either because they matched a contact or were duplicate leads, then send that list to yourself as the admin through an email handler which you'll call at the end of your class. Run a check to see if the errorlist isempty() If it's not, call your class (or method) to send the list to yourself. If it is, then there's nothing more to do.

In this manner, you no longer need to be concerned with a DML rollback because of data that needs manual interpretation or that's otherwise being diverted from your lead DML insertion handler to your contact DML update handler.

I hope this approach makes sense to you. This really is about data errors/issues and not about faults in your code while its running.

EDIT

Here's some sample code of how this works. I'm only going to include snippets because this if from a very lengthy and complex trigger.

for(Id eId: evtIDs){

   RalURL = trigger.newmap.get(eId).Assignment_Link__c;
   RalURLID = RalURL.replace(sfdcBaseURL + '/','');
   system.debug('RalURLID = ' + RalURLID);

   try{
      raID = Id.valueOf(RalURLID);

   }catch (System.StringException e){
      raID = null;
      /* if raID == null, we must remove the associated e.ID from the list of Id's to update! */

      /* Will add e.Id to a set to filter on later */
      nRAIds.add(eId);

      ProcErrs.put(trigger.newmap.get(eId).WhatID,'CreateAssignmentsTrigger Error line 390 at'+ system.now() +' Null Assignment ID obtained from Event related Assignment Link = '+ string.valueOf(trigger.newmap.get(eId).Assignment_Link__c) +' from Event ID = '+ string.Valueof(eId) +' on Opportunity ID = '+ trigger.newmap.get(eId).WhatID);
      /* ^^^ This is our map of errors for later processing! ^^^ */
   }    

   if(raID != null){
      AssnId2up.add(raID);
      AsnId2EvtId.put(raID,eId);
      /* Now have filtered lists and maps for processing */ 
   }

} // end for(Id eId: evtIDs)



for(Id AId: AssnId2up){

    ... Body of trigger ...

   Upasn.add(uassgn);

} // end for(Id AId: AssnId2up)

if(Upasn.isEmpty() == false) update Upasn;
/* ^^^ above line performs primary DML operation for trigger ^^^ */

/* lines below process any data errors via an error handler class by passing the map to the error handler class to email it to the the admin */
if(ProcErrs.isEmpty() == false){ 

   Automation_Error_Handler AutoErrHnd2 = new Automation_Error_Handler();   
   AutoErrHnd2.Automation_Error_Handler(ProcErrs);

} // end if(ProcErrs.isEmpty()

Below are some snippets of code to illustrate how my error handler class worked which was designed to handle errors from a number of different triggers.

/* This class processes exceptions and other errors from triggers */
/* then passes all of them at once to an email handler for processing */ 
/* as a single email message notification to the Database Admin for */
/* corrective action. */

public class Automation_Error_Handler {

    public map<Id, String> errmap {get;}
    public map<Id, string> emlmp;   
    public map<Id, string>mssgmap;
    public string trigname;


    public void Automation_Error_Handler(map<Id,String>errmap)
    {
        map<Id, string>emlmp = (map<Id,string>)errmap;
        set<Id>Idset = new set<Id>();
        Idset = errmap.keyset();
        String trigname;
        String es;

        for(Id ids: Idset){
            system.debug('ID = '+ ids + ' ErrMssg = '+ errmap.get(ids) );
        }

        for(Id ObjId: Idset){

            es = errmap.get(ObjId);
            system.debug('es = '+ errmap.get(ObjId));

            if(es.startsWith('ApprovalStatusTrigger') == true) trigname = 'ApprovalStatusTrigger';

            if(es.startsWith('CreateAssignmentsTrigger') == true) trigname = 'CreateAssignmentsTrigger';

            if(es.startsWith('Opps2EventsTrigger') == true) trigname = 'Opps2EventsTrigger';

            if(es.startsWith('FATAL') == true) trigname = 'FATAL_TRIGGER_ERROR';

            } /* end for(Id i: Idset) */

        if(emlmp.isEmpty() == false){
            InboundEmail_Automation_Error_Handler.send_Error_Emails(trigname, emlmp);   
            /* Sends trigname along with emlmp to outbound email handler portion of an */
            /* inbound email handler for processing of error messages via email to the Admin. */

        } /* end if(emlmp.isEmpty() == false) */

    } /* end method */  

} /* end class */

Note: an inbound email handler class wasn't a necessity. A call to the regular SF outbound email handler could also have been used for processing of the data error messages in the list that were sent to the Admin, thus the reason that class isn't included here.

  • I think I'm generally following along. I can certainly code it in such a way to add "problem" leads to a list or lists, depending on the type of problem. However, how do I control allowing the ones that have no problems to insert as normal while preventing the others from creating new leads? I don't necessarily want to review the problem leads, I just want to either stop them from going in if there are duplicates within the batch or to update an existing contact and prevent the lead from going in. Maybe I'm just not fully understanding here... – Todd Sprinkel Mar 5 '15 at 22:20
  • Am going to be tied up most of the rest of the day, but will post some sample code for you when I get a chance to grab it from my archives. – crmprogdev Mar 5 '15 at 22:39
  • See my edited answer above. Have included some snippets of code to illustrate how I did this with one trigger in particular. Problematic records are identified and added to a map for reporting through the error handler class. the others are allow to pass thru and a new list/map are created for regular processing without needing to call an add error method. – crmprogdev Mar 6 '15 at 23:51
  • What does your error handler class then look like? – Todd Sprinkel Mar 9 '15 at 20:13
  • I didn't include it because it's rather complicated since it supports a number of triggers. Let me see if I can pull together something that catches the gist of it. Essentially it takes the map, sorts the error messages by trigger and record ID, then emails the error messages to the Admin. – crmprogdev Mar 9 '15 at 21:11

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.