function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
zigzagzigzag 

How to update a User record from an Account trigger

We use the PRM partner portal, and in order to keep the approvals workflow simple we have set it up so the Partner User's Manager gets approval requests.

 

This Manager field should always be the same as the Partner Account's Owner, and we would like it to be automatically updated for all of the relevant Partner Users when the Owner field changes on the Account.


It should be a simple thing to do, but turned out to be more complex than I thought because Salesforce doesn't allow a User record to be updated in the same transaction as an Account object - which means that you can't update the User object in an Account trigger.

 

The solution is to split the code into two parts: the trigger, and a separate class that does all the work and is marked @future.  In doing this, salesforce executes the code asynchroneously and it all works.

 

In case it's useful to someone, here is the complete functioning code:

 

 

// Trigger on Account changes
// Looks for changes to the Owner field
// When a Partner user is associated with the Account, make sure
// the Manager field on that User is updated to have the same value
//
// This is necessary because the approvals workflow for Partner-entered Leads
// uses on the Manager field.
//
// NOTE: Relies on the userUpdateAsyncClass class for most of the work because
// Salesforce can't update a User in an Account trigger.
//
// VERSION HISTORY
// 8 Aug 2009/Allan M: Initial version
//
trigger PartnerAccountOwnerChange on Account (before update) {
System.Debug('DBG: In PartnerAccountOwnerChange trigger');

List<Account> theAccounts = new List<Account>();
for (Account newAccount:System.Trigger.new) {
Account oldAccount = System.Trigger.oldMap.get(newAccount.Id);
if (oldAccount.OwnerId == newAccount.OwnerId) {
System.Debug('DBG: Owner was "' + oldAccount.OwnerId + '" and is now "' + newAccount.OwnerId + '"');
continue; // Ignore changes that do not affect the Account Owner
}

// We could also filter on Type to consider only Partner accounts
theAccounts.add(newAccount);
}

if (theAccounts.isEmpty()) {
System.debug('DBG: Nothing to do; no relevant changes');
return; // Nothing to do
}

System.debug('UPDATE: There may be something to do.');

List<Id> AccountIds = new List<Id>();
for (Account a: theAccounts)
AccountIds.add(a.Id);

// Perform the change. However, because Salesforce doesn't allow the User object
// to be updated in the same transaction that updates an Account object (which
// is what triggered this trigger, it has to be done in an asynchroneous static
// method annotated as @future.
userUpdateAsyncClass.updatePartnerUsersForAccounts(AccountIds);
} // trigger

 The bulk of the work is done in a separate class called userUpdateAsyncClass:

 

// Class used by the PartnerAccountOwnerChange trigger
//
// Works around the fact that a User cannot be updated by a trigger caused by an Account change
//
// VERSION HISTORY
// 8 Aug 2009/Allan M: Initial version
//
public class userUpdateAsyncClass {

@future
public static void updatePartnerUsersForAccounts(Id[] accountIds) {

// Retrieve the account objects for the Ids
List<Account> theAccounts = new List<Account> (
[select Id, Name, OwnerId from Account where Id in :AccountIds]
);

// Bulk retrieve all Partner User objects
// Might be better to retrieve based on PRM Profile rather than UserType?
List<User> theUsers = new List<User> (
[select Id, Name, ContactId, ManagerId from User where UserType = 'PowerPartner']
);

// Retrieve all of the associated Contact records that relate to the trigger Accounts
Set<Id> ContactIds = new Set<Id>();
for (User u: theUsers)
ContactIds.add(u.ContactId);
Map<Id, Contact> theContacts = new Map<Id, Contact>(
[select Id, AccountId from Contact where Id in :ContactIds and AccountId in :AccountIds]
);

if (theContacts.isEmpty()) {
System.Debug('DBG No matching Partner user objects need updating.');
return; // Nothing to do
}

// I need the actual User objects for the update to work, for some reason :(
Set<Id> theNewOwnerIds = new Set<Id>();
for (Account a: theAccounts)
theNewOwnerIds.add(a.OwnerId);
Map<Id, User> userMap = new Map<Id, User>(
[select Id, Name from User where Id in :theNewOwnerIds]
);

// Now do the actual update
for (User u:theUsers) {
Contact c = theContacts.get(u.ContactId);
if (c == null)
continue; // This user is not affected

System.Debug('DBG: Look for manager for User "' + u.Name + '"');
Id newOwnerId = null;
for (Account a:theAccounts){
System.Debug('DBG: Checking ' + c.AccountId + ' against ' + a.Id );
if (c.AccountId == a.Id)
newOwnerId = a.OwnerId;
} // theAccounts

if (newOwnerId == null) {
System.Debug('ERROR: ??? Cannot find new Owner!?!');
continue;
}

System.Debug('DBG: Setting new Manager to ' + newOwner);
u.ManagerId = newOwnerId;
User newOwner = userMap.get(newOwnerId);
u.Manager = newOwner;

try {
update u;
} catch (DmlException e) {
System.debug('ERROR: ' + e.getMessage());
}
} // theUsers
}
}

 

Thoughts, comments or improvements to get this done easier are much welcome.  Feel free to make use of the code to make your life easier in some way too :)

 

Allan Mertner

 

 

 

 

harryZharryZ
good job, that's really sweet.