1

In each of my validation rules I have specified a list of Record Type IDs which the rule should apply to.

AND(

OR( 
RecordTypeId = '012700000005r08', 
RecordTypeId = '012700000005qzy', 
RecordTypeId = '012700000005r0D', 
RecordTypeId = '012700000005r0N'
),

AND( 
NOT(TEXT(StageName) = "1) Suspect"), 
NOT(TEXT(StageName) = "On Hold"), 
NOT(TEXT(StageName) = "Closed/Lost") 
),

OR( 
ISBLANK(Annual_Settlement_Value__c), 
Annual_Settlement_Value__c <= 0, 
ISBLANK(Average_transaction_Value_POS__c), 
Average_transaction_Value_POS__c <= 0) 

)

When a user makes a change to a related record and that change fires an Apex Trigger, that trigger is being blocked by the validation rule. I had thought that the trigger was ignoring the specified Record Type ID.

The records being changed are from a custom object called Account Asset. When the record is created, it fires the below trigger which is designed to populate the fields listed on opportunities, of a type that isn't used by my division's users. None of the changes that this trigger is supposed to make are needed by my users and every field that's referenced on the Account Asset object (apart from the standard fields) will be null.

    public class UAccountAsset 
{
  public static void opportunityPopulation(List<Account_Asset__c> records, Map<ID,Account_Asset__c> oldMap)
  {
    boolean isInsert = (oldMap == null);
    List<Opportunity> opportunitiesToUpdate = new List<Opportunity>();
    for(Account_Asset__c aac : records)
    {
      //system.debug('-- start loop: ' + aac);
      if(aac.Opportunity__c!=null &&
      (isInsert == true || (isInsert==false && oldMap.get(aac.Id).Opportunity__c != aac.Opportunity__c)))
      {
        Opportunity o = new Opportunity(id=aac.Opportunity__c);
        o.Account_Asset__c = aac.Id;
        opportunitiesToUpdate.add(o);
        //system.debug('-- filtered: ' + o);
      }
    }
    //system.debug('-- inclass: ' + isinsert + ' ' + opportunitiestoupdate);

    if(opportunitiesToUpdate.size()>0)
    {
      update opportunitiesToUpdate;
    }
  }



  public static void setAccountAssetStoppedPumping(Set<Id> recordIds)
  {  
    List<Account_Asset__c> recordsToUpdate = new List<Account_Asset__c>();

    DateTime now = DateTime.now();

    Date currentMonth = Date.newInstance(now.year(), now.month(), 1); //Set to the first of the month
    Date currentMonthMinus1 = Date.newInstance(now.year(), now.addMonths(-1).month(), 1);
    Date currentMonthMinus2 = Date.newInstance(now.year(), now.addMonths(-2).month(), 1);
    Date currentMonthMinus3 = Date.newInstance(now.year(), now.addMonths(-3).month(), 1);

    for(Account_Asset__c record : [select 
                      Id,
                      Stopped_Pumping__c,
                      (select
                          Id,
                          Gallon_Month__c
                        from
                          Gallons_Data__r
                        where
                          (Gallon_Month__c <= :currentMonth and
                          Gallon_Month__c > :currentMonthMinus3)
                        order by 
                          Gallon_Month__c desc)
                    from
                      Account_Asset__c
                    where 
                      Id in :recordIds])
    {
      UGears.UDebug.printDebug('GALLONS DATA: ' + record.Gallons_Data__r);

      Boolean stoppedPumping = false;
      Boolean hasCurrentMonthData = false;
      Boolean hasCurrentMonthMinus1Data = false;
      Boolean hasCurrentMonthMinus2Data = false;

      for(Gallons_Data__c gallonsData : record.Gallons_Data__r)
      {
        if(gallonsData.Gallon_Month__c != null)
        {
          if(gallonsData.Gallon_Month__c.month() == currentMonth.month())
          {
            //We have a record for this month
            stoppedPumping = false;
            hasCurrentMonthData = true;

            break;
          }
          else if(gallonsData.Gallon_Month__c.month() == currentMonthMinus1.month())
          {
            hasCurrentMonthMinus1Data = true;
          }
          else if(gallonsData.Gallon_Month__c.month() == currentMonthMinus2.month())
          {
            hasCurrentMonthMinus2Data = true;
          }    
        }
      }

      if(hasCurrentMonthData == false)
      {
        if(hasCurrentMonthMinus1Data == true && hasCurrentMonthMinus2Data == true)
        {
          stoppedPumping = true;
        }
      }

      record.Stopped_Pumping__c = stoppedPumping;  

      recordsToUpdate.add(record);          
    }    

    UGears.UDebug.printDebug('RECORDS TO UPDATE: ' + recordsToUpdate);            

    try
    {
      update recordsToUpdate;
    }  
    catch(DmlException e)
    {
      System.debug('EXCEPTION: ' + e);
    }  
  }

