Decorative image of a frog thinking

Mastering Apex Triggers: A Step-by-Step Guide

In Salesforce, triggers play a crucial role in automating business processes by executing custom actions before or after events such as insertions, updates, or deletions of records. Apex triggers allow developers to add complex logic to record operations, ensuring data integrity and enabling seamless integration with other systems. Understanding various trigger scenarios is essential for implementing efficient and effective solutions in Salesforce. This is part one in the Trigger Scenarios in APEX series. In this post, we will explore different trigger scenarios in Apex, providing practical examples and best practices to help you leverage triggers for automating workflows, enforcing validation rules, and maintaining data consistency. Whether you’re a seasoned developer or just starting with Salesforce, these scenarios will equip you with the knowledge to handle common business requirements and optimize your Salesforce applications. Let’s dive in!

# 1 ~ Create a field on Account named ‘Client Contact’ (lookup to Contact). Once an Account is inserted a Contact will be created with the same name as that of the Account and that Contact’s Id will be mapped to the Client Contact field on the Account.

In Salesforce, automating the creation and association of related records can significantly enhance data management and streamline business processes. One common scenario involves creating a Contact record whenever an Account is inserted and associating this Contact with the Account. Specifically, we want to create a field on the Account object named ‘Client Contact’ (a lookup to Contact). When an Account is inserted, a new Contact with the same name as the Account will be automatically created, and the ‘Client Contact’ field on the Account will be populated with the newly created Contact’s Id. In this post, we will demonstrate how to achieve this using an Apex trigger, ensuring seamless integration between Accounts and Contacts.

# Apex Class:

public class ClientContactLookupOnAccount {

    public static void clientContactLookupOnAccountMethod(List<Account> accList){
        
        List<Contact> conList = New List<Contact>();
        Set<Id> accId = New Set<Id>();
        
        for(Account acc : accList){
            Contact c = New Contact();
            c.LastName = acc.Name;
            c.AccountId = acc.Id;
            conList.add(c);
            accId.add(acc.Id);
        }
        
        if(conList.size()>0){
            insert conList;
        }
        
        Map<Id, Contact> conMap = New Map<Id, Contact>();
        
        for(Contact c : conList){
            conMap.put(c.AccountId, c);
        }
        
        List<Account> aList = [SELECT Id, Name FROM Account WHERE Id IN :accId];
        
        if(aList.size()>0){
            for(Account a : aList){
                if(conMap.containsKey(a.Id)){                
                    a.Client_Contact__c = conMap.get(a.Id).Id;
                }
            }
        }
        if(aList.size()>0){
            update aList;   
        }
    }
}
Apex

# Apex Trigger:

trigger ClientContactLookupOnAccountTrigger on Account (after insert) {

    if(Trigger.isAfter && Trigger.isInsert){
        ClientContactLookupOnAccount.clientContactLookupOnAccountMethod(Trigger.new);
    }
}
Apex

# Test Class:

@isTest
public class ClientContactLookupOnAccountTest {
	@testSetup
    static void testDataSetup(){
        Account a = New Account();
        a.Name = 'Test Account One';
        insert a;
    }
    
    @isTest
    static void testClientContactLookupOnAccount(){
        List<Account> a = [SELECT Id, Name FROM Account WHERE Name = 'Test Account One'];
        
        Test.startTest();
        ClientContactLookupOnAccount.clientContactLookupOnAccountMethod(a);
        Test.stopTest();
        
        Account a1 = [SELECT Id, Name, Client_Contact__r.Name FROM Account WHERE Name = 'Test Account One'];
        System.assertEquals(a1.Name, a1.Client_Contact__r.Name);
    }
}
Apex

# 2 ~ Write a trigger to update the Account with total number of Opportunity records and the sum of the Amount field for all Opportunity records related to the Account.

In Salesforce, keeping track of the opportunities related to an account is crucial for sales management and forecasting. One useful approach is to update the Account record with the total number of related Opportunity records and the sum of the Amount field for all these Opportunities. This ensures that key metrics are readily available on the Account record, providing valuable insights into sales performance. In this post, we will show how to create an Apex trigger that automatically updates an Account with the total count of its Opportunities and the sum of their Amounts, helping you maintain accurate and up-to-date information for better decision-making and reporting.

# Apex Class:

