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
- 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.
- Greedy Least-Loaded Allocation: Regions with the highest task volume are assigned iteratively to whichever team member currently has the lowest cumulative workload.
-
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);
}
}
}
Practical Deployment Tips
-
Dynamic Member Fetching: Instead of hardcoding usernames, query Joget's
DirectoryManagerAPI (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)