  public static void setAccountAssetsSignedButNotActivated(Set<Id> recordIds)
  {
    List<Account_Asset__c> recordsToUpdate = new List<Account_Asset__c>();

    for(Account_Asset__c record : [select 
                      Id,
                      Signed_But_Not_Activated__c,
                      Core_Card_Created__c                      
                    from
                      Account_Asset__c
                    where 
                      Id in :recordIds and
                      Activated__c = null and 
                      Core_Card_Created__c != null and 
                      IsDeleted = false])
    {
      if(record.Core_Card_Created__c < Date.today().addDays(-15))
      {
        record.Signed_But_Not_Activated__c = true;
      }
      else
      {
        record.Signed_But_Not_Activated__c = false;
      }

      recordsToUpdate.add(record);
    }

    try
    {
      update recordsToUpdate;
    }
    catch(DmlException e)
    {
      System.debug('EXCEPTION: ' + e);
    }
  }

  public static void setAccountAssetsActivatedButNotPumped(Set<Id> recordIds)
  {
    List<Account_Asset__c> recordsToUpdate = new List<Account_Asset__c>();

    for(Account_Asset__c record : [select 
                      Id,
                      Activated__c,
                      Last_Activity__c                      
                    from
                      Account_Asset__c
                    where 
                      Id in :recordIds and                      
                      Status_N__c = 'Active' and 
                      IsDeleted = false])
    {
      if(record.Last_Activity__c == null && record.Activated__c < Date.today().addDays(-30))
      {
        record.Activated_Not_Pumped__c = true;
      }
      else
      {
        record.Activated_Not_Pumped__c = false;
      }

      recordsToUpdate.add(record);
    }

    UGears.UDebug.printDebug('RECORDS TO UPDATE: ' + recordsToUpdate);

    try
    {
      update recordsToUpdate;
    }
    catch(DmlException e)
    {
      System.debug('EXCEPTION: ' + e);
    }
  }
}
  • Alex S - note that VRs always fire after before insert/update triggers execute. You also don;t need to use hard coded recordType ids in the VR rules, you can reference the developer names – cropredy Mar 2 '15 at 17:34
  • @crop1645 thanks, I was aware of the order of execution. Does using developer names offer any benefits over Record Type Ids? – Alex S Mar 2 '15 at 17:37
  • (1) documentation clarity is the primary advantage; (2) if you are initially defining recordTypes in Sandbox and building logic accordingly, the ids wont be the same when you deploy to PROD – cropredy Mar 2 '15 at 20:05
  • @crop1645 ok, thanks for the suggestion. – Alex S Mar 2 '15 at 20:07
0

The solution is to specify both the Record Type ID and the Profile ID which the validation rule should apply to.

Since Salesforce knows which user has made the change to a record, which caused the trigger to fire, it can check that the validation rule should be applied for that user profile, as well as the record type.

To avoid having to update the list of specified Profile IDs in my validation rules every time a new profile is created, I have created a hierarchy custom setting for our Profile IDs, containing check box fields. Since the rules will usually apply to all users, I only have to specify the one field containing all of our Profile IDs when I create the rule -

$Setup.WEPID__c.WE_All__c = True

along with our Record Type IDs.

I have also created separate fields which - as an example - group users by their team, which I can list if I only want the rule to apply to certain groups of users.

$Setup.WEPID__c.WE_Product__c = True, 
$Setup.WEPID__c.WE_RM__c = True, 
$Setup.WEPID__c.WE_Sales__c = True 

As long as the Profile ID of the user who's changing the record is not listed in the specified custom setting field's values (or if it is, the check box is not ticked), the rule will not block any triggers that they fire when they change a record.

| improve this answer | |
  • Seems to me you just put a bandaid on it. It still does not solve the issue as to why the rule was evaluating to true when you think it should not have....Either you are misunderstanding what the trigger is doing or your validation rule is not working as expected. Without more context though there is not much of an answer anyone here can give. Maybe an example of a record that is being inserted/updated that is causing the unexpected result (along with field and values) – Eric Mar 2 '15 at 4:55
  • @Eric thanks, I see your point. I was confused by the fact that I thought changes were being made to custom objects linked to opportunities with a non-matching record type. You've prompted me to look into this further and that's not the case. Even so, I can't tell why the trigger is trying to make changes to our opportunities that then cause it to be blocked. I need a way for it to distinguish between records that need updating (other division's) and those that don't (ours). – Alex S Mar 2 '15 at 7:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.