I have created a Trigger and Class that works in dev environment, but I'm trying to deploy it to production, and it is testing all of my code with all the other classes in the environment. The only error that is caused in the other unrelated classes that are tested, occurs because of Line 9 of my class, involving the Account. What I am trying to do is whenever a user is created, a contact is created with that user's info, and is placed into the 'Company Users' account. It has something to do with my @future methods, and how it's trying to add the contacts to the specific account, while the other @future methods are still running. If anyone has any ideas on how I can resolve this, I would be so thankful.
The @future mechanism is being used to avoid the problem of sObjects That Cannot Be Used Together in DML Operations, in this case the User
object.
Trigger
trigger UserInsertContactInsert1 on User (after insert, after update) {
if(Trigger.isAfter && Trigger.isInsert){
Set<ID> usrIds = new Set<ID>();
for(User u : Trigger.new){
usrIds.add(u.id);
}
UserContactSyncClass1.syncContact(usrIds);
}
if(Trigger.isAfter && Trigger.isUpdate){
Set<ID> idSet = new Set<ID>();
Set<ID> idSet2 = new Set<ID>();
for(User u : trigger.new){
for(User r : trigger.old){
if(u.ID == r.ID && u.isActive==false && r.isActive==true){
idSet.add(u.ID);
}
if (u.ID == r.ID && u.isActive==true && r.isActive==false) {
idSet2.add(u.ID);
}
}
}
UserContactSyncClass1.InactiveContact(idSet);
UserContactSyncClass1.ActivateContact(idSet2);
}
}
Class
public class UserContactSyncClass1 {
@future
public static void syncContact(Set<Id> userSet){
List<Contact> cList = new List<Contact>();
List<User> userList = [Select ID, FirstName, LastName, Email, Phone, True_Market_Unit__c, True_Market_Name__c FROM User WHERE ID IN :userSet];
List<String> mList = new List<String>();
List<String> mktList = new List<String>();
**Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Company Users' Limit 1];**
// Loop through Users, to get List of True Market Number
for(User usr : userList){
mList.add(usr.True_Market_Unit__c);
}
// Use the True Market Number and Match to the True Market Number in Markets
for(Market__c mkt : [SELECT Name, Unit_Number__c FROM Market__c WHERE Unit_Number__c IN :mList]){
mktList.add(mkt.Name);
}
for(User usr : userList){
for(String mk : mktList){
usr.True_Market_Name__c = mk;
}
System.debug(usr.True_Market_Name__c);
}
for(User u : userList){
cList.add(new Contact(FirstName=u.FirstName, LastName=u.LastName, User_True_Market_Name__c=u.True_Market_Name__c, Email=u.Email, Phone=u.Phone, OwnerID=u.ID, Related_User__c=u.Id, AccountId = acc.Id));
}
upsert cList;
}
@future
public static void InactiveContact(Set<ID> userSet){
List<Contact> cList = [Select ID, Contact_Status__c FROM Contact WHERE Related_User__c = :userSet];
for(Contact c : cList){
c.Contact_Status__c = 'Inactive';
}
update cList;
}
@future
public static void activateContact(Set<ID> userSet){
List<Contact> cList = [Select ID, Contact_Status__c FROM Contact WHERE Related_User__c = :userSet];
for(Contact c : cList){
c.Contact_Status__c = 'Active';
}
update cList;
}
}
Best Answer
Given that the reason to use a future method is to separate the DML of the User from the DML of the Contact object, only one of these DML needs to move to a future method. The change driving the process is the change to the User, so it seems natural to keep that logic synchronous and move the change to the Contact out into the future: Contact will always follow User.
So if I was writing this, I would use this trigger:
and this class:
Both probably contain typos.
If the above code still results in "ENTITY_IS_LOCKED" DmlExceptions, then moving to using the Queueable mechanism instead of the future mechanism is probably going to be necessary so you can catch the exception and retry. (You can't kick-off a new future method from a future method.) Skipping work if you are already in a future context won't work as legitimate updates of unrelated objects could get dropped.