From d26bff14d97997d29e8cb471b000da80b2361a6d Mon Sep 17 00:00:00 2001 From: Michael Breiner Date: Thu, 11 Jan 2024 10:28:06 -0800 Subject: [PATCH] Bulkify roundRobinAssigner.cls Moving SOQL and DML outside of For Loops --- .../default/classes/roundRobinAssigner.cls | 161 ++++++++---------- 1 file changed, 71 insertions(+), 90 deletions(-) diff --git a/force-app/main/default/classes/roundRobinAssigner.cls b/force-app/main/default/classes/roundRobinAssigner.cls index f62f850..58d31d1 100644 --- a/force-app/main/default/classes/roundRobinAssigner.cls +++ b/force-app/main/default/classes/roundRobinAssigner.cls @@ -1,82 +1,89 @@ public without sharing class roundRobinAssigner { public static final integer customSettingNameLimitLength = 38; + // Invocable method to be called from Process Builder or Flow @InvocableMethod(label='Assign objects') - public static void assign(list rras) { - // what was the last user index? - list RRSettings = [ - SELECT id, GroupName__c, FieldName__c, Object__c, IndexOfLastUsed__c + public static void assign(List rras) { + if(rras.isEmpty()) { + return; // Exit if there are no assignments to process + } + + // Retrieve existing RoundRobin settings + List RRSettings = [ + SELECT Id, GroupName__c, FieldName__c, Object__c, IndexOfLastUsed__c FROM RoundRobin__c ]; - map> usersByGroup = new Map>(); - List objectList = new List(); - - // get the records + // Collect all unique group names from assignments + Set groupNames = new Set(); for (RoundRobinAssignment rra : rras) { - // default field - if (rra.fieldName == null) { - rra.fieldName = 'OwnerId'; - } + groupNames.add(rra.groupName); + } - list groupUsers = new List(); - // see if we have it already in our map - if (usersByGroup.get(rra.groupName) == null) { - groupUsers = [ - SELECT Group.Name, GroupId, UserOrGroupId - FROM GroupMember - WHERE Group.Name = :rra.groupName - ]; - //store in the map so we don't have to query about this group again - usersByGroup.put(rra.groupName, groupUsers); - } else { - groupUsers = usersByGroup.get(rra.groupName); + // Bulk fetch GroupMember records for each group + Map> usersByGroup = new Map>(); + for (GroupMember gm : [SELECT Group.Name, GroupId, UserOrGroupId FROM GroupMember WHERE Group.Name IN :groupNames]) { + if (!usersByGroup.containsKey(gm.Group.Name)) { + usersByGroup.put(gm.Group.Name, new List()); } - // what kind of object is this, anyway? - Id rId = id.valueOf(rra.recordId); - string Obj = rid.getSObjectType().getDescribe().getName(); - - RoundRobin__c matchingRR = recordMatcher( - RRSettings, - rra.fieldName, - Obj, - rra.groupName - ); - - sObject so = rid.getSObjectType().newSObject(rid); - integer nextIndex = getNextIndex(matchingRR, groupUsers); - so.put(rra.fieldName, groupUsers[nextIndex].UserOrGroupId); + usersByGroup.get(gm.Group.Name).add(gm); + } - matchingRR.IndexOfLastUsed__c = nextIndex; + List objectList = new List(); + Map newRRSettingsMap = new Map(); - // if the original list didn't contain the matching one, we need to add it here - boolean isNew = true; - for (RoundRobin__c rr : RRSettings) { - // we already had that on our list - if (rr.Id == matchingRR.Id) { - rr.IndexOfLastUsed__c = matchingRR.IndexOfLastUsed__c; - isNew = false; + for (RoundRobinAssignment rra : rras) { + // Default to 'OwnerId' if fieldName is not specified + String fieldName = rra.fieldName != null ? rra.fieldName : 'OwnerId'; + Id rId = Id.valueOf(rra.recordId); + String Obj = rId.getSObjectType().getDescribe().getName(); + + // Generate unique name for new RoundRobin setting + String uniqueName = nameShortener(Obj, fieldName, rra.groupName); + RoundRobin__c matchingRR = newRRSettingsMap.get(uniqueName); + + // Find or create a new RoundRobin setting for the assignment + if (matchingRR == null) { + matchingRR = findMatchingRR(RRSettings, fieldName, Obj, rra.groupName); + if (matchingRR == null) { + matchingRR = new RoundRobin__c( + FieldName__c = fieldName, + Object__c = Obj, + GroupName__c = rra.groupName, + Name = uniqueName + ); + newRRSettingsMap.put(uniqueName, matchingRR); } } - if (isNew) { - //we hadn't seen it before - system.debug('inserting a RR custom setting'); - RRSettings.add(matchingRR); - } - objectList.add(so); + // Create SObject record to update and add to objectList + SObject recordToUpdate = rId.getSObjectType().newSObject(rId); + List groupUsers = usersByGroup.get(rra.groupName); + Integer nextIndex = getNextIndex(matchingRR, groupUsers); + recordToUpdate.put(fieldName, groupUsers[nextIndex].UserOrGroupId); + matchingRR.IndexOfLastUsed__c = nextIndex; + + objectList.add(recordToUpdate); } + // Perform bulk DML operations outside of the loop update objectList; - - // because we may have incremented them + insert newRRSettingsMap.values(); update RRSettings; } - public static integer getNextIndex( - RoundRobin__c match, - list groupUsers - ) { + // Helper method to find a matching RoundRobin setting + private static RoundRobin__c findMatchingRR(List records, String fieldName, String objName, String groupName) { + for (RoundRobin__c rr : records) { + if (rr.FieldName__c == fieldName && rr.Object__c == objName && rr.GroupName__c == groupName) { + return rr; + } + } + return null; + } + + // Determine the next index for Round Robin assignment + public static integer getNextIndex( RoundRobin__c match, list groupUsers) { if (groupUsers.size() == 0) throw new IllegalArgumentException( 'Round Robin Assigner: No members of ' + match.GroupName__c + ' found'); else if (match.IndexOfLastUsed__c == null) { @@ -96,36 +103,9 @@ public without sharing class roundRobinAssigner { return integer.valueOf(match.IndexOfLastUsed__c + 1); } } - - public static RoundRobin__c recordMatcher( - list records, - string FieldName, - string Obj, - string GroupName - ) { - for (RoundRobin__c rr : records) { - if ( - rr.FieldName__c == FieldName && - rr.Object__c == Obj && - rr.GroupName__c == GroupName - ) { - return rr; - } - } - - // we never found it, so create one - RoundRobin__c rrNew = new RoundRobin__c(); - rrNew.FieldName__c = FieldName; - rrNew.Object__c = Obj; - rrNew.GroupName__c = GroupName; - // because name is required - rrNew.Name = nameShortener(Obj, FieldName, GroupName); - - // how "too long" is it? - insert rrNew; - return rrNew; - } - + + + // Shortens the composite name to fit within the character limit public static string nameShortener( string Obj, string FieldName, @@ -152,8 +132,8 @@ public without sharing class roundRobinAssigner { return Obj + '-' + FieldName + '-' + GroupName; } - - //what Process Builder/Flow Builder can reference for the input variables + + // Inner class to represent each assignment operation public class RoundRobinAssignment { @InvocableVariable(required=true label='group name') public string groupName; @@ -165,3 +145,4 @@ public without sharing class roundRobinAssigner { public string fieldName; } } +