+ Start a Discussion
Elliot32Elliot32 

Help with Triggers on Formula field - Inconsistent Behavior

Hi All,

Disclaimer: I am a beginner developer and am still working to understand Apex; I have strong Admin experience. Please see my code below:
 
trigger EPS_LeadCycleCreator on Lead(after update)
{
    List<Lead_Cycle__c> LeadCycles = new List<Lead_Cycle__c>();
    
    Set<Id> ownerIds = new Set<Id>();
    for(Lead l : trigger.new)
    {
        Lead oldLead = trigger.oldMap.get(l.Id);
        
        Boolean oldLeadIsNo = oldLead.Status.equals('No');
        Boolean newLeadIsNo = l.Status.equals('No');
        
        Boolean oldLeadIsInactive = oldLead.Trigger_Owner_Active__c == false;
        Boolean newLeadIsInactive = l.Trigger_Owner_Active__c == false;
        
        if((!oldLeadIsNo && newLeadIsNo) || (!oldLeadIsInactive && newLeadIsInactive))
        {
            ownerIds.add(l.OwnerId);
        }
    
    }
    
    Map<Id, User> ownerMap = new Map<Id, User>([ Select Id, ProfileId, Profile.Name From User where Id IN: ownerIDs ]);
    
    for(Lead l : trigger.new)
    {
        Lead oldLead = trigger.oldMap.get(l.Id);
        
        Boolean oldLeadIsNo = oldLead.Status.equals('No');
        Boolean newLeadIsNo = l.Status.equals('No');
        
        Boolean oldLeadIsInactive = oldLead.Trigger_Owner_Active__c == false;
        Boolean newLeadIsInactive = l.Trigger_Owner_Active__c == false;
        
        if((!oldLeadIsNo && newLeadIsNo) || (!oldLeadIsInactive && newLeadIsInactive))
        {
            if(ownerMap.containsKey(l.OwnerId) && ownerMap.get(l.OwnerId).Profile.Name == 'Sales Associate')
            {
                Lead_Cycle__c LeadCycle = new Lead_Cycle__c();
                LeadCycle.Lead__c = l.id;
                LeadCycle.Cycle_Start_Date__c = l.LastTransferDate;
                LeadCycle.Cycle_End_Date__c = system.today();
                LeadCycle.OwnerId = '00560000002VLHw';
            
                LeadCycles.add(LeadCycle);
            }
        }
    
    }   
    insert LeadCycles;
}
 
trigger EPS_OwnerActiveFalseChecker on Lead (before update) 
{
    for (Lead l : Trigger.new)
    {
        if(system.trigger.OldMap.get(l.Id).Owner_Active__c != system.trigger.NewMap.get(l.Id).Owner_Active__c)
        {
            l.Trigger_Owner_Active__c = l.Owner_Active__c;
        }
    }
}

The top trigger attempts to accomplish the following: If a User with a Profile of 'Sales Associate' has its Lead Status change to 'No' from a different value or if the field Trigger_Owner_Active__c changes to False, then create a Record associated to the Lead.

The bottom trigger attempts to copy changes in a formula field into a text field. The formula field is Owner_Active__c which looks to see if the User (Owner of the Lead) is Active, the text field is Trigger_Owner_Active__c. This serves two purposes: 1) To see if the User changes to Active = false OR if the Owner changes to a Queue (Queues cause this field to be marked false).

My issues are as follows:
1. When I change the Lead Owner to a queue, the text field changes with the formula field BUT the new Lead_Cycle__c record is not created
2. When I change the Lead Owner (User) to Active = false, the Trigger_Owner_Active__c field does NOT change with the formula field

Thanks for giving my question consideration,
Elliot
Best Answer chosen by Elliot32
Anirudh SinghAnirudh Singh
Hi Elliot,

I have a few suggestions and points which are causing the functionality not to work as desired by you.
Please find below the points:
1. You should not use multiple Triggers on the same Object. It is not a best practice. As, the order in which the execution of the Triggers will happen cannot be determined. I think this is the problem you are facing with the inconsistency, as here, you are using two Triggers two Triggers on the same Object Lead.

2. If you want to run the logic for before or after or insert or update, use Trigger Context Variables.

