Skip to content

July 11, 2015

Developing a Salesforce Trigger

by Joe Kuan

Lets assume we need to develop a trigger to perform two checks on opportunities:

  • When creating or updating an opportunity, the Type selection box must be selected with a value
  • When an opportunity stage is selected to ‘Closed/Won’, the opportunity must contain Products (OpportunityLineItem) entry

First, look at how we create an opportunity in Salesforce:

screen

Adding an opportunity with products is a two stages process. The opportunity need to be created first, then the products can be added in the subsequent update. Since this trigger example is a check, the trigger is performed before the insert and update operations. Here is the code for checking the opportunity Type:

trigger UpsertOpportunityTypeCheck on Opportunity (before insert, before update) {

   for (Opportunity o: trigger.new) {
      if (o.Type == null) {
         o.addError('Error: Opportunity Type cannot be empty');
      }
   }
}

Here we create a trigger function named, UpsertOpportunityTypeCheck, which is called before the insert and update operations. Trigger.new is a set of triggered Salesforce objects (SObjects filtered by Opportunity type) containing the current version of record values to be inserted or updated. Trigger object contains other properties allowing us to examine record values before updated or deleted. For example, trigger.old is the opposite which contains the previous version of the records. We will later investigate in what occasions we need to examine record previous values. In this trigger, since we only interest what the Type value is currently set to, we focus on the current context which is trigger.new. So if the o.Type is null, then we report an error. Let save/compile the trigger code and sees the effect.

We create an opportunity with empty Type value and click save. As we can see, an error occurs:

screen

Lets move on to create another trigger which make sure the opportunity has product entry(ies) when the stage is set to Closed/Won. Here is the code of the trigger, UpdateOpportunityProductsCheck:

trigger UpdateOpportunityProductsCheck on Opportunity (before update) {

  for(Opportunity o: trigger.new){ 

      // Only close/won matters with products entry
      if (o.isClosed == false || o.isWon == false) {
          continue;
      }

      if (o.HasOpportunityLineItem == false) {
          o.addError('Error: Cannot close won opportunity (' + o.name + ') because there is no product entries');
      }
  }
}

First of all, this trigger can only be applied to before update because adding opportunity products is a two stage process; i.e. we need to create opportunity record first, then add products to the existing opportunity. If the opportunity is not close/won, we ignore the check. Otherwise, we report an error if there is no product (OpportunityLineItem) associated with the opportunity. Below is a screenshot of setting the opportunity to close/won without product entries:

screen

In fact, we can combine both trigger code into a single trigger function even the second trigger only supports for update. To do that, we can wrap the second part of trigger code with trigger.isUpdate condition check. So that the code is only executed when an opportunity is being updated.

trigger OpportunityCheck on Opportunity (before insert, before update) {

   for (Opportunity o: trigger.new) {
      if (o.Type == null) {
         o.addError('Error: Opportunity Type cannot be empty');
      }

      if (trigger.isUpdate) {
         // Only close/won matters with products entry
         if (o.isClosed == false || o.isWon == false) {
             continue;
         }

         ....
      }
   }
}

Salesforce provides these flexibilities for developers to implement in different approaches.

Suppose now we want to enhance the UpdateOpportunityProductsCheck trigger that when all the condition checks meet, appends the string ‘ (Won !!) ‘ to the opportunity name before updating it.

trigger UpdateOpportunityProductsCheck on Opportunity (before update) {

  for (Opportunity o: trigger.new) { 

      // Check code
      ....

      o.name += ' (Won !!) ';
  }
}

We add a product to the opportunity and set the stage to close/won. Here is the output of the opportunity with name changed.

screen

However, there is a problem. If we update the Type to another value, the trigger is always called upon close/won opportunity. Hence, the opportunity name will become longer and longer.

screen

One way to avoid this is to compare the record values between the current and previous versions. To do that, we need to retrieve the previous set of record values and compare with the current set to check whether the stage has been changed to close/won. Here is the improved code to only call it once.

trigger UpdateOpportunityProductsCheck on Opportunity (before update) {

  for (Opportunity o: trigger.new) { 

      // Check code
      ....

      Opportunity oldOpp = trigger.oldMap.get(o.id);
      if (oldOpp.isClosed == false && oldOpp.isWon == false &&
          o.isClosed == true && o.isClosed == true) {
          o.name += ' (Won !!!)';
      }
  }
}

Trigger.oldMap contains the previous version of objects. Here, we simply use the Id field to get the older version of object and compare. Now the name is only updated when we change the stage to close/won. Any other field change won’t affect the opportunity name.

There are other fields in the Trigger class which can provide additional information for the trigger operation depending on the developer needs. Alternatively, you can setup a custom boolean field to signify a specific property change if you find this approach is a bit heavy.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments

%d bloggers like this: