function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Nicholas SewitzNicholas Sewitz 

Last Activity Date By Account Owner

Hi I am trying to populate a custom field on the Account object with the most recent Task Activity Date where the Task Owner = Account Owner. This is the trigger I have so far, I can't figure out how to add the t.ownerid = a.ownerid filter.
 
trigger LastActivityByOwner on task(after insert) {

  map<id,Account> accounts = new map<id,account>();
    
  for(task record:trigger.new) {
      
    if(record.ownerid.getsobjecttype()==user.sobjecttype) {
        
      if(record.ownerid!=null) {
        accounts.put(record.accountid,new account(id=record.accountid,Last_Activity_By_Owner__c=record.ActivityDate));
      }
    }
  }
  update accounts.values();
}

 
Nicholas SewitzNicholas Sewitz
I tried this code as well but keep getting this error:

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger LastActivityByOwner caused an unexpected exception, contact your administrator: LastActivityByOwner: execution of AfterInsert caused by: System.FinalException: Record is read-only: Trigger.LastActivityByOwner: line 11, column 1


 
trigger LastActivityByOwner on task(after insert) {
    
    List<Id> accIds = new List<Id>();
    List<Account> accList = new List<Account>();
    for(Task t : trigger.new){
        accIds.add(t.AccountId);
    }
    accList = [select Last_Activity_By_Owner__c, OwnerId from Account where id in:accIds];
    for(Task t : Trigger.new){
        if(t.OwnerId == accList[0].OwnerID){
            t.ActivityDate = accList[0].Last_Activity_By_Owner__c;
        }
    }
}
Greg HGreg H
Hi Nicholas.

I would go in a different direction...

First, you need to make sure that the Task record being processed is linked to an Account. If it's not then you don't need to process it any further. If it is then you need to perform a query of the Account object in order to grab the OwnerId for the Account. Once you have all of the Account.OwnerId values you may then iterate over all of the Tasks again to see if the Task owner is the same as the related Account owner. Then, you will construct a list of Account records to be updated with the Last_Activity_By_Owner__c value based upon the Task that was inserted. Last, you will update the Account records.

I highly recommend that you perform the Account updates asynchronously because you don't really need to hold up the Task trigger processing to accomplish your goal. This may need to be tweaked a little but this should be a good start for the trigger:
/*I highly recommend that you use a trigger framework instead of creating multiple triggers on any single object. If this is the only trigger on the Task object then the below logic should work fine. If you decide to use a trigger framework then I happen to like the one built by Kevin O'Hara (https://github.com/kevinohara80/sfdc-trigger-framework).*/
trigger lastActivityByOwner on task(after insert) {
	
	Set<Id> accountIds = new Set<Id>(); //set for all of the Account Ids that we will query for
	Map<Id, Id> taskIdToAccountId = new Map<Id, Id>(); //Task.Id > Task.AccountId
	Map<Id, Id> taskIdToOwnerId = new Map<Id, Id>(); //Task.Id > Task.OwnerId
	Map<Id, Date> taskIdToActivityDate = new Map<Id, Date>(); //Task.Id > Task.ActivityDate
	
	for (Task t : Trigger.new) { //for all records
		if (t.AccountId != null) { //if the AccountId is not null
			accountIds.add(t.AccountId); //add the Account Id to our set
			taskIdToAccountId.put(t.Id, t.AccountId); //populate the map using the Task Id as the key and the Account Id as the value
			taskIdToOwnerId.put(t.Id, t.OwnerId); //populate the map using the Task Id as the key and the Owner Id as the value
			taskIdToActivityDate.put(t.Id, t.ActivityDate); //populate the map using the Task Id as the key and the ActivityDate as the value
		}
	}
	
	if (!accountIds.isEmpty()) { //if the accountIds set is not empty
		accountUtil.handleLastActivitybyOwnerFuture(accountIds, taskIdToAccountId, taskIdToOwnerId, taskIdToActivityDate); //call our asynchronous method to perform additional processing with the data we already acquired
	}

}

 
And here is the class called by the trigger that will perform the asynchronous Account updates.
public class accountUtil {
	
	//updates the Last_Activity_By_Owner__c value
	@future //future annotation indicates asynchronous execution
	public static void handleLastActivitybyOwnerFuture(Set<Id> accountIds, Map<Id, Id> taskIdToAccountId, Map<Id, Id> taskIdToOwnerId, Map<Id, Date> taskIdToActivityDate) {
		
		Map<Id, Id> accountIdToOwnerId = new Map<Id, Id>(); //Account.Id > Owner.Id
		Map<Id, Account> mapOfAccountDetails = new Map<Id, Account>(); //map for all Accounts that we gather via SOQL
		List<Account> accountsToBeUpdated = new List<Account>(); //all the Account records that will be updated
		
		mapOfAccountDetails = [SELECT Id, OwnerId FROM Account WHERE Id in :accountIds]; //query for all the relevant Account records
		
		for (Id i : taskIdToAccountId.keySet()) { //for all the Tasks that were related to Account records
			Id accountOwnerId; //variables for Account.OwnerId
			Id taskOwnerId; //variables for Task.OwnerId
			Id accountId; //variable for Task.AccountId
			if (taskIdToAccountId.containsKey(i)) { //if there is an Account Id for this Task Id in the taskIdToAccountId map
				accountId = taskIdToAccountId.get(i); //grab the Account Id
			}
			if (taskIdToOwnerId.containsKey(i)) { //if there is an Owner Id for this Task Id in the taskIdToOwnerId map
				taskOwnerId = taskIdToOwnerId.get(i); //grab the Owner Id
			}
			if (mapOfAccountDetails.containsKey(accountId)) { //if we found the Account in our query
				accountOwnerId = mapOfAccountDetails.get(i).OwnerId; //grab the Owner Id
			}
			if (taskOwnerId == accountOwnerId) { //if the Owners are a match
				Account accountToBeUpdated = new Account(Id = accountId); //construct a new Account sObject
				accountToBeUpdated.Last_Activity_By_Owner__c = taskIdToActivityDate.get(i); //populate the Date from the passed map
				accountsToBeUpdated.add(accountToBeUpdated); //add the Account to our list
			}
		}
		
		if (!accountsToBeUpdated.isEmpty()) { //if the accountsToBeUpdated list is not empty
			update accountsToBeUpdated; //process updates
		}
	}

}

 
Let me know if you have questions. And sorry for the verbose code.  But I thought it would be more helpful.
-greg
 