I have collated both the Triggers into one and used Trigger Context Variables. Please find below the Trigger:
trigger LeadTrigger on Lead(before update, after update)
{
    //The code inside this IF Block will execute for before update.
    //Trigger.isBefore and Trigger.isUpdate are Trigger Context Variables.
    if(Trigger.isBefore && Trigger.isUpdate)
    {
        for (Lead l : Trigger.new)
        {
            if(system.trigger.OldMap.get(l.Id).Owner_Active__c != system.trigger.NewMap.get(l.Id).Owner_Active__c)
            {
                l.Trigger_Owner_Active__c = l.Owner_Active__c;
            }
        }
    }
    
    //The code inside this IF Block will execute for after update.
    //Trigger.isAfter and Trigger.isUpdate are Trigger Context Variables.
    if(Trigger.isAfter && Trigger.isUpdate)
    {
        List<Lead_Cycle__c> LeadCycles=new List<Lead_Cycle__c>();
        
        Set<Id> ownerIds=new Set<Id>();
        for(Lead l: Trigger.new)
        {
            Lead oldLead=Trigger.oldMap.get(l.Id);
            
            Boolean oldLeadIsNo=oldLead.Status.equals('No');
            Boolean newLeadIsNo=l.Status.equals('No');
            
            Boolean oldLeadIsInactive=oldLead.Trigger_Owner_Active__c==false;
            Boolean newLeadIsInactive=l.Trigger_Owner_Active__c==false;
            
            if((!oldLeadIsNo && newLeadIsNo) || (!oldLeadIsInactive && newLeadIsInactive))
            {
                ownerIds.add(l.OwnerId);
            }
            
        }
        
        Map<Id, User> ownerMap=new Map<Id, User>([ Select Id, ProfileId, Profile.Name From User where Id IN: ownerIDs ]);
        
        for(Lead l: Trigger.new)
        {
            Lead oldLead=Trigger.oldMap.get(l.Id);
            
            Boolean oldLeadIsNo=oldLead.Status.equals('No');
            Boolean newLeadIsNo=l.Status.equals('No');
            
            Boolean oldLeadIsInactive=oldLead.Trigger_Owner_Active__c==false;
            Boolean newLeadIsInactive=l.Trigger_Owner_Active__c==false;
            
            if((!oldLeadIsNo && newLeadIsNo) || (!oldLeadIsInactive && newLeadIsInactive))
            {
                if(ownerMap.containsKey(l.OwnerId) && ownerMap.get(l.OwnerId).Profile.Name=='Sales Associate')
                {
                    Lead_Cycle__c LeadCycle=new Lead_Cycle__c();
                    LeadCycle.Lead__c=l.id;
                    LeadCycle.Cycle_Start_Date__c=l.LastTransferDate;
                    LeadCycle.Cycle_End_Date__c=system.today();
                    
                    //Important: Please don't hardcode the Id, Id may change in different Salesforce environments.
                    //If you want to populate the Owner as a specific user fetch the record using a SOQL outside the for Loop and then
                    //assign the User Id to the OwnerId field.
                    LeadCycle.OwnerId='00560000002VLHw';
                    
                    LeadCycles.add(LeadCycle);
                }
            }
            
        }   
        insert LeadCycles;
    }
}
3. Important: Please don't hardcode the Id, Id may change in different Salesforce environments. If you want to populate the Owner as a specific user fetch the record using a SOQL outside the for Loop and then assign the User Id to the OwnerId field.

4. I think by the statement "When I change the Lead Owner (User) to Active = false, the Trigger_Owner_Active__c field does NOT change with the formula field", you mean that when you change the User record from Active=true to Active=false.
Since, you are updating the User record, the formula field is update but it doesnt create a update call on Lead record. So, Lead Trigger will not execute and hence the below logic will not execute:
if(Trigger.isBefore && Trigger.isUpdate)
    {
        for (Lead l : Trigger.new)
        {
            if(system.trigger.OldMap.get(l.Id).Owner_Active__c != system.trigger.NewMap.get(l.Id).Owner_Active__c)
            {
                l.Trigger_Owner_Active__c = l.Owner_Active__c;
            }
        }
    }
