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
ChickenOrBeefChickenOrBeef 

How to make trigger bypass validation rules?

Hey everyone,

Let me briefly explain the trigger I wrote (my frist trigger actually). On the opportunity, we have a look-up field to other opportunities called 'Renewed Opportunity'. The idea is that when you create an opp that is a renewal, you use that field to reference the previous opportunity that is being renewed.

The trigger looks at the stage of the new renewal opp and updates a field on the referenced 'Renewed Opporunity' called 'Renewal Status'. The goal is to be able to see if an opp ended up renewing or not.

The issue here is that many of our older opportunities don't meet the validation rules we currently have in place. So if you populate that look-up field with an opp with missing data, you'll get an error message preventing you from updating the new, renewal opp you're creating.

Obviously, one solution would be to go back and update all our older opps, but that's practically impossible. So here is my question:

1) Is there something I can write in either the trigger or the validation rules to set up a bypass? For the validation rules, I tried writing in 'NOT(ISCHANGED(Renewal_Status__c))', but it seems that as long as a record is referenced, the validation rules will be required. The trigger doesn't even have to update the record.

2) If option 1 is not possible, is there a way to write an error message in the trigger that at least explains to the user in a clear manner that they have to update the referenced opp? I'd also like to list in the error the validation rules that must be met, but that would be a bonus.

In case you want to take a look at my trigger, here it is:


trigger RenewalProcess on Opportunity (after insert, after update) {
   
   Set<String> allOpps = new Set<String>();
    for(Opportunity renewalOpp : Trigger.new) {
        if (renewalOpp.Renewed_Opportunity__c != null) {
            allOpps.add(renewalOpp.Renewed_Opportunity__c);
         }
    }

    List<Opportunity> potentialOpps = [SELECT Id FROM Opportunity WHERE Id IN :allOpps];

    Map<String,Opportunity> opportunityMap = new Map<String,Opportunity>();
        for (Opportunity o : potentialOpps) {
            opportunityMap.put(o.id,o);
        }
     
     List<Opportunity> oppsToUpdate = new List<Opportunity>();
       
        for (Opportunity renewalOpp : Trigger.new) {
            if (renewalOpp.Renewed_Opportunity__c != null && renewalOpp.StageName.equals('Closed Won')) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.Renewal_Status__c = 'Renewed';
                oppsToUpdate.add(renewedOpp);
            }
            else if(renewalOpp.Renewed_Opportunity__c != null && !renewalOpp.IsClosed) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.Renewal_Status__c = 'In Negotiations';
                oppsToUpdate.add(renewedOpp);
            }
            else if(renewalOpp.Renewed_Opportunity__c != null && (renewalOpp.StageName.equals('Closed Lost') || renewalOpp.StageName.equals('Closed Stalled'))) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.Renewal_Status__c = 'Did Not Renew';
                oppsToUpdate.add(renewedOpp);
            }
           
           
        }
   
    update oppsToUpdate;
   
}



Let me know if you need anymore info from me!
-Greg
Best Answer chosen by ChickenOrBeef
justin_sfdcjustin_sfdc
Hi there,

The other solution is kinda like the second one. Introduce a dummy checkbox field that is not visible to anyone. And let the Validation Rule bypass the record if that checkbox is true. Now, in your trigger before you update oppsToUpdate, first update that dummyCheckbox to true. In this way, the validation rule will be bypassed. Also, make sure you make the checkbox false as soon as the trigger does its action. In order to do this, you can create a workflow that will do the field update of that dummy checkbox to false whenever the value is true.

Please let me know if you would need any help on this.

Thanks,
Justin~sfdc

All Answers

Satish_SFDCSatish_SFDC
Is it possible to deactivate the validation rules. This might work for a short time but is not a permanent solution.

The other option would be to modify the validation rules.

First create a checkbox field and modify the validation rule by adding an AND clause which checks and runs the validation rule only if this checkbox is true.
By default this will be false, so the validation rule will always fail.


Regards,
Satish Kumar
ChickenOrBeefChickenOrBeef
Hey Satish,