Nicholas SewitzNicholas Sewitz
Hey Greg this is amazing, I actually already have a trigger framework and handler class that i will try and incorporate this into. Something that is immediately coming up is this line from your class 
 
mapOfAccountDetails = [SELECT Id, OwnerId FROM Account WHERE Id in :accountIds]; //query for all the relevant Account records

is throwing an error "Illegal assignement from List to Map"
Nicholas SewitzNicholas Sewitz
Fyi this is the start of my trigger framework:
 
trigger TaskTrigger on Task (before insert, after insert, after update, after delete, after undelete) {
    if (Trigger.isBefore) ActivityTriggerHandler.TaskBeforeInsert(Trigger.New);
    
    // this method is called to handle all date and count field updates for opportunity
    if (Trigger.isAfter) {
        // get the parent opportunities from these tasks
        String opp_pre = Schema.SObjectType.Opportunity.getKeyPrefix();
        
        Set<Id> oppids = new Set<Id>();
        for (Task t : Trigger.isDelete ? Trigger.old : Trigger.new) {
            if (t.WhatId != null && ((String)t.WhatId).startsWith(opp_pre)) oppids.add(t.WhatId);
        }
        if (!oppids.isEmpty()) ActivityTriggerHandler.updateOpptyActivityDateandCount(oppids);
    	// 
   		Set<Id> accountIds = new Set<Id>(); //set for all of the Account Ids that we will query for
		Map<Id, Id> taskIdToAccountId = new Map<Id, Id>(); //Task.Id > Task.AccountId
		Map<Id, Id> taskIdToOwnerId = new Map<Id, Id>(); //Task.Id > Task.OwnerId
		Map<Id, Date> taskIdToActivityDate = new Map<Id, Date>(); //Task.Id > Task.ActivityDate
	
		for (Task t : Trigger.new) { //for all records
			if (t.AccountId != null) { //if the AccountId is not null
                accountIds.add(t.AccountId); //add the Account Id to our set
                taskIdToAccountId.put(t.Id, t.AccountId); //populate the map using the Task Id as the key and the Account Id as the value
                taskIdToOwnerId.put(t.Id, t.OwnerId); //populate the map using the Task Id as the key and the Owner Id as the value
                taskIdToActivityDate.put(t.Id, t.ActivityDate); //populate the map using the Task Id as the key and the ActivityDate as the value
            }
		}
	
        if (!accountIds.isEmpty()) { //if the accountIds set is not empty
            ActivityTriggerHandler.handleLastActivitybyOwnerFuture(accountIds, taskIdToAccountId, taskIdToOwnerId, taskIdToActivityDate); //call our asynchronous method to perform additional processing with the data we already acquired
        } 
    
    }
    
}

 
Greg HGreg H
My bad. Remove the earlier declaration and use this in place of that line:
Map<Id, Account> mapOfAccountDetails = new Map<Id, Account>([SELECT Id, OwnerId FROM Account WHERE Id in :accountIds]); //query for all the relevant Account records

-greg
Nicholas SewitzNicholas Sewitz
want to see to see something epic: Trigger
 
trigger TaskTrigger on Task (before insert, after insert, after update, after delete, after undelete) {
    if (Trigger.isBefore) ActivityTriggerHandler.TaskBeforeInsert(Trigger.New);
    
    // this method is called to handle all date and count field updates for opportunity
    if (Trigger.isAfter) {
        // get the parent opportunities from these tasks
        String opp_pre = Schema.SObjectType.Opportunity.getKeyPrefix();
        
        Set<Id> oppids = new Set<Id>();
        for (Task t : Trigger.isDelete ? Trigger.old : Trigger.new) {
            if (t.WhatId != null && ((String)t.WhatId).startsWith(opp_pre)) oppids.add(t.WhatId);
        }
        if (!oppids.isEmpty()) ActivityTriggerHandler.updateOpptyActivityDateandCount(oppids);
        
        // this part is used for the handleLastActivitybyOwnerFuture part of the Activity Trigger Handler
        
   		Set<Id> accountIds = new Set<Id>(); //set for all of the Account Ids that we will query for
		Map<Id, Id> taskIdToAccountId = new Map<Id, Id>(); //Task.Id > Task.AccountId
		Map<Id, Id> taskIdToOwnerId = new Map<Id, Id>(); //Task.Id > Task.OwnerId
		Map<Id, Date> taskIdToActivityDate = new Map<Id, Date>(); //Task.Id > Task.ActivityDate
	
		for (Task t : Trigger.new) { //for all records
			if (t.AccountId != null) { //if the AccountId is not null
                accountIds.add(t.AccountId); //add the Account Id to our set
                taskIdToAccountId.put(t.Id, t.AccountId); //populate the map using the Task Id as the key and the Account Id as the value
                taskIdToOwnerId.put(t.Id, t.OwnerId); //populate the map using the Task Id as the key and the Owner Id as the value
                taskIdToActivityDate.put(t.Id, t.ActivityDate); //populate the map using the Task Id as the key and the ActivityDate as the value
            }
		}
	
        if (!accountIds.isEmpty()) { //if the accountIds set is not empty
            ActivityTriggerHandler.handleLastActivitybyOwnerFuture(accountIds, taskIdToAccountId, taskIdToOwnerId, taskIdToActivityDate); //call our asynchronous method to perform additional processing with the data we already acquired
        } 
    
    }
    
}

 
Nicholas SewitzNicholas Sewitz
Class:
 
