Auto Assign tasks/incidents based on workload and skills

YenGar
Mega Sage

Hello community,

I'm searching for some functionality that allows task auto-assignment based on skill and workload of the group members. I've looked into Queue Manager from the Share and it provides great functionality for auto-assigning tasks based on workload/weight of tasks assigned to users but not related to Skills Management. Has anyone looked into combining the script include from Skills management (SkillsUtils) that filters the users according to the skills needed for the task and queue manager auto-assigning functionality? Any idea of how this could be done?

Any advice is appreciated!


Thank you,

Yeny

3 REPLIES 3

sachin_namjoshi
Kilo Patron
Kilo Patron

Hi Yeny,



This is provided OOB with HR module to route HR cases based on workload and skills,


Create an HR assignment rule



YOu can utilize same util to implement auto routing of incidents based on skills.



Regards,


Sachin


Hi Sachin,



Thank you for your response! Unfortunately, after activating the HR plugin and looking everywhere in the HR assignment rules, I didn't get anywhere that I could see could help me with what I am trying to do.



What I am trying to achieve is to process the skillsUtils() script include from the Queue manager business rule that auto-assigns the tasks. I am obviously not doing it right because it isn't doing anything... below is what I am trying to do... Does it make sense? Is it even possible from what you can see?



Script include - filters the users within the group that match the task skills needed


var SkillsUtils = Class.create();


SkillsUtils.prototype = {


/**


* build assigned_to reference qualifier using task assignment group and skills


* @param GlideRecord task


* @return String reference qualifier query for sys_user


* usage: javascript:var util = new SkillsUtils(); util.assignedToRefQual(current, "roles=itil");


*/


assignedToRefQual: function(/*GlideRecord*/task, defaultQual) {


if (JSUtil.nil(defaultQual))


    defaultQual = "";


var skills = task.skills.split(",");


var group = task.assignment_group;


if (skills && skills != "") {


var hasSkills = true;


var allSkilledUserIds = this.getAllSkilledUserIds(skills);


}


if (group && group != "") {


var hasGroup = true;


var groupUsers = this._getMembers(group);


}



//no inputs to use, return default query


if (!hasSkills && !hasGroup)


return defaultQual;



//group but no skills specified, return group members


if (hasGroup && !hasSkills) {


var groupUserList = [];


while (groupUsers.next())


groupUserList.push(groupUsers.getUniqueValue());



return "sys_idIN" + groupUserList.toString();


}



//skills but no group specified, return skilled users


if (!hasGroup && hasSkills) {


return "sys_idIN" + allSkilledUserIds.toString();


}


//only thing left is to process both the group and skills


var skilledMemberList = [];


while (groupUsers.next()) {


//is user in list of all skilled users


var au = new ArrayUtil();


if (au.contains(allSkilledUserIds, groupUsers.getUniqueValue()))


skilledMemberList.push(groupUsers.getUniqueValue());


}


return "sys_idIN" + skilledMemberList.toString();


},



/**


* qualify assignment groups with those with all required skills


* usage: javascript:var util = new SkillsUtils(); util.assignmentGroupRefQual(current);


*/


assignmentGroupRefQual: function(/*GlideRecord*/ task) {


var skills = task.skills.split(",");


if (JSUtil.nil(skills))


return "type=null";   //default qualifier


var allSkilledGroupIds = this.getAllSkilledGroupIds(skills);


return "sys_idIN" + allSkilledGroupIds;


},



/**


* get all group with all skills


* @param Array skills


* @return Array group sys_id


*/


getAllSkilledGroupIds: function(/*Array*/skills) {


//how many skills do we need


var skillsCount = skills.length;


var groupIds = new Object();


for (var i = 0; i < skills.length; i++) {


var groupsProcessed = new Object();


var gr = new GlideRecord("sys_group_has_skill");


gr.addQuery("skill", skills[i]);


gr.query();


while (gr.next()) {


//only process if this isn't a duplicate


if (!groupsProcessed[gr.group]) {


if (!groupIds[gr.group])


groupIds[gr.group] = 1;


else


groupIds[gr.group] += 1;



//don't process this group/skill again if duplicate


groupsProcessed[gr.group] = true;


}


}


//clear temp tracking object


usersProcessed = null;


}



var skilledGroups = [];


for (group in groupIds) {


if (groupIds[group] == skillsCount)


skilledGroups.push(group);


}



return skilledGroups;


},



/**


* get all users with all skills


* @param Array skills


* @return Array user sys_id


*/


getAllSkilledUserIds: function(/*Array*/skills) {


//how many skills do we need


var skillsCount = skills.length;


var userIds = new Object();


for (var i = 0; i < skills.length; i++) {


var usersProcessed = new Object();


var gr = new GlideRecord("sys_user_has_skill");


gr.addQuery("skill", skills[i]);


gr.query();


while (gr.next()) {


//only process if this isn't a duplicate


if (!usersProcessed[gr.user]) {


if (!userIds[gr.user])


userIds[gr.user] = 1;


else


userIds[gr.user] += 1;



//don't process this user/skill again if duplicate


usersProcessed[gr.user] = true;


}


}


//clear temp tracking object


usersProcessed = null;


}



var skilledUsers = [];


for (user in userIds) {


if (userIds[user] == skillsCount)


skilledUsers.push(user);


}



return skilledUsers;


},



_getMembers: function(/*String*/groupID) {


var gr = new GlideRecord("sys_user_grmember");


gr.addQuery("group", groupID);


gr.query();


var members = [];


while (gr.next()) {


members.push(gr.user.sys_id.toString());


}



//get active user records


var users = new GlideRecord("sys_user");


users.addActiveQuery();


users.addQuery("sys_id", members);


users.query();


return users;


},



type: "SkillsUtils"


}



