+ Start a Discussion
Esti LeiserEsti Leiser 

Edit trigger to work after insert too

Hi! I have a trigger that runs when records are updated (before update). I want it to also run after insert, for new records, but I'm not sure how.

Here's the trigger:
trigger AddPrimaryContactToOpp on Opportunity (before update) {
// THIS TRIGGER WILL OVERWRITE ANY CONTACT DEFINED IN THE CUSTOM FIELD OPPORTUNITY_CONTACT__C ON THE OPPORTUNITY OBJECT.
// SET THIS FIELD TO READ ONLY OR CHANGE THE FUNCTIONALITY BELOW TO AVIOD DATA BEING OVERWRITTEN BY MISTAKE...
   for (Opportunity o : Trigger.new) {	
       // CREATE ARRAY OF ALL CONTACT ROLES ON THIS OPPORTUNITY. THE REASON WHY WE DONT PICK THE PRIMARY CONTACT ROLE ONLY
       // IS BECAUSE THE PRIMARY FLAG IS NOT SET WHEN LEADS ARE CONVERTED TO OPPORTUNITIES. ONLY WHEN CREATING OPPORTUNITIES
       // MANUALLY FROM THE CONTACT OBJECT THE IS PRIMARY CHECKBOX IS CHECKED...
      
       OpportunityContactRole[] contactRoleArray =
       [select ContactID, isPrimary from OpportunityContactRole where OpportunityId = :o.id ORDER BY isPrimary DESC, createdDate];
       system.debug('>>> contact role array is ' + JSON.serialize(contactRoleArray));
       if (contactRoleArray.size() > 0) {
         
           // IF PRIMARY IS DEFINED THEN THIS WILL BE THE FIRST OBJECT. IF NOT THE FIRST ADDED CONTACT ROLE WILL BE ADDED...
           o.Contact__c = contactRoleArray[0].ContactID;
          
       }
       system.debug('>>> contact__c is ' + o.Contact__c);
   }
 }
Adding before insert didn't work because the OCRs are not yet created. Adding after insert didn't work either - it gave an error.

Any ideas? Thanks!

PS: this code is not bulkified (I think), so I'd love to take care of that too!
 
Best Answer chosen by Esti Leiser
Suneel#8Suneel#8
In that case, you can try below code. You need to requery the records from database instead of updating records in trigger.new
if((trigger.isBefore && trigger.isUpdate)||(trigger.isAfter && trigger.isInsert)){
      	List<Opportunity> l=new List<Opportunity>();
        Map<Id,Opportunity> m=new Map<Id,Opportunity>([select id,name,(select ContactID, isPrimary from OpportunityContactRoles where isPrimary=true ) from Opportunity where id in :trigger.newmap.keySet()]);
        Map<Id,Id> mAfterInsert=new Map<Id,Id>();
        for(Opportunity o:trigger.newmap.values()){
            if(m.get(o.id).OpportunityContactRoles.size()>0){
                if(trigger.isAfter && trigger.isInsert){
                    mAfterInsert.put(o.id,m.get(o.id).OpportunityContactRoles[0].ContactID);
                }else{
            		o.Contact__c =m.get(o.id).OpportunityContactRoles[0].ContactID;
                }    
            }    
        }
		if(trigger.isAfter && trigger.isInsert){
            for(Opportunity o:[select Id from Opportunity where id in :mAfterInsert.keySet()]){
            	o.Contact__c=mAfterInsert.get(o.id);
                l.add(o);
            }
        	update l;
      	}
    }

 

All Answers

Suneel#8Suneel#8
Will you have records in Contact Roles right after insertion of Opportunity?What is the error you are receiving when you apply the trigger on after insert?Below code is bulkified version of the same.You can try this.Using a SOQL in a for loop is not a best practice.Try to avoid the same
if(trigger.isBefore && trigger.isUpdate){
        Map<Id,Opportunity> m=new Map<Id,Opportunity>([select id,name,(select ContactID, isPrimary from OpportunityContactRoles where isPrimary=true ) from Opportunity where id in :trigger.newmap.keySet()]);
        for(Opportunity o:trigger.newmap.values()){
            if(m.get(o.id).OpportunityContactRoles.size()>0){
            	System.debug('-->'+m.get(o.id).OpportunityContactRoles.size());
            	o.Contact__c =m.get(o.id).OpportunityContactRoles[0].ContactID;
            	System.debug('-->'+o.id+'   -->'+m.get(o.id).OpportunityContactRoles[0].ContactID);
            }    
        }        
    }


 
Esti LeiserEsti Leiser
I tried running the trigger 'before insert', but it returns no value because the OCRs don't exist yet.