1) Deactivating the validation rules is not an option since we're not doing a mass import/update or anything like that. This is something our users will encounter every time they create/update a renewal opportunity.

2) I think what I'm going to do is simply bypass the validation rules by only requiring them if the Renewal Status field is blank. This may provide a slight loophole in our rules, but I suppose it works for now.

If anyone has any other idea, let me know!

Thanks,
Greg
Satish_SFDCSatish_SFDC
Well, 
Cannot think of any other option apart from the ones you mentioned above. But let us know if you have any issues, and we will try it out.

Regards,
Satish Kumar
justin_sfdcjustin_sfdc
Hi there,

The other solution is kinda like the second one. Introduce a dummy checkbox field that is not visible to anyone. And let the Validation Rule bypass the record if that checkbox is true. Now, in your trigger before you update oppsToUpdate, first update that dummyCheckbox to true. In this way, the validation rule will be bypassed. Also, make sure you make the checkbox false as soon as the trigger does its action. In order to do this, you can create a workflow that will do the field update of that dummy checkbox to false whenever the value is true.

Please let me know if you would need any help on this.

Thanks,
Justin~sfdc
This was selected as the best answer
ChickenOrBeefChickenOrBeef
Hey Justin,

So I would edit my code to say this?

for (Opportunity renewalOpp : Trigger.new) {

            if (renewalOpp.Renewed_Opportunity__c != null && renewalOpp.StageName.equals('Closed Won')) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.dummyCheckbox = TRUE;
                renewedOpp.Renewal_Status__c = 'Renewed';
                oppsToUpdate.add(renewedOpp);
            }
            else if(renewalOpp.Renewed_Opportunity__c != null && !renewalOpp.IsClosed) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.dummyCheckbox = TRUE;
                renewedOpp.Renewal_Status__c = 'In Negotiations';
                oppsToUpdate.add(renewedOpp);
            }
            else if(renewalOpp.Renewed_Opportunity__c != null && (renewalOpp.StageName.equals('Closed Lost') || renewalOpp.StageName.equals('Closed Stalled'))) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.dummyCheckbox = TRUE;
                renewedOpp.Renewal_Status__c = 'Did Not Renew';
                oppsToUpdate.add(renewedOpp);
            }


Or would that go somewhere else in the trigger?

And you're basically saying that the trigger will make the field true while the trigger is run, and then a workflow rule will make it false as soon as the trigger is over? Interesting....
justin_sfdcjustin_sfdc
Yes, that should work. I would update the dummy checkbox separately  after the for loop and then continue with the rest of the code. 
In this way, you dont have to update it to true for every single condition as well. 

Thanks,
justin~sfdc
ChickenOrBeefChickenOrBeef
Hey Justin,

Is this what you're referring to? See bolded for new code:


List<Opportunity> firstOppsToUpdate = new List<Opportunity>();

        for (Opportunity renewalOpp : Trigger.new) {
             
if (renewalOpp.Renewed_Opportunity__c != null) {
             Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
             renewedOpp.dummyCheckbox = TRUE;

             firstOppsToUpdate.add(renewedOpp);
           }
        }

update firstOppsToUpdate;


List<Opportunity> oppsToUpdate = new List<Opportunity>();
      
        for (Opportunity renewalOpp : Trigger.new) {
            if (renewalOpp.Renewed_Opportunity__c != null && renewalOpp.StageName.equals('Closed Won')) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.Renewal_Status__c = 'Renewed';
                oppsToUpdate.add(renewedOpp);
            }
            else if(renewalOpp.Renewed_Opportunity__c != null && !renewalOpp.IsClosed) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.Renewal_Status__c = 'In Negotiations';
                oppsToUpdate.add(renewedOpp);
            }
            else if(renewalOpp.Renewed_Opportunity__c != null && (renewalOpp.StageName.equals('Closed Lost') || renewalOpp.StageName.equals('Closed Stalled'))) {
                Opportunity renewedOpp = opportunityMap.get(renewalOpp.Renewed_Opportunity__c);
                renewedOpp.Renewal_Status__c = 'Did Not Renew';
                oppsToUpdate.add(renewedOpp);
            }
          
          
        }
  
    update oppsToUpdate;