Business Rule - Async business rule that auto assigns tasks based on workfload



//Used by the Queue Manager application to auto assign tasks


//according to rules listed in the Queue Rules module



var debug = gs.getProperty("queue.manager.debug"); //true or false


var currentGroupWeighting = gs.getProperty("queue.manager.current_group_weighting"); //true or false



processQueueRules();


//Iterate through applicable active rules until a match is found


function processQueueRules(){


var groupID = current.assignment_group + '';


var qRule = new GlideRecord("queue_rules");


qRule.addQuery("table", current.sys_class_name);


qRule.addActiveQuery();


qRule.orderBy("order");


qRule.query();


while (qRule.next()) {


//Check if group is included in the Queue Rule


//No groups listed means we try for *all* groups


if (qRule.groups.nil() || qRule.groups.indexOf(groupID) > -1){


//If applicable, check that Queue Rule conditions match


if ((qRule.conditional == false) || (qRule.conditional == true && checkCondition(current, qRule.condition))){


if (debug == "true")


gs.log('QueueMan: Begin processing Queue Rule ' + qRule.number + ' on ' + current.number + ' with assignment group ' + current.assignment_group.name + '.');


applyRule(qRule, groupID);


break;


}


else if (debug == "true")


gs.log('QueueMan: ' + qRule.number + ' conditions not met for ' + current.number + ' - skipping.');


}


else if (debug == "true")


gs.log('QueueMan: ' + qRule.number + ' - Assignment group ' + current.assignment_group.name + ' no match for ' + current.number + ' - skipping.');


}


}


//Apply applicable active rule to the Task and Assignment Group members


//Generates overall weight for each member of the queue, according to Queue Weights



