0

Still fumbling my way through this problem:

Help understand Apex class / bulkilfy

I decided to go ahead and create a query in Apex that gets the Leads I need to be reassigned and push those into the RoundRobin apex class. I've got the query working, tested in developer console and is working. My next I wanted to try is to get the list of leads into the RoundRobin class, I figured I can start to mess with the scheduler to try to accomplish that. Ive scheduled it to run every 15 minutes for testing and its just sitting their as "Scheduled". I suspect maybe the code Ive borrowed from the internet isnt actually working or executing anything? Ive included the code below. Thank you!

global class scheduledCron implements Schedulable{
    global void execute(SchedulableContext SC) {
        new RRA();
    }
}

and here is the apex with the queries for users and leads

public with sharing class RRA {

    list<roundRobinAssigner.RoundRobinAssignment> rras = new list<roundRobinAssigner.RoundRobinAssignment>();

    List<User> users = [SELECT Id FROM User WHERE Lead_Reassignment__c = true AND IsActive = true AND ProfileId = '00e4P000000KDMk'];
 
    List<Lead> leads = [SELECT Id 
                        FROM Lead
                        WHERE OwnerID IN : users
                        AND Next_Business_Date__c = 2020-08-31 
                        AND (Status__c = 'New' OR 
                        Status__c = 'Attempting Contact 1' OR
                        Status__c = 'Attempting Contact 2' OR
                        Status__c = 'Attempting Contact 3' OR
                        Status__c = 'Attempting Contact 4' OR
                        Status__c = 'Attempting Contact 5')
                        ];
                        
     public void createrrassigns(){
        for (lead a:leads){
            roundRobinAssigner.RoundRobinAssignment rra = new roundRobinAssigner.RoundRobinAssignment();
            rra.groupName = 'Online_Round_Robin';
            rra.recordId = a.id;
            rras.add(rra);
            }

            roundRobinAssigner.assign(rras);
      }


}

and the RoundRobin assigner class just incase:

public without sharing class roundRobinAssigner {

    public static final integer customSettingNameLimitLength = 40;
    

    @InvocableMethod(label='Assign objects')
    public static void assign(list<RoundRobinAssignment> rras) {

        // what was the last user index?
        list<RoundRobin__c> RRSettings = [select id, GroupName__c, FieldName__c, Object__c, IndexOfLastUsed__c from RoundRobin__c];

        Set<Id> recordIds = new Set<Id>();

        map<string, list<GroupMember>> usersByGroup = new map<string, list<GroupMember>>();
        List<SObject> objectList = new List<SObject>();

        
        // get the records
        for (RoundRobinAssignment rra:rras){
            // default field
            if (rra.fieldName == null){
                rra.fieldName = 'OwnerId';
            }

            list<groupMember> groupUsers = new list<groupMember>();
            // 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);
            }
            // 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);

            matchingRR.IndexOfLastUsed__c = nextIndex;

            // 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;
                }
            }
            if (isNew){
                //we hadn't seen it before
                system.debug('inserting a RR custom setting');
                RRSettings.add(matchingRR);
            }

            objectList.add(so);
        }

        update objectList;

        // because we may have incremented them
        update RRSettings;

    }

    public static integer getNextIndex(RoundRobin__c match, list<GroupMember> groupUsers){
        if (match.IndexOfLastUsed__c == null){
            // it's null, so grab the first one!
            return 0;
        } else if (match.IndexOfLastUsed__c >= groupUsers.size()-1){
            // you've used them all
            system.debug(match.IndexOfLastUsed__c + ' is the last match, which is >= the group size of ' + groupUsers.size() + 'so we are going back to 0' );
            return 0;
        } else {
            // return the next one
            return integer.valueOf(match.IndexOfLastUsed__c + 1);
        }
    }

    public static RoundRobin__c recordMatcher(list<RoundRobin__c> 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;
    }

    public static string nameShortener(string Obj, string FieldName, string GroupName) {

        integer tooLongness = Obj.length() + FieldName.length() + GroupName.length() + 2 - customSettingNameLimitLength; // for separators
        if (tooLongness <=0) {
            return Obj + '-' + FieldName + '-' + GroupName;
        }

        integer wholesToChop = Math.round(Math.floor(tooLongness / 3));
        integer remainderToChop = Math.mod(tooLongness, 3);

        Obj = Obj.left(Obj.length() - wholesToChop);
        FieldName = FieldName.left(FieldName.length() - wholesToChop);
        GroupName = GroupName.left(GroupName.length() - wholesToChop);

        if (remainderToChop > 0) {
            GroupName = GroupName.left(GroupName.length() - 1);
        }

        if (remainderToChop > 1) {
            FieldName = FieldName.left(FieldName.length() - 1);
        }

        return Obj + '-' + FieldName + '-' + GroupName;
    }

    //what Process Builder/Flow Builder can reference for the input variables
    public class RoundRobinAssignment {
        @InvocableVariable(required=true label='group name')
        public string groupName;

       @InvocableVariable(required=true label='recordId')
       public string recordId;

        @InvocableVariable(required=false label='field name (default = ownerId)')
        public string fieldName;
    }


}

I pretty much have no freakin idea what Im doing and Im going a bit crazy, thanks for any insights here!!

0

I'm not sure why your schedulables would be sitting in "scheduled" (unless you've scheduled it at some point in the future and it hasn't exectuted yet), but from the code it looks like your schedulable doesn't actually do anything.

global class scheduledCron implements Schedulable{
    global void execute(SchedulableContext SC) {
        new RRA();
    }
}

Tracing the execution, what this code does is call the constructor on the RRA class. It doesn't have a constructor defined, so it's going to call a generic object constructor (which does nothing other than allocate what's needed for a new object "under the hood" so to speak) and initialize the properties in RRA. The schedulable should effectively end at that point without any data being touched. I believe you want to do something more like this:

global class scheduledCron implements Schedulable{
    global void execute(SchedulableContext SC) {
        RRA assigner = new RRA();
        assigner.createrrassigns(); // call the method that does the work
    }
}

But in situations like this it helps to do two things.

  1. Test out your RRA class by invoking it directly. Make sure it's doing what you want it to do.
  2. Separately, try making a scheduler that does something simple, like creating a dummy Case record or sending an email. That way you can get a feel for how to schedule a class like this.
| improve this answer | |
  • Thank you! your number 1 suggestion was actually what I was wanting to do because I know a piece of the code is working but not the other half when the list should get passed over to the main class.... how do I go about invoking it directly? In the developer console? – Sgrove Sep 10 at 17:44
  • Yes, copy and paste the contents of scheduledCron.execute into Execute Anonymous – nbrown Sep 10 at 17:47
  • Got it thanks! Anyway to step through where errors are happing im geting List index out of bounds: 0 – Sgrove Sep 10 at 17:57
  • That means you are trying to access an index in an empty list. There are a number of other StackExchange posts that go into this error if you search for it. – nbrown Sep 10 at 19:15
  • Hi @Sgrove, if this answer helped you don't forget to mark it as accepted. – nbrown Sep 14 at 19:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.