Created
July 18, 2016 16:43
-
-
Save gbutt/d73f5edc63891078b8620a5c21c16154 to your computer and use it in GitHub Desktop.
Apex class for updating last sales activity and next sales activity when tasks are created or updated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public with sharing class TasksUpdateSalesActivity { | |
public static final String STATUS_COMPLETED = 'Completed'; | |
@TestVisible | |
private Task[] newLeadContactTasks { | |
get { | |
if (newLeadContactTasks == null) { | |
newLeadContactTasks = new Task[]{}; | |
// process trigger.new and only return tasks associated with a lead or contact | |
for (Task t : (Task[])trigger.new) { | |
if (isLeadOrContact(t.WhoId)) { | |
newLeadContactTasks.add(t); | |
} | |
} | |
} | |
return newLeadContactTasks; | |
} | |
set; | |
} | |
@TestVisible | |
private Map<Id, Task> oldTasks { | |
get { | |
return (Map<Id, Task>)trigger.oldMap; | |
} | |
set; | |
} | |
@TestVisible | |
private Map<Id, SObject> updateMap { get; set; } | |
// data service can be mocked in a unit test | |
@TestVisible | |
private DataService dataService { | |
get { | |
if (dataService == null) { | |
dataService = new DataService(); | |
} | |
return dataService; | |
} | |
set; | |
} | |
/* PUBLIC METHODS */ | |
// Called from Task trigger, ideally after insert or after update | |
// Sets last sales actvity fields on leads and contacts associated with tasks | |
// Sets next sales activity fields on leads and contacts associated with tasks | |
// for unit testing, you can set the newLeadContactTasks and oldTasks properties | |
public void setSalesActivityOnLeadOrContact() { | |
this.updateMap = new Map<Id, SObject>(); | |
setLastActivityFields(); | |
setNextActivityFields(); | |
updateSobs(); | |
} | |
/* PRIVATE METHODS */ | |
// check if tasks are recently completed. | |
// register an update for their associated lead/contact | |
private void setLastActivityFields(){ | |
for(Task t : this.newLeadContactTasks) { | |
if(isJustCompleted(t)) { | |
SObject leadToUpdate = registerWithUpdateMap(t.WhoId); | |
leadToUpdate.put('Last_Sales_Activity__c', t.Subject); | |
leadToUpdate.put('Last_Sales_Activity_Date__c', Date.today()); | |
leadToUpdate.put('Last_Sales_Rep_ID__c', t.OwnerId); | |
} | |
} | |
} | |
// check if there are any remaining incomplete tasks for leads | |
// register an update for their associated lead/contact | |
// null out these fields if there are no incomplete sales tasks | |
private void setNextActivityFields(){ | |
Set<Id> whoIds = new Set<Id>(); | |
for (Task t : this.newLeadContactTasks) { | |
whoIds.add(t.WhoId); | |
} | |
Map<Id, Task> nextSalesTasks = dataService.selectIncompleteTasksByWhoId(whoIds); | |
for(Id whoId: whoIds) { | |
// create a dummy task that will null out the next sales activity fields on the lead or contact, if a next activity is not found | |
Task t = new Task(WhoId = whoId, Subject = null, ActivityDate = null, OwnerId = null); | |
if(nextSalesTasks.containsKey(whoId)){ | |
t = nextSalesTasks.get(whoId); | |
} | |
SObject leadToUpdate = registerWithUpdateMap(whoId); | |
leadToUpdate.put('Next_Sales_Activity__c',t.Subject); | |
leadToUpdate.put('Next_Sales_Activity_Date__c',t.ActivityDate); | |
leadToUpdate.put('Next_Sales_Activity_Rep__c',t.OwnerId); | |
} | |
} | |
// check if task was completed in this context | |
private Boolean isJustCompleted(Task task){ | |
return (task.Status == STATUS_COMPLETED && | |
( this.oldTasks == null || | |
this.oldTasks.get(task.Id).Status != STATUS_COMPLETED | |
)); | |
} | |
private Boolean isLeadOrContact(Id whoId) { | |
return isLead(whoId) || isContact(whoId); | |
} | |
// check if id belongs to a lead | |
private Boolean isLead(Id leadId) { | |
return Lead.getDescribe().getKeyPrefix() == leadId.substring(0, 3); | |
} | |
// check if id belongs to a contact | |
private Boolean isContact(Id contactId) { | |
return Contact.getDescribe().getKeyPrefix() == contactId.substring(0, 3); | |
} | |
// returns existing registered lead/contact, or creates a new one | |
private SObject registerWithUpdateMap(Id whoId) { | |
SObject whoToUpdate = this.updateMap.get(whoId); | |
// create new lead/contact and register with map | |
if (whoToUpdate == null) { | |
if (isLead(whoId)) { | |
whoToUpdate = new Lead(Id=whoId); | |
} else { | |
whoToUpdate = new Contact(Id=whoId); | |
} | |
this.updateMap.put(whoId, whoToUpdate); | |
} | |
return whoToUpdate; | |
} | |
// remove converted leads and update the rest | |
private void updateSobs() { | |
removeConvertedLeads(); | |
SObject[] updateSobs = unchunkUpdateMap(); | |
dataService.updateSobs(updateSobs); | |
} | |
// we cannot update converted leads, so remove them from the map before processing | |
private void removeConvertedLeads() { | |
Set<Id> leadIds = new Set<Id>(); | |
for (Id whoId : this.updateMap.keySet()) { | |
if (LeadsExt.isLead(whoId)) { | |
leadIds.add(whoId); | |
} | |
} | |
List<Lead> convertedLeads = dataService.selectConvertedLeadsByLeadIds(leadIds); | |
for (Lead lead : convertedLeads) { | |
this.updateMap.remove(lead.Id); | |
} | |
} | |
// when updating multiple sobject types in a single dml call, they must be grouped together | |
private SObject[] unchunkUpdateMap() { | |
Lead[] leads = new Lead[]{}; | |
Contact[] contacts = new Contact[]{}; | |
for (Id sobId : this.updateMap.keySet()) { | |
if (isLead(sobId)) { | |
leads.add((Lead)this.updateMap.get(sobId)); | |
} else if (isContact(sobId)) { | |
contacts.add((Contact)this.updateMap.get(sobId)); | |
} | |
} | |
SObject[] returnSobs = new SObject[]{}; | |
returnSobs.addAll((SObject[])leads); | |
returnSobs.addAll((SObject[])contacts); | |
return returnSobs; | |
} | |
// all SOQL and DML operations are encapsulated in a data service. This allows them to be overridden in a unit test. | |
public class DataService { | |
// retrieve next task to complete for leads and contacts by ids | |
// note: ordering of query by whoid and activity date ensures we get the earliest next due task | |
// param: whoIds - set of lead and contact ids | |
// returns: map of tasks keyed by lead or contact id | |
public virtual Map<Id, Task> selectIncompleteTasksByWhoId(Set<Id> whoIds){ | |
Map<Id, Task> results = new Map<Id, Task>(); | |
List<Task> allResults = [SELECT WhoId, OwnerId, Subject, ActivityDate | |
FROM Task | |
WHERE Status != :STATUS_COMPLETED | |
AND WhoId IN :whoIds | |
AND ActivityDate != null | |
ORDER BY WhoId, ActivityDate ASC]; | |
for(Task task: allResults){ | |
if(!results.containsKey(task.WhoId)) | |
results.put(task.WhoId, task); | |
} | |
return results; | |
} | |
// retrieve converted leads by id | |
public virtual Lead[] selectConvertedLeadsByLeadIds(Set<Id> leadIds){ | |
return [SELECT Id | |
FROM Lead | |
WHERE Id IN :leadIds | |
AND isConverted = true]; | |
} | |
public virtual void updateSobs(SObject[] sobs) { | |
update sobs; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment