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
SFDCDevQASFDCDevQA 

Initial term of field expression must be a concrete SObject

I'm hitting the error that Initial term of field expression must be a concrete SObject: LIST<UserTerritory> at line 8 column 79 in my Test class.  I'm trying to call the list but apparently I'm doing something wrong.  Can someone please help me get the Territory ID out of the list of TerritoryUsers?

Thanks so much,
Amanda

@IsTest(SeeAllData=true)
public class TestOpportunityTerritory {

  static testMethod void testOpportunityTerritory() {
    user u = [select id from User where name = 'Will Culbertson'];
   
    List <UserTerritory> ut = [Select ut.UserId, ut.TerritoryId From UserTerritory ut Where ut.userId =:u.Id];
    List <Territory> t = [select t.id, t.name from Territory t where t.id IN :ut.territoryid];
    Account a = new Account(Name='Test Account');
        insert a;    
       
        test.startTest();
        System.RunAs(u){
       
        //Perform the dml action on which trigger gets fired , like insert, update ,delete , undelete, in your case you have to update the opportunity record that you created in above 
       Opportunity o = new Opportunity(Name='Test Opportunity',closedate=system.today(), stagename='Prospecting', AccountID=a.id, ownerid=u.id);
        insert o; 
       
        test.stopTest();
       
        for(Opportunity opp:[SELECT Territoryid FROM Opportunity
            WHERE CreatedDate = TODAY
            and CreatedById = :u.id])
       
       
        System.assertEquals(ut.TerritoryID, opp.territoryid);
}
}      
}
Best Answer chosen by SFDCDevQA
bob_buzzardbob_buzzard
Unfortunately I don't have territory management enabled on any org I'm currently working with, so I can't verify these queries, but the following should give you the list of territories for the user:

user u = [select id from User where name = 'Will Culbertson'];
List <UserTerritory> ut = [Select ut.UserId, ut.TerritoryId From UserTerritory ut Where ut.userId =:u.Id];

// build the set of territory ids
Set<Id> terrIds=new Set<Id>();
for (UserTerritory uTerr : ut)
{
  terrIds.add(uTerr.TerritoryId);
}

// now query back the territory records matching the ids in the set
List <Territory> t = [select t.id, t.name from Territory t where t.id IN :terrIds];


All Answers

bob_buzzardbob_buzzard
You can't have the bolded item below in your query:

List <Territory> t = [select t.id, t.name from Territory t where t.id IN :ut.territoryid];

ut is a list of UserTerritories, but you are treating it like a single instance. If you are expecting a single instance in the list, you can simply specify to use the first element in the list:

List <Territory> t = [select t.id, t.name from Territory t where t.id IN :ut[0].territoryid];
SFDCDevQASFDCDevQA
I replied twice via email but the replies aren't on here so maybe they never went through.  Here's what I said:

Thanks. To give some more details I had originally tried creating the user and territories and user territories from scratch but the user territory does not allow dml actions so I have to pull an existing user.  They may have multiple territories so I want to sort by territory name and limit to just the last territory in the list. I couldn't get the UserTerritory to give me Territory names in order to sort by them so I was then trying to list the territories based on the ones in the UserTerritory List. So how do I get the Territory list to show all the territories in the UserTerritory list?  Or is there a simpler way to do this? Instead of grabbing a specific user I could tell it to pick a user with one territory but that seems even more complicated than what I'm already struggling with.  What do you think?  Ideally it shouldn't grab a specific user but I'm not even sure how to tell it to find the territories for the user territories I already have nevermind trying to say something like "get a random user with just one territory".   Any advice or direction would be greatly appreciated.

Thanks so much,
Amanda
bob_buzzardbob_buzzard
Unfortunately I don't have territory management enabled on any org I'm currently working with, so I can't verify these queries, but the following should give you the list of territories for the user:

user u = [select id from User where name = 'Will Culbertson'];
List <UserTerritory> ut = [Select ut.UserId, ut.TerritoryId From UserTerritory ut Where ut.userId =:u.Id];

// build the set of territory ids
Set<Id> terrIds=new Set<Id>();
for (UserTerritory uTerr : ut)
{
  terrIds.add(uTerr.TerritoryId);
}

// now query back the territory records matching the ids in the set
List <Territory> t = [select t.id, t.name from Territory t where t.id IN :terrIds];


This was selected as the best answer
SFDCDevQASFDCDevQA
Thanks.  That didn't work at first but then I tried a different user name and it did work.  Any idea why my trigger would work with one user with territories but not with another?  The user that it isn't working with has 3 territories and the test class works with him and finds his territories in the list but the assert fails because for some reason his user doesn't set off the trigger.  I logged in as him and created an opp to verify this.  I don't see any reason why this would be happening and if it's happening to him then it will be to others also.  Any idea why it would do this?

Thanks,
Amanda

trigger OpportunityTerritoryUpdate on Opportunity (before insert, before update)
// map tracks records on OpportunityID
{
    Map<String, Opportunity> OpportunityMap = new Map<String, Opportunity>();
   
{
for (Opportunity opp : Trigger.new)
{
        if( opp.TerritoryId == null)
       
        {
              OpportunityMap.put( opp.ownerID, opp );           
        }
       
   

    if( OpportunityMap.keySet().size() > 0 ) // if there are no eligible Opportunity records, end
    {
       //map to keep track of the opportunity owner
    Map<Id,UserTerritory> UserTerritoryCurrentUserMap = new  Map<Id,UserTerritory>();
   
    List<User> UpdateDemo = new List<User>();
   
    //select User for the opportunities with territory required
     for(UserTerritory ut : [Select UserId, TerritoryId, IsActive, Id  From UserTerritory Where isActive=true and userId in :OpportunityMap.keySet() ])
        {
opp.TerritoryId = ut.territoryID;
}
}
}}}
bob_buzzardbob_buzzard
I don't think the final part of the trigger is correct.  You are carrying out all of your processing for each record in the trigger, so opportunity map will grow each time through.  It will work for a single record, but you'll have problems if you bulk load opportunities.

I'm not sure what doesn't work means, but in the final loop, if the user has three territories, you will iterate through all three of those territories, applying each of them in turn before the trigger finishes and the last value sticks.  The value could well change for the same user in different executions, as the order that records are returned from the database depends on a number of variables.
SFDCDevQASFDCDevQA
I figured out the issue with the user, his territories in the sandbox were inactive.   I used some of the syntax you showed to correct it so that it will just grab a current user with an active territory instead of having to specify one.  Seems to be working.  You're right that the trigger isn't bulking though...what am I doing wrong?  I've tried redoing it as below with lists and sets but I'm completely missing how to match those in the lists together....I'm just getting two big lists together and then have no way to tell the system what goes with what.

Thanks,
Amanda
trigger OpportunityTerritoryUpdate on Opportunity (after insert, after update)
{
    List <Opportunity> opportunities = [select Id, Name, OwnerID, Territoryid from Opportunity where ID IN :Trigger.newMap.keySet()];
    Set<Id> OppOwnerIds=new Set<Id>();
        for (Opportunity oppid : opportunities)
        {
          OppOwnerIds.add(oppid.ownerid);
        }
            // now query back the territory records matching the ids in the set
    List <UserTerritory> ut = [select userid, territoryId from UserTerritory where isActive=true and userid IN :oppownerIds];
    List<Opportunity> UpdateTerritory = new List<Opportunity>{};

for(Opportunity opp : opportunities)
{
        if( opp.TerritoryId == null)
       
        {
opp.TerritoryId = ut.territoryID;
UpdateTerritory.add(opp);
}
}
// setup the save point for rollback
    Savepoint sp = Database.setSavepoint();
   
        update UpdateTerritory;
}
SFDCDevQASFDCDevQA
Okay I think I've got it.  It's working and I think it's bulked....does it look bulked to you?  Thanks!!!
trigger OpportunityTerritoryUpdate on Opportunity (after insert, after update)
// map tracks records on OpportunityID
{
    List <Opportunity> opportunities = [select Id, Name, OwnerID, Territoryid from Opportunity where ID IN :Trigger.newMap.keySet()];
    List<Opportunity> UpdateTerritory = new List<Opportunity>{};

    for(Opportunity opp : opportunities)
    {
        if( opp.TerritoryId == null)
       
    {
             
    //select User for the opportunities with territory required
     for(UserTerritory ut : [Select UserId, TerritoryId, IsActive, Id  From UserTerritory Where isActive=true and userId = :opp.Ownerid limit 1])
        {
        opp.TerritoryId = ut.territoryID;
        UpdateTerritory.add(opp);
}
}
}
// setup the save point for rollback
    Savepoint sp = Database.setSavepoint();
   
        update UpdateTerritory;
}
SFDCDevQASFDCDevQA
And here's the final test class that doesn't require a specific user:
@IsTest(SeeAllData=true)
public class TestOpportunityTerritory {

  static testMethod void testOpportunityTerritory() {
     
      List <UserTerritory> ut = [Select ut.UserId, ut.TerritoryId From UserTerritory ut Where ut.isActive=true Limit 1];
      user u = [select id from User where id = :ut[0].userid];
       // build the set of territory ids
       Set<Id> terrIds=new Set<Id>();
        for (UserTerritory uTerr : ut)
        {
          terrIds.add(uTerr.TerritoryId);
        }

        // now query back the territory records matching the ids in the set
        List <Territory> t = [select t.id, t.name from Territory t where t.id IN :terrIds order by t.name ASC];

    Account a = new Account(Name='Test Account', Industry='Other');
        insert a;    
       
        test.startTest();
        System.RunAs(u){
       
        //Perform the dml action on which trigger gets fired , like insert, update ,delete , undelete, in your case you have to update the opportunity record that you created in above 
       Opportunity o = new Opportunity(Name='Test Opportunity Territory',closedate=system.today()+30, stagename='Prospecting', AccountID=a.id, ownerid=u.id);
        insert o; 
       
        test.stopTest();
       
        for(Opportunity opp:[SELECT Territoryid FROM Opportunity
            WHERE CreatedDate = TODAY
            and Name = 'Test Opportunity Territory'
            and CreatedById = :u.id])
       
       
        System.assertEquals(t[0].ID, opp.territoryid);
       
}
}      
}