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
Learning Apex (OOP)Learning Apex (OOP) 

Required fields are missing: [Opportunity, User]: [Opportunity, User]:

Hello,
I am getting the above error message when creating a new Opportunity.

I seems obvious by the nature of the error that there is a missing value on a required field somewhere, but I am not sure how this error message has to be interpreted exactly. Does  it mean that a value on a field "User" on the Opportunity object is missing?
The only "user"-like field I can think of in the Opportunity is the Owner (?) but I would have thought that the Owner is automatically set to the running user right after creating the myOpp.

Here is my apex  trigger:

trigger AddTeamMember on Opportunity (before insert) {
    // Create Account to relate it to the Oppty
    Account acc = new Account();
    acc.Name = 'Test'+' '+ System.today();
    //Every time we insert a new Oppty
    for(Opportunity myOpp : Trigger.new){
        // Assign values to required fields of Oppty
        myOpp.Name = 'Test'+' '+ System.today();
        myOpp.AccountId = acc.Id; 
        myOpp.CloseDate = System.today().addDays(10);
        myOpp.StageName = 'Negotiation/Review';
        // Add one Team Member to myOpp
        OpportunityTeamMember myTeamMember = new OpportunityTeamMember();
        myTeamMember.OpportunityId = myOpp.id;
        myTeamMember.OpportunityAccessLevel = 'Read';
        myTeamMember.TeamMemberRole = 'Sales Manager”';
        myTeamMember.UserId = '0050Y000002MN2F'; //myOpp.Owner.ManagerId
        insert myTeamMember;
    }
}


Here is a screenshot of  the error on the Opportunit object:
error due to trigger on Oppty:
User-added image

Can you spot the error on my trigger?
Best Answer chosen by Learning Apex (OOP)
Abdul KhatriAbdul Khatri
trigger AddTeamMember on Opportunity (after insert, after update) {
    
    List<OpportunityTeamMember> teamMemberListToInsert = new List<OpportunityTeamMember>();
    List<Id> opportunityIdList = new List<Id>();
       
    for (Opportunity opportunity : [SELECT Id, Name, OwnerId, Owner.ManagerId FROM Opportunity WHERE Id IN :Trigger.New]){
        OpportunityTeamMember myTeamMember = new OpportunityTeamMember();
        myTeamMember.OpportunityId = opportunity.Id;
        myTeamMember.TeamMemberRole = 'Sales Manager';
        if(opportunity.Owner.ManagerId == null){
        	myTeamMember.UserId = opportunity.OwnerId;            
        }else{
        	myTeamMember.UserId = opportunity.Owner.ManagerId;
        }
        
        opportunityIdList.add(myTeamMember.OpportunityId);
        teamMemberListToInsert.add(myTeamMember);
    }

    if(teamMemberListToInsert.size() == 0) return;
    
    if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;
    
	List<OpportunityShare> oppShareList = [select Id, OpportunityAccessLevel, RowCause from OpportunityShare where OpportunityId IN :opportunityIdList and RowCause = 'Team'];

    for(OpportunityShare oppShare : oppShareList)
        oppShare.OpportunityAccessLevel = 'Edit';

	update oppShareList;
}

 

All Answers

Wilfredo Morillo 20Wilfredo Morillo 20
You cannot use the opportunity id in before update. It doesn't exist yet. change the trigger to after insert and try again. 

 
Learning Apex (OOP)Learning Apex (OOP)
Hi Wilfredo,

I have updated my trigger and made it an "after trigger", then added an insert myOpp before using the myOpp.id.

But now, I have an infinit loop ah ah which I do not know to resolve:

User-added image

Here is the infinite loop error (Salesforce automatically make the record Read Only):
User-added image

How can I possibly restructure the logic of this trigger to avoid this ugly infinite loop?

(this trigger is trying to add a TeamMember upon saving the opportunity)

Thank you.
Abdul KhatriAbdul Khatri
First never call insert into the loop. That is not a good best practice. Here is a link for your help

https://developer.salesforce.com/page/Apex_Code_Best_Practices