justin_sfdcjustin_sfdc
Yes. let me know if that works!
ChickenOrBeefChickenOrBeef
It works! Thanks Justin!
justin_sfdcjustin_sfdc
My pleasure! :)
SFDC HedgehogSFDC Hedgehog
Hi Everyone,

I know I'm coming late to this party, but when would be the best time to UN-check the magical dummyCheckbox field?

"AfterUpdate" triggers are called before workflow rules - which seems like this solution implies some kind of hourly batch apex or some such to sweep thru and un-check any opportunity (or the record type in question) and set it fo false... otherwise all that you've accomplished is turning off VR for a set of records permaneltly

Thanks for any info.
 
ChickenOrBeefChickenOrBeef
Hey Hedgehog,

You need to have a workflow rule set the field to False. So you'll create a workflow rule that runs when "created, or any time it's edited" and simply have the workflow rule update the field to false. Your apex code will set the field to true while it runs, then the workflow rule will set the field to False after the update occurs. So that record won't be permanantly bypassed.

-Greg
SFDC HedgehogSFDC Hedgehog
Ohhh - OK.  Thanks Greg - makes total sense :-)
 
unidhaunidha
I have scenarion that during lead conversion I need to update the opportunity and create opportunity product.So in Lead after trigger, I update opportunity and at opportunity trigger i have method to create Opportunity product.I am able to by pass the validation rules when using custom setting or dummy field but I having hard time when to reset bypass value.I put the reset at last of the trigger code and also have try to update it using Process Builder , once I run it no longer by pass validation rule.So how do I reset this bypass value after the transaction finish completely?So I can reuse back in other transaction.If not , it will still not by pass the validation rule?
SFDC HedgehogSFDC Hedgehog
Hi,
@ChickenorBeef;   Is there a reason you chose the after event trigger for your opp updates?  
It seems like you'd be better off putting this in a before trigger - it would make your life easier.

Just saying...
 
ChickenOrBeefChickenOrBeef
Hey Hedgehog,

It was my first trigger, so I didn't know what I was doing. I thought everything had to be an After trigger if I was updating a different record.

With that said, are you saying my life would be easier because Before triggers fire earlier in the chain of execution?

-Greg
SFDC HedgehogSFDC Hedgehog
@ChickenorBeef-
Well, After-event triggers can cause cascading calls.  
They're typically used when an update on a set of records of one type (in your case, opps) would require you to make an update on other related records (say, the account that the opps are related to).   Otherwise, you have to set a static boolean that gates the recursion.

In your case, your list of additionally-updated opps (potentialOpps) would fire the RenewalProcess trigger again whether it's a before or after, so I guess it doesn't make a difference.   especially if a renewedOpp points to a renewedOpp which points to a renewedOpp, etc....

IDK about your data, but maybe you can build a single set of   all the potential opps that have possibly based on the accountID, and process them all at once?   All potential opps are restrained by the Accounts they are linked to, no?   So you can use that as your constraint to process your records - unless the Renewed_Opportunity__c can point to a opp that is not related to the account of the trigger set, in which case this below code won't work;
 
