Background: We have a custom object called Training History that stores customer training "minutes". Customers purchase minutes from us in blocks of 60, and we book Events for them in Salesforce for increments of 60 as well, and when the event is Completed, the minutes are deducted from the customers Training History.
We needed a way to stop our users from A.) Booking Events for these customers if the number of minutes being booked exceeded the number of minutes they have available, and, B.) Stop users from editing existing events and marking them as Complete. So, I wrote some before code to handle this requirement in the form of an advanced custom Apex VR. I have this code running
before insert
andbefore update
so that the VR will stop users from:
- Booking New Events that exceed customer training minutes
(Before Insert)
- Completing existing Training Events if they exceed available training minutes
(Before Update)
Here is what my trigger looks like where I call my Class.Method:
if(trigger.isBefore && trigger.isInsert){
if(Disable_updateEventOnBefore == false){
EventUpdateHandler.updateEventonBefore(null,Trigger.new);
}
}
if(trigger.isBefore && trigger.isUpdate){
if(Disable_updateEventOnBefore == false){
EventUpdateHandler.updateEventonBefore(Trigger.oldMap,Trigger.new);
}
}
Problem: Because this same piece of code is run before insert AND before update, and because something else is causing the transaction to go through Order of Execution more than once, it's running twice on a new insert (once before insert and once before update), and double counting the minutes which results in a falsely bloated count of Event minutes being commuted to the db, which can cause the VR to fire incorrectly.
Normally, I would stop my code from parsing the same record more than once by creating a global Set<Id>
and storing the record Id after it has been processed, like this:
static final Set<Id> processedRecords = new Set<Id>();
for(Event e : newEvents){
if(!processedRecords.contains(e.Id){
// do stuff
processedRecords.add(e.Id);
}
}
Since the processedRecords set is set globally in the class, it stores the record Id through multiple invocations and stops it from running the same block of code on that record more than once.
However, before insert records don't have Id values yet. So my code still runs on before insert
like I want it to for new records, but nothing stops it from running again on before update because the e.Id was null on the first run through the code thus meaning that there is nothing stopping it from running that second time.
This issue only effects new inserts, because they don't have an Id. The code is successfully blocked from running more than once on record updates.
Question: How do I best handle this situation? Can I tell the code somehow that it was already executed
before insert
in this transaction, so don't run the block again forbefore update
? I need the same piece of code to be able to run in both scenarios, but only one scenario at a time/transaction (either it was an insert or an update, but should never run for both at once)