Salesforce - Developer - Apex Trigger

salesforce-developer

https://trailhead.salesforce.com/force_com_dev_beginner/apex_triggers/apex_triggers_intro
https://trailhead.salesforce.com/force_com_dev_beginner/apex_triggers/apex_triggers_bulk
https://trailhead.salesforce.com/en/apex_testing/apex_testing_triggers

http://salesforce.stackexchange.com/questions/138869/how-to-cover-afterundelete-method-when-you-cant-undelete/138872
http://salesforce.stackexchange.com/questions/138239/is-it-possible-to-write-trigger-for-before-after-dml/138261#138261
http://salesforce.stackexchange.com/questions/31388/cannot-insert-update-activate-entity-execution-of-afterinsert
http://salesforce.stackexchange.com/questions/104388/how-to-unit-test-exception-handling-when-the-source-of-an-exception-is-external
https://help.salesforce.com/apex/HTViewSolution?urlname=How-to-retrieve-my-records-and-data-that-has-been-lost-or-deleted-1327108681812&language=en_US
http://salesforce.stackexchange.com/questions/107521/restoring-deleted-records-using-apex
https://help.salesforce.com/articleView?id=home_delete.htm&type=0
https://developer.salesforce.com/forums/?id=906F00000008zBQIAY
http://salesforce.stackexchange.com/questions/101111/how-to-cause-dmlexception-delete
https://developer.salesforce.com/docs/atlas.en-us.apex_workbook.meta/apex_workbook/apex7_4.htm

trigger UpdateOpportunityProductGroupsTrig on OpportunityLineItem (after insert, after delete, after update) {
    if (Trigger.isUpdate) {
        UpdateOpportunityProductGroups.doUpdateOpportunityProductGroups(Trigger.Old);
    } else if (Trigger.isDelete) {
        UpdateOpportunityProductGroups.doUpdateOpportunityProductGroups(Trigger.Old);
    } else if (Trigger.isInsert) {
        UpdateOpportunityProductGroups.doUpdateOpportunityProductGroups(Trigger.New);        
    } else if (Trigger.isUndelete) {
        UpdateOpportunityProductGroups.doUpdateOpportunityProductGroups(Trigger.New);
    }

}

public class UpdateOpportunityProductGroups {
    public static void doUpdateOpportunityProductGroups(List<OpportunityLineItem> products) {
        List<String>opportunityIDs = new List<String>();
        for (OpportunityLineItem product : products) {
            opportunityIDs.add(product.opportunityId);
        }
        for (Opportunity[] opportunities : [SELECT NAME,Opportunity_Product_Groups__c FROM Opportunity WHERE Id IN :opportunityIDs]) {
            for (Opportunity opportunity : opportunities) {
                System.debug('Opportunity Name:' + opportunity.name);
                Datetime t = System.now();
                Set<String> productGroups = new Set<String>();
                for (OpportunityLineItem[] lineItems : [SELECT Product_Group__c FROM OpportunityLineItem WHERE opportunityId = :opportunity.Id]) {
                    for (OpportunityLineItem item : lineItems) {
                        if ((item.Product_Group__c != null) && (item.Product_Group__c != '')) {
                            productGroups.add(item.Product_Group__c); 
                        }
                    }
                }
                List<String> productGroupsList = new List<String>(productGroups);
                opportunity.Opportunity_Product_Groups__c = String.join(productGroupsList, ';'); 
            }
            update opportunities;
        }
    }
}