/**
 * this class handles trigger events for Task Trigger and Event Trigger
 *
 * 1. for task before insert, if what.type = account, change whatid to the most recent opportunity on that account
 * 2. for event before insert, if what.type = account, change whatid to the most recent opportunity on that account
 * 3. for task before insert, check to see if description field has From: xxxx@artsymail.com, if it does, write a boolean field IsFromArtsy
 * 4. for task and event before insert, get the value from Gallery_Relations_Type__c, and set the opportunity stage to this value if opportunity's recordtype = Renewal
 * 5. for task/event after update, delete, insert, undelete update opportunity activity fields
 * 6. for task after insert update Last_Activity_By_Owner field on Account
 */
public without sharing class ActivityTriggerHandler {

	public static String account_prefix = Schema.SObjectType.Account.getKeyPrefix();
	public static String contact_prefix = Schema.SObjectType.Contact.getKeyPrefix();

	/**
	 * for task before insert, if what.type = account, change whatid to the most recent opportunity on that account
	 * @param  newtasklist [trigger.new]
	 * @return             null
	 */
	public static void TaskBeforeInsert(List<Task> newtasklist) {
		Set<Id> aids = new Set<Id>();	// holds the accounts ids to select opportunities
		Set<Id> cids = new Set<Id>();	// holds the contact ids to select account
		Map<Id,List<Task>> accountActivityMap = new Map<Id,List<Task>>();	// holds account id to list of activity map
		Map<Id,List<Task>> contactActivityMap = new Map<Id,List<Task>>();	// holds contact id to list of activity map
		
		Map<Id,String> opptostagemap = new Map<Id,String>();	// map to pass to the utility method for oppty stage update

		// first loop through the trigger new and get all the activity that's inserted for the account or contact
		for (Task t : newtasklist) {
			if (t.WhatId != null && String.valueOf(t.WhatId).startsWith(account_prefix)) {
				// account
				aids.add(t.WhatId);
				if (accountActivityMap.get(t.WhatId) != null) accountActivityMap.get(t.WhatId).add(t);
				else accountActivityMap.put(t.WhatId,new List<Task>{t});
			} else if (t.WhoId != null && String.valueOf(t.WhoId).startsWith(contact_prefix) && t.WhatId == null) {
				// contact
				cids.add(t.WhoId);
				if (contactActivityMap.get(t.WhoId) != null) contactActivityMap.get(t.WhoId).add(t);
				else contactActivityMap.put(t.WhoId,new List<Task>{t});
			}

			// if description first line = 'From: xxxx@artsymail.com', set isfromartsy = true
			if (t.Description != null) {
				List<String> lines = t.Description.split('\n');	
				if (lines.get(0).startsWith('From:') && lines.get(0).endsWith('@artsymail.com')) {
			        t.IsFromArtsy__c = true;
			    }
			}

		}

		// so user created the activity on contact
		if (!cids.isEmpty()) {
			// first find all account ids associated with the contact
			for (Contact c : [select AccountId from Contact where Id in :cids]) {
				// now add the account ids to the aids
				aids.add(c.AccountId);
				// now add to account activity map
				accountActivityMap.put(c.AccountId,contactActivityMap.get(c.Id));

			}

		}
		if (!aids.isEmpty()) {
			// select the most recent opportunity from the accounts
			for (Account a : [select Id,(select Id,Name,CreatedDate from Opportunities order by CreatedDate DESC limit 1) from Account where Id in :aids]) {
				if (!a.Opportunities.isEmpty()) {
					// loop through the accountActivityMap's activity list and reassign all whatids
					for (Task t : accountActivityMap.get(a.Id)) {
						t.WhatId = a.Opportunities[0].Id;

						// add oppty id and stage name of the task to the map and update the oppty stage name
						if (t.Gallery_Relations_Type__c == 'Renewal Report Sent' 
							|| t.Gallery_Relations_Type__c == 'Follow-Up Negative'
							|| t.Gallery_Relations_Type__c == 'Follow-Up Positive'
							|| t.Gallery_Relations_Type__c == 'Awaiting Wire Transfer')
							opptostagemap.put(t.WhatId, t.Gallery_Relations_Type__c);
					}
				}
			}
		}
		if (!opptostagemap.isEmpty()) setOpptyStage(opptostagemap);
	}

	/**
	 * for event before insert, if what.type = account, change whatid to the most recent opportunity on that account
	 * @param  newevt [trigger.new]
	 * @return             null
	 */
	public static void EventBeforeInsert(List<Event> newevtlist) {
		Set<Id> aids = new Set<Id>();	// holds the accounts ids to select opportunities
		Set<Id> cids = new Set<Id>();	// holds the contact ids to select account
		Map<Id,List<Event>> accountActivityMap = new Map<Id,List<Event>>();	// holds account id to list of activity map
		Map<Id,List<Event>> contactActivityMap = new Map<Id,List<Event>>();	// holds contact id to list of activity map
		
		Map<Id,String> opptostagemap = new Map<Id,String>();	// map to pass to the utility method for oppty stage update
		
		// first loop through the trigger new and get all the activities that's inserted for the account or contact
		for (Event t : newevtlist) {
			if (t.WhatId != null && String.valueOf(t.WhatId).startsWith(account_prefix)) {
				aids.add(t.WhatId);
				if (accountActivityMap.get(t.WhatId) != null) accountActivityMap.get(t.WhatId).add(t);
				else accountActivityMap.put(t.WhatId,new List<Event>{t});
			} else if (t.WhoId != null && String.valueOf(t.WhoId).startsWith(contact_prefix) && t.WhatId == null) {
				// contact
				cids.add(t.WhoId);
				if (contactActivityMap.get(t.WhoId) != null) contactActivityMap.get(t.WhoId).add(t);
				else contactActivityMap.put(t.WhoId,new List<Event>{t});

			}
		}

		// so user created the activity on contact
		if (!cids.isEmpty()) {
			// first find all account ids associated with the contact
			for (Contact c : [select AccountId from Contact where Id in :cids]) {
				// now add the account ids to the aids
				aids.add(c.AccountId);
				// now add to account activity map
				accountActivityMap.put(c.AccountId,contactActivityMap.get(c.Id));

			}

		}

		if (!aids.isEmpty()) {
			// select the most recent opportunity from the accounts
			for (Account a : [select Id,(select Id,Name,CreatedDate from Opportunities order by CreatedDate DESC limit 1) from Account where Id in :aids]) {
				if (!a.Opportunities.isEmpty()) {
					// loop through the accountActivityMap's activity list and reassign all whatids
					for (Event t : accountActivityMap.get(a.Id)) {
						t.WhatId = a.Opportunities[0].Id;

						// add oppty id and stage name of the task to the map and update the oppty stage name
						if (t.Gallery_Relations_Type__c == 'Renewal Report Sent' 
							|| t.Gallery_Relations_Type__c == 'Follow-Up Negative'
							|| t.Gallery_Relations_Type__c == 'Follow-Up Positive'
							|| t.Gallery_Relations_Type__c == 'Awaiting Wire Transfer')
							opptostagemap.put(t.WhatId, t.Gallery_Relations_Type__c);
					}
				}
			}
		}

		if (!opptostagemap.isEmpty()) setOpptyStage(opptostagemap);
	}

	/**
	 * this method is an utility method called by both event and task before insert, this will set the opportunity stage for the opportunity record type of Renewal
	 * @param opptostagemap
	 */
	public static void setOpptyStage(Map<Id,String> opptostagemap) {
		Id rtId = Schema.SObjectType.Opportunity.getRecordTypeInfosByName().get('Renewal').getRecordTypeId();

		List<Opportunity> oppstoupdate = new List<Opportunity>();	// opps to update

		// loop through the opportunity keyset and update the stagenames, only update if stage is changed
		for (Opportunity opp : [select RecordTypeId, StageName from Opportunity where Id in :opptostagemap.keySet()]) {
			if (opp.RecordtypeId == rtId) {
				if (opptostagemap.get(opp.Id) == 'Renewal Report Sent' && opp.StageName != 'Renewal Report Sent') {
					opp.StageName = 'Renewal Report Sent';
					oppstoupdate.add(opp);
				} else if (opptostagemap.get(opp.Id) == 'Follow-Up Negative' && opp.StageName != 'Follow-Up in Progress - Negative') {
					opp.StageName = 'Follow-Up in Progress - Negative';
					oppstoupdate.add(opp);
				} else if (opptostagemap.get(opp.Id) == 'Follow-Up Positive' && opp.StageName != 'Follow-Up in Progress - Positive') {
					opp.StageName = 'Follow-Up in Progress - Positive';
					oppstoupdate.add(opp);
				} else if (opptostagemap.get(opp.Id) == 'Awaiting Wire Transfer' && opp.StageName != 'Closed - Pending Payment') {
					opp.StageName = 'Closed - Pending Payment';
					oppstoupdate.add(opp);
				} 
			}
		}

		if (!oppstoupdate.isEmpty()) update oppstoupdate;
	}

	/**
	 * this method is called after update, delete, insert, undelete on activity records to update opportunity activity dates
	 */
	@future
	public static void updateOpptyActivityDateandCount(Set<Id> oppids) {

		Map<Id, Opportunity> oppmap = new Map<Id, Opportunity>([SELECT Date_Of_Artsy_Outreach__c,DateTime_of_Artsy_Outreach__c,Date_Of_Initial_Email__c,Date_Of_Initial_Call__c,
        	Date_of_Fair_Outreach__c,Date_of_Subscription_Pitch__c,Date_of_Pitch_Schedule__c,Date_of_Gallery_Response__c,
        	Date_of_Partnership_Application__c, Date_of_Post_Pitch_Activity__c,Date_of_Verbal_Commit__c,
        	Call_Count__c,Email_Count__c,Significant_Email_Count__c,Meeting_Count__c,Total_Activity_Count__c,Fair_Touch_Count__c,
         	Date_of_First_Negative_Response__c,Date_of_First_Positive_Response__c,Date_of_Renewal_Verbal_Commit__c,Date_of_Initial_Credit_Card_Failure__c,
         	Count_of_Failed_Credit_Charges__c,Count_of_Negative_Responses__c,Count_of_Positive_Responses__c,
         	Date_of_Initial_Editorial_Feature__c,Date_of_Initial_Home_Page_Feature__c,Date_of_Initial_Social_Feature__c,Date_of_Initial_Email_Feature__c,
         	Date_of_Renewal_Report_Sent__c,Date_of_Most_Recent_Positive_Response__c,Date_of_Most_Recent_Negative_Response__c,
         	Date_Awaiting_Wire_Transfer__c,Date_Initial_Payment_Reminder_Sent__c,Date_of_Most_Recent_Payment_Reminder__c,
         	Count_of_Editorial_Feature_s__c,Count_of_Home_Page_Feature_s__c,Count_of_Social_Feature_s__c,Count_of_Email_Feature_s__c FROM Opportunity
         	WHERE Id in :oppids]);
		
        // DateTime_of_Artsy_Outreach__c = First activity with a subtype that equals Initial Outreach
        for (Opportunity o : [select Id, (select ActivityDate,Activity_DateTime__c from Tasks where Sub_Type__c like '%Initial%' order by Activity_DateTime__c,ActivityDate ASC limit 1),
                              (select ActivityDate,ActivityDateTime from Events where Sub_Type__c like '%Initial%' order by ActivityDateTime,ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            Date taskdate = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
            Date evtdate = !o.Events.isEmpty() ? o.Events[0].ActivityDate : null;
            Datetime taskdatetime = !o.Tasks.isEmpty() ? (o.Tasks[0].Activity_DateTime__c != null ? o.Tasks[0].Activity_DateTime__c : Datetime.newInstance(o.Tasks[0].ActivityDate.year(),o.Tasks[0].ActivityDate.month(),o.Tasks[0].ActivityDate.day())) : null;
            Datetime evtdatetime = !o.Events.isEmpty() ? o.Events[0].ActivityDateTime : null;

            if (oppmap.get(o.Id).DateTime_of_Artsy_Outreach__c == null) oppmap.get(o.Id).DateTime_of_Artsy_Outreach__c = (taskdatetime != null && evtdatetime != null ? (taskdatetime < evtdatetime ? taskdatetime : evtdatetime) : (taskdatetime != null ? taskdatetime : evtdatetime));
            if (oppmap.get(o.Id).Date_Of_Artsy_Outreach__c == null) oppmap.get(o.Id).Date_Of_Artsy_Outreach__c = (taskdate != null && evtdate != null ? (taskdate < evtdate ? taskdate : evtdate) : (taskdate != null ? taskdate : evtdate));
        
        }

        // Date_Of_Initial_Email__c - The first datetime in which an email is sent related to an opportunity (via either Cirrus or manually). Will need to “flag” somehow the thank you auto-response
        for (Opportunity o : [select Id, Owner.Name, (select ActivityDate,Activity_DateTime__c from Tasks where Type__c = 'Email' and Owner.Name <> 'Nicholas Sewitz' order by Activity_DateTime__c ASC limit 1),
                              (select Activity_DateTime__c,Owner.Name from Events where Type__c = 'Email' and Owner.Name <> 'Nicholas Sewitz' order by Activity_DateTime__c ASC limit 1) from Opportunity where Id in :oppids]) {
            Datetime taskdatetime = !o.Tasks.isEmpty() ? (o.Tasks[0].Activity_DateTime__c != null ? o.Tasks[0].Activity_DateTime__c : Datetime.newInstance(o.Tasks[0].ActivityDate.year(),o.Tasks[0].ActivityDate.month(),o.Tasks[0].ActivityDate.day())) : null;
            Datetime evtdatetime = !o.Events.isEmpty() ? o.Events[0].Activity_DateTime__c : null;

            if (oppmap.get(o.Id).Date_Of_Initial_Email__c == null) oppmap.get(o.Id).Date_Of_Initial_Email__c = (taskdatetime != null && evtdatetime != null ? (taskdatetime < evtdatetime ? taskdatetime : evtdatetime) : (taskdatetime != null ? taskdatetime : evtdatetime));
        }

        //  Date_Of_Initial_Call__c - The first date in which a call is logged as an activity in Salesforce.
        for (Opportunity o : [select Id, (select ActivityDate,Activity_DateTime__c from Tasks where Type__c = 'Call' order by Activity_DateTime__c ASC limit 1),
                              (select Activity_DateTime__c from Events where Type__c = 'Call' order by Activity_DateTime__c ASC limit 1) from Opportunity where Id in :oppids]) {
            Datetime taskdatetime = !o.Tasks.isEmpty() ? (o.Tasks[0].Activity_DateTime__c != null ? o.Tasks[0].Activity_DateTime__c : Datetime.newInstance(o.Tasks[0].ActivityDate.year(),o.Tasks[0].ActivityDate.month(),o.Tasks[0].ActivityDate.day())) : null;
            Datetime evtdatetime = !o.Events.isEmpty() ? o.Events[0].Activity_DateTime__c : null;

            if (oppmap.get(o.Id).Date_Of_Initial_Call__c == null) oppmap.get(o.Id).Date_Of_Initial_Call__c = (taskdatetime != null && evtdatetime != null ? (taskdatetime < evtdatetime ? taskdatetime : evtdatetime) : (taskdatetime != null ? taskdatetime : evtdatetime));
        }

        // Date_of_Fair_Outreach__c - The first date in which an activity is logged where Type__c = Fair Touch
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where Type__c = 'In-Person' and Sub_Type__c like 'Fair%' order by ActivityDate ASC limit 1),
                              (select ActivityDate from Events where Type__c = 'In-Person' and Sub_Type__c like 'Fair%' order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            Date taskdate = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
            Date evtdate = !o.Events.isEmpty() ? o.Events[0].ActivityDate : null;
            if (oppmap.get(o.Id).Date_of_Fair_Outreach__c == null) oppmap.get(o.Id).Date_of_Fair_Outreach__c = (taskdate != null && evtdate != null ? (taskdate < evtdate ? taskdate : evtdate) : (taskdate != null ? taskdate : evtdate));
        }

        // Date_of_Subscription_Pitch__c = First activity with a subtype that equals Pitch
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where Sub_Type__c in ('Pitch', 'Gallery Pitch', 'Office Pitch', 'Other Pitch', 'Fair Pitch', 'Event Pitch') order by ActivityDate ASC limit 1),
                              (select ActivityDate from Events where Sub_Type__c in ('Pitch', 'Gallery Pitch', 'Office Pitch', 'Other Pitch', 'Fair Pitch', 'Event Pitch') order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            Date taskdate = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
            Date evtdate = !o.Events.isEmpty() ? o.Events[0].ActivityDate : null;
            if (oppmap.get(o.Id).Date_of_Subscription_Pitch__c == null) oppmap.get(o.Id).Date_of_Subscription_Pitch__c = (taskdate != null && evtdate != null ? (taskdate < evtdate ? taskdate : evtdate) : (taskdate != null ? taskdate : evtdate));
        }

        // Date_of_Pitch_Schedule__c = First activity with a sub type that equals Pitch Scheduled
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where Sub_Type__c = 'Pitch Scheduled' order by ActivityDate ASC limit 1),
                              (select ActivityDate from Events where Sub_Type__c = 'Pitch Scheduled' order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            Date taskdate = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
            Date evtdate = !o.Events.isEmpty() ? o.Events[0].ActivityDate : null;
            if (oppmap.get(o.Id).Date_of_Pitch_Schedule__c == null) oppmap.get(o.Id).Date_of_Pitch_Schedule__c = (taskdate != null && evtdate != null ? (taskdate < evtdate ? taskdate : evtdate) : (taskdate != null ? taskdate : evtdate));
        }

        // Date_of_Verbal_Commit__c = First activity with a sub type that equals verbal commit
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where Sub_Type__c = 'Verbal Commit' order by ActivityDate ASC limit 1),
                              (select ActivityDate from Events where Sub_Type__c = 'Verbal Commit' order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            Date taskdate = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
            Date evtdate = !o.Events.isEmpty() ? o.Events[0].ActivityDate : null;
            if (oppmap.get(o.Id).Date_of_Verbal_Commit__c == null) oppmap.get(o.Id).Date_of_Verbal_Commit__c = (taskdate != null && evtdate != null ? (taskdate < evtdate ? taskdate : evtdate) : (taskdate != null ? taskdate : evtdate));
        }

        // Date of Gallery Response = First email from an email address that does not include “artsy” by scanning the from address in activity comment field
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where IsFromArtsy__c = false and Type__c = 'Email' order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            if (oppmap.get(o.Id).Date_of_Gallery_Response__c == null) oppmap.get(o.Id).Date_of_Gallery_Response__c = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
        }

        // Date of Post Pitch Activity = First Activity that occurs after the Date of Pitch not from an email address that includes “artsy"
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where IsFromArtsy__c = false and Type__c = 'Email' and Sub_Type__c like '%Pitch%' order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            if (oppmap.get(o.Id).Date_of_Post_Pitch_Activity__c == null) oppmap.get(o.Id).Date_of_Post_Pitch_Activity__c = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
        }

        // Date_of_Partnership_Application__c = first call with subtype = Application
        for (Opportunity o : [select Id, (select ActivityDate from Tasks where Type__c = 'Call' and Sub_Type__c = 'Application' order by ActivityDate ASC limit 1) from Opportunity where Id in :oppids]) {
            if (oppmap.get(o.Id).Date_of_Partnership_Application__c == null) oppmap.get(o.Id).Date_of_Partnership_Application__c = !o.Tasks.isEmpty() ? o.Tasks[0].ActivityDate : null;
        }

    
    /**
     * this method is called after insert on task to update Account fields
     */
	@future //future annotation indicates asynchronous execution
	public static void handleLastActivitybyOwnerFuture(Set<Id> accountIds, Map<Id, Id> taskIdToAccountId, Map<Id, Id> taskIdToOwnerId, Map<Id, Date> taskIdToActivityDate) {
		
		Map<Id, Id> accountIdToOwnerId = new Map<Id, Id>(); //Account.Id > Owner.Id
		Map<Id, Account> mapOfAccountDetails = new Map<Id, Account>([SELECT Id, OwnerId FROM Account WHERE Id in :accountIds]); //query for all the relevant Account records
		List<Account> accountsToBeUpdated = new List<Account>(); //all the Account records that will be updated
		
		
		for (Id i : taskIdToAccountId.keySet()) { //for all the Tasks that were related to Account records
			Id accountOwnerId; //variables for Account.OwnerId
			Id taskOwnerId; //variables for Task.OwnerId
			Id accountId; //variable for Task.AccountId
			if (taskIdToAccountId.containsKey(i)) { //if there is an Account Id for this Task Id in the taskIdToAccountId map
				accountId = taskIdToAccountId.get(i); //grab the Account Id
			}
			if (taskIdToOwnerId.containsKey(i)) { //if there is an Owner Id for this Task Id in the taskIdToOwnerId map
				taskOwnerId = taskIdToOwnerId.get(i); //grab the Owner Id
			}
			if (mapOfAccountDetails.containsKey(accountId)) { //if we found the Account in our query
				accountOwnerId = mapOfAccountDetails.get(i).OwnerId; //grab the Owner Id
			}
			if (taskOwnerId == accountOwnerId) { //if the Owners are a match
				Account accountToBeUpdated = new Account(Id = accountId); //construct a new Account sObject
				accountToBeUpdated.Last_Activity_By_Owner__c = taskIdToActivityDate.get(i); //populate the Date from the passed map
				accountsToBeUpdated.add(accountToBeUpdated); //add the Account to our list
			}
		}
		
		if (!accountsToBeUpdated.isEmpty()) { //if the accountsToBeUpdated list is not empty
			update accountsToBeUpdated; //process updates
		}
	}

}
Nicholas SewitzNicholas Sewitz
Hey Greg, hopefully the final question. Ive done some research and it seems something to do again with moving from Map to List but am getting the error "System.ListException: Duplicate id in list: 001n0000007iIXlAAM" on one of my tests. Thanks again honestly, learning so much here.
 
