Skip to content

Instantly share code, notes, and snippets.

@gbutt
Created July 18, 2016 16:43
Show Gist options
  • Save gbutt/d73f5edc63891078b8620a5c21c16154 to your computer and use it in GitHub Desktop.
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
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