[SalesForce] Make Validation Rule bypass if TRIGGER is run

Is there a way to make a Validation Rule BYPASS via it's logic when a Trigger is doing it's job?

I've already tried various methods including

  • setting a hidden field on the object in the Trigger (can't because
    the Validation Rule denies editing of the object, Opportunity, when
    it's Closed Won)
  • setting a bypass field in the Parent (Account) but everywhere I try,
    I'm coming across limitations (it's read only, or it simply doesn't
    work when I know the Val Rule is correct)
  • many variations of these, and more

Situation:

  1. Opps are only editable (at ALL) by three profiles once they are
    Closed Won (by Validation Rule).

  2. Trigger has to TOUCH groups of Opps (including Closed Wons) at times
    (just edit/save, that's it).

  3. I can't bypass the Validation Rule based on Profile (but when a
    Profile causes the TRIGGER to run, I need the TRIGGER to bypass the
    Validation Rule).

Up until recently, there was not a problem because the Profiles in question would never touch the Opps. But the main Trigger needs (now) to ALSO be run if the ACCOUNT it touched, reading that Account's OPPORTUNITIES and writing data back to the Account. Trying to get this Trigger (below) OR my original (500+ line) trigger to work with this has become a nightmare.

So:

Opportunity Trigger "works fine", and doesn't care about the Validation Rule since the Profiles in question would never edit (they can't) a Closed Won opp. BUT if those Profiles edit an ACCOUNT, we want the Opp Trigger to run on each Opp (including Closed Wons).

Ideally, I wish I could "run the trigger as a different profile", but that apparently is not possible either except in Test Classes.

Account Trigger

trigger PiggybackAccountTrigger on Account (after update) {

    Account[] acctsInTriggerSet = new List<Account>();
    Opportunity[] opptysToUpdate= new List<Opportunity>();

    for(Account a :Trigger.new){  
        acctsInTriggerSet.add(a);
    }

    if( PiggybackTriggerManager.PBOppFromAccountCheck == true ){
        //Do nothing, the calculation is done
    } else {
        For( Opportunity o : [SELECT id, Name
                              FROM Opportunity 
                              WHERE Accountid 
                              IN :acctsInTriggerSet] ) {
            opptysToUpdate.add(o); 
        } 
        update opptysToUpdate;
    }

}

Opportunity Trigger