@future //future annotation indicates asynchronous execution
	public static void handleLastActivitybyOwnerFuture(Set<Id> accountIds, Map<Id, Id> taskIdToAccountId, Map<Id, Id> taskIdToOwnerId, Map<Id, Date> taskIdToActivityDate) {
		
		Map<Id, Id> accountIdToOwnerId = new Map<Id, Id>(); //Account.Id > Owner.Id
		Map<Id, Account> mapOfAccountDetails = new Map<Id, Account>([SELECT Id, OwnerId FROM Account WHERE Id in :accountIds]); //query for all the relevant Account records
		List<Account> accountsToBeUpdated = new List<Account>(); //all the Account records that will be updated
		
		
		for (Id i : taskIdToAccountId.keySet()) { //for all the Tasks that were related to Account records
			Id accountOwnerId; //variables for Account.OwnerId
			Id taskOwnerId; //variables for Task.OwnerId
			Id accountId; //variable for Task.AccountId
			if (taskIdToAccountId.containsKey(i)) { //if there is an Account Id for this Task Id in the taskIdToAccountId map
				accountId = taskIdToAccountId.get(i); //grab the Account Id
			}
			if (taskIdToOwnerId.containsKey(i)) { //if there is an Owner Id for this Task Id in the taskIdToOwnerId map
				taskOwnerId = taskIdToOwnerId.get(i); //grab the Owner Id
			}
			if (mapOfAccountDetails.containsKey(accountId)) { //if we found the Account in our query
				accountOwnerId = mapOfAccountDetails.get(accountId).OwnerId; //grab the Owner Id
			}
			if (taskOwnerId == accountOwnerId) { //if the Owners are a match
				Account accountToBeUpdated = new Account(Id = accountId); //construct a new Account sObject
				accountToBeUpdated.Last_Activity_By_Owner__c = taskIdToActivityDate.get(i); //populate the Date from the passed map
				accountsToBeUpdated.add(accountToBeUpdated); //add the Account to our list
			}
		}
		
		if (!accountsToBeUpdated.isEmpty()) { //if the accountsToBeUpdated list is not empty
			update accountsToBeUpdated; //process updates
		}
	}

 