I am not sure why you have added the insert myOpp in the loop. I guess it shoulud be this simple. Please let me know if it worked
trigger AddTeamMember on Opportunity (after insert) {
    
    List<OpportunityTeamMember> teamMemberListToInsert = new List<OpportunityTeamMember>();
    
    for (Opportunity opportunity : Trigger.New){
        OpportunityTeamMember myTeamMember = new OpportunityTeamMember();
        myTeamMember.OpportunityId = opportunity.Id;
        myTeamMember.OpportunityAccessLevel = 'Read/Write';
        myTeamMember.TeamMemberRole = 'Sales Manager';
        myTeamMember.UserId = opportunity.Owner.ManagerId;
        
        teamMemberListToInsert.add(myTeamMember);
    }

    if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;
}

 
Abdul KhatriAbdul Khatri
Was it helpful?
Learning Apex (OOP)Learning Apex (OOP)
Hi Abdul,
Thank you very much for your thoughts and code.
I can see that you are inserting the TeamMember once you are OUTSIDE of the "for" loop. Thank makes sense.
I have picked your idea and applied it to my code (creating the myTeamMember variable before the "for" loop and inserting it after the "for" loop) and I think that this should resolve the "infinite loop" issue.
But now, I am getting a new error. Here is my updated code and the error below:

User-added image

Error below (indicating that the issue in on Line 11).
The fact that it says "Record is read-only" makes me think of an "infinite loop" error again but I do not know :-(
User-added image

Any idea of why am I getting this error?

Thank you.
Learning Apex (OOP)Learning Apex (OOP)
Hi Abdul,

Below, I am trying with a List (as in your code) to see if that makes any difference:
User-added image
But I am still getting the same error related to line 11.
User-added image

So, I have 2 question marks:
1) the error itself;
2) How is the code, in line 17, going to assign the myOpp.Id value to myTeamMember.OpportunityId if the myOpp.Id value does not exist yet (it has not been inserted yet). And, I have removed any Insert within the 'For' loop in order to avoid causing an 'infinite loop" error.

Any clue?

Thank you.
Learning Apex (OOP)Learning Apex (OOP)
Now, just to try if it helps, I am updating my code by adding myOpp to a List<Opportunity> and I insert this List after/outside the "for" loop.
(But I still get the same error)
User-added image
User-added image
Abdul KhatriAbdul Khatri
Check it now. Do the following. 
User-added image
Learning Apex (OOP)Learning Apex (OOP)
Hi Abdul,
I have removed the blocks you have highlighted in red:

User-added image

And now the error upon saving an Opportunity is:
User-added image
 
Abdul KhatriAbdul Khatri
Please try the below code. Let me know if it works
 
trigger AddTeamMember on Opportunity (after insert) {
    
    List<OpportunityTeamMember> teamMemberListToInsert = new List<OpportunityTeamMember>();
    List<OpportunityShare> opportunityShareListToUpdate = new List<OpportunityShare>();
    List<Id> opportunityIdList = new List<Id>();
    
    for (Opportunity opportunity : Trigger.New){
        OpportunityTeamMember myTeamMember = new OpportunityTeamMember();
        myTeamMember.OpportunityId = opportunity.Id;
        myTeamMember.TeamMemberRole = 'Sales Manager';
        myTeamMember.UserId = opportunity.Owner.ManagerId;
        
        opportunityIdList.add(myTeamMember.OpportunityId);
        teamMemberListToInsert.add(myTeamMember);
    }

    if(teamMemberListToInsert.size() == 0) return;
    
    if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;
    
	List<OpportunityShare> oppShareList = [select Id, OpportunityAccessLevel, RowCause from OpportunityShare where OpportunityId IN :opportunityIdList and RowCause = 'Team'];

    for(OpportunityShare oppShare : oppShareList){
        oppShare.OpportunityAccessLevel = 'Read/Write';
        
        opportunityShareListToUpdate.add(oppShare);
    }
    
    if(opportunityShareListToUpdate.size()>0)
       update opportunityShareListToUpdate;
}

 
Learning Apex (OOP)Learning Apex (OOP)
Hi Abdul,
Thanks for your code; I have copied/past your code and saved it with my Developer Console and have made a test by creating a new Opportunity and checking if a Team Member has been added to such opportunity.

But, it is giving me this error:

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger AddTeamMember caused an unexpected exception, contact your administrator: AddTeamMember: execution of AfterInsert caused by: System.DmlException: Insert failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [User]: [User]: Trigger.AddTeamMember: line 20, column 1