To update the Trigger_Owner_Active__c field value when the User record becomes Active=false. You need to write an after update Trigger on User object.
Like below:
trigger UserTrigger on User(after update)
{
    if(Trigger.isAfter && Trigger.isUpdate)
    {
        Set<Id> userIds=new Set<Id>();
        for(User userRef: Trigger.New)
        {
            //I am assuming the field on User is IsActive which is made false.
            //Take the field which is used in your Org accordingly.
            if(system.Trigger.OldMap.get(userRef.Id).IsActive!=false && system.Trigger.NewMap.get(userRef.Id).IsActive==false)
            {
                userIds.add(userRef.Id);
            }
        }
        
        //Lead records to be updated.
        List<Lead> leadRecordsToUpdate=new List<Lead>();
        
        //Query the Lead records for the update User Record Ids.
        for(Lead leadRecord: [SELECT Trigger_Owner_Active__c FROM Lead WHERE OwnerId IN :userIds])
        {
            //If the Trigger_Owner_Active__c field value on the Lead is not false, then only the Lead record should be updated.
            if(leadRecord.Trigger_Owner_Active__c!=false)
            {
                leadRecord.Trigger_Owner_Active__c=false;
                leadRecordsToUpdate.add(leadRecord);
            }
        }
        
        //Update the Lead Record.
        //On update, the Lead Trigger will get executed and the login in the Trigger will work as needed.
        update leadRecordsToUpdate;
    }
}

Please let me know if this helps.
If yes, please mark the Question as Solved.


Thanks and Regards,
Anirudh Singh

All Answers

Anirudh SinghAnirudh Singh
Hi Elliot,

I have a few suggestions and points which are causing the functionality not to work as desired by you.
Please find below the points:
1. You should not use multiple Triggers on the same Object. It is not a best practice. As, the order in which the execution of the Triggers will happen cannot be determined. I think this is the problem you are facing with the inconsistency, as here, you are using two Triggers two Triggers on the same Object Lead.

2. If you want to run the logic for before or after or insert or update, use Trigger Context Variables.

I have collated both the Triggers into one and used Trigger Context Variables. Please find below the Trigger:
trigger LeadTrigger on Lead(before update, after update)
{
    //The code inside this IF Block will execute for before update.
    //Trigger.isBefore and Trigger.isUpdate are Trigger Context Variables.
    if(Trigger.isBefore && Trigger.isUpdate)
    {
        for (Lead l : Trigger.new)
        {
            if(system.trigger.OldMap.get(l.Id).Owner_Active__c != system.trigger.NewMap.get(l.Id).Owner_Active__c)
            {
                l.Trigger_Owner_Active__c = l.Owner_Active__c;
            }
        }
    }
    
    //The code inside this IF Block will execute for after update.
    //Trigger.isAfter and Trigger.isUpdate are Trigger Context Variables.
    if(Trigger.isAfter && Trigger.isUpdate)
    {
        List<Lead_Cycle__c> LeadCycles=new List<Lead_Cycle__c>();
        
        Set<Id> ownerIds=new Set<Id>();
        for(Lead l: Trigger.new)
        {
            Lead oldLead=Trigger.oldMap.get(l.Id);
            
            Boolean oldLeadIsNo=oldLead.Status.equals('No');
            Boolean newLeadIsNo=l.Status.equals('No');
            
            Boolean oldLeadIsInactive=oldLead.Trigger_Owner_Active__c==false;
            Boolean newLeadIsInactive=l.Trigger_Owner_Active__c==false;
            
            if((!oldLeadIsNo && newLeadIsNo) || (!oldLeadIsInactive && newLeadIsInactive))
            {
                ownerIds.add(l.OwnerId);
            }
            
        }
        
        Map<Id, User> ownerMap=new Map<Id, User>([ Select Id, ProfileId, Profile.Name From User where Id IN: ownerIDs ]);
        
        for(Lead l: Trigger.new)
        {
            Lead oldLead=Trigger.oldMap.get(l.Id);
            
            Boolean oldLeadIsNo=oldLead.Status.equals('No');
            Boolean newLeadIsNo=l.Status.equals('No');
            
            Boolean oldLeadIsInactive=oldLead.Trigger_Owner_Active__c==false;
            Boolean newLeadIsInactive=l.Trigger_Owner_Active__c==false;
            
            if((!oldLeadIsNo && newLeadIsNo) || (!oldLeadIsInactive && newLeadIsInactive))
            {
                if(ownerMap.containsKey(l.OwnerId) && ownerMap.get(l.OwnerId).Profile.Name=='Sales Associate')
                {
                    Lead_Cycle__c LeadCycle=new Lead_Cycle__c();
                    LeadCycle.Lead__c=l.id;
                    LeadCycle.Cycle_Start_Date__c=l.LastTransferDate;
                    LeadCycle.Cycle_End_Date__c=system.today();
                    
                    //Important: Please don't hardcode the Id, Id may change in different Salesforce environments.
                    //If you want to populate the Owner as a specific user fetch the record using a SOQL outside the for Loop and then
                    //assign the User Id to the OwnerId field.
                    LeadCycle.OwnerId='00560000002VLHw';
                    
                    LeadCycles.add(LeadCycle);
                }
            }
            
        }   
        insert LeadCycles;
    }
}
3. Important: Please don't hardcode the Id, Id may change in different Salesforce environments. If you want to populate the Owner as a specific user fetch the record using a SOQL outside the for Loop and then assign the User Id to the OwnerId field.