Nicholas SewitzNicholas Sewitz
the line it calls out for the error is 
if (!accountsToBeUpdated.isEmpty()) { //if the accountsToBeUpdated list is not empty
			update accountsToBeUpdated; //process updates
		}
Greg HGreg H
That error will happen if you try to update the same record more than once. It means that we likely processed more than one Task for the same Account. This is totally reasonable but we will need to make sure that we only pass one value for Last_Activity_By_Owner__c for each Account regardless of the number of Tasks that have been created and associated to the same Account.

Task.ActivityDate is a Date type field. My assumption then is that all of the Tasks processed by the trigger will have the same value for this field so we don't need to compare the values for each Task in order to determine a winning value to use on the Account. If this assumption is incorrect then the logic I am suggesting will need to be modified. Otherwise, this is what you need to do.

Slightly modify the for loop logic in the trigger as follows:
for (Task t : Trigger.new) { //for all records 
	if (t.AccountId != null) { //if the AccountId is not null 
		if (!accountIds.contains(t.AccountId)) { //if the AccountId is not already in our accountIds set
			accountIds.add(t.AccountId); //add the Account Id to our set 
			taskIdToAccountId.put(t.Id, t.AccountId); //populate the map using the Task Id as the key and the Account Id as the value 
			taskIdToOwnerId.put(t.Id, t.OwnerId); //populate the map using the Task Id as the key and the Owner Id as the value 
			taskIdToActivityDate.put(t.Id, t.ActivityDate); //populate the map using the Task Id as the key and the ActivityDate as the value 
		}
	}
}
Let me know how everything goes…
-greg
Nicholas SewitzNicholas Sewitz
Thanks a million Greg. Tested and Deployed, learned so much really appreciate it. For everyone else, this is what the final code looks like. If you need help figuring out the framework feel free to ping me for the rest of it: Trigger
 