trigger PiggybackTrigger on Opportunity (after delete, after insert, after update) {


    List<Opportunity> listOpportunity =     new List<Opportunity>();
    List<Id> listOpportunityId =            new List<Id>();
    List<Id> listAccountId =                new List<Id>();

    Map<Id,Opportunity> mapIdOpportunity =  new Map<Id,Opportunity>();
    List<String> listProductNames =         new List<String>();
    PiggybackTriggerManager objManager =    new PiggybackTriggerManager();


// ----------------------------------------------------------------------------------------------------
    if (trigger.isInsert || trigger.isUpdate) {       // INSERTED or UPDATED opp.........
        for(Opportunity thisOpportunity: trigger.new) {
            if (trigger.isUpdate) {
                if (Trigger.oldMap.get(thisOpportunity.Id).Product__c != null) {
                    listProductNames.add(Trigger.oldMap.get(thisOpportunity.Id).Product__c);
                }
            }

            if (thisOpportunity.Product__c != null) {
                listAccountId.add(thisOpportunity.AccountId);
                listProductNames.add(thisOpportunity.Product__c);
            }
        }
    } else { //  DELETED opps.............................
        for(Opportunity thisOpportunity: trigger.old) {
            if (thisOpportunity.Product__c != null) {
                listAccountId.add(thisOpportunity.AccountId);
                listProductNames.add(thisOpportunity.Product__c);
            }
        }
    }
// ----------------------------------------------------------------------------------------------------


    // Prepare Map for Products...
    List<Piggyback_OppToAcct__c> listPiggyback_OppToAcct =  new List<Piggyback_OppToAcct__c>();
    listPiggyback_OppToAcct =                               new List<Piggyback_OppToAcct__c>([
        Select  p.Trackable__c, 
                p.Subscription__c, 
                p.Subscription_Gaps__c, 
                p.Client_Marker_Only__c, 
                p.FIELD_Closed_Won_Owner__c, 
                p.Piggyback_Product_Reference__c, 
                p.CloseWon_Regardless__c,
                p.Name, 
                p.Id    
                    From Piggyback_OppToAcct__c p 
                    where name in :listProductNames
    ]);

    map<Id, User> userMap = new map<Id, User>([
        Select  u.Id, 
                u.Name 
                    From User u
    ]);


    //Get Account and all opportunities related to these Opp's Account...

    map<String, Piggyback_OppToAcct__c> mapStringPiggyback = new map<String, Piggyback_OppToAcct__c> ();

    for(Piggyback_OppToAcct__c curr:listPiggyback_OppToAcct) {
        mapStringPiggyback.put(curr.Name, curr);
    }

    Map<Id, RecordType> mapIdRecordType = new Map<Id, RecordType>([
        Select  r.SobjectType, 
                r.Name, 
                r.Id    
                    From RecordType r 
                    where r.SobjectType = 'Opportunity'
    ]);

    //Get Account and all opportunities related to these Opp's Account...
    // any fields being referenced need to be pulled in here.......................
    Map<Id, Account> 
        mapIdAccount = new  
        Map<Id, Account>([
            Select 
                a.Id, 
                a.pb_Filter_Edu_NewLd_DEC_Other_Latest__c, 
                pb_Filter_Coll_LostOpp_Latest__c,
                pb_Filter_Edu_LostOpp_Latest__c,
                    (Select     Id, 
                                RecordTypeId, 
                                CloseDate,
                                OwnerId, 
                                Product__c, 
                                CreatedDate, 
                                StageName, 
                                Sub_Stage__c, 
                                Purchase_Amount__c, 
                                Charting_Start__c, 
                                Charting_Expiration__c, 
                                Charting_Package__c, 
                                Charting_Charge_Type__c,
                                Summary_Of_Receivables__c,
                                Payment_Plan__c,
                                Promotions_Received__c,
                                Promotions_Received_Used__c
                                    From Opportunities 
                                    where Product__c != null order by createddate ) 
                    From Account a where id in :listAccountId
        ]);

        // NOTE "order by" above... which sorts the Opps into CreateDate order..................
//     (eventually needs updating to CloseDate order with backup of CreateDate possibly.............)



    Map<Id, Account> updateMap = new Map<Id, Account>{};
// previous method...    List<Account> updateList = new List<Account>();

    for(Id thisAccountId: mapIdAccount.keySet()) {    //  Begin For loop of ACCOUNTS.......
        SObject accObj = new Account(Id = thisAccountId);

        //Additional loop to clear out old values....
        for(Piggyback_OppToAcct__c o:listPiggyback_OppToAcct) {
// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
//   nulls set to ALL values internally 2012-10-24     accObj = objManager.SetNullFieldsForAccount(accObj, o.Piggyback_Product_Reference__c);
// *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
        }

        Account objAccount = mapIdAccount.get(thisAccountId);

        List<Opportunity> listCurrOps = new List<Opportunity>();
        listCurrOps = objAccount.Opportunities;

        Map<String, List<Opportunity>> mapString_Opportunity = new Map<String, List<Opportunity>> ();
        mapString_Opportunity = objManager.GetDistinctProductsForAccount(objAccount);



.............................................................................
.... removed various Calculations performed to be "put into" the Account ....
.... removed accObj.put section where data is actually set to Account ....
.............................................................................




            PiggybackTriggerManager.PBOppFromAccountCheck = true;

            if( !updateMap.containsKey((String)accObj.get('Id') ) ) { //check that Acc Id not already added
                updateMap.put((String)accObj.get('Id'), (Account)accObj);

                if(updateMap.size() >= 100) {
                    Database.update( updateMap.values() );
                }
            }

        } // -- end FOR currProd mapString --

    } //  END For loop of ACCOUNTS.......

// catchall for any remaining...
    Database.update( updateMap.values() );

}

EDIT Code for BEFORE TRIGGER written… final fixed version

trigger PiggybackAccountTriggerNoVal on Account (before update) {

    Account[] acctsInTriggerSet2 = new List<Account>();

    for(Account a :Trigger.new){  

        Account oldVal = Trigger.oldMap.get(a.Id);

        if( a.Opp_Closed_Won_Val_Bypass__c == true ) {
              a.Opp_Closed_Won_Val_Bypass2__c = true;
        } else if ( PiggybackTriggerManager.PBOppFromAccountCheck != true ) {
              a.Opp_Closed_Won_Val_Bypass__c = true;
        }

    }

}

/* Two WORKFLOWS work to clear the Bypass fields when needed to make this work */

Best Answer

Use a hierarchical custom setting.

The setting just has two fields.

Location -> Lookup to user/profile
Skip_Validation__c -> Set it to true to skip validation.

Update your validation rules to not fire when Skip validation = true for the custom setting.

&& NOT($Setup.MyCustomSetting__c.Skip_Validation__c)

As the last step, your trigger shuts off validation at the start and enables it at the end.

MyCustomSetting__c tempSkipValidation = 
  MyCustomSetting__c.getInstance();

if(tempSkipValidation == null) {
  tempSkipValidation = new MyCustomSetting__c();
}

tempSkipValidation.Skip_Validation__c = true;
upsert tempSkipValidation;

//WHATEVER TRIGGER NORMALLY DOES HERE

tempSkipValidation.Skip_Validation__c = false;
update tempSkipValidation;