Name

sn_hr_core.hr_CaseAjax

Description

Handles HR Case Ajax requests.

Script

/**
* hr_CaseAjax
*
* Wraps all ajax request methods used by HR Core.
*/
var hr_CaseAjax = Class.create();
hr_CaseAjax.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

  initialize: function(request, responseXML, gc) {
      global.AbstractAjaxProcessor.prototype.initialize.call(this, request, responseXML, gc);
  },

  /**
   * getMLPredictorResults - Get the predictions for the useCase of case
   * @private - called from getCasePredictions
   * Security requirements (implied): user has case writer role
   * @params:
   *         caseId - HR Case sys_id
   *         useCase - HR AI configuration Use Case name
   *         domain
   * predicted output value from the ml_predictor_results table
   */
  getMLPredictorResults: function(caseID, useCase, domain) {

      // fetch solution from HR Ai condfig
      var aiConfig = new GlideRecord('sn_hr_core_ai_configuration');
      aiConfig.addQuery('use_case', useCase);
      aiConfig.addQuery('sys_domain', domain);
      aiConfig.query();
      var solutionDef;
      if (aiConfig.next())
          solutionDef = aiConfig.solution_capability_definition;
      else {
          aiConfig = new GlideRecord('sn_hr_core_ai_configuration');
          aiConfig.addQuery('use_case', useCase);
          aiConfig.addQuery('sys_domain', 'global');
          aiConfig.query();
          aiConfig.next();
          solutionDef = aiConfig.solution_capability_definition;
      }
      if (gs.nil(solutionDef))
          return;

      // Return if solution definition is not active
      if (!aiConfig.solution_capability_definition.active)
          return;

      // fetch the solution and pass solution id 
      var sol = new GlideRecord('ml_solution');
      sol.addQuery('active', true);
      sol.addQuery('solution_name', aiConfig.solution_capability_definition.solution_name);
      sol.query();
      if (!sol.next())
          return;

      var predictions = {};

      // fetch predicted results
      var predictorResult = new GlideRecord('ml_predictor_results');
      predictorResult.addQuery('source_sys_id', caseID);
      predictorResult.addQuery('solution', sol.sys_id);
      predictorResult.orderByDesc('sys_created_on');
      predictorResult.setLimit(1);
      predictorResult.query();
      if (predictorResult.next()) {
          predictions[useCase] = predictorResult.predicted_output_value;
      }

      return predictions;
  },

  /**
   * Get the Predicted HR Service for the given HR Case
   * Security requirements (from getHRServicePredictions): user can write to case and read the predicted service
   */
  getPredictionsForCaseTransfer: function() {

      var domain = this.getParameter('sysparm_domain');
      var subject_person = this.getParameter('sysparm_subject_person');
      var sys_id = this.getParameter('sysparm_sys_id');

      return this.getHRServicePredictions(sys_id, subject_person, domain);
  },

  /**
   * @private - called from getPredictionsForCaseTransfer
   * Security requirements: user can write to case and read the predicted service
   */
  getHRServicePredictions: function(caseId, subjectPerson, domain) {

      var hrCase = new GlideRecord('sn_hr_core_case');
      var predictions = {};
      if (hrCase.get(caseId) && hrCase.canWrite()) {
          if (!gs.nil(hrCase.predicted_hr_service)) {
              var gr = new GlideRecord('sn_hr_core_service');
              if (gr.get(hrCase.predicted_hr_service) && gr.canRead()) {
                  if (gr.getValue('active') == 1) {
                      var coe_display = gr.getValue('service_table');

                      var service = gr.getValue('name');
                      var service_id = gr.getUniqueValue();

                      predictions = {
                          coe_display: coe_display,
                          service_display: service,
                          service_id: service_id
                      };
                      if (predictions && new sn_hr_core.hr_CaseCreation().hasAccessForService(subjectPerson, true, predictions.service_id))
                          return JSON.stringify(predictions);
                  }
              }

          } else {
              predictions = this.getHRCasePredictions(hrCase, domain, hr.CASE_CATEGORIZATION);
              if (predictions && new sn_hr_core.hr_CaseCreation().hasAccessForService(subjectPerson, true, predictions.service_id)) {
                  hrCase.setValue('predicted_hr_service', predictions.service_id);
                  hrCase.update();
                  return JSON.stringify(predictions);
              }
          }
      }
  },

  /**
   * Security requirements: none
   */
  getPredictions: function(hrCaseGr) {
      if (!(new GlidePluginManager().isActive('com.glide.platform_ml')))
          return;

      var predictions = new sn_hr_core.HRMLUtils().getHRPredictions(hrCaseGr);
      return predictions;
  },

  /**
   * @private (unused OOB)
   * Security requirements: user has case writer role
   */
  predict: function(short_description, description, domain) {
      var hr_case = new GlideRecord('sn_hr_core_case');
      hr_case.short_description = short_description;
      hr_case.description = description;

      if (!(new GlidePluginManager().isActive('com.glide.platform_ml')))
          return hr_case;

      if (!gs.hasRole('sn_hr_core.case_writer'))
          return hr_case;

      var predictor = new global.MLPredictor();
      var gr = new GlideRecord('sn_hr_core_ai_configuration'); //configuration table
      gr.addQuery('use_case', 'email_categorization');
      gr.addQuery('sys_domain', domain);
      gr.query();

      // No need for canRead here. This is just a configuration record that determines which solution def to use.
      if (!gr.next()) {
          gr = new GlideRecord('sn_hr_core_ai_configuration');
          gr.addQuery('use_case', 'email_categorization');
          gr.addQuery('sys_domain', 'global');
          gr.query();
          if (!gr.next())
              return hr_case;
      }
      var name = gr.solution_capability_definition.solution_name.toString(); //Get solution name from configuration
      var solution = predictor.findActiveSolution(name);
      if (solution)
          predictor.applyPredictionForSolution(hr_case, solution);
      return hr_case;
  },

  /**
   * Security requirements: none
   */ 
  getHRCasePredictions: function(hrCase, domain, usecase) {
      if (!(new GlidePluginManager().isActive('com.glide.platform_ml')))
          return false;

      var hrServiceId = this.hrCasePredict(hrCase, domain, usecase);
      if (gs.nil(hrServiceId))
          return false;

      var gr = new GlideRecord('sn_hr_core_service');
      if (gr.get(hrServiceId) && gr.canRead()) {
          if (gr.getValue('active') == 1) {
              var coe_display = gr.getValue('service_table');
              var service = gr.getValue('name');
              var service_id = hrServiceId;
              var predictions = {
                  coe_display: coe_display,
                  service_display: service,
                  service_id: service_id
              };
              return predictions;
          }
      } else
          return false;
  },

  /**
   * @private - called from getHRCasePredictions
   * Security requirements (implied) - user can read case and predicted service
   */ 
  hrCasePredict: function(hrCase, domain, usecase) {
      if (!(new GlidePluginManager().isActive('com.glide.platform_ml')))
          return;
      // Look for configuration in @param domain, otherwise check 'global'
      var hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration');
      hrAIConfiguration.addQuery('use_case', usecase);
      hrAIConfiguration.addQuery('sys_domain', domain);
      hrAIConfiguration.addActiveQuery();
      hrAIConfiguration.query();
      if (!hrAIConfiguration.next()) {
          hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration');
          hrAIConfiguration.addQuery('use_case', usecase);
          hrAIConfiguration.addQuery('sys_domain', 'global');
          hrAIConfiguration.addActiveQuery();
          hrAIConfiguration.query();
          if (!hrAIConfiguration.next())
              return;
      }

      var solutionDefinition = new GlideRecord('ml_capability_definition_classification');
      if (!solutionDefinition.get(hrAIConfiguration.solution_capability_definition))
          return;
      try {
          var mlSolution = sn_ml.ClassificationSolutionStore.get(hrAIConfiguration.solution_capability_definition.solution_name.toString());
          if (gs.nil(mlSolution)) {
              return;
          }
          var options = {};
          options.mluc = "MLUC-HR-00003";
          options.top_n = 1;
          options.apply_threshold = true;
          var result = mlSolution.getActiveVersion().predict(hrCase, options);
          if (result === null) {
              //instead of returning null returning empty array
              return [];
          }
          var resultObj = JSON.parse(result);
          var keys = Object.keys(resultObj);
          if (gs.nil(keys)||resultObj[keys[0]].length===0) {
              return;
          }
          var hrServiceSysId = resultObj[keys[0]][0]["predictedSysId"];
          return hrServiceSysId;
      } catch (err) {
          var logError = function(err) {
              gs.error(err);
          };
          gs.getMessage('Error while predicting: {0}', err.toString(), logError);
      }

  },

  /**
   * Returns null if the capability field is empty, otherwise returns the capability of the solution
   */
  _validSolutionCapability: function(solution) {
      var capability = solution.getCapability();
      if ((capability == null) || (typeof capability == 'undefined') || ('' == '' + capability))
          return null;
      return capability;
  },

  /**
   * Security requirements: none 
   */ 
  getDefaultVIPPriority: function() {
      var vipPriority = hr.DEFAULT_HIGH_PRIORITY;
      var vipPropertyName = 'sn_hr_core.hr_vip_default_priority';
      var priority = gs.getProperty(vipPropertyName);
      vipPriority = priority ? priority : vipPriority;
      return vipPriority;
  },

  _getDefaultPriorityFromService: function(service) {
      var grService = new GlideRecord('sn_hr_core_service');
      if (grService.get(service) && grService.canRead()) {
          var priorityFromTemplate = new sn_hr_core.hr_TemplateUtils()._getTemplateProperty(grService.template, 'priority');
          if (priorityFromTemplate != null)
              return priorityFromTemplate;
      }
  },

  /**
   * Security requirements: can read the user records of `sysparm_users` or
   * can read HR service (sysparm_service) default priority or
   * can create a new case
   */ 
  getPriority: function() {
      var service = this.getParameter('sysparm_service');
      var usersParm = this.getParameter('sysparm_users');
      var users = usersParm ? new global.JSON().decode(usersParm) : [];
      if (this._containsVipUser(users))
          return this.getDefaultVIPPriority();
      else if (service) {
          var priority = this._getDefaultPriorityFromService(service);

          if (!priority) {
              var glideRecord = new GlideRecord("sn_hr_core_case");
              if (glideRecord.canCreate()) {
                  glideRecord.newRecord();
                  priority = glideRecord.getValue('priority');
              }
          }
          return priority;
      }
  },

  /**
   * Security requirements: can read HR Profile represented by userSysId
   */
  getNoticePeriod: function(userSysId) {
      if (!userSysId)
          return '';

      var hrProfile = new GlideRecord(hr.TABLE_PROFILE);

      hrProfile.addQuery('user', userSysId);
      hrProfile.query();

      if (hrProfile.next() && hrProfile.canRead())
          return hrProfile.notice_period + '';
      else
          return '';
  },

  /**
   * Security requirements: can read HR position represented by positionSysId
   */
  getDepartment: function(positionSysId) {
      if (!positionSysId)
          return {};

      var department = {};

      var position = new GlideRecord(hr.TABLE_POSITION);
      position.addQuery('sys_id', positionSysId);
      position.query();

      if (position.next() && position.canRead()) {
          department.name = 'department';
          department.value = position.department + '';
          department.displayValue = position.getDisplayValue('department') + '';
      }

      return department;
  },

  /**
   * Security requirements: can read department represented by departmentSysId
   */
  getManager: function(departmentSysId) {
      if (!departmentSysId)
          return {};

      var manager = {};

      var department = new GlideRecord(hr.TABLE_DEPARTMENT);
      department.addQuery('sys_id', departmentSysId);
      department.query();

      if (department.next() && department.canRead()) {
          manager.name = 'manager';
          manager.value = department.dept_head + '';
          manager.displayValue = department.getDisplayValue('dept_head') + '';
      }

      return manager;
  },

  /**
   * Security requirements: none
   */
  isPhoneNumberValid: function(number) {
      if (gs.nil(number)) {
          number = this.getParameter('sysparm_phoneNumber');
      }
      if (gs.nil(number)) {
          return new global.JSON().encode({
              valid: false,
              number: ''
          });
      }
      var invalidResultsArray = [];
      if ((number + "").indexOf(',') > 0) {
          var numbers = (number + "").split(',');
          for (var num in numbers) {
              if (gs.nil(numbers[num])) {
                  invalidResultsArray.push(false);
              }
              if (numbers[num]) {
                  var data = JSON.parse(this._validateNumber(numbers[num]));
                  if (!data.valid)
                      return new global.JSON().encode({
                          valid: false
                      });
              }
          }
          if (invalidResultsArray.length > 0) {
              return new global.JSON().encode({
                  valid: false
              });
          }
          return new global.JSON().encode({
              valid: true
          });

      } else {
          return this._validateNumber(number);
      }
  },

  _validateNumber: function(number) {
      var record = new GlideRecord('sn_hr_core_profile');
      if (!record.canRead())
          return;
      record.initialize();
      // home_phone is used only for validating any phone number
      var gePN = record.home_phone;
      var data = {};
      data.valid = gePN.setPhoneNumber(number + "", true);
      if (!data.valid) {
          var numberWithUsersCode = this.getUsersCountryCode() + number;
          data.valid = gePN.setPhoneNumber(numberWithUsersCode, true);
      }
      if (data.valid) {
          data.number = gePN.getGlobalDisplayValue();
      } else {
          data.number = '';
      }
      return new global.JSON().encode(data);
  },

  /**
   * Get the country code for the current logged-in user
   * Security requirements: none (assumes user can read their own user record)
   */
  getUsersCountryCode: function() {
      var defaultCode = '+1';
      var user = new GlideRecord('sys_user');
      user.get(gs.getUserID());
      var country = user.getDisplayValue('country');
      if (country) { //Users having country code 'US' have country value null.
          var prefix = '+';
          var code = new GlideRecord('sys_phone_territory');
          code.get('name', country);
          return prefix + code.ccc;
      }
      return defaultCode;
  },

  _inactivePreviousSignature: function(tableName, documentId, keepLastSignActive) {
      var record = new GlideRecordSecure(tableName);
      if (record.get(documentId) && !record.active) {
          return; //don't delete signature for completed tasks
      }
      var gr = new GlideRecord('signature_image');
      gr.addQuery('user', gs.getUserID());
      gr.addQuery('table', tableName);
      gr.addQuery('document', documentId);
      gr.addActiveQuery();
      gr.orderByDesc('signed_on');
      gr.query();
      if (keepLastSignActive)
          gr.next();
      while (gr.next()) {
          gr.setValue('active', false);
          gr.update();
      }
  },

  /**
   * Security requirements: can read and write to the record represented by tableName and tableId
   */
  documentBody: function(tableName, tableId, targetTable, targetId, canEdit, keepLastSignActive) {
      // Ensure you can read and write to the target record, unless the target record does not exist
      var task = new GlideRecord(targetTable);
      var tableGr = new GlideRecord(tableName);
      if (task.isValid() && task.get(targetId)) {
          if (!(task.canRead() && task.canWrite()))
              return {};
      } else if (tableGr.isValid() && tableGr.get(tableId)) {
          if (!(tableGr.canRead() && tableGr.canWrite()))
              return {};
      } else
          return {};

      if (canEdit == 'true')
          this._inactivePreviousSignature(tableName, tableId, keepLastSignActive);
      var hrform = new GeneralHRForm(tableName, tableId, targetTable, targetId);
      return canEdit == 'true' ? hrform : {
          body: hrform.remove_all_variables(hrform.body)
      };
  },

  /**
   * Security requirements: can read and write to the record represented by tableName and tableId
   */
  setDocumentBody: function(documentBody, tableName, tableId, targetTable, targetId, canEdit, keepLastSignActive) {
      var response = {};
      // Ensure you can read and write to the target record, unless the target record does not exist
      var task = new GlideRecord(targetTable);
      var tableGr = new GlideRecord(tableName);

      if (task.isValid() && task.get(targetId)) {
          if (!(task.canRead() && task.canWrite()))
              return {
                  error: gs.getMessage('Invalid user. User does not have read and write access to {0}', targetTable)
              };
      } else if (tableGr.isValid() && tableGr.get(tableId)) {
          if (!(tableGr.canRead() && tableGr.canWrite()))
              return {
                  error: gs.getMessage('Invalid user. User does not have read and write access to {0}', tableName)
              };
      } else {
          return {
              error: gs.getMessage('Invalid target record.')
          };
      }

      if (documentBody.trim() == '')
          gs.debug("[setDocument] The document is empty, returning");
      else {
          if (canEdit == 'false') {
              var templateBody = this.documentBody(tableName, tableId, targetTable, targetId, 'true', keepLastSignActive);
              documentBody = templateBody.body;
          }
          var draftId;
          var gr = new GlideRecord("draft_document");
          gr.addQuery('table', tableName);
          gr.addQuery('document', tableId);
          gr.addActiveQuery();
          gr.query();

          if (gr.next() && gr.canWrite()) {
              draftId = gr.getUniqueValue();
              gr.setValue('body', documentBody);
              gr.body.setDisplayValue(documentBody);
              gr.target_table = targetTable;
              gr.target_id = targetId;
              gr.update();

              response = {
                  success: gs.getMessage('Document draft updated.')
              };
          } else {
              gr.initialize();

              gr.table = tableName;
              gr.document = tableId;
              gr.body = documentBody;
              gr.target_table = targetTable;
              gr.target_id = targetId;
              gr.user = gs.getUserID();

              draftId = gr.insert();

              //Update the draft_document reference in HR CASE if a row is getting inserted in draft_document table for case.
              var caseId = '';
              if (new hr_CoreUtils().isCase(tableName)) {
                  var caseGr = new GlideRecord(tableName);
                  caseGr.get(tableId);
                  if (!gs.nil(draftId)) {
                      caseGr.setValue('draft_document', draftId);
                      caseId = caseGr.update();
                  }
              }
              response = {
                  success: caseId ? gs.getMessage('Document draft created and HR case document reference updated.') : gs.getMessage('Document draft created.')
              };
          }
          return response;
      }
  },

  /**
   * Tests for an sn_hr_core_profile/sys_user record that contains the specified UserID
   * Security requirements: can read HR Profile represented by sysparm_user or can
   *  create an HR profile
   *
   * @param user user field of record being searched
   * @return sys_id of sn_hr_core_profile if record exists for this field combination,
   *         empty string otherwise
   */
  ajaxFunction_createOrGetProfileFromUser: function() {
      var userId = this.getParameter('sysparm_user');
      if (!userId) {
          gs.debug("[getProfileFromUser] Didn't receive any parameters, returning");
          return;
      }
      var hrProfile = new GlideRecord(hr.TABLE_PROFILE);
      if (hrProfile.get('user', userId))
          return hrProfile.canRead() ? hrProfile.sys_id : '';
      else if (hrProfile.canCreate())
          return this._createProfileFromUser(userId);
  },

  /**
   * Creates profile when called from HR case form
   */
  _createProfileFromUser: function(userId) {
      var gr = new GlideRecord('sn_hr_core_profile');
      if (!gr.canCreate()) {
          gs.debug("[createProfileFromUser] No create access granted to sn_hr_core_profile table");
          return;
      } else if (!userId) {
          gs.debug("[createProfileFromUser] Didn't receive any parameters, returning");
          return;
      }
      var user = this._getUserNameFromSysId(userId);
      gs.addInfoMessage(gs.getMessage('HR profile is created for {0}', user));
      var answer = new hr_Profile().createProfileFromUser(userId);
      return answer;
  },

  /**
   * Security requirements: sn_hr_core.profile_reader role
   */
  ajaxFunction_canAccessProfile: function() {

      if (new sn_hr_core.hr_CoreUtils().impersonateCheck())
          return false;

      var roles = new hr_Utils();
      if (roles.checkUserHasRole(hr.ROLE_HR_PROFILE_READER))
          return true;

      var profileId = this.getParameter('sysparm_profile');
      var caseId = this.getParameter('sysparm_caseId');
      if (gs.nil(profileId)) {
          if (gs.getUserID() == this._getOpenedForFromCaseId(caseId))
              return true;
          profileId = this._getProfileIdFromCaseId(caseId);
          if (gs.nil(profileId))
              return false;
      }

      // Check if the profile is related to the current user
      var profile = new GlideRecord('sn_hr_core_profile');
      if (profile.get(profileId)) {
          var profileUser = profile.getValue("user");

          if (!gs.nil(profileUser) && (gs.getUserID() == profileUser || new global.HRSecurityUtilsAjax().userReportsTo(profileUser, gs.getUserID(), '')))
              return true;
      }
      return false;
  },

  _getProfileIdFromCaseId: function(caseId) {
      var gr = new GlideRecordSecure('sn_hr_core_case');
      if (gs.nil(caseId) || !gr.get(caseId))
          return '';

      return gr.hr_profile;
  },

  _getOpenedForFromCaseId: function(caseId) {
      var gr = new GlideRecordSecure('sn_hr_core_case');
      if (gs.nil(caseId) || !gr.get(caseId))
          return '';

      return gr.opened_for;
  },

  /**
   * Security requirements: can read variable names from hr_benefit_questions variable set
   * @deprecated (unused OOB with HR Scoped: Core)
   */
  ajaxFunction_getCatalogVariableNames: function() {
      //this._log
      //    .debug("[getCatalogVariableNames] Retrieving all the variables for the hr_case_management variable set");

      var answer = {
          variables: []
      };

      var gr = new GlideRecord("item_option_new");
      gr.addQuery("variable_set.name", "hr_benefit_questions");
      gr.query();

      while (gr.next()) {
          if (gr.canRead())
              answer.variables.push(gr.name + "");
      }

      if (gs.isDebugging())
          gs.debug("[getCatalogVariableNames] variables -- " + answer.variables.join(", "));

      return new global.JSON().encode(answer);
  },

  /**
   * Security requirements: sn_hr_core.case_writer role
   */
  getTemplateFields: function() {
      if (!new hr_Utils().checkUserHasRole('sn_hr_core.case_writer'))
          return;
      
      var templateSysId = this.getParameter('sysparm_template_sys_id');
      var usersParm = this.getParameter('sysparm_users');
      var users = usersParm ? new global.JSON().decode(usersParm) : [];

      var templateData = new sn_hr_core.hr_TemplateUtils()._getTemplateData(templateSysId);
      // g_form does not fully support setValue(field, "javascript:...")
      for (var key in templateData)
          if (String(templateData[key].value).indexOf("javascript:") > -1)
              delete templateData[key];

      if (templateData.hasOwnProperty("priority") && this._containsVipUser(users))
          templateData["priority"] = this.getDefaultVIPPriority();

      return new global.JSON().encode(templateData);
  },

  _containsVipUser: function(userArr) {
      var userGr = new GlideRecord("sys_user");
      for (var i = 0; i < userArr.length; i++)
          if (userArr[i] && userGr.get(userArr[i]) && userGr.canRead() && userGr.vip)
              return true;

      return false;
  },

  /**
   * Security requirements: user can read topic detail represented by sysparm_detail_sys_id
   */
  getCoeFromDetail: function() {
      var detailId = this.getParameter('sysparm_detail_sys_id');
      var detailGr = new GlideRecord("sn_hr_core_topic_detail");

      if (detailGr.get(detailId) && detailGr.canRead() && !gs.nil(detailGr.topic_category.coe))
          return detailGr.topic_category.coe.toString();

      return "";
  },

  _getData: function(gr) {
      var data = {};
      var elements = gr.getElements();
      for (var i = 0; i < elements.length; i++) {
          if (!elements[i].canRead())
              continue;
          var ed = elements[i].getED();
          var name = ed.getName();
          data[name] = gr.getValue(name);
      }
      data['ZZ_YY_display_value'] = gr.getDisplayValue();
      return data;
  },

  /**
   * Security requirements: user can read table and records represented by query
   */
  getGlideRecordSecureData: function(table, query) {
      return this._getGlideRecordSecureData(table, query);
  },

  /**
   * Security requirements: user can read table and records represented by query
   */
  getGlideRecordSecureSetData: function(table, query) {
      return this._getGlideRecordSecureSetData(table, query);
  },

  /**
   * Security requirements: user can read table and records represented by query
   */
  getGlideRecordSecureDataWS: function() {
      var table = this.getParameter('sysparm_tableName');
      var query = this.getParameter('sysparm_query');
      return this._getGlideRecordSecureData(table, query);
  },

  /**
   * Security requirements: user can read table and records represented by query
   */
  getGlideRecordSecureSetDataWS: function() {
      var table = this.getParameter('sysparm_tableName');
      var query = this.getParameter('sysparm_query');
      return this._getGlideRecordSecureSetData(table, query);
  },

  _getGlideRecordSecureData: function(table, query) {
      var gr = new GlideRecordSecure(table);
      if (gr.get(query))
          return new global.JSON().encode(this._getData(gr));
      else
          return new global.JSON().encode(false);
  },

  _getGlideRecordSecureSetData: function(table, query) {
      var records = [];
      var gr = new GlideRecordSecure(table);
      gr.addEncodedQuery(query);
      gr.query();
      while (gr.next())
          records.push(this._getData(gr));
      return new global.JSON().encode(records);
  },

  /**
   * Sends an event request to reactivate the specified user
   * Security requirements: sn_hr_core.profile_writer
   *
   * @param sysparm_user - id of the user to reactivate
   * @return true if the user request has been added to the queue
   */
  ajaxFunction_reactivateUser: function() {
      var userId = this.getParameter('sysparm_user');
      var grProfile = new GlideRecord(hr.TABLE_PROFILE);

      if (!userId) {
          gs.warn("[reactivateUser] Didn't receive id of user to reactivate, returning");
          return false;
      } else if (!grProfile.get('user', userId)) {
          gs.warn("[reactivateUser] Unable to retrieve profile for user to reactivate, returning");
          return false;
      } else if (!gs.hasRole(hr.ROLE_HR_PROFILE_WRITER)) {
          gs.warn("[reactivateUser] User does not have permission to reactivate the user, returning");
          return false;
      }

      gs.eventQueue("sn_hr_core_profile.reactivate_user", grProfile, '', '');

      return true;
  },

  /**
   * Checks whether the user with the sepcified sysId given can be found
   * Security requirements: can read user record represented by sysparm_user
   *
   * @param sysparm_user - id field of record being searched for
   * @return true if the record was found, false otherwise
   */
  ajaxFunction_checkUserActive: function() {
      var userId = this.getParameter('sysparm_user');
      if (!userId) {
          gs.warn("[checkUserActive] Didn't receive any parameters, returning");
          return;
      }
      var grUser = new GlideRecord('sys_user');
      if (!grUser.get(userId) || !grUser.canRead())
          return;
      
      if (grUser.getValue('active'))
          return true;

      return;
  },

  /**
   * Security requirements: can write to sn_hr_core_direct_deposit table records
   *   represented by sysparm_query; (records where canWrite fails will not be updated)
   */
  ajaxFunction_inactivateRecords: function() {
      var tableName = this.getParameter('sysparm_tableName');
      if (tableName !== 'sn_hr_core_direct_deposit')
          return;

      var query = this.getParameter('sysparm_query');
      var filter = this.getParameter('sysparm_filter');

      var re = /^employee=([a-z0-9]{32})$/;
      // Only allow queries and filters of the form employee={sys_id}
      if (!query.match(re) || !filter.match(re))
          return;

      var numRecordsDeactivated = 0;
      var gr = new GlideRecord(tableName);
      gr.addEncodedQuery(query);
      gr.addActiveQuery();
      gr.query();
      while(gr.next()) {
          if (!gr.canWrite() || !gr.isValidField('active') || !gr.active.canWrite())
              continue;
          gr.setValue('active', 'false');
          gr.update();
          numRecordsDeactivated++;
      }
      var msg;
      if (numRecordsDeactivated == 1)
          msg = gs.getMessage('1 record inactivated.');
      else
          msg = gs.getMessage('{0} records inactivated.', numRecordsDeactivated.toString());

      msg += ' ';
      var clickHere = gs.getMessage('Click here.');

      msg += '<a href="' + tableName + '_list.do?sysparm_query=' + filter + '">' + clickHere + '</a>';
      gs.addInfoMessage(msg);
      return;
  },

  /**
   * Security requirements: can read task record represented by sys_id and
   *    its parent task record (if the task is an sn_hr_core_task)
   * @deprecated (unused OOB)
   */ 
  getOfficeSpaceParams: function(sys_id) {
      var params = {
          'user_to_be_moved': '',
          'short_description': ''
      };

      var grTask = new GlideRecord('task');
      if (!grTask.get(sys_id + ''))
          return params;

      var className = grTask.sys_class_name;

      if (className == 'sn_hr_core_task') {
          grTask = new GlideRecord('sn_hr_core_task');
          if (grTask.get(sys_id + '') && grTask.canRead()) {
              params["short_description"] = grTask.short_description + '';

              var parentCaseGR = new GlideRecord('sn_hr_core_case');
              if (parentCaseGR.get(grTask.getValue('parent')) && parentCaseGR.canRead())
                  params['user_to_be_moved'] = parentCaseGR.subject_person + '';
          }
      } else {
          var hierarchyUtils = new GlideTableHierarchy(grTask.sys_class_name);
          var baseTable = hierarchyUtils.getBase();
          if (baseTable == 'sn_hr_core_case') {
              grTask = new GlideRecord(className + '');

              if (grTask.get(sys_id + '') && grTask.canRead()) {
                  params['user_to_be_moved'] = grTask.subject_person + '';
                  var tasks = new GlideRecord('sn_hr_core_task');
                  tasks.addQuery('parent', sys_id);
                  tasks.query();
                  while (tasks.next() && tasks.canRead()) {
                      if (tasks.sc_cat_item) {
                          // Set params for Select Office Space SC Catalog item
                          if (tasks.sc_cat_item.sys_id == '65f6ad093b143200705d86a734efc43b')
                              params['short_description'] = tasks.short_description + '';
                      }
                  }
              }
          }
      }
      return params;
  },

  /**
   * Security requirements: user can read direct deposits for employee represented in param
   */
  directDepositValidation: function(param) {
      var record = JSON.parse(param);
      var amounts = [];
      var percentages = [];
      var balances = [];
      populateArrays(record);

      var deposits = new GlideRecordSecure('sn_hr_core_direct_deposit');
      deposits.addActiveQuery();
      deposits.addQuery('employee', record.employee);
      deposits.addQuery('sys_id', '!=', record.sys_id);
      deposits.query();
      while (deposits.next()) {
          populateArrays(deposits);
      }

      if (record.deposit_type == 'amount')
          return validateAmount(record);
      else if (record.deposit_type == 'percentage')
          return validatePercentage(record);
      else if (record.deposit_type == 'balance')
          return validateBalance(record);

      function populateArrays(gr) {
          if (gr.deposit_type == 'amount')
              amounts[gr.sys_id] = gr.deposit_amount;
          else if (gr.deposit_type == 'percentage')
              percentages[gr.sys_id] = gr.deposit_percentage.toString();
          else if (gr.deposit_type == 'balance')
              balances.push(gr.sys_id);
          else {
              return new global.JSON().encode(gs.getMessage("Deposit type is required"));
          }
      }

      function validateAmount(record) {
          if (record.deposit_amount == 0 || !record.deposit_amount)
              return new global.JSON().encode(gs.getMessage("Deposit amount must be greater than zero"));

          if (Object.keys(percentages).length > 0)
              return new global.JSON().encode('conflict');

          return true;
      }

      function validatePercentage(record) {
          if (!record.deposit_percentage || record.deposit_percentage <= 0 || record.deposit_percentage > 100)
              return new global.JSON().encode(gs.getMessage("Deposit percentage must be greater than zero and less than or equal to 100"));

          var sum = 0;

          for (var key in percentages)
              sum += Number(percentages[key]);

          if (sum > 100)
              return new global.JSON().encode(gs.getMessage("Total direct deposit instructions for {0} exceed 100%", record.employee_name));
          else if (sum == 100 && Object.keys(balances).length > 0)
              return new global.JSON().encode(gs.getMessage("Total direct deposit instructions for {0} are already 100%", record.employee_name));


          if (Object.keys(amounts).length > 0)
              return new global.JSON().encode('conflict');

          return true;
      }

      function validateBalance(record) {
          if (Object.keys(percentages).length > 0) {
              var sum = 0;
              for (var key in percentages)
                  sum += Number(percentages[key]);

              if (sum >= 100)
                  return new global.JSON().encode(gs.getMessage("A balance deposit type cannot be added because {0} has direct deposit instructions totalling 100%", record.employee_name));
          }

          if (Object.keys(balances).length > 1)
              return new global.JSON().encode('conflict');

          return true;
      }
  },

  /*
   * Returns an array of objects. Each object contains:
   * -Field : whose value is update
   * -Table : To which table that field belongs to(user or profile)
   * -newValue : The value requested by user.
   * Security requirements: user can read case represented by sysparm_caseId
   *
   * @param - Unique value of case record.
   * This function uses payload object for finding
   * all the modified fields.
   */
  getModifiedFields: function() {
      var caseId = this.getParameter('sysparm_caseId') + '';
      var grCase = new GlideRecord(sn_hr_core.hr.TABLE_CASE);
      if (grCase.get(caseId) && grCase.canRead()) {
          var modifiedFields = this.getCaseModifiedFields(grCase);
          return new global.JSON().encode(modifiedFields);
      }
  },

  /*
   * Public method to determine all modified fields from case record
   * Security requirements: user can read grCase
   */
  getCaseModifiedFields: function(grCase) {
      if (!grCase || !grCase.canRead())
          return;

      //Cat item variables implementation may differ from form fields
      var mismatchFields = ["country", 'country_of_birth', 'home_phone', 'work_mobile', 'mobile_phone'];

      var fields = [];
      if (!grCase.payload)
          return fields;

      var payload = JSON.parse(grCase.payload);
      var grProfile = grCase.hr_profile.getRefRecord();
      var grUser = grCase.subject_person.getRefRecord();

      //Loop through the fields from RP
      for (var prop in payload) {
          var isMisMatchProperty = mismatchFields.indexOf(prop) > -1;

          //Filter out mismatched fields
          if (!isMisMatchProperty) {

              //Check if field belongs to HR Profile
              if (grProfile.hasOwnProperty(prop)) {
                  fields.push(this._getModifiedFieldInfo(grProfile, 'profile', prop, payload[prop]));
                  continue;

                  //Check if field belongs to Sys User record
              } else if (grUser.hasOwnProperty(prop)) {
                  fields.push(this._getModifiedFieldInfo(grUser, 'user', prop, payload[prop]));
                  continue;
              }

              //Handle mismatch fields
          } else {
              if ((prop == 'country' || prop == 'country_of_birth') && grProfile.getValue(prop) != payload[prop])
                  fields.push(this._getModifiedFieldInfo(grProfile, 'profile', prop, this.getCountryName(payload[prop])));
              else if (prop == 'home_phone' || prop == 'work_mobile' || prop == 'mobile_phone') {
                  var phoneNumber = this.sanitizePhoneNumber(payload[prop]);
                  if (phoneNumber != grProfile.getValue(prop))
                      fields.push(this._getModifiedFieldInfo(grProfile, 'profile', prop, payload[prop]));
              }
          }
      }
      fields = fields.filter(function(field) {
          if (field)
              return field;
      });
      return fields;
  },

  //Returns the object that contains updated field info
  _getModifiedFieldInfo: function(grProfile, table, prop, newValue) {
      var currentValue = this.sanitize(grProfile.getValue(prop), '');

      //Checking if value is modified
      if (currentValue !== newValue) {
          return ({
              table: table,
              field: prop,
              newValue: this.sanitize(newValue, 'Empty')
          });
      }
  },

  /**
   * @private
   */
  sanitizePhoneNumber: function(number) {
      return number.replace(/[\s-()]/g, "");
  },

  /**
   * @private
   */
  getCountryName: function(countryId) {
      var c = new GlideRecord('core_country');
      return (c.get(countryId) && c.canRead()) ? c.getDisplayValue() : gs.getMessage('Empty');
  },

  /**
   * @private
   */ 
  sanitize: function(value, defaultVal) {
      return (value) ? value : defaultVal;
  },

  /*
   * Public method to add info message when opened_for is a user without HR profile
   * Security requirements: user can read hr profile represented by user id in sysparm_sys_id
   */
  addMsgForOpenedForHRProfile: function() {
      var gr = new GlideRecord('sn_hr_core_profile');
      gr.addQuery('user', this.getParameter('sysparm_sys_id'));
      gr.query();
      if (!gr.canRead())
          return;
      else if (!gr.hasNext()) {
          var user = this._getUserNameFromSysId(this.getParameter('sysparm_sys_id'));
          if (user)
              gs.addInfoMessage(gs.getMessage('HR profile will be created for {0}', user));
          else
              gs.addInfoMessage(gs.getMessage('HR profile will be created for opened_for user'));
      }
  },

  _getUserNameFromSysId: function(sysId) {
      var grUser = new GlideRecord('sys_user');
      grUser.addQuery('sys_id', sysId);
      grUser.query();
      if (grUser.next() && grUser.canRead())
          return grUser.name;
  },

  /*
  	This method takes document type and a user and returns a comma separated 
  	string of PDF template sys_ids filtered by the provided document type and 
  	provided user's HR criteria

  	Security requirements (from getPDFTemplateBasedOnDocumentType): user can read 
  	templates with document type represented by sysparm_document_type. (Templates
  	that cannot be read will not be returned)

  	@param sysparm_document_type: Document Type to filter PDF Templates
  	@param sysparm_subject_person: Subject person whose HR criteria will be 
  	checked against the HR Criteria of PDF Templates
  
  	@return Comma separated string of PDF Template sys_ids
  */
  ajaxFunction_getPDFTemplate: function() {
      var documentType = this.getParameter('sysparm_document_type');
      var subjectPerson = this.getParameter('sysparm_subject_person');
      var answer = new sn_hr_core.hr_Utils().getPDFTemplateBasedOnDocumentType(documentType, subjectPerson);

      return answer;
  },

  /*
  	This method returns an array of objects containing sys_id and 
  	display value of PDF Templates matching document type and 
  	user's HR criteria

  	Security requirements (from getPDFTemplateBasedOnDocumentType): user can read 
  	templates with document type represented by sysparm_document_type. (Templates
  	that cannot be read will not be returned)

  	@param sysparm_document_type: Document Type to filter PDF Templates
  	@param sysparm_subject_person: Subject person whose HR criteria will be 
  	checked against the HR Criteria of PDF Templates
  
  	@return JSON encoded object containing sys_id and display value of matching PDF Template
  */
  ajaxFunction_getMatchingPDFTemplate: function() {
      var documentType = this.getParameter('sysparm_document_type');
      var subjectPerson = this.getParameter('sysparm_subject_person');

      var answer = new sn_hr_core.hr_Utils().getPDFTemplateObjectsForDocumentType(documentType, subjectPerson);

      return answer && (answer.length == 1) ? new global.JSON().encode(answer[0]) : '';
  },

  /* 
   * This method is called by the Approve and Reject UI actions on sysapproval_approver record
   * Security requirements: none (it is used to do security checks)
   */
  canBeApprovedByHRAdmin: function(current) {
      return current.state == 'requested' &&
          current.sysapproval && hr.TABLE_CASE_EXTENSIONS.toString().indexOf(current.sysapproval.sys_class_name) >= 0 &&
          new hr_Utils().checkUserHasRole('sn_hr_core.admin');
  },

  /* 
   * This method is called by the read ACL on sysapproval_approver
   * Security requirements: none (it is used to do security checks)
   */
  hasHRAccess: function(current) {
      var hasCorrectHRRole = new hr_Utils().checkUserHasRole(sn_hr_core.hr.ROLE_HR_CASE_READER);
      var validImpersonation = new sn_hr_core.hr_CoreUtils().impersonateCheck() == false;

      var hasAccessForOpenForUser = false;
      var hrCase = new GlideRecord(current.sysapproval.sys_class_name.toString());
      if (hrCase.get(current.document_id)) {
          var openForUser = hrCase.opened_for;
          hasAccessForOpenForUser = ((openForUser != null) && (gs.getUserID() == openForUser.sys_id));
      }

      return validImpersonation && (hasAccessForOpenForUser || hasCorrectHRRole || new global.ApprovalDelegationUtil().isMyApproval(current));
  },

  /*
   * Utility method that returns list of all the table that extend (including) sn_hr_core_case.
   * Security requirements: can read the HR Case table
   *
   * @returns
   *  List of all the table that extend (including) sn_hr_core_case table.
   *
   */
  getAllHRCaseTables: function() {
      var HR_CASE_TABLE = "sn_hr_core_case";
      var grCase = new GlideRecord(HR_CASE_TABLE);
      if (!grCase.canRead())
          return;
      return new GlideTableHierarchy(HR_CASE_TABLE).getAllExtensions();
  },

  /*
   * This method is called by the UI Action Close Complete, related to HR Case. 
   * Updates the work notes and state to 'closed incomplete'
   * Security requirements: can write to the record represented by sysparm_obj_id and
   *  sysparm_table_name
   */
  closeIncompleteAction: function() {
      //gets the parameters passed in by the UI Action
      var objSysId = this.getParameter('sysparm_obj_id');
      var tblName = this.getParameter('sysparm_table_name');
      var newWorkNote = this.getParameter('sysparm_work_note');
      //opens the record 
      var task = new GlideRecord(tblName);
      if (task.get(objSysId) && task.canWrite()) {
          //updates the worknotes, time work ended (if null), and the state
          this._updateNotes(task, newWorkNote, false);
          this._workEnd(task);
          task.state = hr.STATE_CLOSE_INCOMPLETE;
          task.update();
          //checks the request table for a parent of our record
          //changed the state to closed_cancelled for our record if found
          var gr = new GlideRecord('sc_request');
          gr.addQuery('parent', objSysId);
          gr.addActiveQuery();
          if (!gr.canRead())
              return;
          gr.setValue('request_state', 'closed_cancelled');
          gr.updateMultiple();
      }
      return gr;
  },

  /**
   * Security reqiurements: can read the case represented by the case_id parameter
   */
  getSuspendReasons: function() {
      var reasons = [];
      var grCase = new GlideRecord("sn_hr_core_case");
      var caseId = this.getParameter('case_id');
      var tableName = this.getParameter('sysparm_table_name');
      var parent = this.getParameter('sysparm_parent_case_table_name');

      if (tableName === 'sn_hr_core_task')
          tableName = parent;

      if (!gs.nil(caseId))
          grCase.get(caseId);

      if (!grCase.canRead() || !grCase.getElement("sla_suspended_reason").canRead())
          return new global.JSON().encode(reasons);

      var choiceList = GlideChoiceList.getChoiceList(tableName, 'sla_suspended_reason');
      choiceList.removeNone();

      for (var i = 0; i < choiceList.getSize(); i++) {
          var choice = choiceList.getChoice(i);

          reasons.push({
              displayValue: choice.label.toString(),
              value: choice.value.toString()
          });
      }

      return new global.JSON().encode(reasons);
  },

  /*
   * This method is called by the UI Action Suspend/Activate, related to HR Case. 
   * Updates the work notes and state to 'Suspended'
   * Security requirements: can write to the case represented by sysparm_obj_id and
   *   sysparm_table_name
   */
  suspendCaseAction: function() {
      var objSysId = this.getParameter('sysparm_obj_id');
      var tblName = this.getParameter('sysparm_table_name');
      var newWorkNote = this.getParameter('sysparm_work_note');
      var newReason = this.getParameter('sysparm_suspend_reason');

      var hrCase = new GlideRecord(tblName);
      hrCase.get(objSysId);
      if (!hrCase.isValid() || !hrCase.canWrite())
          return null;

      this._setSuspensionReason(hrCase, newReason);
      this._updateNotes(hrCase, newWorkNote, false);
      hrCase.state = hr.STATE_SUSPENDED;
      hrCase.update();
      return hrCase;
  },

  /**
   * This function takes a table name and sys_id and checks if the 
   * passed in corresponding record has any children that are open. 
   * Security requirements: can read parent case
   * 
   * @returns {boolean} - true if an open child case exists, false otherwise.
   *
   */
  hasOpenChildCases: function() {
      var tblName = this.getParameter('sysparm_table_name');
      if (tblName !== 'sn_hr_core_case')
          return null;

      var parentId = this.getParameter('sysparm_parent_id');

      var parentCase = new GlideRecord('sn_hr_core_case');
      if (!parentCase.get(parentId) || !parentCase.canRead())
          return null;

      var notInQuery = [hr_Constants.CASE_DRAFT, hr_Constants.CASE_CLOSED_COMPLETE, hr_Constants.CASE_CLOSED_INCOMPLETE, hr_Constants.CASE_CANCELLED];
      var openChildQuery = "active=true^stateNOT IN".concat(notInQuery, "^parent=", parentId);

      var gr = new GlideRecord(tblName);
      gr.addQuery(openChildQuery);
      gr.setLimit(1);
      gr.query();
      return gr.hasNext(); //true if the record has open children
  },

  /**
   * Security requirements: user can write to record represented by sysparm_table_name
   *  and sysparm_sys_id
   */
  settingTaskToDraft: function() {
      var sysId = this.getParameter('sysparm_sys_id');
      var className = this.getParameter('sysparm_table_name');
      var workNote = this.getParameter('sysparm_work_note');

      var task = new GlideRecord(className);
      if (task.get(sysId) && task.work_notes.canWrite()) {
          task.work_notes = workNote;
          if (task.work_end.nil()) {
              task.work_end = new GlideDateTime().getDisplayValue();
          }
          task.state = hr.STATE_DRAFT;
          task.update();
      }
  },

  /*
   * This method to be used for E-signature flows of HR Templates
   * Security requirements: user can read record represented by table_name and sys_id
   */
  generateDocumentAndCloseTask: function(table_name, sys_id) {
      var taskGR = new GlideRecord(table_name);
      taskGR.get(sys_id);
      if (taskGR.canRead() && new hr_Delegation().isAssignedOrDelegated(taskGR)) {
          var request = {
              "user_id": gs.getUserID(),
              "sys_id": sys_id
          };
          var hrPdfUtils = new sn_hr_core.hr_PdfUtils();
          var generalHrForm = new sn_hr_core.GeneralHRForm();
          var esignTaskUtil = new sn_esign.esign_taskUtils();
          if (hrPdfUtils.isValidPdfTemplate(table_name, sys_id)) {
              var response = hrPdfUtils.createPdfForDocument(table_name, sys_id, false);
              if (response.indexOf('Error') > 0)
                  gs.addInfoMessage(gs.getMessage("isValidPdfTemplate error response {0}", response));
              else
                  esignTaskUtil.setTaskFinished(request);
          } else if (generalHrForm.hasDraftDocument(table_name, sys_id)) {
              var draftDocument = generalHrForm.getDraftDocument(table_name, sys_id);
              new sn_hr_core.GeneralHRForm(draftDocument.table, draftDocument.document, draftDocument.target_table, draftDocument.target_id).generate();
              esignTaskUtil.setTaskFinished(request);
          }
      }
  },

  /*
   * A private function that updates the comments or worknotes field
   * depending on the passed in parameter of 'isComment'
   */
  _updateNotes: function(gr, newNote, isComment) {
      if (gr.canWrite() && newNote) {
          if (isComment)
              gr.comments = newNote;
          else
              gr.work_notes = newNote;
      }
  },

  /*
   * This is a private function that sets the time work was ended on 
   * a case or task;
   */
  _workEnd: function(gr) {
      if (gr.canWrite() && gr.work_end.nil()) {
          gr.work_end = new GlideDateTime().getDisplayValue();
      }
  },

  /*
   * A private function that checks if the parent has been assigned, if not 
   * give the parent the same assignments as the child task. 
   * 
   * Used for Start Work UI action for HR tasks.
   */
  _checkParentAssignment: function(gr) {
      if (gr.parent.assigned_to.nil() && gr.parent.assignment_group.nil()) {
          var parentCase = new GlideRecord("sn_hr_core_case");
          if (parentCase.get(gr.parent) && parentCase.canWrite()) {
              parentCase.assigned_to = gr.assigned_to;
              parentCase.assignment_group = gr.assignment_group;
              parentCase.update();
          }
      }
  },

  /*
   * This private function writes the reason provided for suspension and
   * sets the approiate fields for a suspended case or task
   */
  _setSuspensionReason: function(gr, reason) {
      if (gr.canWrite() && reason) {
          var isHrCase = new sn_hr_core.hr_CoreUtils().isCase(gr.sys_class_name);
          if (isHrCase) {
              gr.sla_suspended_reason = reason;
              gr.sla_suspended = true;
              gr.sla_suspended_on = new GlideDateTime().getDisplayValue();
          } else {
              gr.suspend_request = true;
              gr.request_suspension_reason = reason;
          }
      }
  },

  /*
   * This private function suspends the parent of the task and sets all
   * approiate fields for a suspended case.
   */
  _suspendParent: function(gr, reason) {
      var parentCase = new GlideRecord("sn_hr_core_case");
      var caseOpen = parentCase.state != (hr.STATE_CLOSE_COMPLETE || hr.STATE_CLOSE_INCOMPLETE || hr.STATE_CANCEL);
      if (parentCase.get(gr.parent) && caseOpen && parentCase.sla_suspended == false && parentCase.canWrite()) {
          this._setSuspensionReason(parentCase, reason);
          parentCase.state = hr.STATE_SUSPENDED;
          parentCase.update();
      }
  },

  /*
   * The function querys the task table and sets all suspend requests for 
   * siblings tasks of the current task to false.
   */
  _updateSiblings: function(gr) {
      if (gr.canWrite()) {
          var siblings = new GlideRecord("sn_hr_core_task");
          siblings.addQuery('parent', gr.parent.sys_id);
          siblings.addQuery('suspend_request', true);
          siblings.setValue('suspend_request', false);
          siblings.updateMultiple();
      }
  },

  /*
   * This function is called by the UI Action Suspend and the UI Action
   * Request Additional User Information, related to HR Task.
   * 
   * Suspends the current parent case for the task and updates the work notes.
   * Security requirements: user can write to record represented by sysparm_table_name
   *  and sysparm_sys_id
   */
  suspendTask: function() {
      var tblName = this.getParameter("sysparm_table_name");
      var sysId = this.getParameter("sysparm_sys_id");
      var reason = this.getParameter("sysparm_reason");
      var newNote = this.getParameter("sysparm_new_note");

      var gr = new GlideRecord(tblName);
      if (gr.get(sysId) && gr.canWrite()) {
          this._updateSiblings(gr);
          this._updateNotes(gr, newNote, false);
          this._setSuspensionReason(gr, reason);
          this._suspendParent(gr, reason);
          gr.update();
      }
  },

  /*
   * This function is called by the Start Work UI Action related to HR Task.
   *
   * Assigns the parent case the same assignees and sets the state to work in progress
   * Security requirements: user can write to record represented by sysparm_table_name 
   *  and sysparm_sys_id
   */
  startTask: function() {
      var tblName = this.getParameter("sysparm_table_name");
      var sysId = this.getParameter("sysparm_sys_id");

      var gr = new GlideRecord(tblName);
      if (gr.get(sysId) && gr.canWrite()) {
          this._checkParentAssignment(gr);
          gr.state = hr.STATE_WORK_IN_PROGRESS;
          gr.update();
      }
  },

  /*
   * This function is called by Close Incomplete UI action related to HR Task.
   * 
   * Closes the task as Close Incomplete and updates the work notes.
   * It will clone the task if the user requests to create a follow.
   * Security requirements: user can write to record represented by sysparm_table_name
   *  and sysparm_sys_id
   */
  closeIncompleteTask: function() {
      var tblName = this.getParameter("sysparm_table_name");
      var sysId = this.getParameter("sysparm_sys_id");
      var newNote = this.getParameter("sysparm_new_note");
      var createFollow = this.getParameter("sysparm_create_follow");
      var sysIdToReturn;
      var gr = new GlideRecord(tblName);
      if (gr.get(sysId) && gr.canWrite()) {
          sysIdToReturn = gr.sys_id;
          if (createFollow == "Yes") {
              var clonedTask = new hr_Task().cloneTask(gr, true);
              sysIdToReturn = clonedTask.sys_id;
          }
          this._updateNotes(gr, newNote, false);
          this._workEnd(gr);
          gr.state = hr.STATE_CLOSE_INCOMPLETE;
          gr.update();
      }
      return sysIdToReturn;

  },

  /*
   * This function is called by Close Complete Ui Action related to HR task.
   * 
   * Closes the task to Closed Complete and updates the work notes.
   * Security requirements (from closeHRTaskWithComment): user can write to record
   *  represented by sysparm_table_name and sysparm_sys_id
   */
  closeTask: function() {
      var tblName = this.getParameter("sysparm_table_name");
      var sysId = this.getParameter("sysparm_sys_id");
      var newNote = this.getParameter("sysparm_new_note");

      this.closeHRTaskWithComment(tblName, sysId, newNote);
  },

  /**
   * Add work note and close complete HR task
   * Security requirements: user can write to record represented by table and uniqueID
   * @params:
   *  table: String value of HR task table name
   *  uniqueID: String value of HR task sys_id
   *  comment: Work note
   *
   * @returns Object { 
   *  success: boolean true/ false
   *  error: string message
   * }
   */
  closeHRTaskWithComment: function(table, uniqueID, comment) {

      var response = {};
      var hrTaskGR = new GlideRecord(table);

      if (hrTaskGR.get(uniqueID)) {

          if (hrTaskGR.canWrite()) {
              this._updateNotes(hrTaskGR, comment, false);
              this._workEnd(hrTaskGR);

              hrTaskGR.state = hr.STATE_CLOSE_COMPLETE;
              response.success = hrTaskGR.update() ? true : false;
          } else
              response.error = 'No write access to record.';
      } else 
          response.error = 'Record not found.';
      return response;
  },

  /*
   * This function is called by Cancel Task Ui Action related to HR Task.
   * 
   * Cancels the task by setting the state to cancelled and updates worknotes.
   * Security requirements: user can write to record represented by sysparm_table_name
   *  and sysparm_obj_id
   */
  cancelAction: function() {
      //gets the parameters passed in by the UI Action
      var objSysId = this.getParameter('sysparm_obj_id');
      var tblName = this.getParameter('sysparm_table_name');
      var newWorkNote = this.getParameter('sysparm_work_note');
      //opens the record 
      var grCase = new GlideRecord(tblName);
      if (grCase.get(objSysId) && grCase.canWrite()) {
          //updates the worknotes and state fields
          this._updateNotes(grCase, newWorkNote, false);
          grCase.state = hr.STATE_CANCEL;
          grCase.update();
      }
  },

  /**
   * Security requirements: user has sn_hr_core.basic role and
   *    can read the case represented by sysparm_sys_id and sysparm_table_name
   */
  escalateCase: function() {
      if (!gs.hasRole('sn_hr_core.basic'))
          return;

      //Get the passed in parameters
      var caseSysId = this.getParameter('sysparm_sys_id');
      var tblName = this.getParameter('sysparm_table_name');
      var newWorkNote = this.getParameter('sysparm_work_note');
      var caseExtensions = new GlideTableHierarchy("sn_hr_core_case").getAllExtensions();
      if (caseExtensions.indexOf(tblName) < 0)
          return;
      //Get the record
      var caseToEscalate = new GlideRecord(tblName);
      if (!caseToEscalate.get(caseSysId) || !caseToEscalate.canRead())
          return;

      this._updateNotes(caseToEscalate, newWorkNote, false);
      //Query the next escalation tier
      var gr = new GlideRecord('sn_hr_core_tier_definition');
      gr.addQuery('escalate_from', caseToEscalate.assignment_group);
      gr.addNotNullQuery('escalate_to');
      gr.query();
      if (gr.next()) {
          caseToEscalate.assignment_group = gr.escalate_to;
          caseToEscalate.assigned_to = '';
          // auto assignment BR will try to find an agent
      }
      caseToEscalate.update();
  },

  /**
   * Returns boolean true if user is assigned_to on case (or delegated) and has sn_hr_core.case_writer role
   * Security requirements: userID must be the same as the logged in user's ID (cannot call this API for
   *     other users)
   * @param {string} userID - sys_id of the user
   * @param {string} caseID - the sys_id of the case
   * @returns {boolean} - true if user is assigned_to on case (or delegated) and has sn_hr_core.case_writer role otherwise false
   */
  isAssignedToCaseWriter: function(userID, caseID) {
      if (gs.getUserID() !== userID)
          return false;
      
      //verify user is assigned to on case
      var hrCaseGR = new GlideRecord('sn_hr_core_case');
      if (!hrCaseGR.get(caseID))
          return false;

      var isAssignee = new hr_Delegation().isAssignedOrDelegated(hrCaseGR, userID);
      return (isAssignee && new hr_Utils().checkUserHasRole('sn_hr_core.case_writer'));
  },

  /**
   * getCasePredictions - Get all the predictions for the given case
   * Security requirements: user has sn_hr_core.case_writer role and can read case
   *   represented by sysparm_case_id
   * @param:
   *       sysparm_case_id - HR Case sys_id
   */
  getCasePredictions: function() {
      if (!gs.hasRole('sn_hr_core.case_writer'))
          return;

      if (!(new GlidePluginManager().isActive('com.glide.platform_ml')))
          return;

      var caseId = this.getParameter('sysparm_case_id');
      var toPredict = this.getParameter('sysparm_predict');
      var predictions = {};

      var caseGR = new GlideRecord('sn_hr_core_case');
      if (!caseGR.get(caseId) || !caseGR.canRead())
          return;

      var subjectPerson = caseGR.subject_person;
      var domain = caseGR.domain;

      if (toPredict == 'HR_SERVICE' && gs.getProperty("sn_hr_core.case_auto_categorization") == 'true') {
          var service = this.getHRServicePredictions(caseId, subjectPerson, domain);
          if (!gs.nil(service)) {
              service = JSON.parse(service);
              if (!gs.nil(service.service_id) && service.service_id != caseGR.hr_service) {
                  service.message = gs.getMessage('Predicted HR Service is {0}. To transfer this case initiate Transfer Case.', service.service_display);
                  predictions.service = service;
              }
          }
      }

      if (toPredict == 'ASSIGNMENT_GROUP' && gs.getProperty("sn_hr_core.case_auto_assignment") == 'true') {

          var reassignCount = caseGR.reassignment_count;
          if (gs.nil(reassignCount) || reassignCount == 0) {
              var result = this.getMLPredictorResults(caseId, hr.CASE_ASSIGNMENT, domain);
              if (!gs.nil(result)) {
                  var aGroup = '';
                  if (!gs.nil(result[hr.CASE_ASSIGNMENT]))
                      aGroup = result[hr.CASE_ASSIGNMENT];
                  aGroup = aGroup.split(',')[0];
                  if (!gs.nil(aGroup) && aGroup != caseGR.assignment_group.name)
                      predictions.assignmentGroup = {
                          assignmentGroup: aGroup,
                          message: gs.getMessage('Predicted Assignment group is {0}.', aGroup)
                      };
              }

          }

      }

      return JSON.stringify(predictions);

  },

  /**
   * Security requirements: none
   */
  isEndDateAfterStartDate: function() {
      var startDate = new GlideDateTime(this.getParameter('sysparm_start_date'));
      var endDate = new GlideDateTime(this.getParameter('sysparm_end_date'));
      return endDate.compareTo(startDate) >= 0;
  },

  /** 
   * Description: Returns solution definition and solution for given use case and domain
   * Security requirements: none
   * @param {String} sys_domain
   * @param {String} Use Case of HRAI Configuration  
   * @return {Object} Glide Record of valid solution definition and active solution 
   */
  getValidSolution: function(domain, useCase) {
      var hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration'); //configuration table
      hrAIConfiguration.addQuery('use_case', useCase);
      hrAIConfiguration.addQuery('sys_domain', domain);
      hrAIConfiguration.query();
      if (!hrAIConfiguration.next()) {
          hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration');
          hrAIConfiguration.addQuery('use_case', useCase);
          hrAIConfiguration.addQuery('sys_domain', 'global');
          hrAIConfiguration.query();
          if (!hrAIConfiguration.next())
              return null;
      }

      var solutionDefinition = new GlideRecord('ml_capability_definition_base');
      if (!solutionDefinition.get(hrAIConfiguration.solution_capability_definition))
          return null;
      var predictor = new global.MLPredictor();
      var name = solutionDefinition.solution_name.toString(); //Get solution name from configuration
      var solution = predictor.findActiveSolution(name);
      if (solution) {
          var solutionInfo = {
              solutionDefinition: solutionDefinition,
              solution: solution
          };
          return solutionInfo;
      }
  },

  /** 
   * Description: Predicts ETTR for HR cases
   * Security requirements: none
   * @param {GlideRecord} hrCase
   * @param {String} solution_name
   * @return {Object} Contains lowerbound, upperbound, point estimate of predicted ETTR
   */
  getERTPrediction: function(hrCase, solution_name) {
      var options = {};
      options.mluc = "MLUC-HR-00012";
      var sysId = hrCase.sys_id;
      options.confidence_level = GlideApplicationProperty.getValue('sn_hr_core.estimated_resolution_time_confidence_level');
      var mlSolution = sn_ml.RegressionSolutionStore.get(solution_name);
      var result = mlSolution.getActiveVersion().predict(hrCase, options);
      var ans = (JSON.parse(result)[sysId]);
      return ans;
  },

  /** 
   * Description: Returns whether to display ETTR for Agent
   * Security requirements (from getERTDisplayAgent): user can read case represented by sysparm_sysId
   * @param {sys_id} sys_id of HR Case
   * @return {boolean} True if ETTR display is enabled for Agent
   */
  getERTDisplayAgentAJAX: function() {
      var sysId = this.getParameter('sysparm_sysId');
      var result = this.getERTDisplayAgent(sysId);
      return result;
  },

  /**
   * Description: Returns whether to display ETTR for Agent
   * Security requirements: user can read case represented by sysparm_sysId
   * @private
   * @param {sys_id} sys_id of HR Case
   * @return {boolean} True if ETTR display is enabled for Agent
   */
  getERTDisplayAgent: function(sys_id) {
      var gr = new GlideRecord("sn_hr_core_case");
      if (!gr.get(sys_id) || !gr.canRead())
          return false;
      var isSolutionDefActive = this.isSolutionDefinitionActive(gr.sys_domain, hr.ESTIMATED_RESOLUTION_TIME);
      if (!isSolutionDefActive)
          return false;
      var tableName = gr.sys_class_name.toString();
      var prop = this.getERTEnabledCOEsForAgent();
      if (prop.indexOf(tableName) > -1)
          return true;
      return false;
  },

  /** 
   * Description: Returns list of COEs for which ETTR display is active for Employee
   * @private
   * @return {array} List of COEs
   */
  getERTEnabledCOEsForEmp: function() {
      var prop = GlideApplicationProperty.getValue('sn_hr_core.COE_ETTR_display_employee').split(',');
      prop.forEach(function(entry) {
          entry.trim();
      });
      return prop;
  },

  /** 
   * Description: Returns list of COEs for which ETTR display is active for Agent
   * @private
   * @return {array} List of COEs
   */
  getERTEnabledCOEsForAgent: function() {
      var prop = GlideApplicationProperty.getValue('sn_hr_core.COE_ETTR_display_agent').split(',');
      prop.forEach(function(entry) {
          entry.trim();
      });
      return prop;
  },

  _checkERTDisplayPropertiesEmployee: function(gr) {
      var isSolutionDefActive = this.isSolutionDefinitionActive(gr.sys_domain, hr.ESTIMATED_RESOLUTION_TIME);
      if (!isSolutionDefActive)
          return false;
      var prop = this.getERTEnabledCOEsForEmp();
      var tableName = gr.sys_class_name.toString();
      if (prop.indexOf(tableName) > -1)
          return true;
      return false;
  },

  /** 
   * Description: Returns ETTR value if the feature is active and display is enabled for employees
   * Security requirements: user can read hr case represented by sys_id
   * @return {String} ETTR 
   */
  getERTValueEmployee: function(sys_id) {
      var gr = new GlideRecord("sn_hr_core_case");
      if (!gr.get(sys_id) || !gr.canRead())
          return '-1';

      if (!gs.nil(gr) && !gs.nil(gr.max_ettr) && this._checkERTDisplayPropertiesEmployee(gr))
          return gr.max_ettr.toString();
  },

  /** 
   * Description: Returns whether a solution definition is active corresponding to a given use case and domain
   * @private
   * @param {String} sys_domain
   * @param {String} Use Case of HRAI Configuration
   * @return {Boolean} True if solution definition is active.
   */
  isSolutionDefinitionActive: function(domain, useCase) {
      var configuration;
      var hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration'); //configuration table
      hrAIConfiguration.addQuery('use_case', useCase);
      hrAIConfiguration.addActiveQuery();
      var orCondition = hrAIConfiguration.addQuery('sys_domain', domain);
      orCondition.addOrCondition('sys_domain', 'global');
      hrAIConfiguration.query();

      while (hrAIConfiguration.next()) {
          if (hrAIConfiguration.sys_domain == domain) {
              configuration = hrAIConfiguration;
              break;
          } else if (hrAIConfiguration.sys_domain == 'global')
              configuration = hrAIConfiguration;
      }
      var solutionDefinition = new GlideRecord('ml_capability_definition_base');
      if (gs.nil(configuration) || !solutionDefinition.get(configuration.solution_capability_definition))
          return false;

      return solutionDefinition.active;
  },

  /** 
   * Description: Returns list of domains, other than global, for which solution definition corresponding to a ETTR use case is active 
   * Security requirements: none (used by HR Agent workspace, HR Agent workspace V2, and app-hr-mobile [employee HR Cases])
   * @return {Array} Array of sys_domain
   */
  getERTActiveSolutionDomains: function() {
      var domains = [];
      var useCase = hr.ESTIMATED_RESOLUTION_TIME;
      var hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration'); //configuration table
      hrAIConfiguration.addQuery('use_case', useCase);
      hrAIConfiguration.addQuery('sys_domain', '!=', 'global');
      hrAIConfiguration.addActiveQuery();
      hrAIConfiguration.query();
      while (hrAIConfiguration.next()) {
          if (hrAIConfiguration.solution_capability_definition.active)
              domains.push(hrAIConfiguration.sys_domain);
      }
      return domains;
  },

  /** 
   * Description: If solution definition corresponding to global domain Use Case is active, returns list of COEs for which ETTR display is enabled for given persona, 
   * Security requirements: none (used by HR Agent workspace, HR Agent workspace V2, and app-hr-mobile [employee HR Cases])
   * @return {Array} Array of COEs
   */
  getCOEsWhenGlobalSolutionActive: function(persona) {
      var prop = [];
      var useCase = hr.ESTIMATED_RESOLUTION_TIME;
      var hrAIConfiguration = new GlideRecord('sn_hr_core_ai_configuration'); //configuration table
      hrAIConfiguration.addQuery('use_case', useCase);
      hrAIConfiguration.addQuery('sys_domain', 'global');
      hrAIConfiguration.addActiveQuery();
      hrAIConfiguration.setLimit(1);
      hrAIConfiguration.query();
      if (hrAIConfiguration.next() && hrAIConfiguration.solution_capability_definition.active) {
          if (persona == "employee")
              prop = this.getERTEnabledCOEsForEmp();
          else if (persona == "agent")
              prop = this.getERTEnabledCOEsForAgent();
      }
      return prop;
  },

  type: "hr_CaseAjax"
});

Sys ID

b25370019f22120047a2d126c42e7000

Offical Documentation

Official Docs: