function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
SBgooSBgoo 

Make a trigger work with bulk changes

Hello, I wrote my 3rd trigger in order to update the first contact of an account anytime there is a change on the account.

It seems to work properly but... not with bulk changes on accounts. Could anyone kindly have a look and give me some hints?

Thanks!


trigger UpdateFirstContactOfAccount on Account (after update) {

  System.debug('UpdateFirstContactOfAccount BEGIN');

if(limitrecursion.runtwice()){

  System.debug('UpdateFirstContactOfAccount RECURSIONLIMIT PASSED');

  //aa
  Set<Id> AccIDs = new Set<Id>();
  //for(Contact con: Trigger.new) AccIDs.add(con.AccountId);
  for(Account acc: Trigger.new) {
      System.debug('added id to AccIDs: ' + acc.id);
      AccIDs.add(acc.Id);
  }

 

    // Fetch all the Contacts for these Accounts, selecting only the oldest one of each Account
    List<Contact> Cons = new List<Contact>([
        select
             Id, AccountId
             from Contact
             where AccountId = : AccIDs ORDER BY CreatedDate ASC NULLS LAST LIMIT 1
    //         where AccountId in : AccIDs

     ]);

    // Build a Map, keyed by AccID, of Lists of the related Cons
    Map<Id, List<Contact>> acMap = new Map<Id, List<Contact>>();
    for (Contact c: Cons) {
       if (acMap.containsKey(c.AccountId)) {
            List<Contact> clist2;
            clist2 = acMap.get(c.AccountId);
            clist2.add(c);
            acMap.put(c.AccountId, clist2);
       } else {

            System.debug('added id to acMap: acc ' + c.AccountId + ' contact ' + c.Id);

            List<Contact> clist1 = new List<Contact>();
            clist1.add(c);
            acMap.put(c.AccountId, clist1);
       }
    }



List<Account> ACCS = new List<Account>();

for(Id aid: AccIDs)
{
  if (acMap.containsKey(aid)) {

           for (Contact cc: acMap.get(aid)) {
             System.debug('Account ID' + aid + ' included');
                ACCS.add(
                     New Account(
                        First_Contact__c = cc.Id ,
                        id=aid)
                );
           }
  }
  else
  {
  //remove first contact
  System.debug('Account ID' + aid + ' not included');
  ACCS.add(
                     New Account(
                        First_Contact__c = Null ,
                        Id=aid)
                );
 
  }
}
      
       update ACCS; 










}
else
{
System.debug('UpdateFirstContactOfAccount STOPPED BECAUSE OF REC LIMIT');
}


}
dwright-glgroupdwright-glgroup

Hi  Sebastiano,

My understanding of what you want is:

- When the first contact is created in an account, you want the account to have the "First_Contact__c" field point to that contact.
- When the first contact is deleted in an account, you want the account to have the "First_Contact__c" field point to the now oldest contact in the account.

If ths is right, the trigger you want to write is not an Account trigger, but a Contact trigger.  In that trigger, have the "after insert" look at the account field First_Contact__c to see if it is null, and if so, update it with the new id. Have the "after delete" look at the account field First_Contact__c to see if it is the current record, if so, get all contacts of all accounts where this is true, sorted by account id then create date, and loop through this list to find which contact, if any, should be the new "first contact" for each account.

If you already have accounts and contacts and need to seed the initial data, I'd suggest you write a throw-away batch class to do it.

Also, it is a "best practice" to have the trigger code delegate the actual work to a class, like in the following:

trigger ContactTriggger on Contact (after insert, before delete) {
    ContactTriggerHandler handler = new ContactTriggerHandler();
    if (Trigger.isInsert && Trigger.isAfter) {
        handler.OnAfterInsert(Trigger.new);
    }
    if (Trigger.isDelete && Trigger.isAfter) {
        handler.OnAfterDelete(Trigger.old);
    }
}


Here is the trigger handler code I'd suggest:
 

public class ContactTriggerHandler {

