I am trying to trigger territory assignment rules on accounts via the API (the only way you can, apparently), and then change the account owner based on the territories that are assigned. It’s an interesting process - a managed app first makes an API call in a future method, which updates the account, which triggers my class, which makes another queueable API call to run the assignment rules, which ideally I can then use to update the account owner.
The problem is that my API call apparently updates the account when it runs the assignment rules, which recursively hits the account trigger, which makes the API call, which hits the account trigger, etc. I am able to stop recursion in future or queueable contexts, but how can I stop the API from firing the trigger again?
Note 1: there are no errors given when the code runs. It just fires infinitely.
Note 2: I cannot assign any static variable, because the API transaction is occurring in its own context. For the same reason, !System.isQueueable is not preventing the recursion either.
Trigger looks like this:
if (Trigger.isUpdate) {
AssignAcctOwnerFromTerritory.AssignOwnerFromTerritory(trigger.new); //must run in after trigger so that it can grab the territory ID assigned by ETM assignment rules
}
Class that calls the queueable callout:
public class AssignAcctOwnerFromTerritory {
public static void AssignOwnerFromTerritory(List<Account> AccsToUpdate){
//run ETM assignment rules
Map<ID, Account> AcctMap = new Map<ID, Account>(AccsToUpdate);
Set<String> AccIDs = new Set<String>((List<String>) New List<ID>(AcctMap.keyset()));
if(!System.isQueueable()){
RunTerritoryRules Job = new RunTerritoryRules();
Job.AcctIDs = AccIDs;
System.debug('Queuing ETM assignments');
System.enqueueJob(job);
}
}
The callout:
public class RunTerritoryRules implements Queueable, Database.AllowsCallouts {
public Set<String> AcctIDs = null;
public String SessionID = null;
public void execute(QueueableContext context) {
System.debug('AcctIDs queued for ETM rules API call = '+AcctIDs);
SessionID = Page.TerritoriesSessionID.getContent().toString();
List<String> AccIDStrings = new List<String>();
if(AcctIDs != null){
for(String AccID : AcctIDs){
AccIDStrings.add(AccountTag.replace('{ACCID}', AccID));
}
}
requestTemplate = requestTemplate.replace('{ACCLISTS}', String.join(AccIDStrings, ' '));
requestTemplate = requestTemplate.replace('{SESSID}', SessionID);
HttpRequest request = new HttpRequest();
request.setEndpoint(System.URL.getSalesforceBaseUrl().toExternalForm()+'/services/Soap/u/41.0/'+UserInfo.getOrganizationId());
request.setMethod('POST');
request.setHeader('Content-Type', 'text/xml;charset=UTF-8');
request.setHeader('SOAPAction', '""');
request.setBody(requestTemplate);
String s = String.valueOf(new Http().send(request).getBodyDocument());
System.debug(s);
}
String accountTag = '<urn:sObjects> '+
'<urn1:type>Account</urn1:type> '+
'<urn1:Id>{ACCID}</urn1:Id> '+
'</urn:sObjects> ' ;
String requestTemplate = '<soapenv:Envelope '+
'xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"'+
' xmlns:urn="urn:partner.soap.sforce.com"'+
' xmlns:urn1="urn:sobject.partner.soap.sforce.com">'+
'<soapenv:Header> '+
'<urn:AssignmentRuleHeader> '+
'<urn:useDefaultRule>true</urn:useDefaultRule> '+
'<urn:assignmentRuleId></urn:assignmentRuleId> '+
'</urn:AssignmentRuleHeader> '+
'<urn:SessionHeader> '+
'<urn:sessionId>{SESSID}</urn:sessionId> '+
'</urn:SessionHeader> '+
'</soapenv:Header> '+
'<soapenv:Body> '+
'<urn:update> '+
' {ACCLISTS}'+
'</urn:update> '+
'</soapenv:Body> '+
'</soapenv:Envelope>';
Relatedly, in the logs, the API call appears as a different transaction from the queueable handler in which it is called, which is different from what I’ve seen making API calls to external web services. Why is that? If the territories will not update within the same transaction as the queueable class in which it is called, I don’t know how I could make the final account owner update. In other integrations I’ve written, the result of the API call (a JSON string for example) is returned within the context of the running class, so it can be used for processing thereafter.
Many thanks