@IsTest
public class UpdateOpportunityProductGroupsTest {
    static testmethod void testNewUpdateDelete() {
        // This test is not doing anything much.  It should not
        // cause the "Too many SOQL queries" issue, but I see 
        // that error when trying to deploy a change set. 
        // That error may not be related to this test, but I
        // am adding Test.startTest and Test.stopTest anyway
        // to see if they are related somehow.
        Test.startTest();
        Datetime t = System.now();
        Account acc = new Account(Name = 'Test');
        insert acc;

        Opportunity o = new Opportunity(
            Name = 'Test Opportunity - ' + t.format(),
            AccountID = acc.Id,
            Amount = 2000,
            CloseDate=Date.today(),
            StageName='Close Won',
            Type='New Customer'
        );
        insert o;

        Product2 newProd = new Product2(Name = 'test product', family = 'test family', Product_Group__c = 'Prospect');
        insert newProd;

        // PricebookEntry.Product2.Product_Group__c
        PriceBookEntry pbEntry = new PriceBookEntry(
            UnitPrice = 300,
            PriceBook2Id = Test.getStandardPricebookId(),
            Product2Id = newProd.Id,
            IsActive = true
        );
        insert pbEntry ;

        OpportunityLineItem item = new OpportunityLineItem(
            pricebookentryid=pbEntry.Id,
            TotalPrice=2000,
            Quantity = 2,
            OpportunityID = o.Id
        );
        insert item;

        o = [SELECT Opportunity_Product_Groups__c FROM Opportunity WHERE Id = :o.Id];
        System.assertEquals('Prospect', o.Opportunity_Product_Groups__c);

        item.UnitPrice = 0;
        update item;
        o = [SELECT Opportunity_Product_Groups__c FROM Opportunity WHERE Id = :o.Id];
        System.assertEquals('Prospect', o.Opportunity_Product_Groups__c);

        ID itemId = item.Id;
        delete item;
        o = [SELECT Opportunity_Product_Groups__c FROM Opportunity WHERE Id = :o.Id];
        System.assertEquals('', o.Opportunity_Product_Groups__c == null ? '' : o.Opportunity_Product_Groups__c);

        // Was facing some error "Entity type is not undeletable" when trying to 
        // undelete item
        // System.debug('ID:' + itemId);
        // item = [SELECT Id, Name FROM OpportunityLineItem WHERE Id = :itemId ALL ROWS];
        /*
        undelete item;
        o = [SELECT Opportunity_Product_Groups__c FROM Opportunity WHERE Id = :o.Id];
        System.assertEquals('Prospect', o.Opportunity_Product_Groups__c);
*/
        Test.stopTest();
    }
}
// Salesforce - Developer - Apex - Trigger:

A trigger is a piece of code that is executed when an event (DML events) occurs.

// Events:

1. Before Insert
2. After Insert
3. Before Update
4. After Update
5. Before Delete
6. After Delete

Before triggers (events) are used to update or validate record values before they are
saved to the database.  After triggers (events) are used to access field values that
are set by the system and to effect changes in other records.  The records that
fires the after trigger are read-only.

// Properties and methods:

Trigger.New: returns the record in context in insert or update event.  These
  records can only be modified in before trigger.
Trigger.Old: returns the record in context in update or delete event.

Trigger.oldMap: a map of IDs to the old versions of the sObject records.  This
  map is only available in update and delete triggers.

Trigger.size: The total number of records in a trigger invocation, both old
  and new.

Trigger.New and Trigger.Old always returns a list because an insert operation
may insert multiple records (bulk insert), and an update operation may involve
multiple records being updated even with just one update statement.

Trigger.isInsert returns true if it is an insert trigger
Trigger.isUpdate returns true if it is an update trigger
Trigger.isDelete returns true if this is a delete trigger
Trigger.isUnDelete
Trigger.isBefore
Trigger.isAfter

// Apex Triggers - Order of Execution:

1. Before Trigger Execution
2. System Validation Rules Execution
3. Record Saved in Memory (Not DB)(ID Assigned)
4. After Trigger Execution
5. Assignement Rules
6. Auto-Response Rules
7. Workflow Rules (Trigger Step 1 if Field Update)

http://www.salesforce.com/us/developer/docs/
apexcode/Content/apex_triggers_order_of_execution.htm

Two ways of creating triggers:

We can create a trigger without creating a class:

trigger <triggerName> on <objectName> (events) {
}

For example:

// Create a trigger that throws an error if a certain condition
// is not met.  A trigger can also be associated with multiple
// events.
trigger triggerName on Opportunity(Before Insert, Before Update) {
  for(Opportunity a : Trigger.New) {
    if (Trigger.isInsert && a.Amount < 5000) {
      a.addError('Amount cannot be less than 5000 for new opportunity');
    } else if (Trigger.isInsert && a.Amount < 3000) {
      a.addError('Amount cannot be less than 3000 for existing opportunity');
    }
  }
}

trigger op_trigger_2 on Opportunity (After Insert) {
  Contact c = new Contact();
  for (Opportunity o: Trigger.new) {
    c.AccountID = o.AccountID;
    c.FirstName = 'Opportunity';
    c.LastName = 'Owner';
    insert c;
  }
}

We can also create a trigger by creating a class.  Inside the class, we need
to define a method with an List of sObjects as parameter (Trigger.new). Inside
this method, we need to create a Trigger, and call the method

