Using the Queue Abstract Data Type to Assign Records to Users

The list data type is rather versatile, and its use is essential in many programmatic solutions on the Salesforce platform. However, there are some scenarios when lists alone do not provide the most elegant solution. One example is routing the assignment of accounts based on each user’s current capacity.

Suppose we want to assign the oldest unassigned account to a user at the moment when a new account is entered into Salesforce. When working with these accounts, we might want to order them by received date, with the first entry containing the oldest date. How can we design a routing tool so that the next account to assign is stored at the front of a list?

An account queue, ordered by received date
Figure 1. An account queue, ordered by received date

Queue Abstract Data Type

The queue abstract data type is well suited for this type of problem. Our first step should be to define the Queue interface and what methods we want to include:

public interface Queue{
    //returns the number of entries in the queue
    Integer size();
 
    //returns true if there are no entries in the queue
    boolean isEmpty();
 
    //places the record at the end of the queue
    void enqueue(SObject o);
 
    //returns the entry at the front of the queue but does not remove it
    SObject first();
 
    //returns and removes the entry at the front of the queue
    SObject dequeue();
}

Next we need to implement this interface for accounts:

public class AccountQueue implements Queue{
 
    private List<Account> accounts;
 
    //default constructor
    public AccountQueue(){
        this.accounts = new List<Account>();
    }
 
    //returns the number of accounts in the queue
    public Integer size(){
        return accounts.size();
    }
 
    //returns true if there are no accounts in the queue
    public boolean isEmpty(){
        return accounts.isEmpty();
    }
 
    //places the account at the end of the queue
    public void enqueue(SObject o){
        Account newAccount = (Account) o;
        accounts.add(newAccount);
    }
 
    //returns the account at the front of the queue
    public Account first(){
        if(isEmpty()){
            return null;
        }
 
        return accounts.get(0);
    }
 
    //returns and removes the account at the front of the queue
    public Account dequeue(){
        if(isEmpty()){
            return null;
        }
 
        Account firstAccount = accounts.get(0);
        accounts.remove(0);
        return firstAccount;
    }
}

On the Account object, we should create two custom fields. The first is called Assigned, which is a lookup to the User object. The second is the Received Date which is a date field. On the User object, we can create a number field called Capacity, which will tell us to how many more applications a User can be assigned. Once this number reaches zero, we should not assign any more applications to that particular user.

In order for this process to occur when an account is inserted, we will need an Account trigger:

trigger Account on Account (before insert) {
    if(trigger.isBefore && trigger.isInsert){
        new AccountTriggerHandler().beforeInsert(trigger.new);
    }
}

Here is the trigger handler:

public class AccountTriggerHandler {
 
    //list of accounts that are available for assignment to a User
    private List<Account> unassignedAccounts {
        get{
            if(unassignedAccounts == null){
                unassignedAccounts = [SELECT ID,
                Name,
                Received_Date__c
                FROM Account
                WHERE Received_Date__c != null AND Assigned__c = null ORDER BY Received_Date__c];
            }
 
            return unassignedAccounts;
        }
 
        private set;
    }
 
    //Account queue where the account at the front of the queue has the oldest received date
    private AccountQueue unassignedAccountQueue {
        get{
            if(unassignedAccountQueue == null){
                unassignedAccountQueue = new AccountQueue();
                for(Account a : unassignedAccounts){
                    unassignedAccountQueue.enqueue(a);
                }
 
            }
 
            return unassignedAccountQueue;
        }
 
        private set;
    }
 
    //Map of users that are able to receive assigned applications
    private Map<ID,User> userMap {
        get{
            if(userMap == null){
                userMap = new Map<ID,User>([SELECT ID, Capacity__c FROM User WHERE Capacity__c != null]);
            }
            return userMap;
        }
        private set;
    }
 
    public void beforeInsert(List<Account> accountList){
        //obtain the number of accounts in the trigger
        Integer numberOfAccountsToAssign = accountList.size();
 
        //hold a list of accounts that will be assigned to users
        List<Account> accountsToAssign = new List<Account>();
 
        for(Integer i = 0; i < numberOfAccountsToAssign; i++){
            //obtain the id of the next user that can receive an application
            ID userIDNextToAssign = getNextAssignedUser();
 
            //reduce that user's capacity by 1
            reduceCapacity(userIDNextToAssign);
 
            //determine the next account that is to be assigned
            Account unassignedAccount = unassignedAccountQueue.dequeue();
 
            //if there were any accounts remaining in the queue, assign that account
            if(unassignedAccount != null){
                unassignedAccount.Assigned__c = userIDNextToAssign;
                accountsToAssign.add(unassignedAccount);
            }
        }
 
        //update unassigned accounts
        update accountsToAssign;
 
        //update the user records
        update userMap.values();
 
    }
 
    //return the id of the user that will be assigned to the next available account
    private ID getNextAssignedUser(){
        ID largestCapacityUserID;
 
        //find the user id of the largest capacity user
        Integer maxCapacity = 0;
 
        for(ID userID : userMap.keySet()){
            Integer userCapacity = (Integer) userMap.get(userID).Capacity__c;
            if(maxCapacity < userCapacity && userCapacity > 0){
                maxCapacity = userCapacity;
                largestCapacityUserID = userID;
            }
        }
 
        return largestCapacityUserID;
    }
 
    //recude the capacity of the user with id userID by 1
    private Map<ID,User> reduceCapacity(ID userID){
        //decrease the capacity of that user by 1
        if(userID != null){
            User usr = userMap.get(userID);
            usr.Capacity__c = usr.Capacity__c - 1;
        }
 
        return userMap;
    }
}

The trigger fires when a new account is entered into Salesforce, then searches for the account with the oldest received date, and assigns it to the user with the highest capacity. To demonstrate, we can set one user to have a capacity of 1, and a second user to have a capacity of 2. If we insert three accounts, then the three accounts with the oldest received date will be distributed between both users.

Here are the existing accounts before we insert the new accounts:

Figure 2. State of existing accounts before the new ones are inserted.
Figure 2. State of existing accounts before the new ones are inserted (click image to enlarge).

Here are the assignments after we add the new accounts:

Figure 3. The older accounts have now been assigned after inserting the new ones
Figure 3. The older accounts have now been assigned after inserting the new ones (click image to enlarge).

Since three accounts were inserted into Salesforce, we needed to assign three accounts.

  • Mario had a capacity of 2, so he was assigned the first account in the queue.
  • Next, Taylor, who had a capacity of 1, was assigned an account. He was then at a full capacity of 0.
  • Mario, now at a capacity of 1, received the next account in the queue.

Alternative Approaches

There are some alternative ways to approach this problem using only lists, but using the Queue interface simplifies the implementation. One approach could have been to reverse the order of the unassigned accounts, starting with the newest account as the first item, and the oldest as the last. This might be unintuitive and would require the developer to store or compute the size of the list in order to access the last element. Another approach might have been to keep the same order as the queue solution, but to simply remove the element from the front of the list using the remove(index) function. The queue implementation abstracts this process nd removes the requirement to continue to check if the list is empty, as the dequeue method already provides that functionality.

The queue abstract data type is a natural fit for any first in first out business requirement. Queues can also be extended to other objects in Salesforce, rather than just the Account object. Queues and other abstract data types can provide templates for solutions to many programming challenges and Salesforce projects are no exception.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top