Line 20, as you can see in your code (mine has exactly the same lines) is this line: insert teamMemberListToInsert;

(as your code is starting to incorporate things that I still have not learnt yet, I now totally depend on you).

Thank you.

 
Abdul KhatriAbdul Khatri
give a try to this
 
trigger AddTeamMember on Opportunity (after insert) {
    
    List<OpportunityTeamMember> teamMemberListToInsert = new List<OpportunityTeamMember>();
    List<OpportunityShare> opportunityShareListToUpdate = new List<OpportunityShare>();
    List<Id> opportunityIdList = new List<Id>();
    
    for (Opportunity opportunity : Trigger.New){
        OpportunityTeamMember myTeamMember = new OpportunityTeamMember();
        myTeamMember.OpportunityId = opportunity.Id;
        myTeamMember.OpportunityAccessLevel = 'Read/Write';
        myTeamMember.TeamMemberRole = 'Sales Manager';
        myTeamMember.UserId = opportunity.ManagerId;
        
        opportunityIdList.add(myTeamMember.OpportunityId);
        teamMemberListToInsert.add(myTeamMember);
    }

    if(teamMemberListToInsert.size() == 0) return;
    
    if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;
    
	List<OpportunityShare> oppShareList = [select Id, OpportunityAccessLevel, RowCause from OpportunityShare where OpportunityId IN :opportunityIdList and RowCause = 'Team'];

    for(OpportunityShare oppShare : oppShareList)
        oppShare.OpportunityAccessLevel = 'Read/Write';

	update oppShareList;
}

 
Learning Apex (OOP)Learning Apex (OOP)
I have copied/past your (above) code but my Dev. Console was "complaining" about line 12, so I have ammended it with this line:
myTeamMember.UserId = opportunity.Owner.ManagerId;

After saving and testing, I get the following error:

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger AddTeamMember caused an unexpected exception, contact your administrator: AddTeamMember: execution of AfterInsert caused by: System.DmlException: Insert failed. First exception on row 0; first error: INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST, Opportunity Access: bad value for restricted picklist field: Read/Write: [OpportunityAccessLevel]: Trigger.AddTeamMember: line 21, column 1



 
Abdul KhatriAbdul Khatri
OK The below code must work. Please make sure that you have Manager Defined under the user for the owner of the opportunity, in this case for yourself. If that TeamMembere.OwnerId is a mandatory field so it need to be defined. If the null is assigned to that field it will complain about REQUIRED_FIELD_MISSION issue so make sure you define or you can add a null if Manager is not availabe then assigned to the owner of the opportunity




 
Abdul KhatriAbdul Khatri
trigger AddTeamMember on Opportunity (after insert, after update) {
    
    List<OpportunityTeamMember> teamMemberListToInsert = new List<OpportunityTeamMember>();
    List<Id> opportunityIdList = new List<Id>();
       
    for (Opportunity opportunity : [SELECT Id, Name, OwnerId, Owner.ManagerId FROM Opportunity WHERE Id IN :Trigger.New]){
        OpportunityTeamMember myTeamMember = new OpportunityTeamMember();
        myTeamMember.OpportunityId = opportunity.Id;
        myTeamMember.TeamMemberRole = 'Sales Manager';
        if(opportunity.Owner.ManagerId == null){
        	myTeamMember.UserId = opportunity.OwnerId;            
        }else{
        	myTeamMember.UserId = opportunity.Owner.ManagerId;
        }
        
        opportunityIdList.add(myTeamMember.OpportunityId);
        teamMemberListToInsert.add(myTeamMember);
    }

    if(teamMemberListToInsert.size() == 0) return;
    
    if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;
    
	List<OpportunityShare> oppShareList = [select Id, OpportunityAccessLevel, RowCause from OpportunityShare where OpportunityId IN :opportunityIdList and RowCause = 'Team'];

    for(OpportunityShare oppShare : oppShareList)
        oppShare.OpportunityAccessLevel = 'Edit';

	update oppShareList;
}

 
This was selected as the best answer
Learning Apex (OOP)Learning Apex (OOP)
Hello Abdul,

Thank you for your new code. I now works fine.

I am now going to review it as it includes things that I have not learnt/used yet.