public class trigger_class {
  public static void check_opp(List<Opportunity> opps) {
    // Trigger.new is passed to this method.
    Double totalAmount = 0;
    for (Opportunity o1 : [select Amount from Opportunity 
      where createdDate=TODAY AND createdByID = :UserInfo.getUserId()]) {
      totalAmount += o1.Amount;
    }

    for (Opportunity o2 : opps) {
      totalAmount += o2.Amount;
      if (totalAmount > 1000000) {
        o2.addError('You have exceeded the daily limit');
      }
    }
  }
}

After we created the class, we must create a trigger as well.

trigger op_trigger1 on Opportunity (before insert) {
  className.methodName(Trigger.New);
}

Go to Setup
Type 'log' into the search box at the top

Cascading Triggers: A trigger updates another record, which updates another
record, which eventually consumes all the trigger resources, forcing an error.
There is no silver bullet for this issue.  Static variables can be used to
prevent recursive or cascading logic from happening.  Logic can be moved to
Batch Apex if it is too large or complex.

Force.com code security scanner.

Trigger Handler pattern: do not put business logic in the trigger.

Null Reference Errors: A trigger attempts to access an instance of an object
that is empty.  A trigger attempts to access a property of an object that is
empty.  This is common error among all programming languages.  Always perform
null checks in your Apex code.  Ensure SOQL is pulling all fields references
in your Apex code.  Consider using Apex DAO objects to encapsulate queries.
Configure Objects to have default (non-null) values.

Too Many SOQL Queries:  In a given trigger, you cannot execute more than 100
SOQL queries.  This is typically due to either simple for loops executing
SOQL, or more complex cascading trigger logic.  Remove SOQL from loops, or
recursively called methods.  For large query logic, consider moving the logic
into Apex Batch instead of an Apex trigger.  Condense child-parent into one
query.  Make sure that triggers are bulkified.

trigger accountTestTrggr on Account (before insert, before update) {
  // This queries all Contacts related to the incoming Account records in a 
  // single SOQL query.  This is also an example of how to use child 
  // relationships in SOQL
  List<Account> accountsWithContacts = [
    select id, name, (select id, salutation, description, 
      firstname, lastname, email from Contacts) 
    from Account where Id IN :Trigger.newMap.keySet()
  ];

  List<Contact> contactsToUpdate = new List<Contact>{};

  // For loop to iterate through all the queried Account records 
  for (Account a: accountsWithContacts) {

    // Use the child relationships dot syntax to access the related Contacts
    for (Contact c: a.Contacts){
      System.debug('Contact Id[' + c.Id + '], FirstName[' + 
        c.firstname + '], LastName[' + c.lastname +']');
      c.Description = c.salutation + ' ' + c.firstName + ' ' + c.lastname; 
      contactsToUpdate.add(c);
    }          
  }

  //Now outside the FOR Loop, perform a single Update DML statement. 
  update contactsToUpdate;
}

trigger accountTrigger on Account (before delete, before insert, before update) {
  // This code efficiently queries all related Closed Lost and
  // Closed Won opportunities in a single query.
  List<Account> accountWithOpptys = [
    select id, name, (select id, name, closedate, stagename 
      from Opportunities 
      where accountId IN :Trigger.newMap.keySet() 
        and  (StageName='Closed - Lost' or StageName = 'Closed - Won')
    ) 
    from Account where Id IN :Trigger.newMap.keySet()
  ];

  //Loop through Accounts only once
  for (Account a&nbsp;: accountWithOpptys) {
    // Loop through related Opportunities only once
    for (Opportunity o: a.Opportunities) {
      if (o.StageName == 'Closed - Won') {
        System.debug('Opportunity Closed Won...do some more logic here...');
    } else if (o.StageName =='Closed - Lost') {
        System.debug('Opportunity Closed Lost...do some more logic here...');
    }
  }
}

To add a task to an opportunity:

trigger ClosedOpportunityTrigger on Opportunity (after insert, after update) {
  List<task> tasks =New List<task>();
  for (opportunity opp:trigger.new) {
    if (opp.stagename=='Closed Own') {
      task t=new task(
        whatid=opp.id, 
        Status = 'Active',
        Subject = 'Follow Up Test Task',
        ActivityDate = system.today()
      );
      tasks.add(t); 
    }
  }
  insert tasks;
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License