When I run it 'after insert' I get this error:
Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger AddPrimaryContactToOpp caused an unexpected exception, contact your administrator: AddPrimaryContactToOpp: execution of AfterInsert caused by: System.FinalException: Record is read-only: Trigger.AddPrimaryContactToOpp: line 15, column 1

 
Esti LeiserEsti Leiser
I tried putting in your code, but it's not working after insert - only before update.
Suneel#8Suneel#8
Yes, trigger.new is not updatable after insert/after update.Will the Contact Roles will be created along with Opportunity?If so how?If you are creating through Apex you can add this code there as well.
Esti LeiserEsti Leiser
This is for opportunities created from the New Opportunity button on Contact page, so the OCRs should be created automatically.
Suneel#8Suneel#8
In that case, you can try below code. You need to requery the records from database instead of updating records in trigger.new
if((trigger.isBefore && trigger.isUpdate)||(trigger.isAfter && trigger.isInsert)){
      	List<Opportunity> l=new List<Opportunity>();
        Map<Id,Opportunity> m=new Map<Id,Opportunity>([select id,name,(select ContactID, isPrimary from OpportunityContactRoles where isPrimary=true ) from Opportunity where id in :trigger.newmap.keySet()]);
        Map<Id,Id> mAfterInsert=new Map<Id,Id>();
        for(Opportunity o:trigger.newmap.values()){
            if(m.get(o.id).OpportunityContactRoles.size()>0){
                if(trigger.isAfter && trigger.isInsert){
                    mAfterInsert.put(o.id,m.get(o.id).OpportunityContactRoles[0].ContactID);
                }else{
            		o.Contact__c =m.get(o.id).OpportunityContactRoles[0].ContactID;
                }    
            }    
        }
		if(trigger.isAfter && trigger.isInsert){
            for(Opportunity o:[select Id from Opportunity where id in :mAfterInsert.keySet()]){
            	o.Contact__c=mAfterInsert.get(o.id);
                l.add(o);
            }
        	update l;
      	}
    }

 
This was selected as the best answer
Esti LeiserEsti Leiser
Your new code did the trick.
However, my test class is not passing anymore! Maybe it's because the test adds the opportunity and then the OCR, so the trigger runs before the OCR exists.
Here's the apex test. Any ideas?
@isTest
public class TestAddPrimaryContactToOppTrigger {
	@isTest
  static void testOpportunityInsert(){
    Account A1 = new Account(Name = 'Test Account');
    insert A1;

    Contact C1 = new Contact(LastName='Tester', AccountID=A1.ID, Email='tester@nowhere.com');
    insert C1;
    
    Opportunity O1 = new Opportunity(
        AccountId = A1.ID,
        Name = 'Test Account-Automated',
        CloseDate = System.today() + 90,
        StageName = 'posted');
    insert O1;
    
    OpportunityContactRole OCR1 = new OpportunityContactRole(
        OpportunityId = O1.Id,
        ContactId = C1.ID,
        IsPrimary = TRUE);
    insert OCR1;


    system.AssertEquals(O1.Contact__c,C1.ID);
    
    }
 
}

 
Suneel#8Suneel#8
You are right Esti, as OCRs can't be created along with Oppty in your test class it fails. Before update part can be tested and below is the code.You need to requery opportunity for the contact id after updation to get the updated field value
@isTest
public class TestAddPrimaryContactToOppTrigger {
	@isTest
  static void testOpportunityInsert(){
    Account A1 = new Account(Name = 'Test Account');
    insert A1;

    Contact C1 = new Contact(LastName='Tester', AccountID=A1.ID, Email='tester@nowhere.com');
    insert C1;
    Opportunity O1 = new Opportunity(
        AccountId = A1.ID,
        Name = 'Test Account-Automated',
        CloseDate = System.today() + 90,
        StageName = 'Prospecting');
    insert O1;
    OpportunityContactRole OCR1 = new OpportunityContactRole(
        OpportunityId = o1.Id,
        ContactId = C1.ID,
        IsPrimary = TRUE);
    insert OCR1;
	o1.stageName='Qualification';
    update o1;
	Id idey=[select id,contact__c from Opportunity where name='Test Account-Automated'].contact__c;
    system.AssertEquals(idey,C1.ID);
    
    } 
}

 
Esti LeiserEsti Leiser
Thanks for answering!

When I tried running that test, I got this error on line 24:
System.QueryException: List has no rows for assignment to SObject

 
Suneel#8Suneel#8
Can you check the stage names in your picklist?Do they match?I have changed them
 
Esti LeiserEsti Leiser
You're right - I don't have a "Qualification" stage. I changed the code to use "Posted", but I still get the same error.
Suneel#8Suneel#8
Also if you need 100% coverage on the trigger code,write below trigger and change the contact id to the one available in your org and run the test class.Later you need to inactivate/delete the trigger as this is just to achieve 100% coverage for the trigger
trigger DummyTrigger on Opportunity (after insert) {
    for(Opportunity o:trigger.new){
            OpportunityContactRole OCR1 = new OpportunityContactRole(
        OpportunityId = o.Id,
        ContactId = '0039000001GAQYt',
        IsPrimary = TRUE);
    insert OCR1;

    }
}

 
Suneel#8Suneel#8
Hope you have changed 'Prospecting' too.Can you check if Opportunity and OCR are getting created by adding below line and checking their value in the logs
insert OCR1;
System.debug('->o1'+o1.id+'  '+oCR1.id);
o1.stageName='Qualification';
update o1;

 
Esti LeiserEsti Leiser
'Prospecting" is one of my choices.
I inserted that code, and the debug line is:

USER_DEBUG|[21]|DEBUG|->o1006V00000053Tu2IAE 00KV0000001VBZPMA4
 
Suneel#8Suneel#8
Can you try modifying last two lines as below and post your full test class code if it is still throwing the error along with complete error message

    Id idey=[select id,contact__c from Opportunity where id=:o1.id].contact__c;
    system.AssertEquals(idey,C1.ID);
Esti LeiserEsti Leiser
Changed that, and it passed now!!!
Suneel#8Suneel#8
OK great .Can you please mark it as solved
Esti LeiserEsti Leiser
Sure! THanks for your help...