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
Matt FieldMatt Field 

Automatically add Opportunity Team Member to Opportunity

I am looking for help with automatically adding a person to an opportunity team.  I have a user lookup on Product2 called Financial_Analyst__c.  This is the financial analyst assigned to the individual product.  What I am looking to do is to automatically add that person to the opportunity team when the opportunity is created.  Any help will be greatly appreciated.  Thanks.

Best Answer chosen by Admin (Salesforce Developers) 
MTBRiderMTBRider

If some of the products do not have financial analyst, then just check to make sure the key exists in the map before you try to add the team member.  See below.

 

        ...

	for (OpportunityLineItem oli : trigger.new) {
		if (pbe.containsKey(oli.PriceBookEntryID) && finAnalysts.containsKey(pbe.get(oli.PriceBookEntryID).Product2Id)) {  //Check that the keys exist in both maps before adding team member
			OpportunityTeamMember otm = new OpportunityTeamMember(OpportunityId = oli.OpportunityId,   //THIS IS CHANGED: Reference the Opportunity ID via the Opp Line Item
			UserId = finAnalysts.get(pbe.get(oli.PriceBookEntryID).Product2Id).Financial_Analyst__c,   //THIS IS CHANGED: use the Maps created above to get the Financial Analyst User ID
			TeamMemberRole = 'Financial Analyst');
			oppTeam.add(otm);
		}
	}
	
        ...

 

All Answers

MTBRiderMTBRider

I just got done doing something like this and it is doable, but alittle more complex than you might think.  Your implementation may be simpler than mine.  My clients wanted to add teams to opps based on the product that was selected for the opportunity.  So, there were multiple team members and if the product on the opp was subsequently changed, I had to determine if the team members needed to change too.

 

Basically you will need to add the team members to the OpportunityTeamMember and the OpportunityShare tables.  Add the team member(s) to the OpportunityTeamMember table first then use Database.Saveresult[] to determine that the insert was successful and then add the same member(s) to the OpportunityShare table.

 

Hope that helps.

Matt FieldMatt Field

Thanks for the response.  I am having problems with the Apex code, as I am new to Apex.  Do you have any sample code you can post here that will help me?  Thanks.

MTBRiderMTBRider

I assume that you are going to do this in an update and insert Trigger on Opportunity?  If so you will want to bulkify the code which you can read about in the developer guide if you are not already familar.  

 

It also sounds like you will have one product specialist stored in the Products2 table for each product that will be added to each opportunity?  My code had multiple members that were added to the opportunity.  The teams were defined and stored in the Groups table so there were more tables involved for my implementation, but here is some of the relevant Opportunity Team code that I have modified and simplified a bit that may help for your implementation.  Since this is edited code from what I implemented, it is not tested and hopefully I have no typos and all the brackets match up.... 

 

The first code snippet loops through all the record in the trigger and gathers the info needed for the OpportunityTeamMember inserts.  

 

List<OpportunityTeamMember> oppTeam = new List<OpportunityTeamMember>();    //Define a list to hold all the updates 
for (Opportunity opp : trigger.new) {                        //Loop through all records in the trigger
    OpportunityTeamMember otm = new OpportunityTeamMember(OpportunityId = opp.Id,
				UserId = UserIdForProductSpecialist,    //This is the ID for your product specialist
				TeamMemberRole = 'Product Specialist...or whatever you call them',
				Added_By__c = 'system');     //Since users can manually added team members, I created a custom field to indicate team members added by the system 
    oppTeam.add(otm);
}

 The second  code snippet inserts into the OpportunityTeamMember table and then, for each successful insert into that table, inserts a sibling record into the OpportunityShare table.