public class OpportunityCountAndSumAmountOnAccount {
    
    
    //Method for when the record is getting inserted (After Insert and After undelete)
    public static void sumOfAmountOnAccOnInsert(List<Opportunity> oppNewList){
        
        Set<Id> accIds = New Set<Id>();
        List<Account> accListToBeUpdated = New List<Account>();
        for(Opportunity opp : oppNewList){
            if(opp.AccountId != Null){
                accIds.add(opp.AccountId);
            }
        }
        accListToBeUpdated = getAmountSum(accIds);
        if(!accListToBeUpdated.isEmpty()){
            update accListToBeUpdated;
        }
    }
    
    
    //Method for when the record is being updated (After Update)
    public static void sumOfAmountOnAccOnUpdate(List<Opportunity> oppNewList, Map<Id, Opportunity> oldOppMap){
        
        Set<Id> accIds = New Set<Id>();
        List<Account> accListToBeUpdated = New List<Account>();
        for(Opportunity opp : oppNewList){
            if(opp.AccountId != Null && opp.Amount != oldOppMap.get(opp.Id).Amount){
                accIds.add(opp.AccountId);
            }
        }
        accListToBeUpdated = getAmountSum(accIds);
        if(!accListToBeUpdated.isEmpty()){
            update accListToBeUpdated;
        }
    }
    
    
    //Method for when the record is getting deleted (After Delete)
    public static void sumOfAmountOnAccOnDelete(List<Opportunity> oppOldList){
        
        Set<Id> accIds = New Set<Id>();
        List<Account> accListToBeUpdated = New List<Account>();
        for(Opportunity opp : oppOldList){
            if(opp.AccountId != Null){
                accIds.add(opp.AccountId);
            }
        }
        accListToBeUpdated = getAmountSum(accIds);
        if(!accListToBeUpdated.isEmpty()){
            update accListToBeUpdated;
        }
    }
    
    public static List<Account> getAmountSum(Set<Id> accIds){
    
        List<Account> accList = New List<Account>();
        Map<Id, Decimal> accountAmtToBeUpdated = New Map<Id, Decimal>();
        Map<Id, Integer> accountOppCount = New Map<Id, Integer>();
        
        for(Opportunity op: [SELECT Id, Name, AccountId, Amount FROM Opportunity WHERE AccountId IN :accIds]){
        
            if(accountOppCount.containsKey(op.AccountId)){
                Decimal tempAmount = accountAmtToBeUpdated.get(op.AccountId);
                tempAmount = tempAmount + op.Amount;
                accountAmtToBeUpdated.put(op.AccountId, tempAmount);
                
                Integer count = accountOppCount.get(op.AccountId);
                count = count + 1;
                accountOppCount.put(op.AccountId, count);
            }
            else{
                accountAmtToBeUpdated.put(op.AccountId, op.Amount);
                accountOppCount.put(op.AccountId, 1);
            }  
        }
        if(!accountOppCount.isEmpty()){
            for(Id i : accountOppCount.keyset()){
                Account acc = New Account();
                acc.Id = i;
                acc.No_Of_Opp__c = accountOppCount.get(i);
                acc.Account_Amount__c = accountAmtToBeUpdated.get(i);
                accList.add(acc);
            }   
        }
        return accList;
    }
}
Apex

# Apex Trigger:

trigger OpportunityCountAndSumAmount on Opportunity (after insert, after update, after delete, after undelete) {

    if(Trigger.isAfter){
        if(Trigger.isInsert || Trigger.isUndelete){
            OpportunityCountAndSumAmountOnAccount.sumOfAmountOnAccOnInsert(Trigger.new);
        }
        else if(Trigger.isUpdate){
            OpportunityCountAndSumAmountOnAccount.sumOfAmountOnAccOnUpdate(Trigger.new, Trigger.oldMap);
        }
        else if(Trigger.isDelete){
            OpportunityCountAndSumAmountOnAccount.sumOfAmountOnAccOnDelete(Trigger.old); 
        }
    } 
}
Apex

# Test Class:

@isTest
public class OpportunityCountAndSumAmountTest {
    
    @TestSetup
    static void setupdat() {
        // Create a test account
        Account testAccount = new Account(Name = 'Test Account');
        insert testAccount;
        
        // Create the first contact and set it as primary
        Opportunity opp1 = new Opportunity(Name = 'Test Opp One', AccountId = testAccount.Id, Amount = 10000, CloseDate=System.Today(), StageName='Prospecting');
        Opportunity opp2 = new Opportunity(Name = 'Test Opp Two', AccountId = testAccount.Id, Amount = 20000, CloseDate=System.Today(), StageName='Prospecting');
        Opportunity opp3 = new Opportunity(Name = 'Test Opp Three', AccountId = testAccount.Id, Amount = 30000, CloseDate=System.Today(), StageName='Prospecting');
        
        insert New List<Opportunity>{opp1, opp2, opp3};
    }
    