function applyRule(rule, group, user){


var weightMatrix = []; //Array in format [userID:weight]


//current has the task record for which the rule is being executed.



//This is what I've added from the script include


var skills = current.skills;


var skillUtil = new SkillsUtils();


var skilledUsers = skillUtil.getAllSkilledUserIds(skills);



//Gather all 'available' group members, calculate their 'weight', and skills


//and populate array. All 'weights' are added to a random number


//between 0 and 1, in order to randomly account for any ties


var userWeight;


var userID;


var groupMember = new GlideRecord("sys_user_grmember");


groupMember.addQuery("available", true);


groupMember.addQuery("group", group);


groupMember.addQuery("user", "IN", skilledUsers);


groupMember.query();


while (groupMember.next()) {


userID = groupMember.user + '';


userWeight = Math.random(); //Assign base weight of random number 0-1


userWeight += calcUserWeight(rule.sys_id.toString(), userID);


weightMatrix.push(userID + ":" + userWeight);


}


if (debug == "true"){


var fullMatrix = '';


for (var i=0; i < weightMatrix.length; i++) {


var userSysID = weightMatrix[i].split(':')[0];


var userWt = weightMatrix[i].split(':')[1];


var userName = new GlideRecord("sys_user");


if (userName.get(userSysID)) {


fullMatrix += userName.getDisplayValue() + ' : ' + userWt + '\n';


}


}


gs.log('QueueMan: Weight Matrix for ' + current.assignment_group.name + ' on ' + current.number + ' = \n' + fullMatrix);


}



//Assign the record to the person with the lightest weight


var lightestPersonID = weightMatrix[getLightest(weightMatrix)].split(":")[0];


if (debug == "true"){


var lightestPersonWeight = weightMatrix[getLightest(weightMatrix)].split(":")[1];


var lightest = new GlideRecord("sys_user");


if (lightest.get(lightestPersonID)) {



gs.log('QueueMan: ' + current.number + ' assigned to ' + lightest.getDisplayValue() + ' with a total weight of ' + lightestPersonWeight + ', due to Queue Rule ' + rule.number + '.');


}


}


current.assigned_to = lightestPersonID;


//current.assigned_to = skilledUsers;


//Using this setting may cause issues with saving 'Additional comments'


//or 'Work notes' on first save of a record. Recommend to leave blank.


var updater = gs.getProperty("queue.manager.updated_by_name");


if (updater != ''){


current.sys_updated_by = updater;


current.autoSysFields(false);


}


current.update();


}



//Calculate and return individual user 'weight', based on Weight Rules


//Weights are based on all active tasks in each user's queue


//Total 'weight' will indicate the user's current workload


function calcUserWeight(rule, user){


var weight = 0;


var weightRule = new GlideRecord("queue_weight");


weightRule.addQuery("queue_rule", rule);


weightRule.query();


//Iterate through Queue Weights, counting the total work 'weight' of all


//active tasks for each individual user in the assignment group


while (weightRule.next()) {


var taskCount = new GlideAggregate(weightRule.table);


taskCount.addAggregate("COUNT");


taskCount.addEncodedQuery(weightRule.condition);


taskCount.addQuery('assigned_to', user);


if (currentGroupWeighting == "true")


taskCount.addQuery('assignment_group', current.assignment_group);


taskCount.addActiveQuery();


taskCount.query();


while (taskCount.next()) {


weight += (taskCount.getAggregate("COUNT") * weightRule.weight);


}


}


return weight;


}



//Return the user SysID with the lightest weight


function getLightest(matrix){


//Build array of weights only w/ indexes matching full array


var weightsOnly = [];


for (var i=0; i < matrix.length; i++) {


weightsOnly[i] = matrix[i].split(":")[1];


}


//Compare weights, maintaining the index of the lightest person


var lightestWeightIndex = 0;


for (var j=1; j < weightsOnly.length; j++) {


if (parseFloat(weightsOnly[j]) < parseFloat(weightsOnly[lightestWeightIndex]))


lightestWeightIndex = j;


}


//Return the index of the lightest weight person


return lightestWeightIndex;


}



//Check conditions against Queue Rule and Queue Weight


function checkCondition(gr, query) {


return GlideFilter.checkRecord(gr,query);


}



Any suggestions?



Thank you so much!


Yeny