// this part is used for the handleLastActivitybyOwnerFuture part of the Activity Trigger Handler
        
        Set<Id> accountIds = new Set<Id>(); //set for all of the Account Ids that we will query for
        Map<Id, Id> taskIdToAccountId = new Map<Id, Id>(); //Task.Id > Task.AccountId
        Map<Id, Id> taskIdToOwnerId = new Map<Id, Id>(); //Task.Id > Task.OwnerId
        Map<Id, Date> taskIdToActivityDate = new Map<Id, Date>(); //Task.Id > Task.ActivityDate
        
        for (Task t : Trigger.new) { //for all records
            if (t.AccountId != null) { //if the AccountId is not null
                if(!accountIds.contains(t.AccountID)) {
                    accountIds.add(t.AccountId); //add the Account Id to our set
                    taskIdToAccountId.put(t.Id, t.AccountId); //populate the map using the Task Id as the key and the Account Id as the value
                    taskIdToOwnerId.put(t.Id, t.OwnerId); //populate the map using the Task Id as the key and the Owner Id as the value
                    taskIdToActivityDate.put(t.Id, t.ActivityDate); //populate the map using the Task Id as the key and the ActivityDate as the value
                }         
            }
        }
        
        if (!accountIds.isEmpty()) { //if the accountIds set is not empty
            ActivityTriggerHandler.handleLastActivitybyOwnerFuture(accountIds, taskIdToAccountId, taskIdToOwnerId, taskIdToActivityDate); //call our asynchronous method to perform additional processing with the data we already acquired
        } 
        
    }

