Rocket Image

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 three 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 ~ Write a trigger to prevent insertion of opportunity record if the total amount of all the opportunity created in a particular day by a particular user is more than $500000

In Salesforce, sometimes business rules require preventing the creation of new records under certain conditions. For instance, you may want to prevent users from creating too many opportunities that collectively exceed a specific financial threshold within a single day. To implement this, we can write a trigger that checks if the total amount of opportunities created by a user on the same day surpasses a set limit, such as $500,000. If the limit is breached, the trigger will stop the insertion of any new opportunities, helping to enforce financial controls and prevent excessive commitments. Below is a sample Salesforce trigger that can be used to achieve this.

# Apex Class:

public class LimitOppAmountForUser {
    public static void limitOppAmountForUserMethod(List<Opportunity> oppList){
        
        Map<Id, Decimal> oppAmtMap = New Map<Id, Decimal>();
        
        List<Opportunity> existingOppList = [SELECT Id, Name, Amount, CreatedDate, OwnerId FROM Opportunity WHERE CreatedDate = Today AND OwnerId =: UserInfo.getUserId()];
        
        if(!existingOppList.isEmpty()){
            for(Opportunity existingOpp : existingOppList){
                if(oppAmtMap.containsKey(existingOpp.OwnerId)){
                    Decimal tempAmount = oppAmtMap.get(existingOpp.OwnerId);
                    tempAmount = tempAmount + existingOpp.Amount;
                    oppAmtMap.put(existingOpp.OwnerId, tempAmount);
                } else {
                    oppAmtMap.put(existingOpp.OwnerId, existingOpp.Amount);
                }
            }
        }
        
        if(!oppList.isEmpty()){
            for(Opportunity opp : oppList){
                if(oppAmtMap.containsKey(opp.OwnerId)){
                    Decimal totalAmount = opp.Amount + oppAmtMap.get(opp.OwnerId);
                    if(totalAmount > 500000){
                        opp.addError('Total opportunity Amount cannot exceed $500000 in a day');
                    }
                }
            }
        }
    }
}
Apex

# Apex Trigger:

trigger LimitOppAmountForUserTrigger on Opportunity (before insert, before update, after undelete) {
    if(Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)){
        LimitOppAmountForUser.limitOppAmountForUserMethod(Trigger.new);
    }
    if(Trigger.isAfter && Trigger.isUndelete){
        LimitOppAmountForUser.limitOppAmountForUserMethod(Trigger.new);
    }
}
Apex

# Test Class:

@isTest
public class LimitOppAmountForUserTest {
    
    @testSetup
    static void setupTestData() {
        // Create a test User for assigning Opportunities
        User testUser = [SELECT Id FROM User LIMIT 1];

        // Create test Opportunities for the user (total less than 500,000)
        List<Opportunity> oppList = new List<Opportunity>();
        
        oppList.add(new Opportunity(Name = 'Test Opp 1', Amount = 100000, CloseDate = Date.today(), StageName = 'Prospecting', OwnerId = testUser.Id));
        oppList.add(new Opportunity(Name = 'Test Opp 2', Amount = 200000, CloseDate = Date.today(), StageName = 'Prospecting', OwnerId = testUser.Id));
        oppList.add(new Opportunity(Name = 'Test Opp 3', Amount = 150000, CloseDate = Date.today(), StageName = 'Prospecting', OwnerId = testUser.Id));
        
        insert oppList;
    }
    
