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
Mitch MorrisonMitch Morrison 

Need help with Contact/Event trigger

I am trying to update a custom event field, sales_within_1_year__c, when a custom contact field, sales__c, changes. For example:

Event 1: tied to Contact A & Contact B; sales_within_1_year = $0
Event 2: tied to Contact A & Contact C; sales_within_1_year = $10

Contact A sales changes from $100 to $200.
Event 1: new sales_within_1_year = $100
Event 2: new sales_within_1_year = $110

If the sales for Contact B or C changed, it would only affect the event they are tied to.

I thought my trigger would work, and it does when I change a contact's sales number individually. But when I bulk update contacts, none of the sales_within_1_year numbers change. I was wondering if someone could help me through this. Thanks!

Here is my trigger:

trigger EventSales on Contact (before update) {
                //Create a list of contact IDs that have updating sales
                List<Id> contactIds = new List<Id>();
    for(Contact con : trigger.new) {
        if(Trigger.newMap.get(con.Id).Sales__c == NULL) return;
        else if(trigger.newMap.get(con.Id).Sales__c == 0) return;
        else if(trigger.oldMap.get(con.Id).Sales__c == NULL) {
            contactIds.add(con.Id);
        }
        else if(trigger.newMap.get(con.Id).Sales__c !=
                trigger.oldMap.get(con.Id).Sales__c) {
            contactIds.add(con.Id);
        }
    }
   
    //Create a list of event relations (contact-event pairs) for updating contacts
    List<EventRelation> evRel = [SELECT EventId, RelationId
                                FROM EventRelation
                                WHERE RelationId IN :contactIds];
   
    //Create a map of the updating contacts and their related events
    Map<Id, List<Id>> contactEvents          = new Map<Id, List<Id>>();
   
    //Create a list of event Ids that are going to be updated
    List<Id> eventIds = new List<Id>();
   
    //Put the contact Id and event Id in the map for events that have updating contacts
    for(EventRelation rel :evRel) {
        if(contactEvents.containsKey(rel.RelationId)) {//If the contact is already in the map
            List<Id> relatedEv = contactEvents.get(rel.RelationId);//Grab the list of events tied to the contact
            relatedEv.add(rel.EventId);//Add the new event to the list
            contactEvents.put(rel.RelationId, relatedEv);//Put the updated list into the map
        }
        else {//If the contact isn't in the map
            contactEvents.put(rel.RelationId, new List<Id>{rel.EventId});//Put the contact and event into the map
        }
        //Add the updating event to the eventIds list
        if(eventIds.contains(rel.EventId)) return;
        else eventIds.add(rel.EventId);
    }
   
    //Create a list of events that are going to be updated
    List<Event> listEventsMaker = [SELECT Id, Sales_within_1_year__c
                                   FROM Event
                                   WHERE Id IN :eventIds
                                   AND EndDateTime = LAST_N_DAYS:365];
   
    List<Event> listEvents = new List<Event>();
    for(Event ev :listEventsMaker) {
        if(listEvents.contains(ev)) return;
        else listEvents.add(ev);
    }
   
    //Keep a list of events to update
    List<Event> changedEvents = new List<Event>();
   
    //Update the sales amounts for the events
    for(Id con :contactEvents.keySet()) {//For each contact in the map
        List<Id> thisContact = new List<Id>();//Create a list of events tied to this specific contact
        Contact oldCon = trigger.oldMap.get(con); //create a record for the contact pre-update
        Contact newCon = trigger.newMap.get(con); //create a record for the contact post-update
        if(newCon.Sales__c == NULL) return;
        else if(newCon.Sales__c == 0) return;
        else if(oldCon.Sales__c == NULL) {
            for(Id event :contactEvents.get(con)) {
                thisContact.add(event);
            }
            for(Event ev :listEventsMaker) {
                if(thisContact.contains(ev.Id)) {
                    changedEvents.add(ev);
                    if(ev.Sales_within_1_Year__c == NULL) {
                        ev.Sales_within_1_Year__c = newCon.Sales__c;
                    }
                    else ev.Sales_within_1_Year__c += newCon.Sales__c;
                }
            }
        }
        else if(oldCon.Sales__c
                != newCon.Sales__c) {
            for(Id event :contactEvents.get(con)) {
                thisContact.add(event);
            }
            for(Event ev :listEventsMaker) {
                if(thisContact.contains(ev.Id)) {
                    changedEvents.add(ev);
                    if(ev.Sales_within_1_Year__c == NULL) {
                        ev.Sales_within_1_year__c = (newCon.Sales__c
                                                     - oldCon.Sales__c);
                    }
                    else ev.Sales_within_1_Year__c += (newCon.Sales__c
                                                       - oldCon.Sales__c);
                }
            }
        }
    }
    update changedEvents;
}
 
