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
ChickenOrBeefChickenOrBeef 

After Insert trigger that uses IDs: "Record is read-only"

Hey everyone,

I've written an After Insert trigger that looks at the Created By ID and then transfers the record to a different owner. Here are the issues:

-You can't update the same record in an After trigger or else you'll get the "Record is read-only" error
-I can't use a Before trigger, since my trigger is looking at an ID, which doesn't exist yet in a Before trigger

So how would I go about modifying my code to do what I need? I've heard that I need to either "clone the record" or "query anew", but I'm not exactly sure how to use those approaches with my example. 

Please take a look at my trigger and let me know what I should change. (By the way, this trigger also sends an email, as you can see) .

Here is the error when I test it out in the Sandbox:

User-added image


Here is the trigger (I've bolded the line where the error occurs):

public class ClassLibraryTaskEmail{
    
    public void sendEmail(List<Task> tasks){
        
        Set<String> contactsInTrigger = new Set<String>();
        Map<String,String> contactMap = new Map<String,String>();
        Map<String,String> IDmap = new Map<String,String>();
        List<Task> tasksToTransfer = new List<Task>();
        
        FOR(Task tk : tasks){
            IF(tk.WhoId != NULL && string.valueOf(tk.WhoId).startsWith('003')){
                contactsInTrigger.add(tk.WhoId);
                contactMap.put(tk.Id,tk.WhoId);
            }
        }
        
        List<Contact> validContacts = [SELECT
                                      	Id,AccountId,Account.Assigned_ID__c
                                       FROM
                                      	Contact
                                       WHERE
                                      	Id In: contactsInTrigger];
        
        FOR(Contact c : validContacts){
            IDmap.put(c.Id,c.Account.Assigned_ID__c);
        }
        
        FOR(Task tsk : tasks){
            IF(string.valueOf(tsk.CreatedById).contains('005A0000002rvG0')){
                ID newOwner = IDmap.get(contactMap.get(tsk.Id));
                tsk.OwnerId = newOwner;
                tasksToTransfer.add(tsk);
            }
        }
        
        List<Messaging.SingleEmailMessage> mails = New List<Messaging.SingleEmailMessage>();
        List<String> emails = New List<String>();
        
        List<User> recipients = [SELECT
                                	Id, Email
                                 FROM
                                	User
                                  WHERE
                                	Library_Task_Recipient__c = TRUE];
        
        FOR(User u : recipients){
            IF(u.Email != NULL){
            
            	emails.add(u.Email);
                
            }
            
        }
        
        FOR(Task t : tasks){
            
            IF(emails.size() > 0 && string.valueOf(t.CreatedById).contains('005A0000002rvG0')){
                
                Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
                
                List<String> sendTo = New List<String>();
                
                mail.setToAddresses(emails);
                mail.setReplyTo('greg@offerpop.com');
                mail.setSenderDisplayName('Greg Annunziata');
                
                mail.setSubject('Library Contact Has Filled Out Form');
                String body = 'Greetings director, <br><br>';
                body += 'A contact under a Library account has filled out a form. Please follow up.<br><br>';
                body += 'A link to the task can be found here: https://na7.salesforce.com/'+ t.Id;
                mail.setHtmlBody(body);
                
                mails.add(mail);
                               
            }
            
        }
        
        Messaging.sendEmail(mails);
        UPDATE tasksToTransfer;
        
    }

}

Thanks!
-Greg
Best Answer chosen by ChickenOrBeef
ChickenOrBeefChickenOrBeef
Hey guys,

I ended up having the trigger transfer the associated Account instead of the Task itself. Transferring an Account also transfers the open tasks, so that worked for me.

Thanks!
-Greg

All Answers

Eli Flores, SFDC DevEli Flores, SFDC Dev
A couple things you could try. You could change it to a before trigger and user UserInfo.getUserId() to get the user ID of the person who created the record.

 or if you don't need to do it synchronously, you could push it a future method. 

Though really, it looks like you could recreate this using workflows. 
ChickenOrBeefChickenOrBeef
Hey Eli,

Thanks for the reponse! A few things...

-These tasks aren't created by a person in Salesforce. They're created by RingLead (a de-duping service) and the Created By is set as a certain user called "Offerpop Marketing". So would I still be able to use UserInfo.getUserId() to get the ID of Offerpop Marketing, even though it's not actually Offerpop Marketing creating the task in Salesforce?
-I'm not familiar with 'future methods'. Can you elaborate a bit on what that is?
-I don't think I can use workflows, since I need to look up a field on the account to know which user to transfer the task to.

Thanks!
-Greg
Eli Flores, SFDC DevEli Flores, SFDC Dev
It should. I suspect that Offerpop Marketing is in fact a user in SFDC. If you can see the Offerpop Marketing in the list of Users under setup. If you see it there, Userinfo.getUserId() should work. If not, it might not. 

Future is an annotation you can use to have a certain bit of code execute in the asynchronously. Basically, it places in a queue and executes it in the near future. It's probably the most valuable resource you have, so you want to make sure you use it properly. You can learn a bit more about here but if you can't use UserInfo, this might  get you out of this pickle. 

https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_annotation_future.htm
ChickenOrBeefChickenOrBeef
Thanks Eli, but I think I also may need to look at the Owner of the record as well. Not just the creator.

Is there a way to get that ID in a Before trigger?

Thanks!
-Greg
Eli Flores, SFDC DevEli Flores, SFDC Dev
Sure, all fields are available in the before trigger except for those that depend on the object existing (like LastModifiedDate, CreatedById, etc)
ChickenOrBeefChickenOrBeef
Hey guys,

I ended up having the trigger transfer the associated Account instead of the Task itself. Transferring an Account also transfers the open tasks, so that worked for me.

Thanks!
-Greg
This was selected as the best answer