I am having issues getting sucess with my account trigger. Would really appreciate someone taking a look.
I can't even get a compile :(
The requirement I am trying to code for is this.
If I have contacts owned by a difference user that the Account Owner, if Account Owner is changed, do not update contact owners to the new Account owner. In other words, retains contact owners on change of account owner.
TRIGGER
trigger sbox_Account on Account ( before insert, after insert, before update, after update, before delete, after delete) { Map<Id,String> contactsPendingOwnershipChange = new Map<Id,String>(); if (trigger.isBefore) { if (trigger.isInsert) {} if (trigger.isUpdate) { contactsPendingOwnershipChange = new sbox_ContactUtil.getContactsPendingOwnershipChange(Trigger.new, Trigger.oldMap); } if (trigger.isDelete) {} } if (trigger.IsAfter) { if (trigger.isInsert) {} if (trigger.isUpdate) { sbox_ContactUtil.restoreContactOwnership(contactsPendingOwnershipChange); } if (trigger.isDelete) {} } } CLASS
public with sharing class sbox_ContactUtil { /** This class is a helper class for the Account trigger. There is a default feature in SFDC that triggers when an Account's Owner is changed (not via the API or Workflow automation) which cascades down ownership from the new Account owner, to certain related object records. One such record are the related Contacts. This would be fine, expect it also skips re-firing process builder, and flows (https://help.salesforce.com/articleView?id=000323827&language=en_US&type=1&mode=1). Therefore the only solution was to create a trigger to reverse these changes. **/ public static Map<Id,String> getContactsPendingOwnershipChange(List<Account> newRecords, Map<Id, Account> oldMap) { Map<Id,String> contactsPendingOwnershipChange = new Map<Id,String>(); for (Account a : newRecords) { if (a.OwnerId != oldMap.get(a.Id).OwnerId) { //This Account has had an ownership change, so all Contacts related to this Account //Will automatically have their owner changed to the new owner. for (Account acc : [SELECT Id, OwnerId, (SELECT Id, OwnerId FROM Contacts) FROM Account WHERE Id = :a.Id]) { for (Contact c : acc.Contacts) { if (c.OwnerId != acc.OwnerId) { //These Contacts are not owned by the old owner, therefore we must preserve their ownership. contactsPendingOwnershipChange.put(c.Id, c.OwnerId); } } } } } return contactsPendingOwnershipChange; } public static void restoreContactOwnership(Map<Id,String> contactsPendingOwnershipChange) { //Create and array to store the Contact records that will need to be restored. Contact[] contactsToRestoreOwnership = new Contact[0]; if (!contactsPendingOwnershipChange.isEmpty()) { for (Id key : contactsPendingOwnershipChange.keyset()) { Contact restoreContact = new Contact(OwnerId = contactsPendingOwnershipChange.get(key), Id = key); contactsToRestoreOwnership.add(restoreContact); } update contactsToRestoreOwnership; } } } TEST CLASS
@isTest public class sbox_test_Account { @isTest static void testAccountTrigger() /** Create 3 Users Create Account and Contact Update Contact Owner to User 3 Update Owner of Account to User 2 Validate that Contact Over is restored to User 3 **/ { Integer numUsers = 3; Integer numAccts = 1; Integer numContactsPerAcct = 3; List<User> testUsers = new List<User>(); List<Contact> testContacts = new List<Contact>(); String newUserRoleId = 'Sales Representative'; Profile p = [SELECT Id FROM Profile WHERE Name='Custom - Sales User']; UserRole r = new UserRole(DeveloperName = 'MyCustomRole', Name = 'My Role'); insert r; for(Integer i=0;i<numUsers;i++) { User u = new User(); u.LastName = 'User '+i; u.Alias = 'U'+i; u.CommunityNickname = 'U'+i; u.Email = 'user '+i+'@test.com'; u.Username = 'username'+i; u.UserRoleId = r.Id; u.ProfileId = p.Id; insert u; testUsers.add(u); System.debug('created test user: '+i); } Account a = new Account(); a.RecordTypeId = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Prospect').getRecordTypeId(); a.Name = 'Test Account'; a.BillingStreet = 'Level 99, 999 ThisRd'; a.BillingCity = 'City'; a.BillingState = 'Victoria'; a.BillingPostalCode = '3000'; a.BillingCountry = 'Australia'; a.Phone = '+61 99999999'; a.Website = 'website.com.au'; a.Type = 'Prospect'; a.OwnerId = testUsers[0].Id; //Assigned Accounts to Owner 0 insert a; System.debug('created test account'); for (Integer k=0;k<numContactsPerAcct;k++) { Contact c = new Contact(); c.FirstName = 'Test '+k; c.LastName = 'Test '+k; c.AccountId = a.id; c.OwnerId = testUsers[0].Id; //Assigned all Contacts to Owner 0 insert c; testContacts.add(c); System.debug('created contact: '+k); } //Now update the a contact to be owned by another User testContacts[2].OwnerId = testUsers[2].id; update testContacts[2]; System.debug('Updated contact owner for test Cotnact 2'); //Now update the owner of the Account a.OwnerId = testUsers[1].id; update testUsers[1]; System.debug('Updated Account owner'); //Validate that Contact Over is restored to User 3 System.assertEquals(testContacts[0].OwnerId, testUsers[1].Id); System.assertEquals(testContacts[1].OwnerId, testUsers[1].Id); System.assertEquals(testContacts[2].OwnerId, testUsers[2].Id); } } Errors:
- Invalid type: sbox_ContactUtil.getContactsPendingOwnershipChange (11:46)
UPDATE
I am having issues getting success with my account trigger.
It's worth pointing out that I have a background in programming, but I am not a full time dev. This is basically my first attempt at Apex and triggers.
Would really appreciate someone taking a look.
At first I could not get it to compile, but now it seems more likely a logic issue. I've been using the debugger in VSCode, but I have a suspicion.
The documentation around this automated ownership cascading, indicates that this does not apply if the Account owner is updated by API, or dataloader. Is it possible that the test execution falls into this, thus it's not actually triggering the cascading ownership, like it would if a user modified the Account owner via the UI?
Any way, here is where I am at now, after making some of the improvements offered by David.
The requirement I am trying to code for is this.
If I have contacts owned by a difference user that the Account Owner, if Account Owner is changed, do not update contact owners to the new Account owner. In other words, retains contact owners on change of account owner.
TRIGGER
trigger sbox_Account on Account (before update, after update) { static Map<Id,String> contactsPendingOwnershipChangeResults = new Map<Id,String>(); if (trigger.isBefore) { if (trigger.isUpdate) { contactsPendingOwnershipChangeResults = sbox_ContactUtil.getContactsPendingOwnershipChange(Trigger.new, Trigger.oldMap); } } if (trigger.IsAfter) { if (trigger.isUpdate) { sbox_ContactUtil.restoreContactOwnership(contactsPendingOwnershipChangeResults); } } } CLASS
public with sharing class sbox_ContactUtil { /* This class is a helper class for the Account trigger. There is a default feature in SFDC that triggers when an Account's Owner is changed (not via the API or Workflow automation) which cascades down ownership from the new Account owner, to certain related object records. One such record are the related Contacts. This would be fine, expect it also skips re-firing process builder, and flows (https://help.salesforce.com/articleView?id=000323827&language=en_US&type=1&mode=1). Therefore the only solution was to create a trigger to reverse these changes. */ public static Map<Id,String> getContactsPendingOwnershipChange(Account[] newAccounts, Map<Id, Account> oldMap) { //Create a map of Contact Id's and the Contact's Old Owner, to return from this method. Map<Id,String> contactsPendingOwnershipChange = new Map<Id,String>(); //Iterate of the List of Accounts from trigger.new for (Account a : newAccounts) { //Check if the Account owner has changed. if (a.OwnerId != oldMap.get(a.Id).OwnerId) { /*This Account has had an ownership change, so all Contacts related to this Account will automatically have their owner changed to the new owner. So get all of the Contacts, then iterate through and if their old owner is not the same and the Account's new owner, add them to our Map of contacts that we will need to restore. Note, I assume that the Contact owner has not yet been changed by the default ownership change functionality, given this method was called before update. */ for (Account acc : [SELECT Id, OwnerId, (SELECT Id, OwnerId FROM Contacts) FROM Account WHERE Id = :a.Id]) { for (Contact c : acc.Contacts) { if (c.OwnerId != acc.OwnerId) { //These Contacts are not owned by the old owner, therefore we must preserve their ownership. contactsPendingOwnershipChange.put(c.Id, c.OwnerId); } } } } } return contactsPendingOwnershipChange; } public static void restoreContactOwnership(Map<Id,String> contactsPendingOwnershipChange) { //This method is called after update. //Create and array to store the Contact records that will need to be restored. Contact[] contactsToRestoreOwnership = new Contact[0]; for (Id key : contactsPendingOwnershipChange.keyset()) { Contact restoreContact = new Contact(OwnerId = contactsPendingOwnershipChange.get(key), Id = key); contactsToRestoreOwnership.add(restoreContact); } try { update contactsToRestoreOwnership; } catch (DmlException e) { System.debug('An unexpected error has occurred: ' + e.getMessage()); } } } TEST CLASS
@isTest public class sbox_test_Account { @isTest static void testAccountTrigger() /** Create 3 Users Create Account and 3x Contacts Update Contact Owner to User 3 Update Owner of Account to User 2 Validate that Contact Over is restored to User 3 **/ { Integer numUsers = 3; Integer numAccts = 1; Integer numContactsPerAcct = 3; List<User> testUsers = new List<User>(); List<Contact> testContacts = new List<Contact>(); String newUserRoleId = 'Sales Representative'; Profile pf = [SELECT Id FROM Profile WHERE Name='System Administrator']; UserRole ur = new UserRole(DeveloperName = 'MyTestRole', Name = 'MyTestRole'); insert ur; for(Integer i=0;i<numUsers;i++) { User usr = sbox_TestUtil.createTestUser(ur.Id, pf.Id, 'Test FirstName'+i, 'Test LastName'+i); insert usr; testUsers.add(usr); System.debug('created test user: '+i+'Id = '+testUsers.get(i).Id); } System.runAs(testUsers.get(0)){ Account a = new Account(); a.RecordTypeId = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Prospect').getRecordTypeId(); a.Name = 'Smops Test'; a.BillingStreet = 'Level 99, 999 Test Rd'; a.BillingCity = 'Test City'; a.BillingState = 'Victoria'; a.BillingPostalCode = '3146'; a.BillingCountry = 'Australia'; a.Phone = '+61 3 9882 6909'; a.Website = 'website.com.au'; a.Type = 'Prospect'; a.OwnerId = testUsers.get(0).Id; //Assigned Accounts to Owner 0 insert a; System.debug('created test account. OwnerId= '+a.OwnerId); for (Integer k=0;k<numContactsPerAcct;k++) { Contact c = new Contact(); c.FirstName = 'Test'+k; c.LastName = 'Test'+k; c.AccountId = a.id; c.OwnerId = testUsers.get(0).Id; //Assigned all Contacts to Owner 0 insert c; testContacts.add(c); System.debug('created contact: '+k+'. OwnerId = '+c.OwnerId); } //Now update the a contact to be owned by another User testContacts[2].OwnerId = testUsers.get(2).id; update testContacts.get(2); System.debug('Updated contact owner for test Contact 2. New OwnerId ='+testContacts.get(2).OwnerId); //Now update the owner of the Account a.OwnerId = testUsers.get(1).id; update a; System.debug('Updated Account OwnerId ='+a.OwnerId); for(Integer m=0;m<testContacts.size();m++) { System.debug('Related Contact '+m+' OwnerId = '+testContacts.get(m).OwnerId); } } Contact c0 = [SELECT Id, OwnerId FROM Contact WHERE Id = :testContacts.get(0).Id]; Contact c1 = [SELECT Id, OwnerId FROM Contact WHERE Id = :testContacts.get(1).Id]; Contact c2 = [SELECT Id, OwnerId FROM Contact WHERE Id = :testContacts.get(2).Id]; //This first test just ensures that the automatic cascade of ownership from Account updated Contact 0 to the new Account Owner. System.assert(c0.OwnerId == testUsers.get(1).Id, 'Error: Contact 0 Owner has been modfied by the system on Account Owner change to User 1'); //This also simply tests that the automatic cascade of ownership from Account updated Contact 0 to the new Account Owner. System.assert(c1.OwnerId == testUsers.get(1).Id, 'Contact 1 Owner has been modfied by the system on Account Owner change to User 1'); //This last test checks to verify that we restored the Contact owner to it's prior owner. System.assert(c2.OwnerId == testUsers.get(2).Id, 'Contact 2 Owner has NOT been modfied by the system on Account Owner change and remains owned by User 2'); } } TEST UTIL CLASS
@isTest public class sbox_TestUtil { public static User createTestUser(Id roleId, Id profID, String fName, String lName) { String orgId = UserInfo.getOrganizationId(); String dateString = String.valueof(Datetime.now()).replace(' ','').replace(':','').replace('-',''); Integer randomInt = Integer.valueOf(math.rint(math.random()*1000000)); String uniqueName = orgId + dateString + randomInt; User tuser = new User( firstname = fName, lastName = lName, email = uniqueName + '@test' + orgId + '.org', Username = uniqueName + '@test' + orgId + '.org', EmailEncodingKey = 'ISO-8859-1', Alias = uniqueName.substring(18, 23), TimeZoneSidKey = 'America/Los_Angeles', LocaleSidKey = 'en_US', LanguageLocaleKey = 'en_US', ProfileId = profId, UserRoleId = roleId); return tuser; } } Errors:
- This compiles, however the tests are failing. I believe this may be due to my logic, even if the syntax is now throwing an error.
- My debug logs report that after all of the execution of the test, that the Contact Owner for all three contacts are still Owned by the user[0], so this makes me think that maybe the system automation to update their owners on the Account owner change is not happening in this environment, vs if a user changed the owner in the UI???
- I understand bulification, but I'm unsure how to approach the account bulkification you mentioned. Any guidance would be appreciated.