    // if the contact is the first Contact added to an account, set the "First_Contact__c" field in that account
    // Note, if you're inserting multiple contacts for an account that currently has no "First_Contact__c" set,
    // an arbitrary one will be used.
    public void OnAfterInsert(List<Contact> items) {
        Set<Id> accountIds = new Set<Id>();
        for (Contact c : items) {
            if (c.AccountId != null) {
                accountIds.add(c.AccountId);
            }
        }
        // get entries that still need a "first contact" to be set
        Map<Id, Account> acctMap = new Map<Id, Account>(
            [SELECT id, First_Contact__c FROM Account WHERE Id in :accountIds AND First_Contact__c = null]);
        List<Account> accountsToUpdate = new List<Account>();
        for (Contact c : items) {
            if (c.AccountId != null) {
                Account a = acctMap.get(c.AccountId);
                if (a != null && a.First_Contact__c == null) {
                    a.First_Contact__c = c.Id;
                    accountsToUpdate.add(a);
                }
            }
        }
        // set the "First_Contact__c" fields in accounts that needed one
        update accountsToUpdate;      
    }
   
    // if deleting the formerly "First contact" in an account, find the next oldest contact to make the "first contact"
    public void OnAfterDelete(List<Contact> items) {
        Set<Id> accountIds = new Set<id>();
        for (Contact c : items) {
            if (c.AccountId != null) {
                accountIds.add(c.AccountId);
            }
        }
        Map<Id, Account> acctMap = new Map<Id, Account>(
            [SELECT id, First_Contact__c FROM Account WHERE Id in :accountIds]);
        Set<Id> accountIdsWhereWeDeletedFirstContact = new Set<Id>();
        for (Contact c : items) {
            if (c.AccountId != null) {
                Account a = acctMap.get(c.AccountId);
                if (a != null && a.First_Contact__c == null) {
                    accountIdsWhereWeDeletedFirstContact.add(a.Id);
                }
            }
        }
        List<Contact> remainingContacts = [SELECT id, AccountId FROM Contact WHERE AccountId in :accountIdsWhereWeDeletedFirstContact ORDER BY AccountId, CreatedDate];
        acctMap = new Map<Id, Account>(
            [SELECT id, First_Contact__c FROM Account WHERE Id in :accountIdsWhereWeDeletedFirstContact]);
        // in case we have no more contacts for the account, set First_Contact__c to null
        for (Account a : acctMap.values()) {
            a.First_Contact__c = null;
        }
        for (Contact c : remainingContacts) {
            Account a = acctMap.get(c.AccountId);
            if (a.First_Contact__c == null) {
                a.First_Contact__c = c.Id; 
            }
        }
        List<Account> accountsToUpdate = acctMap.values();
        update accountsToUpdate;      
    }
}

SBgooSBgoo
Hello dwright,

thanks for you suggestion. I guess that you got exacthly the issue.
In fact, the trigger on the account update was designed to update all the old accounts but will not needed in the future.

>>>>>>>>you wrote<<<<<<<<
My understanding of what you want is:
1- When the first contact is created in an account, you want the account to have the "First_Contact__c" field point to that contact.
2- When the first contact is deleted in an account, you want the account to have the "First_Contact__c" field point to the now oldest contact in the account.

There is also the case in which a contact X is moved from one account A1 from another one A2. In such case I'll need to update the new account A1 (in case the contact moved is the oldest among A2 contacts) and update also the account A1 (which might remain with no contacts and First_Contact__c=null OR which might have another contact, earlier than X and oldest among the remaining ones in A1).

Thanks for your support!
dwright-glgroupdwright-glgroup
Yes, you are correct that there is a possibility that a Contact can change accounts.  To handle this, you can extend the trigger and class I supplied with the "after update" case that checks to see if the account changed, including to or from "no account".
I would not suggest using a trigger when you need to compute the initial data; use a batch class for that, which you can run with the System.ScheduleBatch API in the developer console.