DEV Community

Explorer
Explorer

Posted on

Building an Automated Workload Distribution & Task Delegation Engine in Joget

When managing inspection teams, field auditors, or regional support agents in Joget DX, assigning tasks manually creates bottlenecks. Furthermore, simply assigning tasks round-robin doesn't account for differing regional task volumes, leading to uneven workloads across your team.

In this guide, we'll build a Java/BeanShell scheduling script that aggregates task volume across regions, balances workload across available employees using a least-loaded distribution algorithm, and automatically starts workflow assignments for each team member.


How the Balancing Algorithm Works

  1. Workload Aggregation: The script runs on a Joget Scheduler trigger and queries database records over a rolling 30-day window to calculate task volume per region/branch.
  2. Greedy Least-Loaded Allocation: Regions with the highest task volume are assigned iteratively to whichever team member currently has the lowest cumulative workload.
  3. Workflow Process Initiation: Once assignments are calculated, the script uses WorkflowManager.processStart() to launch workflow processes pre-assigned to the appropriate team member.

The Full Script

Place this code inside a BeanShell Tool step or execute it via a scheduled background plugin:

import org.joget.workflow.model.service.WorkflowManager;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.app.model.AppDefinition;
import org.joget.workflow.model.WorkflowProcessResult;
import org.joget.commons.util.LogUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

// 1. Data Models for Workload Tracking
class RegionWorkload {
    String regionCode;
    int taskCount;
    RegionWorkload(String code, int count) {
        this.regionCode = code;
        this.taskCount = count;
    }
}

class TeamMember {
    String username;
    int currentWorkload = 0;
    List assignedRegions = new ArrayList();

    TeamMember(String username) {
        this.username = username;
    }

    void assignRegion(RegionWorkload rw) {
        assignedRegions.add(rw);
        currentWorkload += rw.taskCount;
    }
}

// 2. Fetch Active Team Members
List activeInspectors = new ArrayList();
activeInspectors.add(new TeamMember("inspector_alpha"));
activeInspectors.add(new TeamMember("inspector_beta"));
activeInspectors.add(new TeamMember("inspector_gamma"));

// Date range calculation (Last 30 days)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String toDate = LocalDateTime.now().format(formatter);
String fromDate = LocalDateTime.now().minusDays(30).format(formatter);

Connection con = null;
PreparedStatement stmt = null;
ResultSet rs = null;
List regionList = new ArrayList();

try {
    DataSource ds = (DataSource) AppUtil.getApplicationContext().getBean("setupDataSource");
    con = ds.getConnection();

    if (con != null && !con.isClosed()) {
        // Query workload volume per region
        String sql = "SELECT c_region_code AS region, COUNT(*) AS total_tasks " +
                     "FROM app_fd_inspection_requests " +
                     "WHERE dateCreated BETWEEN ? AND ? " +
                     "GROUP BY c_region_code";

        stmt = con.prepareStatement(sql);
        stmt.setString(1, fromDate);
        stmt.setString(2, toDate);
        rs = stmt.executeQuery();

        while (rs.next()) {
            regionList.add(new RegionWorkload(rs.getString("region"), rs.getInt("total_tasks")));
        }
    }
} catch (Exception e) {
    LogUtil.error("Workload Delegation", e, "Error aggregating regional workload");
} finally {
    if (rs != null) try { rs.close(); } catch (Exception e) {}
    if (stmt != null) try { stmt.close(); } catch (Exception e) {}
    if (con != null) try { con.close(); } catch (Exception e) {}
}

// 3. Sort Regions by Task Count (Highest First)
Collections.sort(regionList, new Comparator() {
    public int compare(Object a, Object b) {
        RegionWorkload r1 = (RegionWorkload) a;
        RegionWorkload r2 = (RegionWorkload) b;
        return Integer.valueOf(r2.taskCount).compareTo(Integer.valueOf(r1.taskCount));
    }
});

// 4. Distribute Workload Greedily to Least-Loaded Employee
for (Object oRegion : regionList) {
    RegionWorkload rw = (RegionWorkload) oRegion;

    // Find employee with lowest current workload
    TeamMember leastLoaded = (TeamMember) activeInspectors.get(0);
    for (Object oEmp : activeInspectors) {
        TeamMember tm = (TeamMember) oEmp;
        if (tm.currentWorkload < leastLoaded.currentWorkload) {
            leastLoaded = tm;
        }
    }

    leastLoaded.assignRegion(rw);
    LogUtil.info("Workload Delegation", "Assigned Region " + rw.regionCode + " (" + rw.taskCount + " tasks) to " + leastLoaded.username);
}

// 5. Trigger Workflow Processes for Each Assignment
WorkflowManager wm = (WorkflowManager) AppUtil.getApplicationContext().getBean("workflowManager");
AppDefinition currentAppDef = AppUtil.getCurrentAppDefinition();
String processDefId = "inspection_app:1:evaluation_process";

for (Object oEmp : activeInspectors) {
    TeamMember tm = (TeamMember) oEmp;
    for (Object oReg : tm.assignedRegions) {
        RegionWorkload rw = (RegionWorkload) oReg;

        Map processVariables = new HashMap();
        processVariables.put("assignee", tm.username);
        processVariables.put("region_code", rw.regionCode);
        processVariables.put("task_count", String.valueOf(rw.taskCount));

        try {
            WorkflowProcessResult result = wm.processStart(processDefId, processVariables);
            LogUtil.info("Workload Delegation", "Started process for " + tm.username + " [Region: " + rw.regionCode + "]");
        } catch (Exception e) {
            LogUtil.error("Workload Delegation", e, "Error launching process for " + tm.username);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Practical Deployment Tips

  • Dynamic Member Fetching: Instead of hardcoding usernames, query Joget's DirectoryManager API (dirManager.getUserList()) to fetch active members in an inspection group dynamically.
  • Scheduled Triggers: Combine this script with a Cron Scheduler or Joget Deadline plugin to run workload rebalancing every Monday morning or at month-end.

Top comments (0)