Class:
 
/**
     * this method is called after insert on task to update Account fields
     */
	@future //future annotation indicates asynchronous execution
	public static void handleLastActivitybyOwnerFuture(Set<Id> accountIds, Map<Id, Id> taskIdToAccountId, Map<Id, Id> taskIdToOwnerId, Map<Id, Date> taskIdToActivityDate) {
		
		Map<Id, Id> accountIdToOwnerId = new Map<Id, Id>(); //Account.Id > Owner.Id
		Map<Id, Account> mapOfAccountDetails = new Map<Id, Account>([SELECT Id, OwnerId FROM Account WHERE Id in :accountIds]); //query for all the relevant Account records
		List<Account> accountsToBeUpdated = new List<Account>(); //all the Account records that will be updated
		
		
		for (Id i : taskIdToAccountId.keySet()) { //for all the Tasks that were related to Account records
			Id accountOwnerId; //variables for Account.OwnerId
			Id taskOwnerId; //variables for Task.OwnerId
			Id accountId; //variable for Task.AccountId
			if (taskIdToAccountId.containsKey(i)) { //if there is an Account Id for this Task Id in the taskIdToAccountId map
				accountId = taskIdToAccountId.get(i); //grab the Account Id
			}
			if (taskIdToOwnerId.containsKey(i)) { //if there is an Owner Id for this Task Id in the taskIdToOwnerId map
				taskOwnerId = taskIdToOwnerId.get(i); //grab the Owner Id
			}
			if (mapOfAccountDetails.containsKey(accountId)) { //if we found the Account in our query
				accountOwnerId = mapOfAccountDetails.get(accountId).OwnerId; //grab the Owner Id
			}
			if (taskOwnerId == accountOwnerId) { //if the Owners are a match
				Account accountToBeUpdated = new Account(Id = accountId); //construct a new Account sObject
				accountToBeUpdated.Last_Activity_By_Owner__c = taskIdToActivityDate.get(i); //populate the Date from the passed map
				accountsToBeUpdated.add(accountToBeUpdated); //add the Account to our list
			}
		}
		
		if (!accountsToBeUpdated.isEmpty()) { //if the accountsToBeUpdated list is not empty
			update accountsToBeUpdated; //process updates
		}
	}

 