trigger RenewalProcess on Opportunity (after insert, after update) {
   
   Set<String> allAccts = new Set<String>();
   if(OpportunityProcessor.retAlreadyUpdated() != true)  {
       OpportunityProcessor.setAlreadyUpdated();   // avoid recursion
       
        for(Opportunity renewalOpp : Trigger.new) {
            if (renewalOpp.Renewed_Opportunity__c != null) {
                allAccts.add(renewalOpp.AccountId);
             }
        }

        // ALL opportunities that have potentially changed
        Map<Id,Opportunity>  potentialOpps = new Map<Id,Opportunity>([SELECT Id, AccountId, StageName, Renewed_Opportunity__c, Renewal_Status__c FROM Opportunity WHERE AccountId IN :allAccts]);
        Set<Opportunity> oppsToUpdate = new Set<Opportunity>();  // SET guarantees unique record
        List<Opportunity> listToUpdate = new List<Opportunity>();
       
            for (Opportunity renewalOpp : potentialOpps.Values()) {
                if (renewalOpp.Renewed_Opportunity__c != null && renewalOpp.StageName.equals('Closed Won')) {
                    renewalOpp.Renewal_Status__c = 'Renewed';
                    oppsToUpdate.add(potentialOpps.get(renewalOpp.Id));
                    }
                else if(renewalOpp.Renewed_Opportunity__c != null && !renewalOpp.IsClosed) {
                    renewalOpp.Renewal_Status__c = 'In Negotiations';
                    oppsToUpdate.add(potentialOpps.get(renewalOpp.Id));
                    }
                else if(renewalOpp.Renewed_Opportunity__c != null 
                            && (renewalOpp.StageName.equals('Closed Lost') 
                            || renewalOpp.StageName.equals('Closed Stalled'))) {
                    renewalOpp.Renewal_Status__c = 'Did Not Renew';
                    oppsToUpdate.add(potentialOpps.get(renewalOpp.Id));
                    }
            }
        for(Opportunity o :oppsToUpdate) {
            listToUpdate.add(o);
            }
        update listToUpdate;   // because SFDC requires a List - not a Set;   
        }
}


public class OpportunityProcessor {
    private static boolean alreadyUpdated = false;
    
    public static boolean retAlreadyUpdated () {
        return alreadyUpdated;
        }
    public static void setAlreadyUpdated() {
        alreadyUpdated = true;
        }
    public static void ResetAlreadyUpdated() {
        alreadyUpdated = false;
        }     
}

But the advantage to this is, you call the trigger once and there is one and only one SELECT and UPDATE.... But I haven't tested it....   
Hope this helps.

 
ChickenOrBeefChickenOrBeef
That was my first trigger, so I handle my code in a much cleaner way now. And I don't even use that trigger anymore lol, so I'm sorry you wrote all that out. I was just curious about the advantages of Before over After.

But I did pick up some useful tips from you anyway, so still much appreciated!

-Greg
ClaiborneClaiborne
Expanding a bit, I ran into a problem when I could not add a field to the Case Milestone object. But, when adding fields to a validation rule, you can add any User field as $User.<fieldName>.

So, I created a field on the User object called Override Validation. Normally, you should hide this field for general editing.

Then, assuming that the old validation rule is something like  
OR(field = 'a', field = 'b')
edit the rule to
AND ( OR(field = 'a', field = 'b'), NOT($user.Override_Validation__c))
So, if Override Validation is false, the rule acts as before. But if Override Validation is true, the rule fails to fire.

Finally, in the trigger or apex code, at the beginning, set Override Validation to true
User u = new User(
    Id = userInfo.getUserId,
    Override_Validation__c = true);
update u;
and at the end of the trigger, after all is done, set the field back to false.
u.Override_Validation__c = false;
update u;
This method provides a way to override any validation rule on any object without having to create a new field on the object. And it only impacts validation rules that you edit to use the new field.


 
Sandip Kulkarni 9Sandip Kulkarni 9
By Pass Validation rule on Quick Action  
  1.  Create a checkbox field ByPass (default value un checked)
  2. Create a validation rule with criteria AND(NOT(ByPass__c ), NOT(ISCHANGED(ByPass__c)) , CUSTOM_VALIDTION_LOGIC_HERE)
  3. Create a Quick Action and add fields to by pass validations for the fields
  4.  Add ByPass checkbox to Quick action layout with predefined value set to checked
  5. Create a process buider if ByPass is set to true then update ByPass to false
Ezra Kenigsberg @ BCEzra Kenigsberg @ BC
Props to @Sandip above--he's seized on a key improvement by using ISCHANGED(). I disagree with his Validation Rule using two clauses--I recommend instead following these steps (https://sites.google.com/site/ezrakenigsberg/blog/abetterwaytobypassdatavalidation).