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 two 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 trigger to update the Account status field based on the statuses of related Contact records
In Salesforce, maintaining accurate status information for related records is essential for effective data management and reporting. A common requirement might be to synchronize the status of an Account with the statuses of its related Contacts. Specifically, we want the Account’s status to reflect the overall status of its Contacts. If all Contacts are “Completed,” the Account should be “Completed.” If all are “Open,” the Account should be “Open.” If all are “In_Progress,” the Account should be “In_Progress.” However, if there is a mix of statuses, the Account should be set to “In_Progress.” In this post, we will demonstrate how to create an Apex trigger that updates the Account status field based on the statuses of its related Contact records, ensuring data consistency and accurate status tracking.
# Apex Class:
public class StatusOfAccountBasedOnContact {
public static void statusOfAccountBasedOnContactMethod(List<Contact> conList){
Set<Id> accId = New Set<Id>();
for(Contact c : conList){
if(c.AccountId != Null){
accId.add(c.AccountId);
}
}
List<Contact> cList = [SELECT Id, Name, AccountId, Contact_Status__c
FROM Contact
WHERE AccountId IN :accId];
Map<Id, String> conMap = New Map<Id, String>();
if(cList.size()>0){
for(Contact c : cList){
if(c.AccountId != Null){
if(conMap.containsKey(c.AccountId)){
if(conMap.get(c.AccountId) != c.Contact_Status__c){
conMap.put(c.AccountId, 'In Progress');
}
} else {
conMap.put(c.AccountId, c.Contact_Status__c);
}
}
}
}
List<Account> accList = [SELECT Id, Name FROM Account WHERE ID IN :conMap.keySet()];
for(Account acc : accList){
if(conMap.containsKey(acc.Id)){
acc.Account_Status__c = conMap.get(acc.Id);
}
}
if(accList.size()>0){
update accList;
}
}
}
Apex# Apex Trigger:
trigger StatusOfAccountBasedOnContactTrigger on Contact (after insert, after update, after delete, after undelete) {
if(Trigger.isAfter && (Trigger.isInsert || Trigger.isUndelete)){
StatusOfAccountBasedOnContact.statusOfAccountBasedOnContactMethod(Trigger.new);
}
if(Trigger.isAfter && (Trigger.isUpdate || Trigger.isDelete)){
StatusOfAccountBasedOnContact.statusOfAccountBasedOnContactMethod(Trigger.old);
}
}
Apex# Test Class:
@isTest
public class StatusOfAccountBasedOnContactTest {
@testSetup
static void setupData(){
//Creating test data for account
List<Account> accList = New List<Account>();
Account a1 = New Account();
a1.Name = 'Test Account One';
accList.add(a1);
Account a2 = New Account();
a2.Name = 'Test Account Two';
accList.add(a2);
insert accList;
//Creating test data for contact
Contact c1 = New Contact(LastName='Con One',AccountId=a1.Id,Contact_Status__c='Open');
Contact c2 = New Contact(LastName='Con Two',AccountId=a1.Id,Contact_Status__c='Open');
Contact c3 = New Contact(LastName='Con Three',AccountId=a2.Id,Contact_Status__c='Completed');
Contact c4 = New Contact(LastName='Con Four',AccountId=a2.Id,Contact_Status__c='Open');
insert New List<Contact>{c1, c2, c3, c4};
}
@isTest
static void testStatusOfAccountBasedOnContactMethod(){
List<Contact> conList = [SELECT Id, LastName, AccountId, Contact_Status__c FROM Contact];
System.debug('thi is ==='+conList);
Test.startTest();
StatusOfAccountBasedOnContact.statusOfAccountBasedOnContactMethod(conList);
Test.stopTest();
Account acc = [SELECT Id, Name, Account_Status__c FROM Account WHERE Name = 'Test Account One'];
Account acc1 = [SELECT Id, Name, Account_Status__c FROM Account WHERE Name = 'Test Account Two'];
System.assertEquals('Open', acc.Account_Status__c);
System.assertEquals('In Progress', acc1.Account_Status__c);
}
}
Apex# 2 ~ Create a trigger to designate one contact as the primary for an account, and ensure that when this primary contact is deleted, the next available contact is set as the primary
In Salesforce, designating a primary contact for each account helps in identifying the main point of contact quickly and efficiently. However, when managing these primary contacts, a common challenge arises: ensuring that if the primary contact is deleted, another contact is automatically set as the primary. This automation is crucial for maintaining continuity and data integrity. In this post, we will demonstrate how to create an Apex trigger that designates one contact as the primary for an account. Additionally, we will ensure that when this primary contact is deleted, the next available contact is automatically set as the new primary. This setup ensures seamless management of primary contacts within your Salesforce environment.
# Apex Class:
public class PrimaryContactOnAccountClass {
public static void primaryContactMethodAfterInsert(List<Contact> contactList){
Set<Id> accId = new Set<Id>();
if(!contactList.isEmpty()){
for (Contact con : contactList) {
if(con.AccountId != Null){
accId.add(con.AccountId);
}
}
}
List<Contact> conList = [SELECT Id, Name, AccountId, isPrimary__c, CreatedDate FROM Contact WHERE AccountId IN :accId Order By CreatedDate ASC];
Map<Id, Boolean> conMap = New Map<Id, Boolean>();
for(Contact con : conList){
if(!conMap.containskey(con.AccountId) && con.isPrimary__c == true && con.AccountId != Null){
conMap.put(con.AccountId, true);
}
}
List<Contact> contactToUpdate = new List<Contact>();
if (!conList.isEmpty()) {
for (Contact con : conList) {
if (con.AccountId != null && !conMap.containsKey(con.AccountId)) {
con.isPrimary__c = true;
contactToUpdate.add(con);
conMap.put(con.AccountId, true);
}
}
}
if (!contactToUpdate.isEmpty()) {
update contactToUpdate;
}
}
public static void primaryContactMethodAfterDelete(List<Contact> contactList){
Set<Id> accIdSet = new Set<Id>();
for(Contact con : contactList){
if(con.AccountId != Null){
accIdSet.add(con.AccountId);
}
List<Contact> conList = [SELECT Id, Name, AccountId, isPrimary__c, CreatedDate FROM Contact WHERE AccountId IN :accIdSet AND isPrimary__c = false Order By CreatedDate ASC Limit 1 ];
List<Contact> contactToUpdate = New List<Contact>();
if(!conList.isEmpty()){
for(Contact c : conList){
if(c.isPrimary__c == false){
c.isPrimary__c = true;
contactToUpdate.add(c);
}
}
}
if(!contactToUpdate.isEmpty()){
update contactToUpdate;
}
}
}
}
Apex# Apex Trigger:
trigger PrimaryContactOnAccountTrigger on Contact (after insert, after delete) {
if(Trigger.isAfter && Trigger.isInsert){
PrimaryContactOnAccountClass.primaryContactMethodAfterInsert(Trigger.new);
}
if(Trigger.isAfter && Trigger.isDelete){
PrimaryContactOnAccountClass.primaryContactMethodAfterDelete(Trigger.old);
}
}
Apex# Test Class:
@isTest
public class PrimaryContactOnAccountClassTest {
@TestSetup
static void setup() {
//Creating test data for account
List<Account> accList = New List<Account>();
Account a1 = New Account();
a1.Name = 'Test Account One';
accList.add(a1);
Account a2 = New Account();
a2.Name = 'Test Account Two';
accList.add(a2);
insert accList;
//Creating test data for contact
Contact c1 = New Contact(LastName='Con One',AccountId=a1.Id,isPrimary__c = true);
Contact c2 = New Contact(LastName='Con Two',AccountId=a1.Id,isPrimary__c=false);
Contact c3 = New Contact(LastName='Con Three',AccountId=a2.Id,isPrimary__c=false);
Contact c4 = New Contact(LastName='Con Four',AccountId=a2.Id,isPrimary__c=false);
insert New List<Contact>{c1, c2, c3, c4};
}
@isTest
static void testPrimaryContactMethodAfterInsert() {
// Fetch the test account
Account testAccountOne = [SELECT Id FROM Account WHERE Name = 'Test Account One' LIMIT 1];
// Fetch the test account
Account testAccountTwo = [SELECT Id FROM Account WHERE Name = 'Test Account Two' LIMIT 1];
Contact c1 = New Contact(LastName='Con One Trigger',AccountId=testAccountOne.Id,isPrimary__c = false);
Contact c2 = New Contact(LastName='Con Two Trigger',AccountId=testAccountTwo.Id,isPrimary__c = false);
insert New List<Contact>{c1, c2};
List<Contact> conList = [SELECT Id, LastName, AccountId, isPrimary__c FROM Contact];
System.debug('thi is ==='+conList);
Test.startTest();
PrimaryContactOnAccountClass.primaryContactMethodAfterInsert(conList);
Test.stopTest();
// Verify the primary contact is set correctly
List<Contact> updatedContacts1 = [SELECT Id, isPrimary__c, LastName FROM Contact WHERE AccountId = :testAccountOne.Id];
system.debug('this is updatedContacts==='+updatedContacts1);
for(Contact con : updatedContacts1) {
if(con.isPrimary__c) {
System.assertEquals('Con One', con.LastName);
}
}
// Verify the primary contact is set correctly
List<Contact> updatedContacts2 = [SELECT Id, isPrimary__c, LastName FROM Contact WHERE AccountId = :testAccountTwo.Id];
system.debug('this is updatedContacts==='+updatedContacts2);
for(Contact con : updatedContacts2) {
if(con.isPrimary__c) {
System.assertEquals('Con Three', con.LastName);
}
}
}
@isTest
static void testPrimaryContactMethodAfterDelete() {
// Fetch the test account
Account testAccount = [SELECT Id FROM Account WHERE Name = 'Test Account One' LIMIT 1];
// Fetch the contacts
Contact contact = [SELECT Id, LastName, AccountId, isPrimary__c FROM Contact WHERE AccountId = :testAccount.Id AND isPrimary__c = true Limit 1];
// Set one contact as primary
Delete contact;
List<Contact> conList = [SELECT Id, LastName, AccountId, isPrimary__c FROM Contact];
System.debug('this is ==='+conList);
// Delete the primary contact to trigger the logic
Test.startTest();
PrimaryContactOnAccountClass.primaryContactMethodAfterDelete(conList);
Test.stopTest();
// Verify the next available contact is set as primary
List<Contact> updatedContacts = [SELECT Id, isPrimary__c, LastName FROM Contact WHERE AccountId = :testAccount.Id];
for(Contact con : updatedContacts) {
if(con.isPrimary__c) {
System.assertEquals('Con Two', con.LastName);
}
}
}
}
Apex# 3 ~ Write a trigger to prevent a Contact to either Insert or Update with “is Primary” checked, if “is Primary” check box is already checked on any of the Sibling contacts associated with the account
In Salesforce, managing primary contacts for an account requires ensuring that only one contact is designated as primary at any given time. This prevents confusion and maintains data integrity. A common business requirement is to enforce that only one contact per account can have the “Is Primary” checkbox checked. This means preventing a contact from being inserted or updated with “Is Primary” checked if another sibling contact (a contact associated with the same account) already has this checkbox checked. In this post, we will demonstrate how to create an Apex trigger that enforces this rule, ensuring there is only one primary contact per account.
# Apex Class:
public class PreventTwoPrimaryContactOnAcc {
public static void preventPrimaryContactOnAccMethod(List<Contact> conList){
Set<Id> accId = New Set<Id>();
if(!conList.isEmpty()){
for(Contact con : conList){
if(con.AccountId != Null){
accId.add(con.AccountId);
}
}
}
List<Contact> existingConList = [SELECT Id, AccountId from Contact WHERE AccountId IN :accId AND isPrimary__c = true];
Map<Id, Contact> conMap = New Map<Id, Contact>();
if(!existingConList.isEmpty()){
for(Contact con : existingConList){
conMap.put(con.AccountId, con);
}
}
if(!conList.isEmpty()){
for(Contact con : conList){
if(conMap.containsKey(con.AccountId)){
con.addError('Primary contact already exists on the Account');
}
}
}
}
}
Apex# Apex Trigger:
trigger PreventTwoPrimaryContactOnAccTrigger on Contact (before insert, before update, after undelete) {
if(Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)){
PreventTwoPrimaryContactOnAcc.preventPrimaryContactOnAccMethod(Trigger.new);
}
if(Trigger.isAfter && Trigger.isUndelete){
PreventTwoPrimaryContactOnAcc.preventPrimaryContactOnAccMethod(Trigger.new);
}
}
Apex# Test Class:
@isTest
public class PreventTwoPrimaryContactOnAccTest {
@TestSetup
static void setup() {
// Create a test account
Account testAccount = new Account(Name = 'Test Account');
insert testAccount;
// Create the first contact and set it as primary
Contact primaryContact = new Contact(LastName = 'Primary Contact', AccountId = testAccount.Id, isPrimary__c = true);
insert primaryContact;
}
@isTest
static void testPreventPrimaryContactOnAccMethod() {
// Fetch the test account
Account testAccount = [SELECT Id FROM Account WHERE Name = 'Test Account' LIMIT 1];
system.debug('this is testAccount==='+testAccount);
// Create a second contact and try to set it as primary
Contact newPrimaryContact = new Contact(LastName = 'New Primary Contact', AccountId = testAccount.Id, isPrimary__c = true);
insert newPrimaryContact;
List<contact> conList = New List<Contact>{newPrimaryContact};
// Perform the DML operation inside a try-catch block to catch the expected exception
Test.startTest();
try {
PreventTwoPrimaryContactOnAcc.preventPrimaryContactOnAccMethod(conList);
insert newPrimaryContact;
System.assert(false, 'Expected a DmlException due to the preventPrimaryContactOnAccMethod trigger logic');
} catch (DmlException e) {
System.assert(e.getMessage().contains('Primary contact already exists on the Account'), 'Unexpected exception message: ' + e.getMessage());
}
Test.stopTest();
}
}
Apex