suresh sanneboina 4suresh sanneboina 4
Hi,

Please try this

I guess this might work

trigger updateAccounts on task(after insert)
{
    Set<Id> accountIds=new Set<Id>();
    for(Task tas:Trigger.new)
    {
        if(tas.AccountId !=null)
        {
            accountIds.add(tas.AccountId);
        }
    }
    
    if(!accountIds.isEmpty())
    {
        Map<Id,Account> mapAccount=new Map<Id,Account>([Select Id,OwnerId from Account where ID IN : accountIds]);
        if(!mapAccount.isEmpty())
        {
            List<Account> lstAccount=new List<Account>();
            for(Task tas:Trigger.new)
            {
                if(tas.AccountId !=null)
                {
                    if(mapAccount.containsKey(tas.AccountId))
                    {
                        if(mapAccount.get(tas.AccountId).OwnerId == tas.OwnerId)
                        {
                            Account acc=new Account(Id=tas.AccountId,Last_Activity_By_Owner__c =tas.ActivityDate);
                            lstAccount.add(acc);
                        }
                    }
                }
            }
            if(!lstAccount.isEmpty())
            {
                update lstAccount;
            }
        }
    }
}
Nicholas SewitzNicholas Sewitz
Hey @greg something has come up. Whenever I insert a task the lastactivitydatebyowner field does not get updated. However when I update the task it does. The trigger however is on afterinsert? Any ideas?
suresh sanneboina 4suresh sanneboina 4
Hi Nicholas,

Please try this code
trigger updateAccounts on task(after insert)
{
    Set<Id> accountIds=new Set<Id>();
    for(Task tas:Trigger.new)
    {
        if(tas.AccountId !=null)
        {
            accountIds.add(tas.AccountId);
        }
    }
    
    if(!accountIds.isEmpty())
    {
        Map<Id,Account> mapAccount=new Map<Id,Account>([Select Id,OwnerId from Account where ID IN : accountIds]);
        if(!mapAccount.isEmpty())
        {
            List<Account> lstAccount=new List<Account>();
            for(Task tas:Trigger.new)
            {
                if(tas.AccountId !=null)
                {
                    if(mapAccount.containsKey(tas.AccountId))
                    {
                        if(mapAccount.get(tas.AccountId).OwnerId == tas.OwnerId)
                        {
                            Account acc=new Account(Id=tas.AccountId,Last_Activity_By_Owner__c =tas.ActivityDate);
                            lstAccount.add(acc);
                        }
                    }
                }
            }
            if(!lstAccount.isEmpty())
            {
                update lstAccount;
            }
        }
    }
}