Skip to content

Instantly share code, notes, and snippets.

@dhoechst
Last active January 30, 2023 21:13
Show Gist options
  • Save dhoechst/4d065c3e61e38cf9c019d8fb59915232 to your computer and use it in GitHub Desktop.
Save dhoechst/4d065c3e61e38cf9c019d8fb59915232 to your computer and use it in GitHub Desktop.
Salesforce Opportunity Territory Assignment Logic
/*** Apex version of the default logic.
* If opportunity's assigned account is assigned to
* Case 1: 0 territories in active model
* then set territory2Id = null
* Case 2: 1 territory in active model
* then set territory2Id = account's territory2Id
* Case 3: 2 or more territories in active model
* then set territory2Id = account's territory2Id that is of highest priority.
* But if multiple territories have same highest priority, then set territory2Id = null
*/
global class OppTerrAssignDefaultLogicFilter implements TerritoryMgmt.OpportunityTerritory2AssignmentFilter {
/**
* No-arg constructor.
*/
global OppTerrAssignDefaultLogicFilter() {}
/**
* Get mapping of opportunity to territory2Id. The incoming list of opportunityIds contains only those with IsExcludedFromTerritory2Filter=false.
* If territory2Id = null in result map, clear the opportunity.territory2Id if set.
* If opportunity is not present in result map, its territory2Id remains intact.
*/
global Map<Id,Id> getOpportunityTerritory2Assignments(List<Id> opportunityIds) {
Map<Id, Id> OppIdTerritoryIdResult = new Map<Id, Id>();
if(activeModelId != null){
List<Opportunity> opportunities =
[Select Id, AccountId, Territory2Id from Opportunity where Id IN :opportunityIds];
Set<Id> accountIds = new Set<Id>();
// Create set of parent accountIds
for(Opportunity opp:opportunities){
if(opp.AccountId != null){
accountIds.add(opp.AccountId);
}
}
Map<Id,Territory2Priority> accountMaxPriorityTerritory = getAccountMaxPriorityTerritory(activeModelId, accountIds);
// For each opportunity, assign the highest priority territory if there is no conflict, else assign null.
for(Opportunity opp: opportunities){
Territory2Priority tp = accountMaxPriorityTerritory.get(opp.AccountId);
// Assign highest priority territory if there is only 1.
if((tp != null) && (tp.moreTerritoriesAtPriority == false) && (tp.territory2Id != opp.Territory2Id)){
OppIdTerritoryIdResult.put(opp.Id, tp.territory2Id);
}else{
OppIdTerritoryIdResult.put(opp.Id, null);
}
}
}
return OppIdTerritoryIdResult;
}
/**
* Query assigned territoryIds in active model for given accountIds.
* Create a map of accountId to max priority territory.
*/
private Map<Id,Territory2Priority> getAccountMaxPriorityTerritory(Id activeModelId, Set<Id> accountIds){
Map<Id,Territory2Priority> accountMaxPriorityTerritory = new Map<Id,Territory2Priority>();
for(ObjectTerritory2Association ota:[Select ObjectId, Territory2Id, Territory2.Territory2Type.Priority from ObjectTerritory2Association where objectId IN :accountIds and Territory2.Territory2ModelId = :activeModelId]){
Territory2Priority tp = accountMaxPriorityTerritory.get(ota.ObjectId);
if((tp == null) || (ota.Territory2.Territory2Type.Priority > tp.priority)){
// If this is the first territory examined for account or it has greater priority than current highest priority territory, then set this as new highest priority territory.
tp = new Territory2Priority(ota.Territory2Id,ota.Territory2.Territory2Type.priority,false);
}else if(ota.Territory2.Territory2Type.priority == tp.priority){
// The priority of current highest territory is same as this, so set moreTerritoriesAtPriority to indicate multiple highest priority territories seen so far.
tp.moreTerritoriesAtPriority = true;
}
accountMaxPriorityTerritory.put(ota.ObjectId, tp);
}
return accountMaxPriorityTerritory;
}
/**
* Get the Id of the Active Territory Model.
* If none exists, return null.
*/
@testvisible
private Id ActiveModelId {
get {
if (ActiveModelId == null) {
List<Territory2Model> models = [Select Id from Territory2Model where State = 'Active'];
if(models.size() == 1){
activeModelId = models.get(0).Id;
}
}
return activeModelId;
}
private set;
}
/**
* Helper class to help capture territory2Id, its priority, and whether there are more territories with same priority assigned to the account.
*/
private class Territory2Priority {
public Id territory2Id { get; set; }
public Integer priority { get; set; }
public Boolean moreTerritoriesAtPriority { get; set; }
Territory2Priority(Id territory2Id, Integer priority, Boolean moreTerritoriesAtPriority){
this.territory2Id = territory2Id;
this.priority = priority;
this.moreTerritoriesAtPriority = moreTerritoriesAtPriority;
}
}
}
@isTest
private class OppTerrAssignDefaultLogicFilterTest {
@isTest
private static void testActiveModel() {
// This method is just for code coverage. You can't activate a territory model from code.
OppTerrAssignDefaultLogicFilter filter = new OppTerrAssignDefaultLogicFilter();
Id modelId = filter.ActiveModelId;
}
@isTest
private static void testOppTerritory() {
Territory2 terr = new Territory2();
Territory2 terr2 = new Territory2();
OppTerrAssignDefaultLogicFilter filter = new OppTerrAssignDefaultLogicFilter();
System.runAs(new User(Id = UserInfo.getUserId())) {
Territory2Model tm = new Territory2Model(Name = 'test', DeveloperName ='test');
insert tm;
filter.ActiveModelId = tm.Id; //set the active model Id since it can't be queried
Territory2Type tt = [Select Id from Territory2Type limit 1];
terr = new Territory2(Name = 'Test Territory', DeveloperName = 'TestTerritory', Territory2ModelId = tm.Id, Territory2TypeId = tt.Id);
insert terr;
terr2 = new Territory2(Name = 'Test Territory2', DeveloperName = 'TestTerritory2', Territory2ModelId = tm.Id, Territory2TypeId = tt.Id);
insert terr2;
}
// Create a test account. Add any other required fields here
Account a = new Account(Name = 'Test Account');
insert a;
ObjectTerritory2Association ota = new ObjectTerritory2Association(AssociationCause = 'Territory2Manual', ObjectId = a.Id, Territory2Id = terr.Id);
insert ota;
// Create a test opportunity. Add any other required fields here
Opportunity opp = new Opportunity(AccountId = a.Id, Name = 'Test Opportunity');
insert opp
Map<Id, Id> resultMap = filter.getOpportunityTerritory2Assignments(new List<Id>{opp.Id});
System.assertEquals(terr.Id, resultMap.get(opp.Id));
ObjectTerritory2Association ota2 = new ObjectTerritory2Association(AssociationCause = 'Territory2Manual', ObjectId = a.Id, Territory2Id = terr2.Id);
insert ota2;
resultMap = filter.getOpportunityTerritory2Assignments(new List<Id>{opp.Id});
System.assertEquals(null, resultMap.get(opp.Id), 'No territory should be assinged as 2 have the same priority');
}
}
@dhoechst
Copy link
Author

