Efficiently Designing Sales Territories with Salesforce Territory Management 2.0
Table of Contents
Why designing sales territories?
There are many ways to design sales territories within Salesforce. In small companies, sales representatives have a list of accounts they own, and own the opportunities related to these accounts. This list can be updated by the sales director each quarter. Ownership works great if your company targets small accounts, and if you have 1 sales hunter for each account.
This model doesn’t work as the sales team grows. What if your accounts have dozens of subsidiaries? What if account based marketing managers, sales development representatives, pre-sales engineers and customer success managers work on the same accounts? Ownership model won’t work in this case.
Usually, when a company reaches this level of complexity, each department manage their territory in Excel files, and on the Salesforce side, the Organization Wide Defaults are set to “Public Read Write” for all main objects such as accounts and opportunities. This isn’t a best practice at all. Salesforce users should only have access to the objects / records they need to be able to work. They could make a mistake and update the wrong account / opportunity. They could see a record they’re not supposed to see. Moreover, these Excel files aren’t a single source of trust, and because each department worked in silo and designed their own territory strategy, the sales team isn’t aligned with other teams. Territories and teams should be visible in Salesforce, and part of the selling strategy.
In this article, I’ll explain how to efficiently design your sales territories with Salesforce Territory Management 2.0. The documentation on this topic gives an overview of standard functionalities, but doesn’t help much if you company has a huge list of named accounts with subsidiaries.
How do you grant access to subsidiaries automatically? How do you maintain your territories?
Understand Territory Management 2.0 basics
First, I suggest you take a look at this Trailhead Trail to learn the basics.
Once you’ve understood the general functionalities, it’s interesting to take a glance at the data model behind it. Here’s the entity relationship diagrams (ERDs) for Territory Management 2.0 Objects.
As you can see, territories open access right for main Sales Cloud objects such as Accounts, Opportunities, Contacts and cases. Thus, the first step is to update your Organization Wide Default to, at least, “Public Read Only” for account and opportunities (and contacts / cases if needed).
Territories for named accounts with subsidiaries
If your company targets huge accounts with hundreds of subsidiaries, and if there are more than one user that works on an account, I suggest you create territories similar to this one with the Data Loader – creating these territories through the Salesforce UI can take ages.
First, you have to create a custom field on the account object. We’ll use this field to create 1 territory per named account. This formula field, usually called “Ultimate Account ID”, returns the Salesforce of the account at the top of the account hierarchy. As you can see, this formula works as long as your hierarchy doesn’t have more than 6 hierarchy levels, which is enough for most companies.
BLANKVALUE( Parent.Parent.Parent.Parent.Parent.Parent.Id, BLANKVALUE( Parent.Parent.Parent.Parent.Parent.Id, BLANKVALUE( Parent.Parent.Parent.Parent.Id, BLANKVALUE( Parent.Parent.Parent.Id, BLANKVALUE( Parent.Parent.Id, BLANKVALUE( Parent.Id, Id))))))
There are no easy way to block a user from adding a 7th level of hierarchy, even with Apex Triggers. However, you can create another custom field that returns the account hierarchy level, and create a report that checks if you have any account for which this formula returns the NULL value. If so, you can alert the account owner to work on his account hierarchy. I named this formula fields : Account Hierarchy Level. In my company, 5 is the maximum.
IF( ISBLANK(Parent.Id) = TRUE, 1, IF( ISBLANK(Parent.Parent.Id) = TRUE, 2, IF( ISBLANK(Parent.Parent.Parent.Id) = TRUE, 3, IF( ISBLANK(Parent.Parent.Parent.Parent.Id) = TRUE, 4, IF( ISBLANK(Parent.Parent.Parent.Parent.Parent.Id) = TRUE, 5,NULL)))))
Here’s how I created a territory for each named account, utilizing the Ultimate Account ID formula field. I created one territory per named account, updated the territory access levels and added a team for this territory, with territory roles.
This territory is defined by this rule here below, based on the “Ultimate Account ID”, which is the account at the top of the account hierarchy. It means that for this account and all its subsidiaries, the 7 users listed above will have read / write access to accounts / opportunities and contacts.
The upload sequence
In some companies, you have hundreds of named accounts. In this case, you want to use the data loader to avoid any manual work.
You already have:
- User records
Manually, create
- Territory2Model records, usually 1 per fiscal year
- Territory2Type records
Then with the Data Loader, upload
- ObjectTerritory2AssignmentRule records
- Territory2 records
- ObjectTerritory2AssignmentRuleItem records
- RuleTerritory2Association records
- UserTerritory2Association records
The ObjectTerritory2Association is the table that associate automatically an account with one or multiple territories. It’s updated when you “Run Assignment Rules”.
How to update these territories?
The most common question will be “How to I update these territories”, as managers used to update their territories in their Excel files. There is a permission that will allow a user to update territories. If needed, I recommend creating a permission set named “Manage Territories”, and add the “Manage Territories” permission under “App Permission”. However, I recommend the following process to update territories:
If you want to update the teams:
- Extract the UserTerritory2Association table as a CSV
- From the Data Loader or Workbench, run this query
- “Select id,UserId,User.Name,Territory2Id, Territory2.Name,RoleInTerritory2 from UserTerritory2Association”
- Send the CSV to the manager who needs to update the territories
- Update the UserTerritory2Association table according to the modifications done
- Use the Data Loader for example.
- From the Data Loader or Workbench, run this query
We can update most settings with similar processes to add strategic accounts to the list. It doesn’t have to be a manual process at all.
Advanced Settings for Sales Operations
As most named account territories rules are based on the account at the top of the hierarchy, we don’t want our Salesforce users to accidentally add a parent account on the account we used to define the territory. For this reason, I’ve added a validation rule on the account object, which prevent a user from adding a parent account on an account where the checkbox “Block Ultimate Account” is selected.
Obviously, we must check the “Block Ultimate Account” checkbox for all strategic accounts.
I also created a script to make sure that the owner of an account is the owner of all children accounts, as long as the “IsExcludedFromRealign” checkbox field is unchecked. It’s a good way to avoid unnecessary chats between sales reps and sales operations regarding account reassignments. It runs daily.
Also, the field level security settings for “IsExcludedFromRealign” and “Block Ultimate Account” fields must be “READ ONLY” for all profiles except Sales Operations profiles.
Here’s the scheduled apex class:
global class ReassignAccounts implements Schedulable { global void execute(SchedulableContext ctx) { Integer count = 1; do { List<Account> AccList = [SELECT Id,OwnerId,Parent.OwnerId FROM Account WHERE Account_Owner_Parent_Owner__c = TRUE AND IsExcludedFromRealign = FALSE AND IsDeleted = FALSE]; for(Account Acc : AccList){ Acc.OwnerId = Acc.Parent.OwnerId ; } update AccList; System.debug(count); count++; } while (count < 5); } }
Here’s the class test:
@isTest private class ReassignAccountsTest { @testSetup static void setupTestData(){ test.startTest(); Account acc = new Account(Name = 'Name123'); Insert acc; test.stopTest(); } static testMethod void test_execute_UseCase1(){ List<Account> acc = [SELECT Id,Name from Account]; System.assertEquals(true,acc.size()>0); ReassignAccounts obj01 = new ReassignAccounts (); String sch = '0 00 1 3 * ?'; system.schedule('Test', sch, obj01); } }