    @isTest
    static void testLimitOppAmountForUserMethod() {
        // Fetch the test user
        User testUser = [SELECT Id FROM User LIMIT 1];
        
        // Create a new opportunity that will exceed the daily limit of $500,000
        Opportunity exceedingOpp = new Opportunity(Name = 'Test Opp Exceed', Amount = 100000, CloseDate = Date.today(), StageName = 'Prospecting', OwnerId = testUser.Id);
        
        Test.startTest();
        try {
            insert exceedingOpp;
            // Call the method to validate the new opportunities
            LimitOppAmountForUserClass.limitOppAmountForUserMethod(new List<Opportunity>{exceedingOpp});
            System.assert(true, 'Expected an error to be thrown for exceeding the daily limit.');
        } catch (DmlException e) {
            System.assert(e.getMessage().contains('Total opportunity Amount cannot exceed $500000 in a day'),
                'Expected error message for exceeding $500,000 limit.');
        }
        Test.stopTest();
    }
}
Apex

# 2 ~ Write a trigger to ensure that an Account cannot have multiple related Contacts with the same Colour. The Colour__c field is a custom field on the Contact object.

To enforce data consistency and avoid duplicate information in Salesforce, you may want to prevent an Account from having multiple related Contacts with the same value in a custom field, such as Colour__c. This requirement can be implemented using a trigger that checks whether any new or updated Contacts have a Colour__c value that matches another Contact already associated with the same Account. If a duplicate colour is found, the trigger will block the insertion or update, ensuring that each Contact under the same Account has a unique colour. Below is an optimized trigger that accomplishes this validation.

# Apex Class:

public class AccountWithDiffColorContacts {
    public static void accountWithDiffColorContactsMethod(List<Contact> conList){
        Set<Id> accId = New Set<Id>();
        
        if(!conList.isEmpty()){
            for(Contact con1 : conList){
                if(con1.AccountId != Null && con1.Colour__c != Null){
                    accId.add(con1.AccountId);
                }
            }
        }
        
        List<Contact> existingConList = [SELECT Id, AccountId, Colour__c from Contact WHERE AccountId IN :accId AND Colour__c != Null];
        
        Map<Id, List<String>> conMap = New Map<Id, List<String>>();
        
        if(!existingConList.isEmpty()){
            for(Contact con2 : existingConList){
                if(conMap.containsKey(con2.AccountId)){
                    conMap.get(con2.AccountId).add(con2.Colour__c);
                } else {
                    List<String> colourList = New List<String>();
                    colourList.add(con2.Colour__c);
                    conMap.put(con2.AccountId, colourList);
                }
            }
        }
        
        if(!conList.isEmpty()){
            for(Contact con3 : conList){
                if(conMap.get(con3.AccountId).contains(con3.Colour__c)){
                    con3.addError('Contact already exists with the same colour on the Account.');
                }
            } 
        }
    }
}
Apex

# Apex Trigger:

trigger AccountWithDiffColorContactsTrigger on Contact (before insert, before update, after undelete) {
	if(Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)){
        AccountWithDiffColorContacts.accountWithDiffColorContactsMethod(Trigger.new);
    }
    
    if(Trigger.isAfter && Trigger.isUndelete){
        AccountWithDiffColorContacts.accountWithDiffColorContactsMethod(Trigger.new);
    }
}
Apex

# Test Class:

@isTest
public class AccountWithDiffColorContactsTest {
    
    @testSetup
    static void setupTestData() {
        // Create test Account
        Account acc1 = new Account(Name = 'Test Account 1');
        insert acc1;
        
        // Create Contacts with unique and duplicate Colour__c values
        List<Contact> contactList = new List<Contact>();

        // Contacts for Account 1
        contactList.add(new Contact(FirstName = 'Contact1', LastName = 'Test1', AccountId = acc1.Id, Colour__c = 'Red'));
        contactList.add(new Contact(FirstName = 'Contact2', LastName = 'Test2', AccountId = acc1.Id, Colour__c = 'Blue'));
        
        insert contactList;
    }
    