Thank you very much.
Learning Apex (OOP)Learning Apex (OOP)
Hello Abdul,

In Line #6 of your last/correct code, I see the following code:
for (Opportunity opportunity : [SELECT Id, Name, OwnerId, Owner.ManagerId FROM Opportunity WHERE Id IN :Trigger.New]){

  //code inside the for loop here

}

I have never used SOQL within the typical "for" loop starting all apex triggers, I normally use:
for (Opportunity myOpp : Trigger.new) {

     //my code here...
}

which, captures the records entering the trigger (either manually or with apex dataloader).

Why are you using this SOQL query here?
How is this sentence read: for (Opportunity opportunity : [SELECT Id, Name, OwnerId, Owner.ManagerId FROM Opportunity WHERE Id IN :Trigger.New])

Thank you very much.
Learning Apex (OOP)Learning Apex (OOP)
Another question, Abdul, to understand your code:

Why have you found it necessary to write this line:
if(teamMemberListToInsert.size() == 0) return;
I am discovering that this IF condition does not have { //code} here. What does the key word return do here?

Thank you very much.
 
Learning Apex (OOP)Learning Apex (OOP)
Another question:

In line 23 of your code, it looks like the insert teamMemberListToInsert (after the if condition) does not need to be surounded by curly brackets?
if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;

Why doesn't it need to be surounded by curly brackets?

I normally would write:

if ( //conditions here) {
  // execute this code here
}

Thank you.
Abdul KhatriAbdul Khatri
  • Regarding line# 6 I used SOQL as I guess it was complaining about the User.ManagerId field so i explicitly added in order to get that field recognized by the loop. Try removing the SOQL and use Trigger.New, see if it works that way, if not you an use this way. This is called loop query syntax. 
    for (Opportunity opportunity : [SELECT Id, Name, OwnerId, Owner.ManagerId FROM Opportunity WHERE Id IN :Trigger.New]){
      //code inside the for loop here
    }
  • Regard the following line
if(teamMemberListToInsert.size() == 0) return;
You can remove it. I have a habit of taken care of unexpected scenarions in case if loop fails for some reason and nothing get added so just handling that scenarion but you can certainly remove that, no issues.
  • Regard curly brackets
if(teamMemberListToInsert.size()>0)    
        insert teamMemberListToInsert;
This is another habitual thing that I followed in order to keep the best practices as much as possible but you can certainly remove them. 

I hope I was able to answer you questions.

If this helped please don't hesitate to mark it best. Thanks.
 
Learning Apex (OOP)Learning Apex (OOP)
Hi Abdul,

Thank you;
I have tried removing the SOQL and it continues to work correctly.
I have also tried removing the line if(teamMemberListToInsert.size() == 0) return, and it continues to work fine as well.

The thing is, I would like to fully understand the following (from your code) so that I can use these tips in the future (even if they are not 100% required for the current code). I am learning, so I am interested in undertanding these tips of yours:

Tip 1:
  • Loop query syntax:  
for (Opportunity opportunity : [SELECT Id, Name, OwnerId, Owner.ManagerId FROM Opportunity WHERE Id IN :Trigger.New]){
  //code inside the for loop here
}
My interpretation of these piece of code is: "for each opportunity record entering the trigger, SELECT (from database) the fields/columns Id, Name, OwnerId and Owner.ManagerId WHERE the value of the (selected) Opportunity Id is equal to an Id included in  the collection Trigger.new" (in other words, ... WHERE Opportunity Id = one of the Ids contained in the collection Trigger.new) 
OK, and once we have selected this records, what do we do with it? I can see that this record is not really being assigned to any variable, right? I mean, the record is not being assigned to the variable "opportunity", right?

Tip 2:

In case the for loop fails for some reason what would this line do?
   if(teamMemberListToInsert.size() == 0) return;
My interpretation of this line is:  "if the collection/list teamMemberListToInsert HAS NO ITEMS(i.e., it's empty), then RETURN"
What would return exactly do here? 
How can I use this Tip somewhere else?

Tip 3: regarding the {curly brackets} , I do not understand your above mentioned answer. Based on my knowledge (I am a beginner), after the condition of the If, the code to be executed if the condition is true, would always be surounded by {curly brackets}. But, your code does not use these brackets after the if condition, hence my perplexity and my question.

Thank you very much.