    @isTest
    static void testSumOfAmountOnAccOnInsert() {
        // Fetch the test account
        Account testAccount = [SELECT Id FROM Account WHERE Name = 'Test Account' LIMIT 1];
        
        // Create a second contact and try to set it as primary
        Opportunity newOppRec = new Opportunity(Name = 'New Opp Rec', AccountId = testAccount.Id, Amount = 40000, CloseDate=System.Today(), StageName='Prospecting');
        insert newOppRec;
        List<Opportunity> oppList = New List<Opportunity>{newOppRec};
            
        Test.startTest();
        OpportunityCountAndSumAmountOnAccount.sumOfAmountOnAccOnInsert(oppList);
        Test.stopTest();
        
        Account acc = [SELECT Id, No_Of_Opp__c, Account_Amount__c FROM Account WHERE Name = 'Test Account' LIMIT 1];
        system.assertEquals(4, acc.No_Of_Opp__c);
        system.assertEquals(100000, acc.Account_Amount__c);
    }
    
    @isTest
    static void testSumOfAmountOnAccOnUpdate() {
        // Fetch the test account
        Account testAccount = [SELECT Id FROM Account WHERE Name = 'Test Account' LIMIT 1];
        
        List<Opportunity> oppToUpdate = [SELECT Id, AccountId, Name, Amount, CloseDate, StageName FROM Opportunity];
        
        Map<Id, Opportunity> oppMap = New Map<Id, Opportunity>(oppToUpdate);
        
        List<Opportunity> oppList = New List<Opportunity>();
        
        for(Opportunity opp : oppToUpdate){
            if(opp.Name == 'Test Opp Three'){
                Opportunity opp3 = new Opportunity(Id = opp.Id, Name = 'Test Opp Three', AccountId = testAccount.Id, Amount = 35000, CloseDate=System.Today(), StageName='Prospecting');
                oppList.add(opp3);
            }
        }
        
        update oppList;        
        
        Test.startTest();
        OpportunityCountAndSumAmountOnAccount.sumOfAmountOnAccOnUpdate(oppList, oppMap);
        Test.stopTest();
        
        Account acc = [SELECT Id, No_Of_Opp__c, Account_Amount__c FROM Account WHERE Name = 'Test Account' LIMIT 1];
        system.assertEquals(3, acc.No_Of_Opp__c);
        system.assertEquals(65000, acc.Account_Amount__c);
    }
    
    @isTest
    static void testSumOfAmountOnAccOnDelete() {
        // Fetch the test account
        Account testAccount = [SELECT Id FROM Account WHERE Name = 'Test Account' LIMIT 1];
        List<Opportunity> oppToUpdate = [SELECT Id, AccountId, Name, Amount, CloseDate, StageName FROM Opportunity];
        List<Opportunity> oppList = New List<Opportunity>();
        
        for(Opportunity opp : oppToUpdate){
            if(opp.Name == 'Test Opp Three'){
                oppList.add(opp);
            }
        }
        
        delete oppList;
                
        Test.startTest();
        OpportunityCountAndSumAmountOnAccount.sumOfAmountOnAccOnDelete(oppList);
        Test.stopTest();
        
        Account acc = [SELECT Id, No_Of_Opp__c, Account_Amount__c FROM Account WHERE Name = 'Test Account' LIMIT 1];
        system.assertEquals(2, acc.No_Of_Opp__c);
        system.assertEquals(30000, acc.Account_Amount__c);
    }
}
Apex

# 3 ~ Write a trigger to ensure that no Account record can have more than two Contacts.

In Salesforce, it’s sometimes necessary to enforce business rules to maintain data integrity and consistency. One such rule might be limiting the number of related records, such as ensuring that an Account does not have more than two Contacts. This can help manage relationships more effectively and avoid data clutter. In this post, we will demonstrate how to create an Apex trigger that enforces this rule by preventing the insertion or update of Contact records if it would result in an Account having more than two Contacts. This approach ensures that your Salesforce data remains organized and adheres to business requirements.