    @isTest
    static void testAccountWithDiffColorContactsMethod() {
        // Fetch Account 1 for testing
        Account acc1 = [SELECT Id FROM Account WHERE Name = 'Test Account 1' LIMIT 1];

        // Create a new Contact with the same Colour__c value (Red) for Account 1, which should cause an error
        Contact duplicateContact = new Contact(FirstName = 'DuplicateContact', LastName = 'TestDuplicate', AccountId = acc1.Id, Colour__c = 'Red');
        
        Test.startTest();
        try {
            insert duplicateContact;
            AccountWithDiffColorContacts.accountWithDiffColorContactsMethod(new List<Contact>{ duplicateContact });
            System.assert(true, 'Expected error for duplicate Colour__c on the same Account');
        } catch (DmlException e) {
            System.assert(e.getMessage().contains('Contact already exists with the same colour on the Account.'), 
                          'Expected error message about duplicate Colour__c');
        }
        Test.stopTest();
    }
}
Apex

# 3 ~ Write a trigger that populates the Account’s Age__c field with the highest age of any Contact associated with that Account.

In Salesforce, there may be instances where business logic requires you to track specific aggregate information from related records. For example, an Account might need to display the highest age of its associated Contacts for reporting or analysis purposes. To achieve this, we can write a trigger that automatically updates the Age__c field on the Account object with the highest age of any Contact linked to that Account. This ensures that the Account always reflects the most up-to-date information based on its related Contacts, streamlining data management and eliminating the need for manual updates.

# Apex Class:

public class ContactWithMaxAgeOnAccount {
    public static void contactWithMaxAgeOnAccountMethod(List<Contact> conList){
        
        Set<Id> accId = New Set<Id>();
        
        if(!conList.isEmpty()){
            for(Contact con : conList){
                if(con.AccountId != Null){
                    accId.add(con.AccountId);
                }
            }
        }
        
        Contact existingContact = [SELECT Id, AccountId, Age__c FROM Contact WHERE AccountId IN :accId ORDER BY Age__c DESC LIMIT 1];
        
        Map<Id, Account> accMap = New Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id =:existingContact.AccountId]);
        
        List<Account> accList = New List<Account>();
        
        if(existingContact != Null){
            if(accMap.containsKey(existingContact.AccountId)){
                Account a = accMap.get(existingContact.AccountId);
                if(a.Id == existingContact.AccountId){
                    a.Age__c = existingContact.Age__c;
                    accMap.put(a.Id, a);
                }
            }
            
            if(!accMap.isEmpty()){
                update accMap.values();
            }
        }
    }
}
Apex

# Apex Trigger:

trigger ContactWithMaxAgeOnAccountTrigger on Contact (after insert, after update, after delete, after undelete) {
    if(Trigger.isAfter){
        if(Trigger.isInsert || Trigger.isUpdate || Trigger.isUndelete){
             ContactWithMaxAgeOnAccount.contactWithMaxAgeOnAccountMethod(Trigger.new);
        }
        if(Trigger.isDelete){
             ContactWithMaxAgeOnAccount.contactWithMaxAgeOnAccountMethod(Trigger.old);
        }
    }
}
Apex

# Test Class:

@isTest
public class ContactWithMaxAgeOnAccountTest {
    @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', Age__c = 60, AccountId=a.Id);
        Contact c2 = New Contact(LastName='Con Two',Age__c = 70, AccountId=a.Id);
        Contact c3 = New Contact(LastName='Con Three', Age__c = 80, AccountId=a.Id);
        insert New List<Contact>{c1, c2, c3};
            }
    
    @isTest
    static void testContactWithMaxAgeOnAccountMehtod(){
        
        // Fetch all contacts and invoke the method
        List<Contact> allContacts = [SELECT Id, AccountId, Age__c FROM Contact];
        Test.startTest();
        ContactWithMaxAgeOnAccount.contactWithMaxAgeOnAccountMethod(allContacts);
        Test.stopTest();
        
        // Fetch Accounts after the update
        List<Account> accountsAfterUpdate = [SELECT Id, Age__c FROM Account WHERE Name = 'Test Account One'];
        
        // Verify the correct Age__c values on each account
        System.assertEquals(80, accountsAfterUpdate[0].Age__c);
        
    }
}
Apex