Name
sn_hr_core.hr_Case
Description
Handles HR Case records
Script
var hr_Case = Class.create();
hr_Case._ignoreChangeEditableFields = {
'opened_for': '',
'priority': '',
'short_description': '',
'description': ''
};
hr_Case.userHasSubjectPersonAccess = function(gr) {
var isSubjectPerson = gr.subject_person == gs.getUserID();
return isSubjectPerson && !gs.nil(gr.hr_service) && hr_Case.serviceHasSubjectPersonAccess(String(gr.hr_service));
};
hr_Case.serviceHasSubjectPersonAccess = function(hrService) {
if (!hr_Case.hasOwnProperty('servicesWithSubjectPersonAccess'))
hr_Case['servicesWithSubjectPersonAccess'] = hr_Case._fetchServicesWithSubjectPersonAccess();
return hr_Case.hasOwnProperty('servicesWithSubjectPersonAccess') && hr_Case['servicesWithSubjectPersonAccess'].indexOf(hrService) > -1;
};
hr_Case._fetchServicesWithSubjectPersonAccess = function() {
var servicesWithSubjectPersonAccess = [];
var hrServiceGr = new GlideRecord('sn_hr_core_service');
hrServiceGr.addQuery('subject_person_access', true);
hrServiceGr.query();
while(hrServiceGr.next())
servicesWithSubjectPersonAccess.push(hrServiceGr.getUniqueValue());
return servicesWithSubjectPersonAccess;
};
/*
* Handle HR Case generation and management through the Case creation process:
* - through an RP
* - script based
* - with a New button
*/
hr_Case.prototype = {
initialize: function(_case, _gs) {
if (!_case)
return;
this._case = _case;
this._gs = _gs || gs;
},
/*
Function to refresh cache - List of HR Services whose cases will be accessible to subject person
*/
updateAccessibleServices: function() {
hr_Case['servicesWithSubjectPersonAccess'] = hr_Case._fetchServicesWithSubjectPersonAccess();
},
/*
* Sets the location, company and department fields on sn_hr_core_case from the user in opened_for
*/
setUserFields: function() {
this._logDebug("[setUserFields] Setting user fields kicking off");
// Don't do anything if we're updating and nothing has changed
if (!this._case.opened_for || !this._case.opened_for.changes()) {
this._logDebug("[setUserFields] no changes to opened_for, exiting");
return;
}
// Dereference these fields from opened_for user; set for example, this._case[location]
this._setFromRefField("opened_for", "location");
this._setFromRefField("opened_for", "company");
this._setFromRefField("opened_for", "department");
},
_userHasCaseWriterRole: function(user) {
var userId = user || gs.getUserID();
var gr = new GlideRecord('sys_user_has_role');
gr.addQuery('user', userId);
gr.addQuery('role.name', hr.ROLE_HR_CASE_WRITER);
gr.setLimit(1);
gr.query();
return gr.hasNext();
},
/*
* Public helper methods for read/write ACLs
*/
/*
* Check if a given user can edit the current case
* @param String userId The Sys ID of the user to be tested (optional)
* @param Boolean failedReadPolicy True if the user failed the read policies for the case's COE
* @return Boolean True if the given user can edit the case
*/
canEditCase: function(userId, failedReadPolicy) {
if (this._case.isNewRecord() || this._case.getValue('sys_id') === null)
return true;
if (!failedReadPolicy) {
if (gs.nil(userId) || userId == gs.getUserID()) {
var roles = new hr_Utils();
if (roles.checkUserHasRole(hr.ROLE_HR_CASE_WRITER) &&
new sn_hr_core.hr_SecurityUtils().getCoeSecurityPolicy(this._case, hr_SecurityUtils.WRITE, userId))
return true;
} else if (this._userHasCaseWriterRole(userId) &&
new sn_hr_core.hr_SecurityUtils().getCoeSecurityPolicy(this._case, hr_SecurityUtils.WRITE, userId))
return true;
}
var currentUserId = gs.nil(userId) ? this._gs.getUserID() : userId;
// Check if it's the user who opened the case
if (!gs.nil(this._case.opened_by) && this._case.opened_by == currentUserId)
return true;
// Check if it's the user for whom the case was opened for
if (!gs.nil(this._case.opened_for) && this._case.opened_for == currentUserId)
return true;
// Check if the user is in the watch list field
if (!gs.nil(this._case.watch_list) && this._case.watch_list.indexOf(currentUserId) > -1)
return true;
// Check if the user is in the collaborators list field
if (this._hasCollaboratorAccess(this._case, currentUserId))
return true;
return false;
},
/*
* Check if a given user can read the current case
* @param String userId The Sys ID of the user to be tested (optional)
* @return Boolean True if the given user can read the case
*/
canReadCase: function(userId) {
var hasRole = new sn_hr_core.hr_Utils().userHasHRRole(userId);
var failedReadPolicy = false;
if(hasRole) {
var passedPolicy;
if (!gs.nil(this._case))
passedPolicy = new sn_hr_core.hr_SecurityUtils().getCoeSecurityPolicy(this._case, hr_SecurityUtils.READ, userId);
else
passedPolicy = true;
if (passedPolicy)
return true;
else
failedReadPolicy = true;
}
userId = gs.nil(userId) ? this._gs.getUserID() : userId;
var roCondition = this._evaluateROConditionForHRCase(userId, failedReadPolicy, true);
if(roCondition)
return true;
var parent = this._hasParentAccess(userId);
if (parent)
return true;
return this._isTaskAssignee(userId);
},
_hasParentAccess: function(userId) {
var access = false;
if (!gs.nil(this._case.parent))
access = (this._case.parent.opened_for == userId || this._case.parent.opened_by == userId || this._case.parent.watch_list.toString().indexOf(userId) >= 0 || this._hasCollaboratorAccess(this._case.parent, userId));
return access;
},
_isTaskAssignee: function(userId) {
if (gs.nil(this._case) || gs.nil(this._case.sys_id))
return false;
var cases = new hr_Utils().getCaseSysIdForTaskAssignee(userId, this._case.sys_id);
return cases.indexOf(this._case.sys_id) >= 0;
},
/*
* Check if a given user has read-only access to the current case
* @param String userId The Sys ID of the user to be tested (optional)
* @param Boolean failedReadPolicy True if the user failed the read policies for the case's COE
* @return Boolean True if the given user has read-only access to the case
*/
_evaluateROConditionForHRCase: function(userId, failedReadPolicy) {
return sn_hr_core.hr_Case.userHasSubjectPersonAccess(this._case) || this.canEditCase(userId, failedReadPolicy) || this.isApproverUserForCase(userId);
},
/*
* Public helper method to determine if user can cancel his/her own case.
*/
canCancelCase: function() {
var currentUserId = this._gs.getUserID();
var openedFor = this._case.opened_for;
if (openedFor == currentUserId && !this._case.submitter_can_cancel)
return false;
else
return true;
},
/**
* Public helper method to cancel an active HR case
* @param {String} [caseId] The id of the case to cancel. Optional parameter. If not provided, will cancel this._case
* @return {Boolean} True if the case is succesfully canceled. False otherwise
*/
cancelCase: function(caseId) {
var grCase;
if (caseId) {
grCase = new GlideRecord(hr.TABLE_CASE);
if (!grCase.get(caseId))
grCase = null;
}
else
grCase = this._case;
if (!grCase || !grCase.active || grCase.getValue('state') === hr.STATE_CANCEL)
return false;
grCase.state = hr.STATE_CANCEL;
return !!grCase.update();
},
/*
* Public helper method to determine whether the case has an approver user
*/
isApproverUserForCase: function(userId) {
if (new sn_hr_core.hr_Delegation().isApproverForRecord(this._case, userId))
return true;
var sysApprovalGR = new GlideRecord('sysapproval_approver');
sysApprovalGR.addQuery('sysapproval', this._case.getUniqueValue());
sysApprovalGR.addQuery('approver', new hr_Utils().getApprovals(userId));
sysApprovalGR.query();
return sysApprovalGR.next();
},
/*
* Public helper functions to trigger case creation events
*/
sendEvents: function() {
var _gr = this._case;
var _gs = this._gs;
this._logDebug("sending events for sn_hr_core_case: " + _gr.number);
// On insert
if (_gr.operation() == "insert")
_gs.eventQueue("sn_hr_core_case.inserted", _gr, _gs.getUserID(), _gs.getUserName());
if (_gr.operation() == "insert" && _gr.assignment_group.nil())
_gs.eventQueue("sn_hr_core_case.inserted.unassigned", _gr, _gs.getUserID(), _gs.getUserName());
if (_gr.operation() == "insert" && !_gr.assignment_group.nil())
_gs.eventQueue("sn_hr_core_case.inserted.assigned", _gr, _gs.getUserID(), _gs.getUserName());
// On update
if (_gr.operation() == "update")
_gs.eventQueue("sn_hr_core_case.updated", _gr, _gs.getUserID(), _gs.getUserName());
// On Assignment group change
if (_gr.operation() == "update" && _gr.assignment_group.changes())
_gs.eventQueue("sn_hr_core_case.assignment_group.changed", _gr, _gs.getUserID(), _gs.getUserName());
// On comments
if (_gr.operation() != "insert" && _gr.comments.changes()){
if(!this._isCommentMadeFromConnect())
_gs.eventQueue("sn_hr_core_case.commented", _gr, _gs.getUserID(), _gs.getUserName());
}
},
/*
* To check whether last AdditonalComment in HR Case is from Connect chat or not
*/
_isCommentMadeFromConnect: function() {
var _gr = this._case;
var case_id = _gr.getUniqueValue();
var lastAdditionalComment = _gr.comments.getJournalEntry(1);
//fetch the live group profile SYSID
var lgp = new GlideRecord('live_group_profile');
lgp.get('document', case_id);
//fetch the live message.
var lmGr = new GlideRecord('live_message');
lmGr.addQuery('group', lgp.getUniqueValue());
lmGr.addQuery('last_message', true);
lmGr.query();
while(lmGr.next()) {
if(lastAdditionalComment.includes(lmGr.message))
return true;
}
return false;
},
/*
* Gets the current calendar duration, utility function
*/
getCalendarDuration: function() {
var start = new GlideDateTime(this._case.getValue("opened_at"));
if (!start)
return;
var end = new GlideDateTime();
end.setDisplayValue(new GlideDateTime().getDisplayValue());
var hrUtil = new sn_hr_core.hr_Utils();
var seconds = hrUtil.calcDuration(start, end);
//Convert seconds to milliseconds and gets the duration value
var duration = new GlideDuration(seconds * 1000);
this._case.calendar_duration = duration;
},
/*
* Populates fields on sn_hr_core_case from another record
*/
_setFromRefField: function(refField, fromField, toField) {
if (!toField)
toField = fromField;
if (this._case[toField]) {
// The destination is not empty: do not update it
return;
} else {
// Set the source
this._logDebug("[setField] " + toField + " set to " + this._case[refField][fromField]);
this._case[toField] = this._case[refField][fromField];
}
},
/*
* Gets calendar duration between two dates
*
* @param startDateTime the start GlideDateTime
* @param endDateTime the end GlideDateTime
* @return the GlideDuration between the dates directly
*/
_calculateCalendarDuration: function(startDateTime, endDateTime) {
//calls duration calculator to get the difference of start date and end date in seconds
var newDuration = new rhino.global.DurationCalculator();
newDuration.calcScheduleDuration(startDateTime, endDateTime);
//Convert seconds to milliseconds and gets the duration value
var gd = new GlideDuration((newDuration.getSeconds() * 1000));
return gd;
},
/*
* Method to iterate through stated sections that are in the properties and
* work out percentage completed.
*/
getUserCompletionStatusForm: function(currentRecord) {
var formSections = gs.getProperty("com.snc.hr.employee_change.sections", "");
var returnSections = {
data: []
};
var totalFields = 0;
var totalFilledFields = 0;
if (formSections != "") {
var uiSections = new GlideRecord("sys_ui_section");
uiSections.addEncodedQuery("name=sn_hr_core_case^view=99b6e2c637311100e90d4e06efbe5dbb^captionIN" + formSections);
uiSections.query();
while (uiSections.next()) {
var validElements = this._getSectionElements(uiSections.sys_id);
totalFields += validElements.length;
var filledFields = 0;
for (var i = 0; i < validElements.length; i++) {
if (this._validateRecordField(currentRecord, validElements[i]))
filledFields++;
}
totalFilledFields += filledFields;
returnSections.data.push(this._checkSectionStatus(uiSections.caption, filledFields, validElements.length));
}
}
returnSections["filled"] = totalFilledFields;
returnSections["total"] = totalFields;
return returnSections;
},
/*
* Gets display value for passed in variable
*
* @param variable The variable whose value is wanted
* @param defaultAnswer The value to return if a display value could not be found
* @return The value to display for the passed in variable
*/
_getDisplayValue: function(variable, defaultAnswer, defaultDisplayValue) {
var value = defaultDisplayValue ? defaultDisplayValue : defaultAnswer;
var tableName = variable.reference.getDisplayValue();
if (!tableName)
return value;
var record = new GlideRecord(tableName);
return (record.get(defaultAnswer)) ? record.getDisplayValue() : value;
},
/*
* Gets all the elements that are related in the section
*/
_getSectionElements: function(sectionId) {
var elements = [];
var uiSectionElement = new GlideRecord("sys_ui_element");
uiSectionElement.addQuery("sys_ui_section", sectionId);
uiSectionElement.addQuery("type", "");
uiSectionElement.query();
while (uiSectionElement.next()) {
if (uiSectionElement.element.substring(0, 1) != ".")
elements.push(uiSectionElement.getDisplayValue("element"));
}
return elements;
},
/*
* Used to validate if the field stated in the element is populated.
*/
_validateRecordField: function(current, element) {
if (element.indexOf(".") > -1) {
var elementArray = element.split(".");
if (elementArray[0] == current.getTableName())
return (current.getDisplayValue(elementArray[1]) == "");
else {
var grRec = new GlideRecord(elementArray[0]);
if (grRec.get(current.getValue(elementArray[0]))) {
return ((grRec.getDisplayValue(elementArray[1]) != "") && (grRec.getDisplayValue(elementArray[1]) != null));
}
}
return false;
} else
return current.getDisplayValue(element) != "";
},
/*
* Takes input section with a total of fields and a number of filled in
* fields. Returns JSON object of progress for section.
*/
_checkSectionStatus: function(caption, count, total) {
var section = {};
section["title"] = "" + caption;
section["cssClass"] = "";
section["status"] = "";
if (count == total) {
section["cssClass"] = "done";
section["status"] = "Done";
}
if (count < total) {
section["cssClass"] = "in_progress";
section["status"] = "In Progress";
}
if (count == 0) {
section["cssClass"] = "not_started";
section["status"] = "Not Done";
}
return section;
},
/*
* Convenience method to prevent the code becoming unreadable from the useful debug statements
*/
_logDebug: function(str) {
if (gs.isDebugging())
gs.debug(str);
},
/**
Checks if the document is already attached to the case or to the task
*/
hasAttachment: function(tableName, currentRecord) {
var template;
if (tableName == hr.TABLE_TASK)
template = currentRecord.parent.pdf_template.getDisplayValue() + '%';
else
template = currentRecord.pdf_template.getDisplayValue() + '%';
var attachment = new GlideRecord('sys_attachment');
attachment.addQuery('table_name', tableName);
attachment.addQuery('table_sys_id', currentRecord.sys_id);
attachment.addQuery('file_name', 'LIKE', template);
attachment.setLimit(1);
attachment.query();
return attachment.hasNext();
},
/**
Returns true if the current record is a glide record, has a field named pdf_template, has a value for that field,
and has an attachment whose name starts with the display value of the pdf template
*/
hasPdfTemplateAttachment: function(currentRecord) {
if (!(currentRecord instanceof GlideRecord))
return false;
if (!currentRecord.pdf_template || !currentRecord.pdf_template.getDisplayValue())
return false;
var attachment = new GlideRecord('sys_attachment');
attachment.addQuery('table_name', currentRecord.getTableName());
attachment.addQuery('table_sys_id', currentRecord.sys_id);
attachment.addQuery('file_name', 'STARTSWITH', currentRecord.pdf_template.getDisplayValue());
attachment.setLimit(1);
attachment.query();
return attachment.hasNext();
},
needUserAction: function(currentRecord, display) {
var tasks = new GlideRecord('sn_hr_core_task');
if (display == "message")
tasks.addActiveQuery();
var inQuery = [hr_Constants.TASK_QUALIFIED,hr_Constants.TASK_ASSIGNED,hr_Constants.TASK_ACCEPTED,hr_Constants.TASK_WORK_IN_PROGRESS,hr_Constants.TASK_CLOSED_COMPLETE,hr_Constants.TASK_CLOSED_INCOMPLETE];
tasks.addQuery('state', 'IN', inQuery);
tasks.addQuery('parent', currentRecord.getUniqueValue());
var delegatedTasks = new hr_Delegation().getDelegatedTasks({ tables : ['sn_hr_core_task'], encoded_query: tasks.getEncodedQuery() });
tasks.addQuery('assigned_to', gs.getUserID()).addOrCondition('sys_id', 'IN', delegatedTasks);
tasks.addNotNullQuery('hr_task_type');
tasks.setLimit(1);
tasks.query();
return tasks.hasNext();
},
/**
* Return true if:
* the user has case writer role
* the case status is assigned or ready
* the case isn't already assigned to the user
* if there is an assignment group, the user is in the assignment group
* @return boolean user can assign the case to self
*/
canAssignToSelf: function() {
var hasRole = this._gs.hasRole("sn_hr_core.case_writer");
if (!hasRole)
return false;
var assignableState = (this._case.state == hr_Constants.CASE_ASSIGNED || this._case.state == hr_Constants.CASE_QUALIFIED);
if (!assignableState)
return false;
var notAssignedToSelf = this._case.assigned_to != this._gs.getUserID();
if (!notAssignedToSelf)
return false;
if (this._case.assignment_group.nil())
return true;
var user_group = new GlideRecord("sys_user_grmember");
user_group.addQuery("user", this._gs.getUserID());
user_group.addQuery("group", "" + this._case.assignment_group);
user_group.query();
var userIsAgentForGroup = user_group.hasNext();
return userIsAgentForGroup;
},
canSignDocument: function() {
if (!new hr_Delegation().isAssignedOrDelegated(this._case))
return false;
var signature = new GlideRecord('signature_image');
signature.addQuery('document', this._case.sys_id);
signature.addActiveQuery();
signature.query();
return !signature.next();
},
/*
* Update a sn_hr_core_case record total_percent_complete field when related tasks are completed
*/
updateHRCasePercentComplete: function(gr) {
// Exit if no parent field
if (gr.parent.nil())
return;
// Exit if unable to get the sn_hr_core_case record
var hrCase = new GlideRecord("sn_hr_core_case");
if (!hrCase.get(gr.getValue("parent")))
return;
var totalGr = new GlideRecord("task");
totalGr.addQuery("parent", gr.getValue("parent"));
totalGr.addQuery("sys_class_name", "!=", "sysapproval_group");
totalGr.query();
var totalGrCount = totalGr.getRowCount();
var completeGr = 0;
while (totalGr.next()) {
if (!totalGr.active)
completeGr++;
}
var percent = parseInt((completeGr / totalGrCount) * 100);
hrCase.setValue("task_percent_complete", percent);
hrCase.update();
return percent;
},
/*
* Validates whether the changes on the record producer require HR
* authorization. Returns yes if requires authorization.
* Called from Employee Update Profile Workflow
*/
requireChgAuthorization: function() {
// Hr case writer can update all fields with no restriction
var roles = new hr_Utils();
if (roles.checkUserHasRole('sn_hr_core.case_writer'))
return 'no';
var fieldsList =
gs.getProperty('sn_hr_core.hr_profile_editable_fields', 'state,home_phone,address,work_mobile,country,work_email,introduction,middle_name,work_phone,zip,personal_email,city,mobile_phone,');
var parameters = new global.JSON().decode(this._case.payload);
var grProfile = new GlideRecord(sn_hr_core.hr.TABLE_PROFILE);
if (grProfile.get(this._case.hr_profile)) {
var hrProfile = new sn_hr_core.hr_Profile(this._case.hr_profile, this._gs);
for (var key in parameters) {
if (hr_Case._ignoreChangeEditableFields.hasOwnProperty(key))
continue;
var val = hrProfile.getDisplayValue(grProfile, key, false);
if (parameters[key] != val && fieldsList.indexOf(key) == -1) {
this._case.comments = gs.getMessage('Change requires an approval, submitting approval request');
return 'yes';
}
}
}
// No Approval required if no keys were found in
return 'no';
},
/*
* Method for Employee Profile Update workflow to update the
* profile record from values provided from record producer.
*/
updateProfile: function() {
var hrProfile = new sn_hr_core.hr_Profile(this._case.hr_profile, this._gs);
hrProfile.updateProfile(current.sys_id, current.hr_profile);
},
/*
* Return boolean if the hr Case has a valid task with given assigned to user
*
*/
allowTaskAccessToAssignedToUser: function(userName) {
var hrTask = new GlideRecord('sn_hr_core_task');
hrTask.addQuery('assigned_to', userName);
hrTask.addQuery('parent', this._case.getUniqueValue());
hrTask.query();
return hrTask.hasNext();
},
/* Checks if the user belongs to the collaborator list for the Case
* @param GlideRecord case Glide Record of the case
* @param String user sysId of the User which is in collaborators or not
* @return Boolean True if the current user is one of the collaborator
*/
_hasCollaboratorAccess: function(caseGr, user) {
return (!gs.nil(caseGr.collaborators) && caseGr.collaborators.indexOf(user) > -1 && this._userHasCaseWriterRole(user));
},
/* Initiate Doc tasks cancellation
* @param current - HR Case Record
*/
initiateDocTasksCancellation: function(current) {
var docTaskUtil = new sn_doc.DocumentTaskUtils();
docTaskUtil.cancelDocumentTasksAndFlow(current);
} ,
type: "hr_Case"
};
Sys ID
7a5370019f22120047a2d126c42e7000