- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-28-2016 11:48 AM
I created a new Business Rule to prevent Projects being closed without closing the associated Project Tasks. The BR does stop the update from occurring, but the problem I am running into is that the Project Tasks are then all set to Closed Completed by some ninja BR or UI Action, which I am failing to find.
Things that I have tried:
- Changing the order of my BR to 0 or 900, it didn't work.
- I used the debugging tool and tried turning off every BR that I thought might be changing the state of the task to no avail.
- Looked over all the UI Policies and Client Scripts finding nothing of use.
My overall goal for this change is:
When closing a project (State Change Closed Complete or Incomplete) - Using a BR to complete action
Look at all the child Project Tasks
If any project task has an open Time card
Stop check
Stop update
Reset State to previous
Alert user
Else finish update of project
Might be an easier way of tackling this than a couple of BRs:
- Prevent closing project if open project tasks or time cards
- Prevent closing project tasks if open time cards
Solved! Go to Solution.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-28-2016 02:33 PM
Ok, here's the full client script and script includes. It should be pretty easy to take the code you need:
Script Includes:
var ProjectUtilsAjax = Class.create();
ProjectUtilsAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getOpenProjectTaskCount: function(parentID) {
var projectID = this.getParameter('sysparm_project_id');
var projectTaskCount = new GlideRecord('pm_project_task');
projectTaskCount.addActiveQuery();
projectTaskCount.addQuery('parent', projectID);
projectTaskCount.query();
return projectTaskCount.getRowCount();
},
getProjectCounts: function() {
var projectID = this.getParameter('sysparm_project_id');
var ptc = this.getRelatedRecordCount(projectID, 'pm_project_task', 'parent');
var ic = this.getRelatedRecordCount(projectID, 'issue', 'parent');
var rc = this.getRiskCount(projectID);
var crc = this.getRelatedRecordCount(projectID, 'change_request', 'parent');
var tcc = this.getTimeCardCount(projectID);
var ed = this.getEffortDeviation(projectID);
var rp = this.hasResourcePlans(projectID);
var d = (ed > 10);
gs.log("Variance: " + d + " = " + ed);
var relatedRecordCounts = {
"pm_project_task":ptc,
"issue":ic,
"risk":rc,
"change_request":crc,
"time_card":tcc,
"effort_deviation":ed,
"effort_deviated":d,
"open_resource_plan":rp
};
return JSON.stringify(relatedRecordCounts);
},
getRelatedRecordCount: function(projectID, tableName, fieldName) {
var projectTaskCount = new GlideRecord(tableName);
projectTaskCount.addActiveQuery();
projectTaskCount.addQuery(fieldName, projectID);
projectTaskCount.query();
return projectTaskCount.getRowCount();
},
getRiskCount: function(projectID) {
var riskGR = new GlideRecord('risk');
riskGR.addQuery('state', '!=', 'closed');
riskGR.addQuery('task', projectID);
riskGR.query();
return riskGR.getRowCount();
},
getTimeCardCount: function(topTaskID) {
var timeCardCount = 0;
var projectRecords = new GlideRecord('planned_task');
projectRecords.addQuery('top_task', topTaskID);
projectRecords.query();
while (projectRecords.next()) {
var timeCard = new GlideRecord('time_card');
timeCard.addQuery('state', '!=', 'Processed');
timeCard.addQuery('state', '!=', 'Approved');
timeCard.addQuery('task', projectRecords.sys_id);
timeCard.query();
var tcc = timeCard.getRowCount();
timeCardCount += tcc;
}
return timeCardCount;
},
getEffortDeviation: function(topTaskID) {
var pu = new ProjectUtils();
return pu.getEffortDeviation();
},
hasResourcePlans: function() {
var projectID = this.getParameter('sysparm_project_id');
gs.log("hasResourcePlans projectID: " + projectID);
var sysIDs = "";
var projectGR = new GlideRecord('planned_task');
if (projectGR.get(projectID)) {
gs.log("hasResourcePlans short_description: " + projectGR.short_description);
var tree = new SNC.NestedIntervalUtilities("planned_task");
gs.log("hasResourcePlans tree: " + tree);
var descendants = tree.getDescendants(projectGR, true);
gs.log("hasResourcePlans descendants: " + descendants);
while (descendants.next()) {
sysIDs += ", " + descendants.sys_id;
}
}
gs.log("hasResourcePlans sysIDs: " + sysIDs);
var resourcePlanGR = new GlideRecord('resource_plan');
resourcePlanGR.addQuery("task", "IN", sysIDs);
resourcePlanGR.addQuery("state", "!=", 7);
resourcePlanGR.addQuery("state", "!=", 8);
resourcePlanGR.query();
if (resourcePlanGR.hasNext()) {
return true;
}
return false;
},
getTaskDescendants: function(task, sysIds) {
var children = new GlideRecord("task");
children.addQuery("parent", task.sys_id);
children.query();
while (children.next()) {
sysIds += ", " + children.sys_id;
sysIds = this.getTaskDescendants (children, sysIds);
}
return sysIds;
},
type: 'ProjectUtilsAjax'
});
Onsubmit Client Script on Project Table:
if(typeof gFormAlreadySubmitted == "undefined" || typeof gFormAlreadySubmitted == "null")
gFormAlreadySubmitted = false;
function onSubmit() {
if (gFormAlreadySubmitted)
return true;
// If not Close/Skip/Cancel then bail
var state = g_form.getValue('state');
if (state != 3 && state != -7 && state != 4)
return true;
// If New Record bail
if (g_form.isNewRecord())
return true;
// Project sys_id
var projectID = gel('sys_uniqueValue').value;
// Open Risk/Issue/Change Request
ga = new GlideAjax('ProjectUtilsAjax');
ga.addParam('sysparm_name', 'getProjectCounts');
ga.addParam('sysparm_project_id', projectID);
ga.getXMLWait();
var projectCounts = ga.getAnswer();
var pco = JSON.parse(projectCounts);
var ptc = pco['pm_project_task'];
var ic = pco['issue'];
var rc = pco['risk'];
var crc = pco['change_request'];
var tcc = pco['time_card'];
if (ic != 0 || rc != 0 || crc != 0 || tcc != 0) {
var msg = "The following records must be closed before the Project can be closed:\n";
if (ic > 0)
msg += pco['issue'] + " Issues\n";
if (rc > 0)
msg += pco['risk'] + " Risks\n";
if (crc > 0)
msg += pco['change_request'] + " Change Requests\n";
if (tcc > 0)
msg += pco['time_card'] + " Time Cards"
alert(msg);
return false;
}
var ed = pco['effort_deviation'];
var d = pco['effort_deviated'];
jslog("Variance: " + d + " = " + ed);
if (d || d == true || d == "true") {
if (g_form.getValue('u_variance_explanation') == '') {
var msg = "Planned and Actual Effort has a variance " + ed +
"% which greater than 10%. Please provide a Variance Explanation.";
alert(msg);
g_form.setVisible('u_variance_explanation', true);
g_form.setMandatory('u_variance_explanation', true);
return false;
}
}
if (ptc > 0) {
shouldProjectClose();
return false;
}
}
function shouldProjectClose() {
var dialogClass = window.GlideModal ? GlideModal : GlideDialogWindow;
var dialog = new dialogClass('glide_confirm_standard');
dialog.setTitle("Confirmation");
dialog.setPreference('warning', true);
dialog.setPreference('title', "All Open Project Tasks will close when the Project is Closed/Cancelled. Proceed?");
dialog.setPreference('defaultButton', 'ok_button');
dialog.setPreference('onPromptComplete', confirm.bind(this));
dialog.setPreference('onPromptCancel', cancel.bind(this));
dialog.render();
}
function confirm() {
gFormAlreadySubmitted = true;
if(g_form.getActionName() == 'sysverb_update_and_stay')
g_form.save();
else
g_form.submit();
}
function cancel() {
// Reset State to Original Value
g_form.setValue('state', g_scratchpad.projectState);
}

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-28-2016 01:27 PM
When closing the Project, check from Open Project Tasks and non-approved Time Cards. What about open Risks, Issues, and/or Change Requests? We use a client script to do the check, which I can share, but just wanted to confirm all the different pieces in play here.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-28-2016 02:01 PM
Thanks for the reply Michael.
Our PMO is mostly concerned about having these Time Cards not complete, because we pipe it over into another system to resolve billing. The absolute biggest concern is there. Would love to check out the script as it probably applies!
Thank you very much!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-28-2016 02:33 PM
Ok, here's the full client script and script includes. It should be pretty easy to take the code you need:
Script Includes:
var ProjectUtilsAjax = Class.create();
ProjectUtilsAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getOpenProjectTaskCount: function(parentID) {
var projectID = this.getParameter('sysparm_project_id');
var projectTaskCount = new GlideRecord('pm_project_task');
projectTaskCount.addActiveQuery();
projectTaskCount.addQuery('parent', projectID);
projectTaskCount.query();
return projectTaskCount.getRowCount();
},
getProjectCounts: function() {
var projectID = this.getParameter('sysparm_project_id');
var ptc = this.getRelatedRecordCount(projectID, 'pm_project_task', 'parent');
var ic = this.getRelatedRecordCount(projectID, 'issue', 'parent');
var rc = this.getRiskCount(projectID);
var crc = this.getRelatedRecordCount(projectID, 'change_request', 'parent');
var tcc = this.getTimeCardCount(projectID);
var ed = this.getEffortDeviation(projectID);
var rp = this.hasResourcePlans(projectID);
var d = (ed > 10);
gs.log("Variance: " + d + " = " + ed);
var relatedRecordCounts = {
"pm_project_task":ptc,
"issue":ic,
"risk":rc,
"change_request":crc,
"time_card":tcc,
"effort_deviation":ed,
"effort_deviated":d,
"open_resource_plan":rp
};
return JSON.stringify(relatedRecordCounts);
},
getRelatedRecordCount: function(projectID, tableName, fieldName) {
var projectTaskCount = new GlideRecord(tableName);
projectTaskCount.addActiveQuery();
projectTaskCount.addQuery(fieldName, projectID);
projectTaskCount.query();
return projectTaskCount.getRowCount();
},
getRiskCount: function(projectID) {
var riskGR = new GlideRecord('risk');
riskGR.addQuery('state', '!=', 'closed');
riskGR.addQuery('task', projectID);
riskGR.query();
return riskGR.getRowCount();
},
getTimeCardCount: function(topTaskID) {
var timeCardCount = 0;
var projectRecords = new GlideRecord('planned_task');
projectRecords.addQuery('top_task', topTaskID);
projectRecords.query();
while (projectRecords.next()) {
var timeCard = new GlideRecord('time_card');
timeCard.addQuery('state', '!=', 'Processed');
timeCard.addQuery('state', '!=', 'Approved');
timeCard.addQuery('task', projectRecords.sys_id);
timeCard.query();
var tcc = timeCard.getRowCount();
timeCardCount += tcc;
}
return timeCardCount;
},
getEffortDeviation: function(topTaskID) {
var pu = new ProjectUtils();
return pu.getEffortDeviation();
},
hasResourcePlans: function() {
var projectID = this.getParameter('sysparm_project_id');
gs.log("hasResourcePlans projectID: " + projectID);
var sysIDs = "";
var projectGR = new GlideRecord('planned_task');
if (projectGR.get(projectID)) {
gs.log("hasResourcePlans short_description: " + projectGR.short_description);
var tree = new SNC.NestedIntervalUtilities("planned_task");
gs.log("hasResourcePlans tree: " + tree);
var descendants = tree.getDescendants(projectGR, true);
gs.log("hasResourcePlans descendants: " + descendants);
while (descendants.next()) {
sysIDs += ", " + descendants.sys_id;
}
}
gs.log("hasResourcePlans sysIDs: " + sysIDs);
var resourcePlanGR = new GlideRecord('resource_plan');
resourcePlanGR.addQuery("task", "IN", sysIDs);
resourcePlanGR.addQuery("state", "!=", 7);
resourcePlanGR.addQuery("state", "!=", 8);
resourcePlanGR.query();
if (resourcePlanGR.hasNext()) {
return true;
}
return false;
},
getTaskDescendants: function(task, sysIds) {
var children = new GlideRecord("task");
children.addQuery("parent", task.sys_id);
children.query();
while (children.next()) {
sysIds += ", " + children.sys_id;
sysIds = this.getTaskDescendants (children, sysIds);
}
return sysIds;
},
type: 'ProjectUtilsAjax'
});
Onsubmit Client Script on Project Table:
if(typeof gFormAlreadySubmitted == "undefined" || typeof gFormAlreadySubmitted == "null")
gFormAlreadySubmitted = false;
function onSubmit() {
if (gFormAlreadySubmitted)
return true;
// If not Close/Skip/Cancel then bail
var state = g_form.getValue('state');
if (state != 3 && state != -7 && state != 4)
return true;
// If New Record bail
if (g_form.isNewRecord())
return true;
// Project sys_id
var projectID = gel('sys_uniqueValue').value;
// Open Risk/Issue/Change Request
ga = new GlideAjax('ProjectUtilsAjax');
ga.addParam('sysparm_name', 'getProjectCounts');
ga.addParam('sysparm_project_id', projectID);
ga.getXMLWait();
var projectCounts = ga.getAnswer();
var pco = JSON.parse(projectCounts);
var ptc = pco['pm_project_task'];
var ic = pco['issue'];
var rc = pco['risk'];
var crc = pco['change_request'];
var tcc = pco['time_card'];
if (ic != 0 || rc != 0 || crc != 0 || tcc != 0) {
var msg = "The following records must be closed before the Project can be closed:\n";
if (ic > 0)
msg += pco['issue'] + " Issues\n";
if (rc > 0)
msg += pco['risk'] + " Risks\n";
if (crc > 0)
msg += pco['change_request'] + " Change Requests\n";
if (tcc > 0)
msg += pco['time_card'] + " Time Cards"
alert(msg);
return false;
}
var ed = pco['effort_deviation'];
var d = pco['effort_deviated'];
jslog("Variance: " + d + " = " + ed);
if (d || d == true || d == "true") {
if (g_form.getValue('u_variance_explanation') == '') {
var msg = "Planned and Actual Effort has a variance " + ed +
"% which greater than 10%. Please provide a Variance Explanation.";
alert(msg);
g_form.setVisible('u_variance_explanation', true);
g_form.setMandatory('u_variance_explanation', true);
return false;
}
}
if (ptc > 0) {
shouldProjectClose();
return false;
}
}
function shouldProjectClose() {
var dialogClass = window.GlideModal ? GlideModal : GlideDialogWindow;
var dialog = new dialogClass('glide_confirm_standard');
dialog.setTitle("Confirmation");
dialog.setPreference('warning', true);
dialog.setPreference('title', "All Open Project Tasks will close when the Project is Closed/Cancelled. Proceed?");
dialog.setPreference('defaultButton', 'ok_button');
dialog.setPreference('onPromptComplete', confirm.bind(this));
dialog.setPreference('onPromptCancel', cancel.bind(this));
dialog.render();
}
function confirm() {
gFormAlreadySubmitted = true;
if(g_form.getActionName() == 'sysverb_update_and_stay')
g_form.save();
else
g_form.submit();
}
function cancel() {
// Reset State to Original Value
g_form.setValue('state', g_scratchpad.projectState);
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-29-2016 05:20 AM
Michael, thank you for your generosity in sharing this include and client script. It is fantastic. I'll get it implemented and tweaked and report back on how it all worked out.
Very Appreciatively,