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
Edward Scott 5Edward Scott 5 

Trigger on Opportunity to pull in Opportunity Team Members

Hi Community,

I have been trying to write a trigger that will update a field called Ad Ops Contact that I created on the Opportunity page. My first attempt was to write a trigger on Opportunity Team Member that updates the opportunity with the name of the person who is the Ad Ops contact but I could only get it to work when I actually updated the opportunity team and not when the opportunity team is added to the opportunity (which happens when the opportunity is created). So basically that trigger on the opportunityteammember worked on update but not on insert. So my second attempt after reading this link about related lists (https://developer.salesforce.com/forums/ForumsMain?state=id#!/feedtype=SINGLE_QUESTION_SEARCH_RESULT&id=906F0000000943LIAQ) was to write it on the opportunity itself. 
trigger updateAdOps on Opportunity (after update, after insert) {

    //Using set to be able to work with setOPP and setOTM sets
    set<Id> setOpp = new set<Id>();
    set<Id> setOTM = new set<Id>();
    
       //Loop through opportunity team members and grab users who have the role of 'Ad Ops'
    for (Opportunity oppTeam : trigger.new) { 
        setOpp.add(oppTeam.Id);
    } 
    
    
     //Create Map
    list<OpportunityTeamMember> lstOTM = new list<OpportunityTeamMember>([SELECT Id, UserId, OpportunityId, User.Name FROM OpportunityTeamMember WHERE Id in :setOTM AND (TeamMemberRole = 'Ad Ops') ]);

    Map<Id,Opportunity> mapOpps = new map<Id, Opportunity>([SELECT Id, Ad_Ops_Contact1__c FROM Opportunity Where Id IN :setOpp ]) ;          
    Opportunity tempOpp;
    
    
        //Load Values 
        for(OpportunityTeamMember otm : lstOTM ){
        tempOpp = mapOpps.get(otm.user.name); // Opportunity Team Member Id
        tempOpp.Ad_Ops_Contact1__c = otm.user.name; //otm.user.name is from user.name in the create map
    
    }
    insert mapOpps.values();  

}

I am getting an error that says Insert can't be done on this opperation. 

Any help would be appreciated.

Thanks,

Edward
Best Answer chosen by Edward Scott 5
JeffreyStevensJeffreyStevens
No - you should be fine on the lookup to user.

That error is probably because I steered you wrong again.  

Because it's and after update after insert trigger - you probably have to "re-aquire" the Opportunity records.

so - modify the trigger to be like this...
 
Trigger updateAdOps on Opportunity (after update, after insert) {

  // Get Ad Ops team members & put in a map of <OpportunityId,AdOpsTeamMemberId>
  map<id,id> mOtms = new map<id,id>();
  list<OpportunityTeamMembers> otms = new list<OpportunityTeamMember>([
      SELECT id,opportunityId,TeamMemberRole,UserId 
      FROM OpportunityTeamMember
      WHERE opportunityId IN :trigger.newmap.keyset()
        AND TeamMemberRole = 'Ad Ops'
      )];

  For(OpportunityTeamMember otm :otms) {
    mOtms.put(otm.OpportunityId,otm.UserId);
  }

  // Re-aquire the Opportinities and Update them with the Ad Ops team member
  list<Opportunity> opportunitiesToUpdate = new list<opportunity>([SELECT id,Ad_Ops_Contact1__c FROM   Opportunity WHERE Id IN :trigger.newmap.keyset()]);
  for(Opportunity o :opportunitiesToUpdate) {
    if(mOtms.containsKey(o.id) {
      o.Ad_Ops_Contact1__c = mOtms.get(o.id);
    }
  }

  if (util_utilities.alreadyExecuted != true) {
    util_utilities.alreadyExecuted = true;
    if(opportunitiesToUpdate.size()>0) {
      update opportunitiesToUpdate();
    }
  }

}

see if that works
 

All Answers

JeffreyStevensJeffreyStevens
I don't think you can do an insert in a trigger to the same object.  You are not create NEW opportunities - you're just wanting to UPDATE the Ad_Ops_Contact1__c field - right?

If that's the case - then you'll want to do an update to the oppty's.   So - that's as simple as changing 20-26 with....  (I think)
 
for(opportunityTeamMember otm :lstOtm) {
  opportunity tempOpportunity = new opportunity();
  tempOpportunity = mapOpps.get(otm.opportunityId);
  tempOpportunity.Ad_Ops_Contact1__c = otm.user.name;
  mapOpps.put(otm.opportunityId,tempOpportunity);
}
 
update mapOpps.values();


But then you'll have another issue - as this trigger is then going to "fire" again - because you're doing an update.  And it will continue to fire over and over - until you hit your SOQL limit (101 queries).
So, in this case - you'll want to use the "Run Once" technique.  Different people do it different ways - but here is how I use it...
 
//Create a utilities class/method like this…
Public with sharing class util_utilities {
	Public static Boolean alreadyExecuted;
}



//Then use like this…
	If (util_utilities.alreadyExecuted != true) {
		Util_utilities.alreadyExecuted = true;
		// Other Code to be executed only once…  - ie
        update mapOpps.values();
	}

Hope that helps

 
Edward Scott 5Edward Scott 5
So would I post the Class util_utilities in the Trigger? Thanks for your help.
JeffreyStevensJeffreyStevens
No - just make it a complete class file by it's self.  
Edward Scott 5Edward Scott 5
Hey Jeff. I am a little lost on the class util_utilities. Where the comment says "use it like this" should that part of the code be placed in the class or in the trigger to make it only fire once? Thanks again for your help.
JeffreyStevensJeffreyStevens
Yep - that "use it like this" part is the code that goes inside of the trigger. Line 8 in my code should be changed to this...

If (util_utilities.alreadyExecuted != true) {
    util_utilities.alreadyExecuted = true;
    update mapOpps.values();
}

That way you update the values once, but not a second + time.

And the util_utilities.cls - all that is in there is the class defintion, and the property (alreadyExecuted) definition. It's a static variable - that's how it keeps it value from one call to the next. 
Edward Scott 5Edward Scott 5
Thanks again Jeffrey for your help. The util_utilities is working. The code isn't getting stuck in a loop anymore but its still not updating the field. I wanted to post the code here again to see if I am doing something wrong. 
trigger updateAdOps on Opportunity (after update, after insert) {

    //Using set to be able to work with setOPP and setOTM sets
    set<Id> setOpp = new set<Id>();
    set<Id> setOTM = new set<Id>();
    
       //Loop through opportunity team members and grab users who have the role of 'Ad Ops'
    for (Opportunity oppTeam : trigger.new) { 
        setOpp.add(oppTeam.Id);
    } 
    
    
     //Create Map
    list<OpportunityTeamMember> lstOTM = new list<OpportunityTeamMember>([SELECT Id, UserId, OpportunityId, User.Name FROM OpportunityTeamMember WHERE Id in :setOTM AND (TeamMemberRole = 'Ad Ops') ]);

    Map<Id,Opportunity> mapOpps = new map<Id, Opportunity>([SELECT Id, Ad_Ops_Contact1__c FROM Opportunity Where Id IN :setOpp ]) ;          
    Opportunity tempOpp;
    
    
        //Load Values 
         for(opportunityTeamMember otm :lstOtm) {
  opportunity tempOpportunity = new opportunity();
  tempOpportunity = mapOpps.get(otm.opportunityId);
  tempOpportunity.Ad_Ops_Contact1__c = otm.user.name;
  mapOpps.put(otm.opportunityId,tempOpportunity);


}
 If (util_utilities.alreadyExecuted != true) {
        Util_utilities.alreadyExecuted = true;   
 update mapOpps.values();

}

}
JeffreyStevensJeffreyStevens
Well - after looking at your code again - I think it might be better with something like this...

 
Trigger updateAdOps on Opportunity (after update, after insert) {

  // Get Ad Ops team members & put in a map of <OpportunityId,AdOpsTeamMemberId>
  map<id,id> mOtms = new map<id,id>();
  list<OpportunityTeamMembers> otms = new list<OpportunityTeamMember>([
      SELECT id,opportunityId,TeamMemberRole,UserId 
      FROM OpportunityTeamMember
      WHERE opportunityId IN :trigger.newmap.keyset()
        AND TeamMemberRole = 'Ad Ops'
      )];

  For(OpportunityTeamMember otm :otms) {
    mOtms.put(otm.OpportunityId,otm.UserId);
  }

  // Update Opportunity with the Ad Ops team member
  for(Opportunity o :trigger.new) {
    o.Ad_Ops_Contact1__c = mOtms.get(o.id);
  }

  if (util_utilities.alreadyExecuted != true) {
    util_utilities.alreadyExecuted = true;
    update trigger.newmap.values();
  }

}

I wish I would have thought of that at first.

You have a set of Opportunity IDs that fired the triggered already - so you can get all of the OpportunityTeamMembers for those opportunities with the first SOQL.  Then put that list in a map of the OpportunityId, to the Ad Op UserID.  Once you have that map populated - you can go through the triggered Opportunities and update the Ad_Ops_Contact1__c field.

You might want to wrap the o.Ad_Ops_Contact1__c = mOtms.get(o.id);  with an If statement for the map.  That would take care of any errors that might happen if there were NO Ad OPs at all.  So like this...
if(mOtms.containsKey(o.id)) {
  o.Ad_Ops_Contact1__c = mOtms.get(o.id);
}

However - I wanted to question you - what the Ad_Ops_Contact1__c field is?  Is that a lookup to the CONTACT or to the USER?  With the naming of the field - it makes me think it's pointing to the contact object.  If that's the case - then you'll have another issue - as the OpportunityTeamMember is identified by the UserId. 

good luck with it.

Jeff
Edward Scott 5Edward Scott 5
Hey Jeff,

The Ad_Ops_Contact1 is a text field that I was able to get to update when I was using the trigger on the opp team member. I switched it over though to a lookup field that goes to the user record. But now I am getting this error saying that the record is read only. User-added image

Should the field that I am updating be a look up to the user record? 

I know that this is so close to working. Thanks again
JeffreyStevensJeffreyStevens
No - you should be fine on the lookup to user.

That error is probably because I steered you wrong again.  

Because it's and after update after insert trigger - you probably have to "re-aquire" the Opportunity records.

so - modify the trigger to be like this...
 
Trigger updateAdOps on Opportunity (after update, after insert) {

  // Get Ad Ops team members & put in a map of <OpportunityId,AdOpsTeamMemberId>
  map<id,id> mOtms = new map<id,id>();
  list<OpportunityTeamMembers> otms = new list<OpportunityTeamMember>([
      SELECT id,opportunityId,TeamMemberRole,UserId 
      FROM OpportunityTeamMember
      WHERE opportunityId IN :trigger.newmap.keyset()
        AND TeamMemberRole = 'Ad Ops'
      )];

  For(OpportunityTeamMember otm :otms) {
    mOtms.put(otm.OpportunityId,otm.UserId);
  }

  // Re-aquire the Opportinities and Update them with the Ad Ops team member
  list<Opportunity> opportunitiesToUpdate = new list<opportunity>([SELECT id,Ad_Ops_Contact1__c FROM   Opportunity WHERE Id IN :trigger.newmap.keyset()]);
  for(Opportunity o :opportunitiesToUpdate) {
    if(mOtms.containsKey(o.id) {
      o.Ad_Ops_Contact1__c = mOtms.get(o.id);
    }
  }

  if (util_utilities.alreadyExecuted != true) {
    util_utilities.alreadyExecuted = true;
    if(opportunitiesToUpdate.size()>0) {
      update opportunitiesToUpdate();
    }
  }

}

see if that works
 
This was selected as the best answer
Edward Scott 5Edward Scott 5
It worked. Thank you for all of your help.
Nook 7Nook 7
Guys, can anyone send me the working trigger? Can't get it right :(