Calculate amount of specified task records which are related to Contact and Account in Salesforce

Pretty often You need to know how many times some Contact has been used in Log a Call functionality. Aggregation of such count in an account level might be pretty useful too. So, let’s implement this functionality. I faced with such requirement a few times with different customers, so I’d like to share my experience.

As all you know, in the perfect world we should use built-in functionality and avoid a custom implementations. So, firstly I was looking for a way to build it with Salesforce native approach. First thing which was considered is a Roll-up summary field. But we can’t use Roll-up summary fields here, because these object has no master-detail relationship by default. Sure, you can create such relation, but it’s no a case for me. The result of quick investigation lets me think that trigger will be an ideal solution for such functionality.

So, firstly I create 2 fields Reference_Count__c on Contact and Account. Both fields has Number type.

After that I create a trigger on Task object which runs after insert, after update and after delete.

There are the following cases:

  • When user creates task with Type = ‘Call’ we have to increase counter on Contact which is related to this task and also we have to increate a count on Account which is related to Contact.
  • When user changes type of task from any to ‘Call’ we have to increase counters.
  • When user changes type of task from ‘Call’ to any other we have to decrease counter for Contact and Account related to this task.
  • Also we have to decrease counters when user removes Task

You can find a whole solution on GitHub.

Let’s break each point to separate method: 1) When user creates task with Type = ‘Call’ we have to increase counter on Contact which is related to this task and also we have to increate a count on Account which is related to Contact.

[java] private static void increaseReferenceCountOnInsert(List<sObject> newRecords) { List<sObject> taskRelatedToContact = new List<sObject>(); for (SObject obj : newRecords) { if (((String)obj.get(‘WhoId’)).startsWith(CONTACT_RECORDS_PREFIX) && ((String)obj.get(‘Type’)) == LOG_A_CALL_TYPE) { taskRelatedToContact.add(obj); } } updateReferenceCountField(taskRelatedToContact, true); } [/java]

2) When user changes type of task from any to ‘Call’ we have to increase counters.

[java] private static void increaseReferenceCountOnUpdate(List<sObject> newRecords, Map<ID, sObject> oldRecordsMap) { List<sObject> taskRelatedToContact = new List<sObject>(); for (SObject obj : newRecords) { if (((String)obj.get(‘WhoId’)).startsWith(CONTACT_RECORDS_PREFIX) && ((String)obj.get(‘Type’)) == LOG_A_CALL_TYPE && ((String)(oldRecordsMap.get((Id)obj.get(‘Id’))).get(‘Type’)) != LOG_A_CALL_TYPE) { taskRelatedToContact.add(obj); } } updateReferenceCountField(taskRelatedToContact, true); } [/java]

The next method is used for 2 cases: 3) When user changes type of task from ‘Call’ to any other we have to decrease counter for Contact and Account related to this task. 4) Also we have to decrease counters when user removes Task

[java] private static void decreaseReferenceCountOnTaskTypeChange(List<sObject> newRecords, Map<ID, sObject> oldRecordsMap) { List<sObject> taskToDecreaseCount = new List<sObject>(); for (SObject obj : newRecords) { if (Trigger.isUpdate) { if (((String)obj.get(‘WhoId’)).startsWith(CONTACT_RECORDS_PREFIX) && ((String)obj.get(‘Type’)) != LOG_A_CALL_TYPE && ((String)(oldRecordsMap.get((Id)obj.get(‘Id’))).get(‘Type’)) == LOG_A_CALL_TYPE) { taskToDecreaseCount.add(obj); } } else if (Trigger.isDelete && ((String)obj.get(‘Type’)) == LOG_A_CALL_TYPE ) { taskToDecreaseCount.add(obj); } } updateReferenceCountField(taskToDecreaseCount, false); } [/java]

You can find that all these methods invoke updateReferenceCountField method

[java] private static void updateReferenceCountField(List<sObject> tasks, Boolean isIncrement) { Map<Id, List<sObject>> tasksByContact = splitListBySpecialKey(tasks, ‘WhoId’); List<Contact> relatedContactsForUpdateCountField = [SELECT Id, Reference_Count__c, AccountId FROM Contact WHERE Id IN: tasksByContact.keySet()]; updateAccounts(relatedContactsForUpdateCountField, tasksByContact, isIncrement); updateContacts(relatedContactsForUpdateCountField, tasksByContact, isIncrement); } [/java]

Which invokes 2 method which actually perform DML

[java] private static void updateAccounts(List<Contact> relatedContactsForUpdateCountField, Map<Id, List<sObject>> tasksByContact, Boolean isIncrement) { // ContactId, Account Map<Id,Id> contactIdByAccountId = new Map<Id,Id>(); for (Contact cont: relatedContactsForUpdateCountField) { contactIdByAccountId.put(cont.AccountId, cont.Id); } List<Account> accountsRelatedToContactsForUpdate = [SELECT Id, Reference_Count__c FROM Account WHERE Id IN: contactIdByAccountId.keySet()];

for (Account acc: accountsRelatedToContactsForUpdate) { if (acc.Reference_Count__c != null) { if (isIncrement) { acc.Reference_Count__c += tasksByContact.get(contactIdByAccountId.get(acc.Id)).size(); } else { acc.Reference_Count__c -= tasksByContact.get(contactIdByAccountId.get(acc.Id)).size(); } } else { acc.Reference_Count__c = tasksByContact.get(contactIdByAccountId.get(acc.Id)).size(); } } update accountsRelatedToContactsForUpdate; }

private static void updateContacts(List<Contact> relatedContactsForUpdateCountField, Map<Id, List<sObject>> tasksByContact, Boolean isIncrement) { for (Contact cont: relatedContactsForUpdateCountField) { Integer amountOfNewTasks = tasksByContact.get(cont.Id).size(); if (amountOfNewTasks > 0) { if (cont.Reference_Count__c != null) { if (isIncrement) { cont.Reference_Count__c += amountOfNewTasks; } else { cont.Reference_Count__c -= amountOfNewTasks; } } else { cont.Reference_Count__c = amountOfNewTasks; } } } update relatedContactsForUpdateCountField; } [/java]

Also we need this utility method for transformation list into map

[java] public static Map<Id, List<sObject>> splitListBySpecialKey(List<sObject> sourceList, String key) { if (sourceList == null) { throw new IncorrectParameterException(‘ERROR: splitListBySpecialKey(sourceList, key) got incorrect first parameter.’); } if (String.isBlank(key)) { throw new IncorrectParameterException(‘ERROR: splitListBySpecialKey(sourceList, key) got incorrect second parameter.’); } Map<Id, List<sObject>> result = new Map<Id, List<sObject>>(); List<sObject> tmpObjs; for (sObject obj : sourceList) { tmpObjs = new List<sObject>(); if (obj.get(key) != null && result.containsKey((Id)obj.get(key))) { tmpObjs = result.get((Id)obj.get(key)); tmpObjs.add(obj); result.put((Id)obj.get(key), tmpObjs); } else if (obj.get(key) != null) { tmpObjs.add(obj); result.put((Id)obj.get(key), tmpObjs); } } return result; } public class IncorrectParameterException extends System.Exception {} [/java]