You need to sign in to do that
Don't have an account?
ChickenOrBeef
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
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
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
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
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
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
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
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....
In this way, you dont have to update it to true for every single condition as well.
Thanks,
justin~sfdc
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;
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.
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
@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...
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
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;
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.
But I did pick up some useful tips from you anyway, so still much appreciated!
-Greg
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 edit the rule to 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 and at the end of the trigger, after all is done, set the field back to false. 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.