We Have a certain Account Hierarchy designed in out Org.It display like this:
Account(Top Account)
---Child Account 1
----Child Account 1.1
----Child Account 1.1.1
----Child Account 1.1.1.1
We can add any number of child accounts to Top Accounts but within an accounts we can only add 5 accounts.
Whenever we add multiple child accounts to Top Account,we update Classficiation values of those child accounts by Top Account Classification values.
Below are the classification Field:
AccountClassification1__c,AccountClassification2__c ......AccountClassification7__c
each classification field is of type picklist and contains value as 1,2,3,4.
Now If Top Account Classification value for AccountClassification1__c,AccountClassification2__c,AccountClassification3__c,AccountClassification4__c is 1,2,3,4 then the same classification value is reflected for all child accounts. It does not matter how many child accounts it has. If the Classification field does not have any value, it will be updated with a blank value.
Below are the code that updates classfication Value:
public with sharing class AccountClassificationServices {
public static Boolean hasClassficationUpdated(Account account, Account oldAccount) {
List<Account> changedAccounts = new List<Account>();
// This stops the loops where classification for children is updated
if (account.ParentId <> null) {
return false;
}
if (oldAccount == null
|| account.AccountCategory__c <> oldAccount.AccountCategory__c
|| account.Account_Subcategory__c <> oldAccount.Account_Subcategory__c
|| account.AccountClassification1__c <> oldAccount.AccountClassification1__c
|| account.AccountClassification2__c <> oldAccount.AccountClassification2__c
|| account.AccountClassification3__c <> oldAccount.AccountClassification3__c
|| account.AccountClassification4__c <> oldAccount.AccountClassification4__c
|| account.AccountClassification5__c <> oldAccount.AccountClassification5__c
|| account.AccountClassification6__c <> oldAccount.AccountClassification6__c
|| account.AccountClassification7__c <> oldAccount.AccountClassification7__c
)
return false;
}
public static List<Account> updateClassification(Map<Id,Account> accMap) {
List<Account> accList = findAccountToUpdate(accMap);
// find ultimate accounts with classification fields
Set<Id> utimateIds = new Set<Id>();
for (Account acc: accList) {
utimateIds.add(acc.UltimateParent__c);
}
Map<Id,Account> ultimateMap = new Map<Id,Account>(new SelectAccounts().execute(utimateIds));
// update statistics from ultimates
List<Account> accToUpdate = new List<Account>();
for (Account acc: accList) {
Account parentAccount = ultimateMap.get(acc.UltimateParent__c);
accToUpdate.add(new Account(
Id = acc.Id,
AccountCategory__c = parentAccount.AccountCategory__c,
Account_Subcategory__c = parentAccount.Account_Subcategory__c,
AccountClassification1__c = parentAccount.AccountClassification1__c,
AccountClassification2__c = parentAccount.AccountClassification2__c,
AccountClassification4__c = parentAccount.AccountClassification4__c,
AccountClassification5__c = parentAccount.AccountClassification5__c,
AccountClassification6__c = parentAccount.AccountClassification6__c,
AccountClassification7__c = parentAccount.AccountClassification7__c,
AllowClassificationUpdate__c = true,
Allow_CategorySubCategory_Update__c = true
));
}
// update classification and reset flag
(new UpdateChildren()).execute(accToUpdate);
resetAllowClassificationUpdate(accToUpdate);
resetAllowCategoryUpdate(accToUpdate);
return accToUpdate;
}
private static List<Account> findAccountToUpdate(Map<Id,Account> accMap) {
Map<Id,Account> updateMap = new Map<Id,Account>();
Set<Id> utimateIds = new Set<Id>();
// split map by two types
for (Id accId: accMap.keySet()) {
Account acc = accMap.get(accId);
if ( acc == null ) {
// whole hierarchy update
utimateIds.add(accId);
} else {
// single record
updateMap.put(acc.Id, acc);
}
}
if ( !utimateIds.isEmpty() ) {
// find all children for hierarchy
for (Account acc: getAllChildrenByUltimateParent(utimateIds)) {
updateMap.put(acc.Id, acc);
}
}
return updateMap.values();
}
public static List<Account> getAllChildrenByUltimateParent(Set<Id> ultimateParentIds) {
return [
select
Id,RecordTypeId,AccountCategory__c, Account_Subcategory__c,UltimateParent__c, AccountClassification1__c, AccountClassification2__c, AccountClassification3__c,
AccountClassification4__c, AccountClassification5__c, AccountClassification6__c, AccountClassification7__c, AllowClassificationUpdate__c, Allow_CategorySubCategory_Update__c,
BillingCountry, BillingCountryCode
from Account
where UltimateParent__c IN :ultimateParentIds and ParentId <> null
];
}
public static Boolean hasAllowClassification(Account account) {
return account.AllowClassificationUpdate__c == true;
}
public static Boolean hasAllowCategory(Account account) {
return account.Allow_CategorySubCategory_Update__c == true;
}
public static void resetAllowClassificationUpdate(List<Account> accounts) {
// accounts = new SelectAccounts().execute(accounts);
for (Account account : accounts) {
account.AllowClassificationUpdate__c = false;
}
(new UpdateChildren()).execute(accounts);
}
public static void resetAllowCategoryUpdate(List<Account> accounts) {
// accounts = new SelectAccounts().execute(accounts);
for (Account account : accounts) {
account.Allow_CategorySubCategory_Update__c = false;
}
(new UpdateChildren()).execute(accounts);
}
@TestVisible
private without sharing class SelectAccounts
{
public List<Account> execute(Set<Id> accIds)
{
return [select
ParentId,RecordTypeId,AccountCategory__c, Account_Subcategory__c,UltimateParent__c, AccountClassification1__c, AccountClassification2__c, AccountClassification3__c,
AccountClassification4__c, AccountClassification5__c, AccountClassification6__c, AccountClassification7__c, AllowClassificationUpdate__c, Allow_CategorySubCategory_Update__c,
BillingCountry, BillingCountryCode
from Account
where Id IN :accIds];
}
}
private without sharing class UpdateChildren
{
public void execute(List<Account> children)
{
if (!children.isEmpty()) {
update children;
}
}
}
}
Below is the Trigger Code:
public with sharing class AccountHandler implements ITrigger {
private List<SObject> accountsWithParentBeforeInsert = new List<SObject>();
private List<Account> accountsReceiveParents = new List<Account>();
private List<Account> accountsLostParents = new List<Account>();
private List<SObject> accountsAfterInsert = new List<SObject>();
private List<SObject> accountsAfterUpdate = new List<SObject>();
private List<SObject> accountsAfterDelete = new List<SObject>();
private List<SObject> accountGroupsAfterDelete = new List<SObject>();
private Map<Id, Account> ultimateAccountClassificationChanged = new Map<Id, Account>();
private Map<Id, Account> ultimateAccountCategoryChanged = new Map<Id, Account>();
private List<Account> accountsHasClassificationSet = new List<Account>();
private List<Account> accountsHasCategorySet = new List<Account>();
public AccountHandler() { }
public void bulkBefore() { }
public void bulkAfter() { }
public void beforeInsert(SObject so) {
}
public void beforeUpdate(SObject oldSo, SObject so) {
Account accountToUpdate = (Account)so;
Account accountBeforeUpdate = (Account)oldSo;
}
public void beforeDelete(SObject so) {
if (AccountServices.isAccountLocked()) {
so.addError(Label.AG_Account_Locked);
}
}
public void afterInsert(SObject so) {
Account acc = (Account)so;
if ( acc.ParentId != null ) {
ultimateAccountClassificationChanged.put(acc.Id, acc);
ultimateAccountCategoryChanged.put(acc.Id, acc);
}
}
public void afterUpdate(SObject oldSo, SObject so) {
Account accountToUpdate = (Account)so;
Account accountBeforeUpdate = (Account)oldSo;
if (accountToUpdate.UltimateParent__c <> accountBeforeUpdate.UltimateParent__c && accountToUpdate.UltimateParent__c <> null ) {
ultimateAccountClassificationChanged.put(accountToUpdate.Id, accountToUpdate);
ultimateAccountCategoryChanged.put(accountToUpdate.Id, accountToUpdate);
} else if (AccountClassificationServices.hasClassficationUpdated(accountToUpdate, accountBeforeUpdate) && accountToUpdate.ParentId == null) {
ultimateAccountClassificationChanged.put(accountToUpdate.Id, null);
ultimateAccountCategoryChanged.put(accountToUpdate.Id, null);
}
}
public void afterDelete(SObject so) {
Account acc = (Account)so;
accountsAfterDelete.add(acc);
}
public void andFinally() {
// Ultimate Parents
if (!accountsWithParentBeforeInsert.isEmpty()) {
AccountHierarchyServices.populateUltimateParents(accountsWithParentBeforeInsert);
}
if (!accountsReceiveParents.isEmpty()) {
AccountHierarchyServices.updateUltimateParents(accountsReceiveParents, false);
}
if (!accountsLostParents.isEmpty()) {
AccountHierarchyServices.updateUltimateParents(accountsLostParents, true);
}
if (!ultimateAccountClassificationChanged.isEmpty()) {
AccountClassificationServices.updateClassification(ultimateAccountClassificationChanged);
}
if (!ultimateAccountCategoryChanged.isEmpty()) {
AccountClassificationServices.updateClassification(ultimateAccountCategoryChanged);
}
}
}
Lets say I have top Account as TopA and has 10 child accounts. There is another Top Account 'TopB' which has 250 Child Accounts. If I want to make 'TopB' as a child to 'TopA' then it executes above code and throws an 'Apex CPU time limit exceeded' exception. Because it updates the TopB and its child accounts Classfication value with TopA classfication value.
The exception occurs when we have a large Account Hierarchy as I mentioned in the example.
The exception occurs on After Update events during the below condition
if (accountToUpdate.UltimateParent__c <> accountBeforeUpdate.UltimateParent__c && accountToUpdate.UltimateParent__c <> null ) {
ultimateAccountClassificationChanged.put(accountToUpdate.Id, accountToUpdate);
ultimateAccountCategoryChanged.put(accountToUpdate.Id, accountToUpdate);
}
Because it calls AccountClassificationService class and trys to update classification values.
Can someone guide me on what needs to be modified in the above code?
Update:
I have written a batch class in order to resolve this CPU Timeout error. Below is the code that i have written.
global class batchAccountUpdate implements Database.batchable<sObject>{
public String query;
global database.querylocator start(Database.BatchableContext info){
system.debug('query Result:'+query);
String query1='select Id,ParentId,RecordTypeId,UltimateParent__c,AccountCategory__c,Account_Subcategory__c ,AccountClassification1__c, AccountClassification2__c, AccountClassification3__c,AccountClassification4__c, AccountClassification5__c, AccountClassification6__c, AccountClassification7__c, AllowClassificationUpdate__c,Allow_CategorySubCategory_Update__c,BillingCountry,BillingCountryCode from Account where Id='+query+'';
system.debug('Query1 Result:'+query1);
system.debug('Start Info:'+info);
return Database.getquerylocator(query);
}
global void execute(Database.BatchableContext info, List<Account> children){
if (!children.isEmpty()) {
update children;
}
}
global void finish(Database.BatchableContext info){
}
}
Below is the method where I am calling the Batch CLass.I am not scheduling the batch class as I want to fire it only on specific condition i.e.During Accounts Re parenting or updating Classification value on Top Account.
public static List<Account> updateClassification(Map<Id,Account> accMap) {
List<Account> accList = findAccountToUpdate(accMap);
// find ultimate accounts with classification fields
Set<Id> utimateIds = new Set<Id>();
for (Account acc: accList) {
utimateIds.add(acc.UltimateParent__c);
}
Map<Id,Account> ultimateMap = new Map<Id,Account>(new SelectAccounts().execute(utimateIds));
SET<ID> keys = ultimateMap.keyset();
// update statistics from ultimates
List<Account> accToUpdate = new List<Account>();
for (Account acc: accList) {
Account parentAccount = ultimateMap.get(acc.UltimateParent__c);
accToUpdate.add(new Account(
Id = acc.Id,
AccountCategory__c = parentAccount.AccountCategory__c,
Account_Subcategory__c = parentAccount.Account_Subcategory__c,
AccountClassification1__c = parentAccount.AccountClassification1__c,
AccountClassification2__c = parentAccount.AccountClassification2__c,
AccountClassification4__c = parentAccount.AccountClassification4__c,
AccountClassification5__c = parentAccount.AccountClassification5__c,
AccountClassification6__c = parentAccount.AccountClassification6__c,
AccountClassification7__c = parentAccount.AccountClassification7__c,
AllowClassificationUpdate__c = true,
Allow_CategorySubCategory_Update__c = true
));
}
string query= '\''+String.join(keys,'\',\'')+'\'';
//Calling Batch Class
batchAccountUpdate execbatch = new batchAccountUpdate();
execbatch.query = query;
database.executebatch(execbatch);
resetAllowClassificationUpdate(accToUpdate);
resetAllowCategoryUpdate(accToUpdate);
return accToUpdate;
}
The above code does not occur CPU Timeout issue but it does display script exception message: "System.QueryException: unexpected token: (Id)".
What I have modified in Apex class is I am adding a Dynamic SOQL query and pass it to the Batch Apex class as I need a filter that recognize to fire Batch Job only on certain condition but every time when I do re parenting or Updates Classification value on Top Accounts,it throws an error message.I am not sure what is wrong in my method as I am using correct syntax but it does not recognize filter criteria 'Keys'and throws me an exception.
Can someone please help me what I am doing wrong here?
QueryException
. This situation is a bit of a mess...