Best Answer chosen by Mitch Morrison
Greg HGreg H
As it relates to your specific question, the issue is where you are adding the Event to the changedEvents list. You are adding the record to the list before you are updating the Sales_within_1_Year__c value on the given Event record. Move the changedEvents.add(ev); method to the logic after the if/then statement. You're doing it two places so make sure to correct it in both.

I am concerned that you are updating this value on Events and not in some other manner. My primary concern is that Event records are archived after 1 year so you lose visibility to them after 365 days. My secondary concern is that any given Org may have many Event records and you will have to update multiple Events each time a change is made to Contact. Since I do not know your business requirements I cannot speak to whether another solution is better suited.

If, after taking my previous two points into consideration, you decide that it still makes sense to do this calculation and apply the values across multiple Event records then I highly recommend that you modify the logic in a way that allows you to process the Updates to Event records asynchronously. This will reduce some of the screen lag time that may happen for Users that have many Event records linked to a COntact that they are updating in the interface. I also recommend that you create a batch job to run daily and re-calculate the Sales_within_1_Year__c values. This is because "things" happen from time-to-time that may cause your trigger logic to fail. Eecuting a scheduled batch job will ensure that your data remains up-to-date.
-greg

All Answers

Greg HGreg H
As it relates to your specific question, the issue is where you are adding the Event to the changedEvents list. You are adding the record to the list before you are updating the Sales_within_1_Year__c value on the given Event record. Move the changedEvents.add(ev); method to the logic after the if/then statement. You're doing it two places so make sure to correct it in both.

I am concerned that you are updating this value on Events and not in some other manner. My primary concern is that Event records are archived after 1 year so you lose visibility to them after 365 days. My secondary concern is that any given Org may have many Event records and you will have to update multiple Events each time a change is made to Contact. Since I do not know your business requirements I cannot speak to whether another solution is better suited.

If, after taking my previous two points into consideration, you decide that it still makes sense to do this calculation and apply the values across multiple Event records then I highly recommend that you modify the logic in a way that allows you to process the Updates to Event records asynchronously. This will reduce some of the screen lag time that may happen for Users that have many Event records linked to a COntact that they are updating in the interface. I also recommend that you create a batch job to run daily and re-calculate the Sales_within_1_Year__c values. This is because "things" happen from time-to-time that may cause your trigger logic to fail. Eecuting a scheduled batch job will ensure that your data remains up-to-date.
-greg
This was selected as the best answer
Mitch MorrisonMitch Morrison
Thanks Greg! In regards to the archiving, I don't think the 1 year limit should be a big issue (and you can push that limit back if you want to, right?). We are going to have other fields on events that track monthly & quarterly sales numbers, which are more important.

The values for sales__c on the contacts get imported from an external database every morning, and I assume this is done in batches (I will have to check), So no users are updating any fields individually. If you still think we should modify the logic asynchronously, how would I go about doing that?

Thanks for you help!
Mitch MorrisonMitch Morrison
Also, I moved changedEvents.add(ev) to the right spots and it still didn't work. I used import wizard for 1500 contact updates and they all went through. The sales values got incremented, but the sales_within_1_year values did not.
Greg HGreg H
Is it possible that some events don't have attendees? You may need to query for the WhoId on the Events as well?
-greg
Mitch MorrisonMitch Morrison
All the events have contact records tied to them. I went & manually incremented the sales number for 6 contacts by $1000. They are all in one event together, and that event's sales within 1 year went up by $6000. So the trigger worked. But for whatever reason, when I mass import contacts updates, the trigger doesn't take effect. No errors pop up, but no event fields get updated. Plus an import of 1500 updates only takes 1 minute, so I don't think the process is breaking.

I really don't have any explanation or ideas on what is happening, but hopefully it can be fixed.