dhoechst commented Dec 1, 2022

I have seen that same error and I'm not really sure what causes it. I could sometimes get that error to go away if I added filters to only have the assignment on smaller groups of opportunities. Opening a case with Salesforce would be my first stop.

@Rnatik
Copy link

Rnatik commented Dec 1, 2022 via email

@Rnatik
Copy link

Rnatik commented Dec 8, 2022

@dhoechst , Our account records have a custom category picklist field. If I want to update the Apex class to only include Opps from Accounts within specific categories, where would I do that in the code? (Think of it as using the standard "Type" field to include/exclude records).

@dhoechst
Copy link
Author

dhoechst commented Dec 9, 2022

I think you could just change the SOQL query on line 27 to filter for only opps that meet that criteria.

@harrisonhoran
Copy link

I am replying here so that other people can be helped by what I have found. I did indeed open a case with support and it was due to validation rules. I was able to tweak the org and I'm running the code again to see if it will now run successfully.

On Thu, Dec 1, 2022 at 11:54 AM Daniel Hoechst @.> wrote: @.* commented on this gist. ------------------------------ I have seen that same error and I'm not really sure what causes it. I could sometimes get that error to go away if I added filters to only have the assignment on smaller groups of opportunities. Opening a case with Salesforce would be my first stop. — Reply to this email directly, view it on GitHub https://gist.github.com/4d065c3e61e38cf9c019d8fb59915232#gistcomment-4387673 or unsubscribe https://github.com/notifications/unsubscribe-auth/ADFUYPJDRRTRBNB5TAXYMZLWLDJ4ZBFKMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DFQKSXMYLMOVS2I5DSOVS2I3TBNVS3W5DIOJSWCZC7OBQXE5DJMNUXAYLOORPWCY3UNF3GS5DZVRZXKYTKMVRXIX3UPFYGLK2HNFZXIQ3PNVWWK3TUUZ2G64DJMNZZDAVEOR4XAZNEM5UXG5FFOZQWY5LFVEYTANRTGE3TENZZU52HE2LHM5SXFJTDOJSWC5DF . You are receiving this email because you commented on the thread. Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub .

Thanks for this thread, all. @Rnatik, I suspect I'm getting this error because of validation rules, as well. When you say you were able to 'tweak the org' and I assume get it to work? Mind explaining what that means? Does that mean that you deleted those validation rules? Or do you only run this class periodically and disable the validation rules when you do?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment