Wednesday 3 April 2013


Automatically Add Contact Roles When Creating Tasks

So I was trying to convince a client to implement Contact Roles recently, and he was shaking his head and saying "That's too complicated" and "My people won't do that".Contact Roles are simple. The idea is that you can identify all of the people who are associated with any Account or Opportunity. You can add Contacts who are associated with other Accounts, too, and for each Contact you must identify the Role that Contact has with the Account or Opportunity. This means that you can add the same Contact more than once, with a different Role each time:

This is huge for tracking Account and Opportunities activity. Without Contact Roles, there's no way to identify all of the people associated with that Account because you can't link Contacts from other Accounts via the Contact object. (Notice the Account column in the screenshot above.)
You can also designate which Contact on an Account is the primary. This is critical! How else will you communicate to your colleagues who the primary Contact is for the Account?
"But," you exclaim, "that's so much extra work!"
We have a solution for that. The first thing you do is create a new Contact Role, "TBD". Next, we will create a trigger to automatically look at every new Task and when that Task is:
  1. related to an Account,
  2. has an associated Contact, and
  3. is not already on the list of Contact Roles for that Account
then we will automatically add that Contact to that Account's list of Contact Roles with a role of "TBD".
// ==================================================================================
//   Object: aiTask
//   Author: John Westenhaver
// Comments: Automatically add Contact Roles whenever a new Task is created related
//           to an Account, with an associated Contact, and that Contact is not yet
//           on the list of Contact Roles for that Account.
// ==================================================================================
//  Changes: 2012-09-14 Initial version.
// ==================================================================================

trigger aiTask on Task (after insert)
{
   public static final String CONTACT_ROLE_TBD = 'TBD';

   // Get a unique list of Account IDs.
   // Map Tasks with both Account and Contact by Contact ID.
   set<Id> accountIds = new set<Id>();
   map<Id, Task> taskMap = new map<Id, Task>();
   for (Task t : system.trigger.new)
   {
      // Accounts have a prefix of 001.
      if (t.WhatId != null && 
          String.valueOf(t.WhatId).substring(0, 3) == '001')
      {
         accountIds.add(t.WhatId);
         // Contacts have a prefix of 003.
         if (t.WhoId != null && 
             String.valueOf(t.WhoId).substring(0, 3) == '003')
         {
            // Map Tasks by Contact where the Task has
            // both an Account and a Contact.
            taskMap.put(t.WhoId, t);
         }
      }
   }
   if (accountIds.size() == 0) return;

   // Get a list of Contact Roles for these Accounts.
   list<AccountContactRole> roleList = 
      [SELECT Id, AccountId, ContactId, Role
         FROM AccountContactRole
        WHERE AccountId IN :accountIds];
   
   // Map Contact Roles by Account AND by Contact.
   // This is a map of maps for ease of indexing.
   // The outside map is indexed by Account and 
   // the inside map is indexed by Contact.
   map<Id, map<Id, AccountContactRole>> roleMap = 
      new map<Id, map<Id, AccountContactRole>>();
   map<Id, AccountContactRole> localMap = 
      new map<Id, AccountContactRole>();
   AccountContactRole localRole;
   for (AccountContactRole acr : roleList)
   {
      // First, get the inner map.
      localMap = roleMap.get(acr.AccountId);
      // If it isn't there, create it.
      if (localMap == null || localMap.size() == 0)
      {
         localMap = new map<Id, AccountContactRole>();
      }
      // Second, get the inner ACR record.
      localRole = localMap.get(acr.ContactId);
      // If it isn't there, add it.
      if (localRole == null)
      {
         localMap.put(acr.contactId, acr);
      }
      // Add the inner map back to the outer map.  
      roleMap.put(acr.AccountId, localMap);
   }
   
   // Loop over the Tasks that we picked out above.
   list<AccountContactRole> insertRecords = 
      new list<AccountContactRole>();
   AccountContactRole acr;
   for (Task t : taskMap.values())
   {
      // First get the outer map based on the Account ID.
      localMap = roleMap.get(t.WhatId);
      if (localMap != null)
      {
         // Next look for a matching ACR record
         // based on the Contact ID.
         localRole = localMap.get(t.WhoId);
         // Not found? Let's add it!
         if (localRole == null)
         {
            acr = new AccountContactRole(
               AccountId = t.WhatId,
               ContactId = t.WhoId,
               Role = CONTACT_ROLE_TBD);
            insertRecords.add(acr);
            // Put this back into the map for future 
            // processing on this transaction.
            localMap.put(t.WhoId, acr);
            roleMap.put(t.AccountId, localMap);
         }
      }
      else
      {
         // There aren't any ACR records for this Account yet,
         // so there is nothing in the roleMap yet.
         acr = new AccountContactRole(
            AccountId = t.WhatId,
            ContactId = t.WhoId,
            Role = CONTACT_ROLE_TBD);
         insertRecords.add(acr);
         // Put this back into the map for future 
         // processing on this transaction.
         localMap.put(t.WhoId, acr);
         roleMap.put(t.AccountId, localMap);
      }
   }
   if (insertRecords.size() > 0) insert insertRecords;
}
Once this trigger is in place, your Contact Roles are created for you. It becomes a simple job to flip through the Contact Roles on the related list and update the TBD Roles to match reality. If a Contact Role already exists, for any Role, then no new record will be created.
This trigger uses an interesting data construct of a map of maps to manage this functionality with a single SOQL statement. I'll leave it as an exercise for the student to adapt this for Opportunities and Events.
Categories: ,

0 comments:

Post a Comment

    Links