4. I think by the statement "When I change the Lead Owner (User) to Active = false, the Trigger_Owner_Active__c field does NOT change with the formula field", you mean that when you change the User record from Active=true to Active=false.
Since, you are updating the User record, the formula field is update but it doesnt create a update call on Lead record. So, Lead Trigger will not execute and hence the below logic will not execute:
if(Trigger.isBefore && Trigger.isUpdate)
    {
        for (Lead l : Trigger.new)
        {
            if(system.trigger.OldMap.get(l.Id).Owner_Active__c != system.trigger.NewMap.get(l.Id).Owner_Active__c)
            {
                l.Trigger_Owner_Active__c = l.Owner_Active__c;
            }
        }
    }
To update the Trigger_Owner_Active__c field value when the User record becomes Active=false. You need to write an after update Trigger on User object.
Like below:
trigger UserTrigger on User(after update)
{
    if(Trigger.isAfter && Trigger.isUpdate)
    {
        Set<Id> userIds=new Set<Id>();
        for(User userRef: Trigger.New)
        {
            //I am assuming the field on User is IsActive which is made false.
            //Take the field which is used in your Org accordingly.
            if(system.Trigger.OldMap.get(userRef.Id).IsActive!=false && system.Trigger.NewMap.get(userRef.Id).IsActive==false)
            {
                userIds.add(userRef.Id);
            }
        }
        
        //Lead records to be updated.
        List<Lead> leadRecordsToUpdate=new List<Lead>();
        
        //Query the Lead records for the update User Record Ids.
        for(Lead leadRecord: [SELECT Trigger_Owner_Active__c FROM Lead WHERE OwnerId IN :userIds])
        {
            //If the Trigger_Owner_Active__c field value on the Lead is not false, then only the Lead record should be updated.
            if(leadRecord.Trigger_Owner_Active__c!=false)
            {
                leadRecord.Trigger_Owner_Active__c=false;
                leadRecordsToUpdate.add(leadRecord);
            }
        }
        
        //Update the Lead Record.
        //On update, the Lead Trigger will get executed and the login in the Trigger will work as needed.
        update leadRecordsToUpdate;
    }
}

Please let me know if this helps.
If yes, please mark the Question as Solved.


Thanks and Regards,
Anirudh Singh
This was selected as the best answer
Elliot32Elliot32
Hi Anirudh - Thanks for the help with this one. The Lead trigger works great, however, I get the following error with the User trigger: 

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger EPS_LeadCycleCreator_User caused an unexpected exception, contact your administrator: EPS_LeadCycleCreator_User: execution of AfterUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 00Qm0000003yctsEAA; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): Lead, original object: User: []: Trigger.EPS_LeadCycleCreator_User: line 30, column 1

I was trying to use this website as a resource to recify: http://www.tgerm.com/2012/04/mixeddmloperation-dml-operation-on.html. Abstractly, I understand what the problem is but my lack of Apex experience makes it difficult to get around this error. Can you provide any guidance into how to rectify? I attached the code again. Thanks.

Elliot
 
trigger EPS_LeadCycleCreator_User on User (after update)
{
    if(Trigger.isAfter && Trigger.isUpdate)
    {
        Set<Id> userIds=new Set<Id>();
        for(User userRef: Trigger.New)
        {
            if(system.Trigger.OldMap.get(userRef.Id).IsActive!=false && system.Trigger.NewMap.get(userRef.Id).IsActive==false)
            {
                userIds.add(userRef.Id);
            }
        }
        
        //Lead records to be updated.
        List<Lead> leadRecordsToUpdate=new List<Lead>();
        
        //Query the Lead records for the update User Record Ids.
        for(Lead leadRecord: [SELECT Trigger_Owner_Active__c FROM Lead WHERE OwnerId IN :userIds])
        {
            //If the Trigger_Owner_Active__c field value on the Lead is not false, then only the Lead record should be updated.
            if(leadRecord.Trigger_Owner_Active__c!=false)
            {
                leadRecord.Trigger_Owner_Active__c=false;
                leadRecordsToUpdate.add(leadRecord);
            }
        }
        
        //Update the Lead Record.
        //On update, the Lead Trigger will get executed and the login in the Trigger will work as needed.
        update leadRecordsToUpdate;
    }
}