if (oppTeam.size() > 0) { 
    Integer insertRecNum = 0; 
    Database.Saveresult[] dbSave1 = Database.insert(oppTeam, false); 
    for (Database.Saveresult recSave : dbSave1) { 
       if (recSave.isSuccess()) { 
         oppShare.add( new OpportunityShare(UserOrGroupID = oppTeam[insertRecNum].UserId, 
                                            OpportunityId = oppteam[insertRecNum].OpportunityId, 
                                            OpportunityAccessLevel = 'Edit')); 
       } else { 
          Database.Error eMsg = recSave.getErrors()[0]; 
          System.debug('.............Insert for OpportunityTeamMember failed for: ' + oppTeam[insertRecNum].UserId);  
          System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++;
    }

   insertRecNum = 0;
   Database.Saveresult[] dbSave2 = Database.insert(oppShare, false); //Now insert a share rec for each team member
   for (Database.Saveresult recSave : dbSave2) {
      if (!recSave.isSuccess()) {
         Database.Error eMsg = recSave.getErrors()[0];
         System.debug('.............Insert for OpportunityShare table failed for: '+oppShare[insertRecNum].UserOrGroupID);
         System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++; 
    }
}

 Let me know if that helps.

Matt FieldMatt Field

Thanks for the code!!

 

I am trying to add the code to my sandbox, but I am getting an error at this line

 

Database.Saveresult[] dbSave2 = Database.insert(oppShare, false); //Now insert a share rec for each team member

 

The error says:  Variable does not exist oppShare

 

Thanks,

 

Matt

MTBRiderMTBRider

When I cut and pasted the code, I missed the initialization of that list.  Just add the line

 

List<OpportunityShare> oppShare = new List<OpportunityShare>();

 

to the code.  You can add it as the first line in the second code box of may last post.

Matt FieldMatt Field

First off, I would like to thank you for your help with this!!

 

I added the line you suggested, but now I am getting a different error.  The error says: "Save Error:  Illegal assignment from Schema.SObjectField to SOBJECT:OpportunityTeamMember".  It is at the line "OpportunityTeamMember otm = new OpportunityTeamMember (OpportunityId = opp.Id),

 

I have included my completed code below:

 

trigger AddFinancialAnalystToOpportunityTeam on Opportunity (after insert, after update) {
	
	List<OpportunityTeamMember> oppTeam = new List<OpportunityTeamMember>();
	for (Opportunity opp : trigger.new) {
		OpportunityTeamMember otm = new OpportunityTeamMember (OpportunityId = opp.Id),
		UserId = Product2.Financial_Analyst__c,
		TeamMemberRole = 'Financial Analyst';
		oppTeam.add(otm);
	}
	
	List<OpportunityShare> oppShare = new List<OpportunityShare>();
	if (oppTeam.size() > 0) { 
    Integer insertRecNum = 0; 
    Database.Saveresult[] dbSave1 = Database.insert(oppTeam, false); 
    for (Database.Saveresult recSave : dbSave1) { 
       if (recSave.isSuccess()) { 
         oppShare.add( new OpportunityShare(UserOrGroupID = oppTeam[insertRecNum].UserId, 
                                            OpportunityId = oppteam[insertRecNum].OpportunityId, 
                                            OpportunityAccessLevel = 'Edit')); 
       } else { 
          Database.Error eMsg = recSave.getErrors()[0]; 
          System.debug('.............Insert for OpportunityTeamMember failed for: ' + oppTeam[insertRecNum].UserId);  
          System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++;
    }

   insertRecNum = 0;
   Database.Saveresult[] dbSave2 = Database.insert(oppShare, false); //Now insert a share rec for each team member
   for (Database.Saveresult recSave : dbSave2) {
      if (!recSave.isSuccess()) {
         Database.Error eMsg = recSave.getErrors()[0];
         System.debug('.............Insert for OpportunityShare table failed for: '+oppShare[insertRecNum].UserOrGroupID);
         System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++; 
    }
}



}

 I would really appreciate it if you could take a look and see where I screwed up.

 

Thanks,

 

Matt

 

Matt FieldMatt Field

I think I found my error.  I had the ")" in the wrong spot, but now I have another error.

 

Invalid initial expression type for field OpportunityTeamMember.UserId, expecting: Id

 


I used the actual field for the financial analyst in Product2 because it could be a different person depending on the product.  How do I refer to the user id in that field?

Matt FieldMatt Field

Just to update, if I use an individual userid, the code works perfectly.  Now I need to be able to select the userid based on the Financial Analyst listed for the product in the Product2 table.  When I try to use:  UserId = Financial_Analyst__c or UserId = Financial_Analyst_ID__c (a formula field that shows the UserId of the Financial Analyst), I get an error saying that the variable doesn't exist.

 

Thanks,

 

Matt

MTBRiderMTBRider

As you have figured out, the UserId column of the Opp team Member table is a reference to the a user in the Salesforce User table and that field is expecting the unique Id of the user from the User table to be stored in it.  I am guessing that you are currently storing a user name (a string) in the Financial_Analyst__c field and then, as you said, a formula back to the User table in the Financial_Analyst_ID__C field?

 

You don't need both of those, it is redundent.  If the Financial_Analyst__c is the name of a user, get rid of it.  Then, change the field type of Financial_Analyst__c to a "Lookup Relationship".  This will give you a field on your product screen with a magnifying glass next to it and allow you to pick a user from the user table to associate with the product as the Financial Analyst.  When that product record is saved, the UserId for the user that was selected will be saved with the product and then you will be able to reference the UserId on the product record in the Apex code and save it to the Opp Team in this code.

Matt FieldMatt Field

Thanks for the quick reply.  I have the Financial Analyst field (Financial_Analyst__c) on Products2 set up as a lookup field.  When I try to reference it in the code, I get an error saying:  "Invalid initial expression type for field OpportunityTeamMember.UserId, expecting:  Id", so I'm assuming that I am not referencing the field correctly.  Here is how it is referenced: 

 

List<OpportunityTeamMember> oppTeam = new List<OpportunityTeamMember>();
	for (Opportunity opp : trigger.new) {
		OpportunityTeamMember otm = new OpportunityTeamMember (OpportunityId = opp.Id,
		UserId = Product2.Financial_Analyst__c,
		TeamMemberRole = 'Financial Analyst');
		oppTeam.add(otm);
	}

 Can you see what I am doing wrong?

 

Again, thank you very much for all of your help.

 

Thanks,

 

Matt

 

MTBRiderMTBRider

Sorry, I thought you said that the Financial_Analyst__c field was a formula field.

 

The issue now is that you are in an opportunity trigger, which only puts opportunity record data in context, not product data.  I am not sure how your org has opportunities implemented.  Are you using pricebooks?  If not, how does the product get associated with the opportunity?  In your implementation, can there be more than one product on the same opportunity?

 

Matt FieldMatt Field

I am using pricebooks and an opportunity can have more than one product associated with it, but there will always be only one financial analyst.  The reason for this is that if there are multiple products associated with the opportunity, the second product will always be install fees, which don't have an analyst assigned.  I tried using Product2.Financial_Analyst__c, but I got the "expecting: Id" error again.  Any ideas?

Matt FieldMatt Field

Bump.  Anyone have any ideas on this?

MTBRiderMTBRider

Hi Matt,

 

Sorry it took so long to get back to you....I have been away from my computer for a few days.  Not sure if you figured this out yet, as I said in my previous post, you cannot simply reference the financial analyst id directly from the Product2 table.  This is because only Opportunity data is in context while in the  Opportunity trigger.  However, because the ProductId that has been associated with the Opportunity being saved is in context (not directly though since you are using pricebooks...you will need to get the product ID via the pricebook tables), you can query for the finanicial analyst Id once you have the product IDs on the Opportunity.

 

Also, because you are using the pricebooks, I think you will want to move all of this code from the After Opportunity Trigger to a After OpportunityLineItem Trigger.  You can copy the code out of you existing Opportunity trigger and paste into a newly created OpportunityLineItem After trigger.  

 

I added some code at the beginning of the trigger that *should* get all of the products IDs that are on the OpportunityLineItems being saved and then uses those Ids to look up the Financial_analyst__c info in the product table.  There are also some changes to the for loop that is building the List used to update the OpportunityTeamMember table, so make sure you understand what is going on there.  A word of caution, although this code compiled in my environment, I did not run it as I do not  have the data set up to test it.  So, no guarantees that this will work.  It may take some more tweaking.

trigger TempAfterLine on OpportunityLineItem (after insert, after update) {
		
       //************ New Code Block  MOVE ALL CODE TO THE OPPORTUNITYLINEITEM TRIGGER

       List<Id> oppLineIds = new List<Id>();
       for (OpportunityLineItem x : trigger.new) {
		oppLineIds.add(x.Id);	
       }
		
	Map<Id, OpportunityLineItem> lineItemMap = new Map<Id, OpportunityLineItem>([Select o.Id, o.PricebookEntry.Product2Id From OpportunityLineItem o WHERE o.OpportunityID IN :oppLineIds]);
		
	//Use the values from the Map just created to make a List of the product Ids on all of the line items to use in the below query
	List<Id> prodIds = new List<Id>();
	for (Id i : lineItemMap.keySet()) {
		prodIds.add(lineItemMap.get(i).PricebookEntry.Product2Id);
	}
		
	//Next, create a Map from the Product2 table that will contain the financial analyst Id.  The product ID is the key 
	Map<Id, Product2> analystMap = new Map<Id, Product2>([SELECT p.Id, p.Financial_Analyst__c FROM Product2 p WHERE Id IN :prodIds AND p.Financial_Analyst__c != null]);
		
	//Now we need to construct a new Map that will have the Opportunity Line Item ID as the key to the User ID in the Financial_Analyst__c field on the product table as the value
	Map<Id, Id> lineItemToAnalystMap = new Map<Id, Id>();
	for (Id i : lineItemMap.keySet()) {
		lineItemToAnalystMap.put(i, analystMap.get(i).Financial_Analyst__c);
	}

       //************ End new code block

	List<OpportunityTeamMember> oppTeam = new List<OpportunityTeamMember>();
	for (OpportunityLineItem oLI : trigger.new) {
		OpportunityTeamMember otm = new OpportunityTeamMember(OpportunityId = oLI.Opportunity.Id,   //THIS IS CHANGED: Reference the Opportunity ID via the Opp Line Item
		UserId = lineItemToAnalystMap.get(oLI.Id),                                                 //THIS IS CHANGED: use the Map created above to get the Financial Analyst User ID
		TeamMemberRole = 'Financial Analyst');
		oppTeam.add(otm);
	}
	
	List<OpportunityShare> oppShare = new List<OpportunityShare>();
	if (oppTeam.size() > 0) { 
    Integer insertRecNum = 0; 
    Database.Saveresult[] dbSave1 = Database.insert(oppTeam, false); 
    for (Database.Saveresult recSave : dbSave1) { 
       if (recSave.isSuccess()) { 
         oppShare.add( new OpportunityShare(UserOrGroupID = oppTeam[insertRecNum].UserId, 
                                            OpportunityId = oppteam[insertRecNum].OpportunityId, 
                                            OpportunityAccessLevel = 'Edit')); 
       } else { 
          Database.Error eMsg = recSave.getErrors()[0]; 
          System.debug('.............Insert for OpportunityTeamMember failed for: ' + oppTeam[insertRecNum].UserId);  
          System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++;
    }

   insertRecNum = 0;
   Database.Saveresult[] dbSave2 = Database.insert(oppShare, false); //Now insert a share rec for each team member
   for (Database.Saveresult recSave : dbSave2) {
      if (!recSave.isSuccess()) {
         Database.Error eMsg = recSave.getErrors()[0];
         System.debug('.............Insert for OpportunityShare table failed for: '+oppShare[insertRecNum].UserOrGroupID);
         System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++; 
    }
}

}

 

Matt FieldMatt Field

MTBRider, I can't tell you how much I appreciate your help with this.  I copied this over to a new trigger on Opportunity Line Item (after insert, after update).  It copied over without any errors, but when I created a new opportunity, the opportunity team was not populated.  I verified that the product had a financial analyst assigned (I tried 3 different products) and each time I found the following error in the debug log:

 

Insert for OpportunityTeamMember failed for:  null

 

Database.Error[getFields=(Opportunity, User);getMessage=Required fields are missing:  [Opportunity, User];getStatusCode=REQUIRED_FIELD_MISSING;]

 

I can't figure out why it isn't finding the OppId or the financial analyst.  Any thoughts?

 

Thanks again for all of your help with this.  Can I give you  5 kudos for 1 issue?  :)

MTBRiderMTBRider

I guess that is what I get for trying to write a bunch of code and not test it!  My bad....

 

Here is the code block from my last post that has been tweaked:

 

trigger TempOLI on OpportunityLineItem (after insert, after update) {
	//************ New Code Block  MOVE ALL CODE TO THE OPPORTUNITYLINEITEM TRIGGER
	
	//Get all the price book entry IDs in the trigger
	List<Id> pbIDs = new List<Id>();
	for (OpportunityLineItem x : trigger.new) {
		pbIDs.add(x.PriceBookEntryID);
	}
	
	//Create a Map of the product Id to price book entry for all the Opp Line Items in the trigger  
	Map<Id, PriceBookEntry> pbe = new Map<Id, PriceBookEntry>([Select p.Id, p.Product2Id From PriceBookEntry p WHERE p.Id IN :pbIDs]);
	List<Id> prodIds = new List<Id>();
	for (Id pb : pbe.keySet()) {
		prodIds.add(pbe.get(pb).Product2Id);
	}
			
	//Create a Map from the Product2 table that will contain the financial analyst Id.  The product ID is the key 
	Map<Id, Product2> finAnalysts = new Map<Id, Product2>([SELECT p.Id, p.Financial_Analyst__c FROM Product2 p WHERE Id IN :prodIds AND p.Financial_Analyst__c != null]);
	
	//****END of new code block
		
	List<OpportunityTeamMember> oppTeam = new List<OpportunityTeamMember>();
	for (OpportunityLineItem oli : trigger.new) {
		OpportunityTeamMember otm = new OpportunityTeamMember(OpportunityId = oli.OpportunityId,   //THIS IS CHANGED: Reference the Opportunity ID via the Opp Line Item
		UserId = finAnalysts.get(pbe.get(oli.PriceBookEntryID).Product2Id).Financial_Analyst__c,   //THIS IS CHANGED: use the Maps created above to get the Financial Analyst User ID
		TeamMemberRole = 'Financial Analyst');
		oppTeam.add(otm);
	}
	
	List<OpportunityShare> oppShare = new List<OpportunityShare>();
	if (oppTeam.size() > 0) { 
    Integer insertRecNum = 0; 
    Database.Saveresult[] dbSave1 = Database.insert(oppTeam, false); 
    for (Database.Saveresult recSave : dbSave1) { 
       if (recSave.isSuccess()) { 
         oppShare.add( new OpportunityShare(UserOrGroupID = oppTeam[insertRecNum].UserId, 
                                            OpportunityId = oppteam[insertRecNum].OpportunityId, 
                                            OpportunityAccessLevel = 'Edit')); 
       } else { 
          Database.Error eMsg = recSave.getErrors()[0]; 
          System.debug('.............Insert for OpportunityTeamMember failed for: ' + oppTeam[insertRecNum].UserId);  
          System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++;
    }

   insertRecNum = 0;
   Database.Saveresult[] dbSave2 = Database.insert(oppShare, false); //Now insert a share rec for each team member
   for (Database.Saveresult recSave : dbSave2) {
      if (!recSave.isSuccess()) {
         Database.Error eMsg = recSave.getErrors()[0];
         System.debug('.............Insert for OpportunityShare table failed for: '+oppShare[insertRecNum].UserOrGroupID);
         System.debug('.............The DB returned the following error for the above record: ' + eMsg);
       }
       insertRecNum++; 
    }
}

}

I broke down and created the data in my environment to actually test this and it successfully looked up the financial analyst Id on the associated product record and used it to insert an Opportunity team.  So, this should work for you.  Take a look at the code inbetween the new code block comments to see what has changed there.  Turns out we did not need as many queries to get all the info we needed.  Also take a look at the "THIS IS CHANGED" comment lines when we are creating the opp team member records because they have changed too.  

 

You will notice the trigger name I used is just a temp name since I will be deleting this code from my environment....you will want to name the trigger to something that will make more sense.

 

Lastly, you will need to also plan for a scenario where a users come back in to an opportunity and changes the product or products on the Opp.  In those cases, it may be possible to have the wrong persons on the Opp team as this code does not go back and delete old team members when the product changes.  This is some code that I am sure you will be able to figure out on your own :) 

Matt FieldMatt Field

The new code copied over without any problems and is working with one exception.  If the product doesn't have a financial analyst assigned (such as for Install Fees), I get an error that says:  System.NullPointerException: Attempt to de-reference a null object.  

 

I could be wrong, but I thought that this line would prevent the error when there is no financial analyst assigned:

 

 

Map<Id, Product2> finAnalysts = new Map<Id, Product2>([SELECT p.Id, p.Financial_Analyst__c FROM Product2 p WHERE Id IN :prodIds AND p.Financial_Analyst__c != null]);

 


I tried to modify it by changing the end to "p.Financial_Analyst__c != ' '  ", but that didn't work.  I also tried changing it to p.Financial_Analyst__c != 'My User Id' ", but I keep getting the same error.

 

Any thoughts?

 

Once again, a thousand thank you's for your help with this.  This is really going to help my company out.

 

Thanks,

 

Matt

 

MTBRiderMTBRider

If some of the products do not have financial analyst, then just check to make sure the key exists in the map before you try to add the team member.  See below.

 

        ...

	for (OpportunityLineItem oli : trigger.new) {
		if (pbe.containsKey(oli.PriceBookEntryID) && finAnalysts.containsKey(pbe.get(oli.PriceBookEntryID).Product2Id)) {  //Check that the keys exist in both maps before adding team member
			OpportunityTeamMember otm = new OpportunityTeamMember(OpportunityId = oli.OpportunityId,   //THIS IS CHANGED: Reference the Opportunity ID via the Opp Line Item
			UserId = finAnalysts.get(pbe.get(oli.PriceBookEntryID).Product2Id).Financial_Analyst__c,   //THIS IS CHANGED: use the Maps created above to get the Financial Analyst User ID
			TeamMemberRole = 'Financial Analyst');
			oppTeam.add(otm);
		}
	}
	
        ...

 

This was selected as the best answer
Matt FieldMatt Field

MTBRider, just tell me where to ship the six-pack to!  It works perfectly.  Thank you very much for all of your work on this. 

 

Thanks,

 

Matt