# Apex Class:

public class PreventMoreThan2ContactsOnAcc{

    public static integer count;
    
    public static void preventMoreThan2Contacts(List<Contact> contactList){
        
        Set<Id> accId = New Set<Id>();
        for(Contact con : contactList){
            accId.add(con.AccountId);
        }
        
        Map<Id, Integer> accConCount = New Map<Id, Integer>();
        
        for(Contact con : [Select Id, AccountId From Contact WHERE AccountId IN : accId]){
            if(con.AccountId != Null){
                if(accConCount.containskey(con.AccountId) ){
                    Integer count = accConCount.get(con.AccountId);
                    count = count + 1;
                    accConCount.put(con.AccountId, count);
                } else {
                    accConCount.put(con.AccountId, 1);
                }
            }
        }
        
        for(Contact con : contactList){
            if(accConCount.containskey(con.AccountId) && accConCount.get(con.AccountId) > 2){
                con.addError('An Account Record cannot have more than 2 Contacts');
            }
        }
    }
}
Apex

# Apex Trigger:

trigger PreventMoreThan2ContactsOnAccTrigger on Contact (before insert, before update, after undelete) {

    if(Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)){
        PreventMoreThan2ContactsOnAcc.preventMoreThan2Contacts(Trigger.new);
    }
    
    if(Trigger.isAfter && Trigger.isUndelete){
        PreventMoreThan2ContactsOnAcc.preventMoreThan2Contacts(Trigger.new);
    }
}
Apex

# Test Class:

@isTest
public class PreventMoreThan2ContactsOnAccTest {
    @testSetup
    static void testDataSetup(){
        Account a = New Account();
        a.Name = 'Test Account One';
        insert a;
        
        //Creating test data for contact
        Contact c1 = New Contact(LastName='Con One',AccountId=a.Id);
        Contact c2 = New Contact(LastName='Con Two',AccountId=a.Id);
        Contact c3 = New Contact(LastName='Con Three',AccountId=a.Id);
        insert New List<Contact>{c1, c2};
            }
    
    @isTest
    static void testpreventMoreThan2Contacts(){
        Account a = [SELECT Id, Name FROM Account WHERE Name = 'Test Account One' Limit 1];
        system.debug('this is testAccount==='+a);
        
        
        //Creating test data for contact
        Contact c3 = New Contact(LastName='Con three',AccountId=a.Id);
        insert c3;
        List<contact> conList = New List<Contact>{c3};        
        
        // Perform the DML operation inside a try-catch block to catch the expected exception
        Test.startTest();
        try {
            PreventMoreThan2ContactsOnACC.preventMoreThan2Contacts(conList);
            insert c3;
            system.debug('this is c3===id'+c3.Id);
            System.assert(false, 'Expected a DmlException due to the preventMoreThan2ContactOnAccMethod trigger logic');
        } catch (DmlException e) {
            System.assert(e.getMessage().contains('An Account Record cannot have more than 2 Contacts'), 'Unexpected exception message: ' + e.getMessage());
        }
        Test.stopTest();
    }
    
    @isTest
    static void testpreventMoreThan2ContactsAfterUndelete() {
        // Retrieve the Account Id
        Account testAccount1 = [SELECT Id FROM Account WHERE Name = 'Test Account One' LIMIT 1];
        
        // Create a new Contact for the Account that already has 2 Contacts
        Contact newContact = new Contact(FirstName = 'Contact3', LastName = 'Test', AccountId = testAccount1.Id);
        insert newContact;
        
        // Soft delete one of the existing contacts
        Contact contactToDelete = [SELECT Id FROM Contact WHERE AccountId = :testAccount1.Id LIMIT 1];
        delete contactToDelete;
        
        // Restore the deleted contact
        undelete contactToDelete;
        
        // Check if undelete operation is prevented by the method
        List<Contact> undeleteList = new List<Contact>{contactToDelete};
        
        Test.startTest();
        try {
            PreventMoreThan2ContactsOnACC.preventMoreThan2Contacts(undeleteList);
            undelete contactToDelete; // This should fail
            System.assert(false, 'Expected DML to fail due to more than 2 contacts on the same Account');
        } catch (DmlException e) {
            System.assert(e.getDmlMessage(0).contains('An Account Record cannot have more than 2 Contacts'), 'Unexpected error message: ' + e.getDmlMessage(0));
        }
        Test.stopTest();
    }
}
Apex