Name

sn_hr_core.hr_CaseCreation

Description

No description available

Script

var hr_CaseCreation = Class.create();
hr_CaseCreation.prototype = {
  initialize: function() {
  	this.TEXT_QUERY = "123TEXTQUERY321";
  	this.sysUserTable = "sys_user";
  	this.searchTables = ["sys_user", "sn_hr_core_profile"]; // TODO Better support extended tables (eg. u_employee extending sys_user)
  	this.priorityTable = null;
  	this.priorityColumn = null;
  	this.LEFT_TASK_FIELDS = "fields_left_task";
  	this.RIGHT_TASK_FIELDS = "fields_right_task";
  	this.CASE_CREATION_SERVICE_CONFIG_SYSID = ["f7ec702beb520300a9e7e26ac106fe56", "a1e6e28bff26201017e447cf793bf11c", "4e3ee81a23030010fb0c949e27bf65a0"];

  	// Create configuration object based on configuration record
  	var evConfigGr = new GlideRecord("sn_hr_core_config_case_creation");
  	evConfigGr.setLimit(1);
  	evConfigGr.query();
  	if (evConfigGr.next()) {
  		// Search attributes
  		this.pageSize = parseInt(evConfigGr.getValue("page_size"));
  		this.minimumInputLength = parseInt(evConfigGr.getValue("minimum_input_length")) || 2;
  		this.forcePartialSearch = evConfigGr.getValue("force_partial_search") == "1";
  		this.allowSkippingVerification = evConfigGr.getValue("allow_skipping_verification") == "1";
  		// Employee Search
  		this.userObjectTable = evConfigGr.getValue("display_table");
  		this.userSearchCondition = (evConfigGr.getValue("limit_users_on_search") == "1") ? evConfigGr.getValue("user_search_condition") : "";
  		this.userColumn = evConfigGr.getValue("user_field");
  		this.additionalDisplayFields = evConfigGr.getValue("additional_display_fields") ? evConfigGr.getValue("additional_display_fields").split(",") : [];
  		this.links = evConfigGr.getValue("links") ? evConfigGr.getValue("links").split(",") : [];
  		if(evConfigGr.getValue("search_tables")){
  			this.searchTables = evConfigGr.getValue("search_tables").split(",");
  			this.priorityTable = evConfigGr.getValue('priority_table_name');
  			this.priorityColumn = evConfigGr.getValue('priority_column');
  		}
  		// Case search
  		this.taskSearchTable = evConfigGr.getValue("task_search_table");
  		this.taskSearchCondition = (evConfigGr.getValue("limit_tasks_on_search") == "1") ? evConfigGr.getValue("task_search_condition") : "";
  		this.taskUserFields = evConfigGr.getValue("task_user_fields") ? evConfigGr.getValue("task_user_fields").split(",") : [];
  		// Employee information
  		this.fields = {
  			left_fields : evConfigGr.getValue("fields_left") ? evConfigGr.getValue("fields_left").split(",") : [],
  			right_fields : evConfigGr.getValue("fields_right") ? evConfigGr.getValue("fields_right").split(",") : []
  		};
  		// Case creation
  		this.taskCreateTable = evConfigGr.getValue("task_create_table");
  		this.serviceCondition = (evConfigGr.getValue("limit_services") == "1") ? evConfigGr.getValue("service_condition") : "";
  		this.fieldsMinimumInputLength = parseInt(evConfigGr.getValue("fields_minimum_input_length")) || 0;
  		this.taskFields = {
  			left_fields : evConfigGr.getValue("fields_left_task") ? evConfigGr.getValue("fields_left_task").split(",") : [],
  			right_fields : evConfigGr.getValue("fields_right_task") ? evConfigGr.getValue("fields_right_task").split(",") : [],
  			bottom_fields : evConfigGr.getValue("fields_bottom_task") ? evConfigGr.getValue("fields_bottom_task").split(",") : []
  		};

  	} else { // Default if record not found
  		// Search attributes
  		this.pageSize = 10;
  		this.minimumInputLength = 4;
  		this.forcePartialSearch = true;
  		this.allowSkippingVerification = true;
  		// Employee Search
  		this.userObjectTable = "sn_hr_core_profile";
  		this.userSearchCondition = "";
  		this.userColumn = "user";
  		this.additionalDisplayFields = ["user.department", "user.employee_number", "user.location"];
  		this.links = [];
  		// Case search
  		this.taskSearchTable = "sn_hr_core_case";
  		this.taskSearchCondition = "";
  		this.taskUserFields = ["subject_person", "opened_for", "opened_by", "watch_list"];
  		// Employee information
  		this.fields = {
  			left_fields : ["user.name", "user.employee_number"],
  			right_fields : ["user.email", "user.zip"]
  		};
  		// Case creation
  		this.taskCreateTable = "sn_hr_core_case";
  		this.serviceCondition = "";
  		this.fieldsMinimumInputLength = 0;
  		this.taskFields = {
  			left_fields : ["opened_for"],
  			right_fields : ["subject_person"],
  			bottom_fields : ["work_notes"]
  		};
  	}

  	this.taskPrefixes = [];
  	var sysNumberGr = new GlideRecord("sys_number");
  	sysNumberGr.addQuery("category", "INSTANCEOF", this.taskSearchTable);
  	sysNumberGr.query();
  	while (sysNumberGr.next())
  		this.taskPrefixes.push(sysNumberGr.getValue("prefix"));
  },

  /**
  * Search for an employee by key words
  * @param searchTermParam String Search term input
  * @param searchPage number Search page used for pagination (starts at 0)
  * @param searchTable String (optional) Table name used for pagination
  * @return Object
  *    Example:
  * {
  *    list : Array
  *        Either an array of Objects from @function getUserObject and irQueryScore for table searches OR
  *        Objects for task searches, Example:
  *            {
  *                display : String Task display value,
  *                users : array of Objects from @function getUserObject,
  *                irQueryScore: integer representing search relevance (high score represents more relevance)
  *            }
  *    total : number The total number of search results (can be more than results returned),
  *    table : String Table name to use for pagination
  * }
  */
  search: function(searchTermParam, searchPage, searchTable) {
  	var list = [];
  	var total = 0;
  	var table = "";
  	var MAX_QUERY_SCORE = 999;

  	// Parse search terms
  	var taskTerms = [];
  	var searchTerms = [];
  	// Example: ^(HRC|HRT)[0-9]+$
  	var taskRegex = "^(" + this.taskPrefixes.join("|") + ")[0-9]+$";
  	var searchTermParams = searchTermParam.split(" ");
  	for (var i = 0; i < searchTermParams.length; i++)
  		if (searchTermParams[i].match(taskRegex, "i"))
  			taskTerms.push(searchTermParams[i]);
  		else if (searchTermParams[i])
  			searchTerms.push(this.appendPartialSearch(searchTermParams[i]));

  	var taskTerm = taskTerms.join(" | ");
  	var searchTerm = searchTerms.join(" ");

  	// TODO This prevents searching users if task term is found, but what if user meant to search users?
  	// Search tasks only if we have task search terms
  	if (taskTerm) {
  		var userTables = new GlideTableHierarchy("sys_user").getAllExtensions();
  		var userObjectTables = new GlideTableHierarchy(this.userObjectTable).getAllExtensions();
  		// Search for relevant tasks
  		var taskSearchGr = new GlideRecord(this.taskSearchTable);
  		taskSearchGr.addQuery(this.TEXT_QUERY, taskTerm);
  		if (searchTerm)
  			taskSearchGr.addQuery(this.TEXT_QUERY, searchTerm);
  		if (this.taskSearchCondition)
  			taskSearchGr.addEncodedQuery(this.taskSearchCondition);
  		taskSearchGr.chooseWindow(searchPage * this.pageSize, (searchPage * this.pageSize) + this.pageSize);
  		taskSearchGr.orderBy("ir_query_score");
  		taskSearchGr.query();
  		while (taskSearchGr.next()) {
  			if (!taskSearchGr.canRead())
  				continue;
  			var taskObject = {
  				displayValue: taskSearchGr.getDisplayValue(),
  				table: taskSearchGr.getRecordClassName(),
  				reference: taskSearchGr.getRecordClassName(),
  				value: taskSearchGr.getUniqueValue(),
  				label: taskSearchGr.getClassDisplayValue(),
  				column_name: "task_object",
  				internal_type: "reference",
  				type: "reference",
  				tooltip: gs.getMessage("Preview record for field: {0}", taskSearchGr.getClassDisplayValue())
  			};
  			// Find all relevant users on a given task
  			var taskUserList = [];
  			var taskUserMap = {};

  			for (var j = 0; j < this.taskUserFields.length; j++) {
  				var element = taskSearchGr.getElement(this.taskUserFields[j]);
  				if (element == null || element.toString() == null || !element.canRead())
  					continue;
  				var eleEd = element.getED();
  				var eleInternalType = eleEd.getInternalType();
  				if (eleInternalType == "reference" || eleInternalType == "glide_list") {
  					var eleTable = eleEd.getReference();
  					// Verify eleTable is a sys_user or extension, or a this.userObjectTable or extension
  					if (!eleTable || (userTables.indexOf(eleTable) == -1 && userObjectTables.indexOf(eleTable) == -1))
  						continue;
  					var elementTableGr = new GlideRecordSecure(eleTable);
  					elementTableGr.addQuery("sys_id", "IN", element.toString());
  					if (searchTerm)
  						elementTableGr.addQuery(this.TEXT_QUERY, searchTerm);
  					elementTableGr.setLimit(100); // Limit results just in case
  					elementTableGr.orderBy(elementTableGr.getDisplayName());
  					elementTableGr.query();
  					while (elementTableGr.next()) {
  						// Skip this record if it was already added (doesn't work for non sys_user references)
  						if (taskUserMap.hasOwnProperty(elementTableGr.getUniqueValue())) {
  							taskUserMap[elementTableGr.getUniqueValue()].display_suffix.push(element.getLabel());
  							continue;
  						}
  						var userElementObject = this.getUserObject(elementTableGr);
  						if (userElementObject) {
  							// This second check is needed if the element does not reference sys_user
  							if (taskUserMap.hasOwnProperty(userElementObject.sys_id)) {
  								taskUserMap[userElementObject.sys_id].display_suffix.push(element.getLabel());
  								continue;
  							}
  							userElementObject.display_suffix = [element.getLabel()];
  							userElementObject.task = taskObject;
  							taskUserMap[userElementObject.sys_id] = userElementObject;
  							taskUserList.push(userElementObject);
  						}
  					}
  				}
  			}
  			if (taskUserList.length > 0) {
  				list.push({
  					display: taskSearchGr.getDisplayValue(),
  					users: taskUserList,
  					irQueryScore: taskSearchGr.getValue('ir_query_score')
  				});
  			}
  		}
  		table = this.taskSearchTable;
  		total = taskSearchGr.getRowCount();

  	// Search user tables
  	// Search this.searchTables, only returning results from one table failing over to the next when no results are found
  	} else {
  		// Skip to requested table if provided
  		var k = 0;
  		if (searchTable)
  			k = this.searchTables.indexOf(searchTable);
  		var priorityTotal = 0;
  		var remainingCount = 0;
  		var startOffset = 0;
  		var endOffset = 0;
  		var remainder = 0;
  		var fieldName = this.priorityColumn;
  		
  		var forcePartialSearchWithPriority = (this.priorityColumn != null) && this.forcePartialSearch; 
  		var forcePartialSearchWithoutPriority = (this.priorityColumn == null) && this.forcePartialSearch;

  		// If priority table is used. Move priority table to the first position in search tables
  		if(forcePartialSearchWithPriority) {
  			for(var idx = 0; idx < this.searchTables.length; idx++){
  				if(this.searchTables[idx] == this.priorityTable){
  					var priorityTable = this.searchTables.splice(idx,1);
  					this.searchTables.unshift(priorityTable[0]);
  				}
  			}
  		}

  		for (/* k is instantiated above */; k > -1 && k < this.searchTables.length && list.length == 0 && total == 0 && (!searchTable || searchTable == this.searchTables[k]); k++) {
  			var columnName = this.priorityColumn;
  			if (forcePartialSearchWithPriority && this.searchTables[k] == this.priorityTable) {
  				var priorityColumnGr = new GlideRecordSecure(this.searchTables[k]);
  				var encodedQueryString = columnName + "STARTSWITH" + searchTermParam;
  				priorityColumnGr.addEncodedQuery(encodedQueryString);
  				if (this.userSearchCondition)
  					priorityColumnGr.addEncodedQuery(this.getUserQuery(this.userSearchCondition, this.searchTables[k]));
  				
  				priorityColumnGr.chooseWindow(searchPage * this.pageSize, (searchPage * this.pageSize) + this.pageSize);
  				priorityColumnGr.query();
  				priorityTotal = priorityColumnGr.getRowCount();
  				while (priorityColumnGr.next()) {
  					var userObject = this.getUserObject(priorityColumnGr);
  					userObject.irQueryScore = MAX_QUERY_SCORE;
  					if (userObject && userObject.display)
  						list.push(userObject);
  				}
  				if (list.length < this.pageSize) 
  					remainingCount = this.pageSize - list.length;
  				
  			}
  			if (priorityTotal > 0 && this.searchTables[k] == this.priorityTable) { // prioirty search has results
  				remainder = priorityTotal % this.pageSize;
  				startOffset = Math.floor((priorityTotal-remainder)/this.pageSize) * this.pageSize;
  				endOffset = startOffset;
  				if (list.length == 0) // prioirty search results are exhausted
  					startOffset += remainder;
  			}
  			// Zing search should be performed when:
  			//     ForcePartialSearch is false OR
  			//     Results from priority search are NOT enough for a page OR
  			//     ForcePartialSearch selected with None option
  			if (remainingCount > 0 || ! this.forcePartialSearch || forcePartialSearchWithoutPriority) {

  				// Search table using search input as key word search
  				var searchGr = new GlideRecordSecure(this.searchTables[k]);
  				searchGr.addQuery(this.TEXT_QUERY, searchTerm);

  				if (forcePartialSearchWithPriority && this.searchTables[k] == this.priorityTable)
  					searchGr.addQuery(columnName, 'NOT LIKE', searchTermParam+"%");
  				
  				if (this.userSearchCondition)
  					searchGr.addEncodedQuery(this.getUserQuery(this.userSearchCondition, this.searchTables[k]));
  				
  				var window = {
  					"start": searchPage * this.pageSize - startOffset,
  					"end": (searchPage * this.pageSize) + this.pageSize - remainder - endOffset
  				};
  				
  				searchGr.chooseWindow(window.start, window.end);
  				searchGr.orderBy("ir_query_score");
  				searchGr.query();
  				total = searchGr.getRowCount();
  				table = this.searchTables[k];
  				while (searchGr.next()) {
  					var userObject = this.getUserObject(searchGr);
  					userObject.irQueryScore = searchGr.getValue('ir_query_score');
  					if (userObject && userObject.display)
  						list.push(userObject);
  				}
  			} else { // Total calculation for pagination.
  				var searchGr = new GlideRecordSecure(this.searchTables[k]);
  				searchGr.addQuery(this.TEXT_QUERY, searchTerm);
  				if (forcePartialSearchWithPriority && this.searchTables[k] == this.priorityTable)
  					searchGr.addQuery(columnName, 'NOT LIKE', searchTermParam+"%");
  				if (this.userSearchCondition)
  					searchGr.addEncodedQuery(this.getUserQuery(this.userSearchCondition, this.searchTables[k]));
  				searchGr.query();
  				total = searchGr.getRowCount();
  			}
  			
  			// If search table is provided, don't failover to next table
  			if (searchTable)
  				break;
  		}
  	}
  	return {
  		list: list,
  		total: total + priorityTotal,
  		table: table
  	};
  
  },

  //Remove fields user cannot write to
  removeFieldsUserCannotWrite: function(tableGr, taskFields) {

  	for (var keyList in taskFields)
  		for (var k = taskFields[keyList].length - 1; k > -1; k--) {
  			var ele = tableGr.getElement(taskFields[keyList][k].column_name);
  			if (ele == null || !ele.canRead() || !ele.canCreate()) // canCreate controls write access for new records
  				taskFields[keyList].splice(k, 1);
  		}
  },

  /**
  * Force a search term to allow partial matching
  * @param searchTerm String Search term to force partial searching on
  * @return String Partialized search term
  */
  appendPartialSearch: function(searchTerm) {
  	if (this.forcePartialSearch
  		&& searchTerm.indexOf("*") == -1
  		&& searchTerm.indexOf('"') == -1
  		&& searchTerm.indexOf("'") != 0 // Not the first character
  		&& searchTerm.indexOf("'") != (searchTerm.length - 1) // Not the last character
  		&& searchTerm != "AND"
  		&& searchTerm != "OR"
  		&& searchTerm != "|")
  		return searchTerm += "*";

  	return searchTerm;
  },

  /**
  * Adapt an encoded query to work when querying this.userObjectTable instead of sys_user table
  * Example: For @param encodedQuery of "active=true^ORmarital_status=single"
  *     return "user.active=true^ORuser.marital_status=single" for sn_hr_core_profile
  *     return "active=true^ORmarital_status=single" for sys_user
  * @param encodedQuery String Field name to parse
  * @param tableName String table name to query on
  * @return String Parsed encoded query
  */
  getUserQuery: function(encodedQuery, tableName) {
  	if (!encodedQuery || tableName != this.userObjectTable || !this.userColumn)
  		return encodedQuery;

  	if (encodedQuery.endsWith("^EQ"))
  		encodedQuery = encodedQuery.substring(0, encodedQuery.length - 3);

  	var queries = encodedQuery.split("^NQ");
  	var queriesRet = [];
  	for (var i = 0; i < queries.length; i++) {
  		var orConditions = queries[i].split("^OR");
  		var orConditionsRet = [];
  		for (var j = 0; j < orConditions.length; j++) {
  			var andConditions = orConditions[j].split("^");
  			var andConditionsRet = [];
  			for (var k = 0; k < andConditions.length; k++)
  				andConditionsRet.push(this.userColumn + "." + andConditions[k]);
  			if (andConditionsRet.length > 0)
  				orConditionsRet.push(andConditionsRet.join("^"));
  		}
  		if (orConditionsRet.length > 0)
  			queriesRet.push(orConditionsRet.join("^OR"));
  	}

  	return queriesRet.join("^NQ");
  },

  /**
  * Return a user object for a given GlideRecord
  * @param record GlideRecord to use as basis for user object
  * @return Object
  *    Example:
  * {
  *    sys_id : sys_id of a user,
  *    display : display value of a user,
  *    table: reference table of user field, or table of the user record,
  *    has_ev_record: boolean representing if Employee Verification record exists,
  *    ev_sys_id : sys_id of employee verification record,
  *    ev_table : table of the employee verification record,
  *    tooltip : display value for tooltip on reference icon,
  *    active: boolean if user is active,
  *    additional_display_fields : (optional) array of field display values,
  *    left_fields : (optional) array from @function getFieldObjects,
  *    right_fields : (optional) array from @function getFieldObjects
  * }
  */
  getUserObject: function(record) {
  	// Attempt to find the evConfig table's record for the passed in record
  	var hasEVRecord = false; // Failover if not found to parsing out the user fields on evconfig
  	var recordTable = record.getTableName();
  	if (recordTable != this.userObjectTable) {
  		var userObjectTableGr = new GlideRecord(this.userObjectTable);
  		if (this.userColumn && userObjectTableGr.isValidField(this.userColumn) && userObjectTableGr.get(this.userColumn, record.getUniqueValue())) {
  			record = userObjectTableGr;
  			hasEVRecord = true;
  		}
  	} else
  		hasEVRecord = true;

  	var userObject = {
  		sys_id: (hasEVRecord && this.userColumn) ? record.getValue(this.userColumn) : record.getUniqueValue(),
  		display: (hasEVRecord && this.userColumn) ? record.getElement(this.userColumn).getDisplayValue() : record.getDisplayValue(),
  		table: (hasEVRecord && this.userColumn && record.getElement(this.userColumn).getED().getInternalType() == "reference") ? record.getElement(this.userColumn).getReferenceTable() : record.getTableName(),
  		has_ev_record: hasEVRecord,
  		ev_sys_id: (hasEVRecord) ? record.getUniqueValue() : '',
  		ev_table: (hasEVRecord) ? record.getTableName() : '',
  		tooltip: gs.getMessage("Preview record for field: {0}", (hasEVRecord && this.userColumn && record.getElement(this.userColumn) != null) ? record.getElement(this.userColumn).getLabel() : record.getClassDisplayValue())
  	};

  	if (this.links.length && userObject.ev_sys_id && userObject.ev_table) {
  		userObject.links = [];
  		for (var j = 0; j < this.links.length; j++) {
  			var linkGr = new GlideRecord("link_generator_mapping");
  			if (linkGr.get(this.links[j]))
  				userObject.links.push({url:'', btnName:linkGr.getDisplayValue('btn_name')});
  		}
  	}

  	// Add "active" property
  	var userPrefix = (hasEVRecord && this.userColumn) ? (this.userColumn + ".") : "";
  	var activeElement = record.getElement(userPrefix + "active");
  	if (activeElement != null && activeElement.toString() != null && activeElement.toString() !== "true")
  		userObject.active = activeElement.toString() === "true";

  	// Add display values for additional display fields
  	if (this.additionalDisplayFields && this.additionalDisplayFields.length) {
  		var additionalDisplayFieldsArr = [];
  		for (var i = 0; i < this.additionalDisplayFields.length; i++) {
  			var fieldName = this.getUserField(this.additionalDisplayFields[i], hasEVRecord);
  			var fieldElement = record.getElement(fieldName);
  			if (fieldElement != null && fieldElement.toString() !== null && fieldElement.canRead())
  				additionalDisplayFieldsArr.push(fieldElement.getDisplayValue());
  		}
  		if (additionalDisplayFieldsArr.length)
  			userObject.additional_display_fields = additionalDisplayFieldsArr;
  	}

  	this._fillWorkspaceUserFields(record, userPrefix, userObject);

  	// Add all field lists to userObject
  	for (var key in this.fields)
  		// Skip mandatory check on EV fields for performance
  		userObject[key] = this.getFieldObjects(record, this.fields[key], hasEVRecord, null, true);

  	return userObject;
  },

  _fillWorkspaceUserFields: function(record, userPrefix, userObject) {
  	var workspaceUserFields = ['avatar', 'vip', 'active'];
  	var workspaceUserValues = {};
  	for (var i = 0; i < workspaceUserFields.length; i++) {

  		var fieldName = userPrefix + workspaceUserFields[i];
  		var fieldElement = record.getElement(fieldName);

  		if (fieldElement !== null && fieldElement.toString() !== null && fieldElement.canRead())
  			workspaceUserValues[workspaceUserFields[i]] = fieldElement.getDisplayValue();
  	}
  	userObject.workspaceUserValues = workspaceUserValues;
  },

  /**
  * Parse a field name to retrieve a dot walked column when a user does not have a record in the specified EV Config table
  * Example: For @param fieldName of "user.employee_number", parse out "employee_number"
  * @param fieldName String Field name to parse
  * @param hasEVRecord (optional) boolean Employee Verification record exists
  * @return String Parsed field name
  */
  getUserField: function(fieldName, hasEVRecord) {
  	if (hasEVRecord || !fieldName)
  		return fieldName;
  	if (fieldName.startsWith(this.userColumn + "."))
  		return fieldName.slice(this.userColumn.length + 1); // Attempt to directly grab the user field

  	return null;
  },

  /**
  * Call @function getFieldObject on an array of field names
  * @param record GlideRecord to use in @function getFieldObject
  * @param fieldList array Array of field names to use in @function getFieldObject
  * @param hasEVRecord boolean Employee Verification record exists for record
  * @param fieldValues - Object - (optional) Object of field-value pairs for the @param record
  * @param skipMandatoryCheck boolean Skip mandatory check on fields
  * @return array of field objects produced from @function getFieldObject
  */
  getFieldObjects: function(record, fieldList, hasEVRecord, fieldValues, skipMandatoryCheck) {
  	// Default field values for non-existent record; attempt to reduce skipping of task numbers
  	if (record && record.isValid() && !record.isValidRecord() && !record.getElement("number").toString()) {
  		record.newRecord();
  		this.setInitialCaseFields(record);
  		// Setting fields for _isFieldMandatory function
  		if (fieldValues)
  			for (var key in fieldValues) {
  				if (fieldValues[key] != null && record.isValidField(key))
  					record.setValue(key, fieldValues[key]);
  			}
  	}

  	//Table hierarchy for field's mandatory check
  	var tableHierarchy = new GlideTableHierarchy(record.getRecordClassName()).getTables();

  	var fields = [];
  	for (var i = 0; i < fieldList.length; i++) {
  		var field = this.getFieldObject(record, this.getUserField(fieldList[i], hasEVRecord), tableHierarchy, skipMandatoryCheck);
  		if (field)
  			fields.push(field);
  	}

  	return fields;
  },

  /**
  * Return a field object for a given GlideRecord and field name
  * @param record GlideRecord to create field object from
  * @param fieldName String Field name to create a field object for
  * @param tableHierarchy Array list of table hierarchy
  * @param skipMandatoryCheck boolean Skip mandatory check on field
  *    Example:
  * @return Object
  * {
  *    display : display value of a field,
  *    label : label of a field,
  *    value : value of a field,
  *    column_name : column name of a field,
  *    max_length : max length of a field,
  *    internal_type : internal type of a field,
  *    type : internal type of a field (used for DEO angular directive)
  *	 referringRecordId : sys_id of record from where field object is created
  * }
  */
  getFieldObject: function(record, fieldName, tableHierarchy, skipMandatoryCheck) {
  	var element = record.getElement(fieldName);
  	if (element == null || element.toString() == null || !element.canRead())
  		return null;

  	var eleEd = element.getED();

  	//verify table hierarchy is set
  	if (!tableHierarchy)
  		tableHierarchy = new GlideTableHierarchy(record.getRecordClassName()).getTables();

  	var fieldObject = {
  		display: element.getDisplayValue(),
  		label: element.getLabel(),
  		value: element.toString(),
  		column_name: fieldName,
  		max_length: eleEd.getLength(),
  		minimumInputLength: this.fieldsMinimumInputLength,
  		internal_type: eleEd.getInternalType(),
  		type: eleEd.getInternalType(),
  		hint: eleEd.getHint(),
  		mandatory: skipMandatoryCheck ? false : this.isFieldMandatory(record, fieldName, tableHierarchy),
  		referringRecordId: record.getUniqueValue(),
  		referringTable: record.getTableName()
  	};
  	if (eleEd.getInternalType() == "reference")
  		this._addReferenceProperties(fieldObject, element, record.getTableName());
  	else if (eleEd.getInternalType() == "glide_list")
  		this._addGlideListProperties(fieldObject, element, record.getTableName());
  	else if (["currency", "price"].indexOf(eleEd.getInternalType()) > -1)
  		this._addCurrencyProperties(fieldObject, element, record.getTableName());
  	else if (eleEd.isChoiceTable())
  		this._addChoiceProperties(fieldObject, element, record.getTableName());

  	return fieldObject;
  },


  /**
   * @Returns boolean:
   *     true if field is set mandatory by,
   *	     1. Table schema
   *       2. Dictionary override
   *	     3. Data Policy
   *	     4. UI Policy
   *     false otherwise
   *
   * NOTE: Client scripts and scripted UI policies are ignored
   *
   * @Params:
   *   record: Gliderecord where the field is defined
   *   fieldName: String value of field name
   *   tableHierarchy: Array list of table hierarchy
   **/
  isFieldMandatory: function(record, fieldName, tableHierarchy) {
      // Check table schema
      var gr = new GlideRecord('sys_dictionary');
      gr.addQuery('name', 'IN', tableHierarchy);
      gr.addQuery('element', fieldName);
      gr.addQuery('mandatory', true);
      gr.query();
      if (gr.next())
          return true;

  	//Check dictionary overrides
  	if (this._isFieldSetMandatoryByDictionaryOverride(record, fieldName, tableHierarchy))
  		return true;

      // Check Data Policies
      if (this._isFieldSetMandatoryByDataPolicy(record, fieldName, tableHierarchy))
          return true;

  	// Check UI Policies
      if (this._isFieldSetMandatoryByUIPolicy(record, fieldName, tableHierarchy))
          return true;

  	return false;
  },

  /**
   * @Returns boolean:
   *     true if field is set mandatory using dictionary override
   *     false otherwise
   *
   * @Params:
   *   fieldName: String value of field name
   *   tableHierarchy: Array list of table hierarchy
   **/
  _isFieldSetMandatoryByDictionaryOverride: function(record, fieldName, tableHierarchy) {
  	var isMandatorySetForRecordTable = false;
  	var mandatorySetOnRecordTable = false;
  	var mandatorySetOnTableHierarchy = false;

  	var gr = new GlideRecord('sys_dictionary_override');
      gr.addQuery('name', 'IN', tableHierarchy);
      gr.addQuery('element', fieldName);
      gr.addQuery('mandatory_override', true);
      gr.query();

  	while(gr.next()) {
  		var mandatory = gr.mandatory.toString();

  		//use field mandatory override set on record table
  		if (gr.name.toString() == record.getRecordClassName()) {
  			isMandatorySetForRecordTable = true;
  			mandatorySetOnRecordTable = (mandatory == 'true');
  		}
  		//OR use mandatory override set on table hierarchy
  		else if (mandatory == 'true')
  			mandatorySetOnTableHierarchy = true;
  	}

  	return isMandatorySetForRecordTable ? mandatorySetOnRecordTable : mandatorySetOnTableHierarchy;
  },

  /**
   * @Returns boolean:
   *     true if field is set mandatory using data policy
   *     false otherwise
   *
   * @Params:
   *   record: Gliderecord where the field is defined
   *   fieldName: String value of field name
   *   tableHierarchy: Array list of table hierarchy
   **/
  _isFieldSetMandatoryByDataPolicy: function(record, fieldName, tableHierarchy) {
  	var dataPolicies = {};
  	var recordTable = record.getRecordClassName();

  	var gr = new GlideRecord('sys_data_policy_rule');
  	gr.addQuery('table', 'IN', tableHierarchy);
  	gr.addQuery('sys_data_policy.active', 'true');
  	gr.addQuery('field', fieldName);
  	gr.addQuery('mandatory', '!=', 'ignore');
  	gr.query();

  	while (gr.next()){
  		var table = gr.table.toString();
  		var inheritPolicy = (gr.sys_data_policy.inherit.toString() == 'true');
  		var reverseIfFalse = (gr.sys_data_policy.reverse_if_false.toString() == 'true');
  		var conditions = gr.sys_data_policy.conditions.toString();
  		var mandatory = (gr.mandatory.toString() == 'true');

  		//data policy for the table
  		if (table == recordTable)
  			dataPolicies[table] = this._validatePolicyCondition(mandatory, record, conditions, reverseIfFalse);
  		//inherited data policies
  		else if (inheritPolicy)
  			dataPolicies[table] = this._validatePolicyCondition(mandatory, record, conditions, reverseIfFalse);
  	}

  	return this._mandatorySetByDataPolicy(dataPolicies, recordTable);
  },

  _mandatorySetByDataPolicy : function(dataPolicies, curTable) {
  	var baseTable = new GlideTableHierarchy(curTable).getBase();

  	if (typeof dataPolicies[curTable] !== 'undefined')
  		return dataPolicies[curTable];
  	else if (curTable != baseTable)
  		return this._mandatorySetByDataPolicy(dataPolicies, baseTable);
  	else
  		return false;
  },

  _validatePolicyCondition: function(mandatory, record, conditions, reverseIfFalse) {
  	var policyConditionMatched = ScopedGlideFilter.checkRecord(record, conditions);

  	if (policyConditionMatched)
  		return mandatory;
  	else
  		return (reverseIfFalse && !mandatory);
  },

  /**
   * @Returns boolean:
   *     true if field is set mandatory using UI policy
   *     false otherwise
   *
    * NOTE: UI policies are evaluated in an ascending order
   *
   * @Params:
   *   record: Gliderecord where the field is defined
   *   fieldName: String value of field name
   *   tableHierarchy: Array list of table hierarchy
   **/
  _isFieldSetMandatoryByUIPolicy: function(record, fieldName, tableHierarchy) {
  	var recordTable = record.getRecordClassName();

  	var gr = new GlideRecord('sys_ui_policy_action');
  	gr.addQuery('table', 'IN', tableHierarchy);
  	gr.addQuery('ui_policy.active', 'true');
  	gr.addQuery('ui_policy.global', 'true');
  	gr.addQuery('field', fieldName);
  	gr.addQuery('mandatory', '!=', 'ignore');
  	gr.orderByDesc('ui_policy.order');
  	gr.query();

  	while (gr.next()){
  		var isRecordTable = (gr.table.toString() == recordTable);
  		var inheritPolicy = (gr.ui_policy.inherit.toString() == 'true');

  		if (isRecordTable || inheritPolicy) {
  			var reverseIfFalse = (gr.ui_policy.reverse_if_false.toString() == 'true');
  			var conditions = gr.ui_policy.conditions.toString();
  			var mandatory = (gr.mandatory.toString() == 'true');

  			return this._validatePolicyCondition(mandatory, record, conditions, reverseIfFalse);
  		}
  	}

  	return false;
  },

  /**
  * Add reference specific properties to a given Object from @function getFieldObject
  * @param fieldObject Object Field object from @function getFieldObject
  * @param element GlideElement Field element
  * @param tableName String Table name to get qualifier for
  * Adds properties:
  *    reference : table name for a reference field,
  *    qualifier : reference qualifier for a reference field,
  *    displayValue : display value for a reference field,
  *	 refTable : string value of table name where field is,
  *    tooltip : display value for tooltip on reference icon,
  *    external_space_id : (optional) space id used for showing floor plan,
  *    external_level_id : (optional) level id used for showing floor plan,
  *    external_building_id : (optional) building id used for showing floor plan,
  *    campus_sys_id : (optional) campus sys_id used for showing floor plan
  */
  _addReferenceProperties: function(fieldObject, element, tableName) {
  	fieldObject.reference = element.getReferenceTable();
  	var qualifier = element.getED().getReferenceQualifier();
  	if (qualifier && !qualifier.startsWith("javascript:"))
  		fieldObject.qualifier = qualifier;
  	fieldObject.displayValue = element.getDisplayValue();
  	fieldObject.refTable = element.getTableName();
  	fieldObject.refId = -1;

  	var eleRecord = new GlideRecord(fieldObject.reference);
  	if (!eleRecord.isValid())
  		return;

  	fieldObject.searchField = eleRecord.getDisplayName();
  	fieldObject.tooltip = gs.getMessage("Preview record for field: {0}", element.getLabel());

  	// Floor Plan specific properties
  	if (element.external_space_id) {
  		fieldObject.external_space_id = element.external_space_id.toString();
  		fieldObject.external_level_id = element.floor.external_level_id.toString();
  		fieldObject.external_building_id = element.building.external_building_id.toString();
  		fieldObject.campus_sys_id = element.building.campus.toString();
  	}
  },

  /**
  * Add glide_list specific properties to a given Object from @function getFieldObject
  * @param fieldObject Object Field object from @function getFieldObject
  * @param element GlideElement Field element
  * @param tableName String Table name to get qualifier for
  * Adds properties:
  *    reference : table name for a glide_list field,
  *    qualifier : reference qualifier for a glide_list field,
  *    displayValue : display value for a glide_list field,
  *    display : display for a glide_list field,
  *    tooltip : display value for tooltip on reference icon,
  *    selectedOptions : Array of glide_list choice objects,
  *        Example:
  *            [{
  *                value: choice value,
  *                label: choice label
  *            }]
  */
  _addGlideListProperties: function(fieldObject, element, tableName) {
  	var eleEd = element.getED();

  	fieldObject.reference = eleEd.getReference();
  	var qualifier = eleEd.getReferenceQualifier();
  	if (qualifier && !qualifier.startsWith("javascript:"))
  		fieldObject.qualifier = qualifier;
  	fieldObject.displayValue = "";
  	fieldObject.display = "";
  	fieldObject.selectedOptions = [];

  	var eleRecord = new GlideRecord(fieldObject.reference);
  	if (!eleRecord.isValid())
  		return;

  	fieldObject.searchField = eleRecord.getDisplayName();
  	fieldObject.tooltip = gs.getMessage("Preview record for field: {0}", element.getLabel());

  	var values = fieldObject.value.split(",");
  	for (var i = 0; i < values.length; i++)
  		if (eleRecord.get(values[i])) {
  			fieldObject.selectedOptions.push({
  				value : eleRecord.getUniqueValue(),
  				label : eleRecord.getDisplayValue()
  			});
  		}
  	fieldObject.value = fieldObject.value.length > 0 ? fieldObject.value : '';
  },

  /**
  * Add currency specific properties to a given Object from @function getFieldObject
  * @param fieldObject Object Field object from @function getFieldObject
  * @param element GlideElement Field element
  * @param tableName String Table name to get qualifier for
  * Adds properties:
  *    currencyCodes : Array of currency code objects,
  *        Example:
  *            [{
  *                code: String currency code (e.g. USD),
  *                symbol: String currency symbol (e.g. $)
  *            }]
  *    currencyCode : String currency code (e.g. USD),
  *    currencyValue : String currency value (e.g. 123.69)
  */
  _addCurrencyProperties: function(fieldObject, element, tableName) {
  	// Get properties for single currency mode
  	if (!this.hasOwnProperty("i18n_single_currency")) {
  		this.i18n_single_currency = gs.getProperty("glide.i18n.single_currency") === "true";
  		if (this.i18n_single_currency)
  			this.i18n_single_currency_code = gs.getProperty("glide.i18n.single_currency.code");
  	}

  	fieldObject.currencyCodes = [];

  	var currenciesGr = new GlideRecord("fx_currency");
  	if (this.i18n_single_currency)
  		currenciesGr.addQuery("code", this.i18n_single_currency_code);
  	else {
  		currenciesGr.addActiveQuery();
  		currenciesGr.orderBy("symbol");
  	}
  	currenciesGr.query();
  	while (currenciesGr.next())
  		fieldObject.currencyCodes.push({ code : currenciesGr.getValue("code"), symbol : currenciesGr.getValue("symbol") });

  	// Get the session code and value when single currency or no value set, otherwise get the set values
  	if (this.i18n_single_currency || element.toString() == "0" || !element.toString()) {
  		fieldObject.currencyCode = element.getSessionCurrencyCode();
  		fieldObject.currencyValue = element.getSessionValue();
  	} else {
  		fieldObject.currencyCode = element.getCurrencyCode();
  		fieldObject.currencyValue = element.getCurrencyValue();
  	}

      // Add the user's currency code to the currency list if it is not there already
  	for (var i = 0; i < fieldObject.currencyCodes.length; i++)
  		if (fieldObject.currencyCodes[i].code === fieldObject.currencyCode)
  			return;

  	fieldObject.currencyCodes.push({ code : fieldObject.currencyCode, symbol: fieldObject.currencyCode });
  },

  /**
  * Add choice specific properties to a given Object from @function getFieldObject
  * @param fieldObject Object Field object from @function getFieldObject
  * @param element GlideElement Field element to get choices for
  * @param tableName String Table name to get choices from
  * Adds property:
  *    choiceList : Array of choice objects,
  *        Example:
  *            [{
  *                value: choice value,
  *                label: choice label
  *            }]
  */
  _addChoiceProperties: function(fieldObject, element, tableName) {
  	var choiceTable = element.getTableName();
  	if (new GlideTableHierarchy(choiceTable).getTableExtensions().indexOf(tableName) > -1)
  		choiceTable = tableName;
  	var choiceList = GlideChoiceList.getChoiceList(choiceTable, element.getName());
  	choiceList.addNone();
  	fieldObject.choiceList = [];
  	for (var i = 0; i < choiceList.getSize(); i++) {
  		var choice = choiceList.getChoice(i);
  		fieldObject.choiceList.push({
  			value: choice.getValue(),
  			label: choice.getLabel()
  		});
  	}
  },

  /**
  * Return all active COE tables
  * Used for select2 COE selector
  * @return Array of Objects,
  *     Example:
  * [{
  *     display: String display value of Topic Category for HR Services,
  *     table: Array of children HR Service objects
  * }]
  */
  getActiveCoes: function() {
  	var coes = [];
  	var inactiveTables = gs.getProperty("sn_hr_core.inactive_tables", "").split(",");
  	var coesWithService = this._getCoesWithService();
  	var caseTables = new GlideTableHierarchy("sn_hr_core_case").getAllExtensions();

  	for (var i = 0; i < caseTables.length; i++) {
  		// Skip inactive tables
  		if (inactiveTables.indexOf(caseTables[i]) != -1 || !coesWithService[caseTables[i]])
  			continue;
  		var tableGr = new GlideRecord(caseTables[i]);

  		// ACL allows users to create ER case through Service Portal,
  		// however we want to limit create access on case creation pages
  		if (tableGr.getTableName() === 'sn_hr_er_case' && !gs.hasRole('sn_hr_er.case_writer'))
  			continue;

  		this.setInitialCaseFields(tableGr);
  		if (tableGr.isValid() && tableGr.canRead() && tableGr.canCreate())
  			coes.push({
  				display: new GlideRecord(caseTables[i]).getClassDisplayValue(),
  				table: caseTables[i].toString()
  			});
  	}

  	coes.sort(function(a, b) {
  		var aDisplay = a.display.toLowerCase();
  		var bDisplay = b.display.toLowerCase();
  		if (aDisplay > bDisplay)
  			return 1;
  		else if (bDisplay > aDisplay)
  			return -1;
  		return 0;
  	});

  	return coes;
  },

  /**
  * Returns a map of all COEs with any available HR Service
  * @return Object,
  *     Example: { 'HR_SERVICE_SYS_ID' : true }
  */
  _getCoesWithService: function() {
  	var coes = {};
  	var hrServices = new GlideAggregate('sn_hr_core_service');
  	if (this.serviceCondition)
  		hrServices.addEncodedQuery(this.serviceCondition);
  	hrServices.addNotNullQuery('topic_detail.topic_category.coe');
  	hrServices.addQuery('sys_id', 'NOT IN', hr.EXCLUDED_SERVICES);
  	hrServices.groupBy('topic_detail.topic_category.coe');
  	hrServices.query();
  	while(hrServices.next())
  		coes[hrServices.getValue('topic_detail.topic_category.coe')] = true;

  	return coes;
  },

  setInitialCaseFields: function(caseGr) {
  	caseGr.setValue('opened_by', gs.getUserID());
  },

  /**
  * Return the HR Services for a user, filtered by HR Criteria
  * Used for select2 HR Service selector for a single user
  * @param userSysId String sys_id of the user to evaluate criteria for
  * @param ignoreServiceCondition boolean Ignore this.serviceCondition when querying services
  * @return Array of Objects,
  *     Example:
  * [{
  *     display: String display value of Topic Category for HR Services,
  *     coe: COE for the service,
  *     children: Array of children HR Service objects
  *         Example:
  *             [{
  *                 display: String display value of HR Service,
  *                 sys_id: String sys_id of HR Service,
  *                 coe: COE for the service,
  *                 template: String sys_id of HR Service's template,
  *                 parent: String Display value of HR Service's Topic Category
  *             }]
  * }]
  */
  getServicesForUser: function(userSysId, ignoreServiceCondition) {
  	// Get the 'active' COEs
  	var coes = [];
  	var criteriaResult = {};
  	var inactiveTables = gs.getProperty("sn_hr_core.inactive_tables", "").split(",");
  	var caseTables = new GlideTableHierarchy("sn_hr_core_case").getAllExtensions();

  	for (var j = 0; j < caseTables.length; j++) {
  		// Skip inactive tables
  		if (inactiveTables.indexOf(caseTables[j]) != -1)
  			continue;
  		var tableGr = new GlideRecord(caseTables[j]);
  		
  		// Tables returned from getAllExtensions may not exist
  		if (!tableGr.isValid())
  			continue;

  		// ACL allows users to create ER case through Service Portal,
  		// however we want to limit create access on case creation pages
  		if (tableGr.getTableName() === 'sn_hr_er_case' && !gs.hasRole('sn_hr_er.case_writer'))
  			continue;

  		this.setInitialCaseFields(tableGr);
  		if (tableGr.canRead() && tableGr.canCreate())
  			coes.push(caseTables[j].toString());
  	}

  	var hrServices = new GlideRecord("sn_hr_core_service");
  	hrServices.addActiveQuery();
  	if (!userSysId)
  		hrServices.addNullQuery("hr_criteria");
  	hrServices.addNotNullQuery("topic_detail.topic_category.coe");
  	hrServices.addQuery("topic_detail.topic_category.coe", "IN", coes.join(","));
  	hrServices.addQuery("value", "!=", hr.BULK_PARENT_CASE_SERVICE);
  	hrServices.addQuery('sys_id', 'NOT IN', hr.EXCLUDED_SERVICES);
  	if (!ignoreServiceCondition && this.serviceCondition)
  		hrServices.addEncodedQuery(this.serviceCondition);
  	hrServices.orderBy("topic_detail.topic_category.name");
  	hrServices.orderBy("topic_detail.topic_category.coe");
  	hrServices.orderBy("name");
  	hrServices.query();
  	var result = [];
  	var categories = {};
  	var hrCriteria = new sn_hr_core.hr_Criteria();
  	while (hrServices.next()) {
  		if (!hrServices.canRead())
  			continue;
  		var criteria = hrServices.getValue("hr_criteria");
  		if (criteria) {
  			criteria = criteria.split(",");
  			var userMatchesService = false;
  			for (var i = 0; i < criteria.length; i++) {
  				// Save off criteria results and check before reevaluating same criteria
  				if (criteriaResult.hasOwnProperty(criteria[i])) {
  					if (criteriaResult[criteria[i]]) {
  						userMatchesService = true;
  						break;
  					}
  					continue;
  				} else if (hrCriteria.evaluateById(criteria[i], userSysId)) {
  					userMatchesService = true;
  					criteriaResult[criteria[i]] = true;
  					break;
  				}
  				criteriaResult[criteria[i]] = false;
  			}
  			if (!userMatchesService)
  				continue;
  		}
  		var categorySysId = hrServices.topic_detail.topic_category.sys_id;
  		var category = hrServices.topic_detail.topic_category.name.toString();
  		var coe = hrServices.topic_detail.topic_category.coe.toString();

  		// ACL allows users to create ER case through Service Portal,
  		// however we want to limit create access on case creation pages
  		if (coe === 'sn_hr_er_case' && !gs.hasRole('sn_hr_er.case_writer'))
  			continue;

  		var coeDisplayName = new GlideRecord(coe).getClassDisplayValue();
  		if (!categories[categorySysId]) {
  			categories[categorySysId] = {
  				display: category,
  				coe: coe,
  				children: [],
  				sys_id: categorySysId + ''
  			};
  			result.push(categories[categorySysId]);
  		}
  		categories[categorySysId].children.push({
  			sys_id : hrServices.getUniqueValue(),
  			coe: coe,
  			coeDisplayName: coeDisplayName,
  			display : hrServices.getDisplayValue(),
  			template : hrServices.getValue("template"),
  			parent : category
  		});
  	}

  	return result;
  },

  /**
  * Used By hr_CaseAjax to check if the subject person has the access to the given HR Service
  */
  hasAccessForService: function(userSysId, ignoreServiceCondition, service_id) {
  	var hrServices = new GlideRecord("sn_hr_core_service");
  	hrServices.addActiveQuery();
  	hrServices.get(service_id);
  	hrServices.query();
  	if (hrServices.next()) {
  		if (!hrServices.canRead())
  			return false;

  		var criteria = hrServices.getValue("hr_criteria");
  		if (!criteria)
  			return true;

  		var hrCriteria = new sn_hr_core.hr_Criteria();
  		criteria = criteria.split(",");
  		var userMatchesService = false;
  		var criteriaResult = {};
  		for (var i = 0; i < criteria.length; i++) {
  			// Save off criteria results and check before reevaluating same criteria
  			if (criteriaResult.hasOwnProperty(criteria[i])) {
  				if (criteriaResult[criteria[i]]) {
  					userMatchesService = true;
  					break;
  				}
  				continue;
  			} else if (hrCriteria.evaluateById(criteria[i], userSysId)) {
  				userMatchesService = true;
  				criteriaResult[criteria[i]] = true;
  				break;
  			}
  			criteriaResult[criteria[i]] = false;
  		}

  		return userMatchesService;
  	}

  	return false;
  },

  /**
  * Get the employee reference to this.userObjectTable for a @param userSysId
  * @param userSysId String sys_id of the user to get the display reference for
  * @param columnLabel String display name of the column this user is referenced on
  * @return Object Display reference information,
  *     Example:
  * {
  *     ev_table : table of the employee verification record,
  *     ev_sys_id : sys_id of the employee verification record,
  *     ev_tooltip : tooltip of the employee verification record
  * }
  */
  getEmployeeReference: function(userSysId, columnLabel) {
  	var employeeReference = {};
  	var employeeGr = new GlideRecordSecure(this.userObjectTable);
  	if (employeeGr.isValid() && employeeGr.get(this.userColumn, userSysId)) {
  		employeeReference.ev_table = employeeGr.getTableName();
  		employeeReference.ev_sys_id = employeeGr.getUniqueValue();
  		employeeReference.ev_tooltip = gs.getMessage("Preview record for field: {0}", columnLabel);
  	}

  	return employeeReference;
  },

  /** Determine whether an array of users contains a vip user
  * @param userArr array List of user sys_id's to check for vip status
  * @return boolean Whether @param userArr contains a VIP user
  */
  containsVipUser: function(userArr) {
  	if (!userArr || !userArr.length)
  		return false;

  	var grUser = new GlideRecord("sys_user");
  	grUser.addQuery("vip", true);
  	grUser.addQuery("sys_id", "IN", userArr.toString());
  	grUser.setLimit(1);
  	grUser.query();
  	return grUser.hasNext();
  },

  /** Check if ducplicate case exists for
  * @param taskTable String table name
  * @param hrSercvice String sys_id of HR service (sn_hr_core_service)
  * @param caseUserId String sys_id of user (sys_user)
  * @param isOpenedFor Boolean True if the caseUserId is the opened_for, false if subject_person
  * @return Object {
  *                  boolean true if duplicate case exits within configured time (sys_properties)
  *				   GlideDateTime in String to filter query
  *				 }
  */
  checkDuplicate: function(taskTable, hrService, caseUserId, isOpenedFor) {
  	var gdt = new GlideDateTime();
  	var createNewCaseTimeOut = gs.getProperty('sn_hr_core.duplicate_hr_case_time_out', gdt);
  	gdt.addDaysLocalTime(-createNewCaseTimeOut);

  	var ga = new GlideAggregate(taskTable);
  	ga.addQuery('active', 'true');
  	ga.addQuery('sys_created_on', '>=', gdt);
  	ga.addQuery('hr_service', hrService);
  	if (isOpenedFor)
  		ga.addQuery('opened_for', caseUserId);
  	else
  		ga.addQuery('subject_person', caseUserId);
  	ga.query();

  	return {
  		showModal: ga.getRowCount() > 0? true :false,
  		filterTime: gdt.toString()
  	};
  },

  /** Create a task for given table and fields
  * @param table String Name of task table to create (can be overwritten by hr_service field)
  * @param fields Object Map of field objects, eg { 'hr_service': { value: 'SYS_ID' } }
  * @return Object Object representing created task (empty if didn't create), e.g. { 'table': 'TABLE_NAME', 'sys_id': 'SYS_ID', 'can_read': false }
  */
  createTask: function(table, fields, includeErrors) {
  	var errors = [];
  	var msg = {};
  	var addError = gs.addErrorMessage;
  	if (includeErrors) {
  		addError = function(msg) {
  			errors.push(msg);
  		};
  		msg = {errors: errors};
  	}

  	var template = '';
  	// Replace @param table with HR Service COE
  	if (fields.hasOwnProperty('hr_service')) {
  		if (fields.hasOwnProperty('opened_for') && fields.opened_for.mandatory && !fields.opened_for.value) {
  			addError(gs.getMessage('Failed to insert record. Opened for value is empty.'));
  			return msg;
  		}
  		var serviceGr = new GlideRecord('sn_hr_core_service');
  		if (!serviceGr.isValid() || !serviceGr.get(fields['hr_service'].value)) {
  			addError(gs.getMessage(
  				'Failed to retrieve service. HR Service does not exist for ' + fields['hr_service'].value));
  			return msg;
  		}
  		table = serviceGr.getElement('topic_detail.topic_category.coe').toString();
  		template = serviceGr.getElement('template').toString();
  	}

  	// Create task record from input payload
  	var newTaskGr = new GlideRecordSecure(table);
  	newTaskGr.newRecord();
  	this.setInitialCaseFields(newTaskGr);
  	if (!newTaskGr.isValid() || !newTaskGr.canCreate()) {
  		addError(gs.getMessage('Failed to insert record. ' + newTaskGr.getLastErrorMessage()));
  		return msg;
  	}

  	// Call applyBefore now so that fields with default values are overwritten by template, but user changes
  	// overwrite template still
  	if (template)
  		new sn_hr_core.hr_TemplateUtils().applyBefore(template, newTaskGr, true);

  	// Set all input fields
  	for (var key in fields) {
  		var ele = newTaskGr.getElement(key);
  		if (newTaskGr.isValidField(key) && ele != null && ele.canRead() && ele.canCreate()) {
  			if (fields[key].setAsDisplayValue)
  				newTaskGr[key].setDisplayValue(fields[key].value);
  			else {
  				newTaskGr.setValue(key, fields[key].value); // Duration fields are not set by setting the element
  				newTaskGr[key] = fields[key].value; // Journal fields are not set using GlideRecord.setValue
  			}
  		}
  	}

  	// Set priority if VIP and priority not on form
  	if (!fields.hasOwnProperty('priority')) {
  		var userArray = [];
  		if (fields.hasOwnProperty('opened_for') && fields['opened_for'].value)
  			userArray.push(fields['opened_for'].value);
  		if (fields.hasOwnProperty('subject_person') && fields['subject_person'].value)
  			userArray.push(fields['subject_person'].value);
  		if (this.containsVipUser(userArray))
  			newTaskGr.setValue('priority',
  				String(gs.getProperty('sn_hr_core.hr_vip_default_priority', hr.DEFAULT_HIGH_PRIORITY) || '2'));
  	}

  	if (newTaskGr.assigned_to != '' && newTaskGr.assignment_group != '' &&
  		!(new hr_CoreUtils()).hasValidAssignee(newTaskGr)) {
  		addError(gs.getMessage('The assignee is not a member of the assignment group'));
  		return msg;
  	}

  	if (newTaskGr.insert()) {
  		if (!newTaskGr.get(newTaskGr.getUniqueValue()) || !newTaskGr.canRead()) {
  			gs.addInfoMessage(gs.getMessage(
  				'You do not have permission to read the created record. {0}', newTaskGr.getLastErrorMessage()));
  			return {
  				can_read: false
  			};
  		}
  		return {
  			table: newTaskGr.getTableName(),
  			sys_id: newTaskGr.getUniqueValue()
  		};
  	}

  	addError(gs.getMessage('Failed to insert record. ' + newTaskGr.getLastErrorMessage()));
  	return msg;
  },

  /** Gets the Verification Fields from Case Configuration
   *  @param userId String The ID of the User we are verifying
   *  @return verificationFields Object
   *  {
   *    display : display value of a field,
   *    label : label of a field,
   *    value : value of a field,
   *    column_name : column name of a field,
   *    max_length : max length of a field,
   *    internal_type : internal type of a field,
   *    type : internal type of a field (used for DEO angular directive)
   *	  referringRecordId : sys_id of record from where field object is created,
   *    readonly : if field is readonly (set to true)
   *	}
   */

  getVerificationFields: function(userID) {
  	var profileTable = this.userObjectTable;
  	var profileRecord = new GlideRecord(profileTable);
  	profileRecord.initialize();

  	//Verification Fields
  	var verificationFields = {};
  	if (profileRecord.isValid() && profileRecord.get('user', userID) && profileRecord.canRead()){
  		for(var k in this.fields)			
  			verificationFields[k] = this.getFieldObjects(profileRecord, this.fields[k], true); 
  	} else {			
  		var userRecord = new GlideRecord(this.sysUserTable);
  		userRecord.initialize();
  		if (userRecord.isValid() && userRecord.get('sys_id', userID) && userRecord.canRead())
  			for (var m in this.fields)
  				verificationFields[m] = this.getFieldObjects(userRecord, this.fields[m], false);
  	}

  	this._setFieldsReadOnly(verificationFields.left_fields, "true");
  	this._setFieldsReadOnly(verificationFields.right_fields, "true");

  	return verificationFields;
  },

  /** Adds readonly attribute to a list of fields
   *  @param listOfFields List Set of fields to set attribute on
   *  @param isReadOnly Boolean True if field is readonly, false if not.
   *  @return listOfField List Fields now have readonly attribute.
   */

  _setFieldsReadOnly: function(listOfFields, isReadOnly){
  	if (listOfFields)
  		for(var k in listOfFields)
  			listOfFields[k]["readonly"] = isReadOnly;
  	return listOfFields;
  },
  
  /** Helper method for fix script to update  
   * legacy and native case creation configurations
   */
  updateCaseCreation: function(){
  	this._updateCaseCreationConfig();
  	this._updateCaseCreationServiceConfig();
  },
  
  /** Helper method for updateCaseCreation to update
   * case creation configuration
   */
  _updateCaseCreationConfig: function(){
  	var errorUtil = new sn_hr_core.ErrorUtil();
  	try {
  		var gr = new GlideRecord('sn_hr_core_config_case_creation');
  		gr.setLimit(1);
  		gr.query();
  		errorUtil.validateGrForError(gr);
  		
  		if (gr.next()) {
  			var leftFields = gr.getValue(this.LEFT_TASK_FIELDS);
  			var rightFields = gr.getValue(this.RIGHT_TASK_FIELDS);

  			if (leftFields === null && rightFields === 'opened_for,subject_person') {
  				gr.setValue(this.LEFT_TASK_FIELDS, 'opened_for');
  				gr.setValue(this.RIGHT_TASK_FIELDS, 'subject_person');
  				if (gr.update() === null)
  					throw errorUtil.throwGrError(gr);
  			} 
  		}
  	}
  	catch (error) {
  		throw errorUtil.handleExceptionMethod('_updateCaseCreationConfig', error);
  	}
  },
  
  /** Helper method for updateCaseCreation to update
   * Tuition Reimbursement configuration record
   */
  _updateCaseCreationServiceConfig: function(){
  	var errorUtil = new sn_hr_core.ErrorUtil();
  	var i = 0;
  	var len = this.CASE_CREATION_SERVICE_CONFIG_SYSID.length;
  	while (i < len) {
  		try {
  			var gr = new GlideRecord('sn_hr_core_config_case_creation_service');
  			if(!gr.get(this.CASE_CREATION_SERVICE_CONFIG_SYSID[i])) {
  				i++;
  				continue;
  			}
  			errorUtil.validateGrForError(gr);

  			var leftFields = gr.getValue(this.LEFT_TASK_FIELDS);
  			var rightFields = gr.getValue(this.RIGHT_TASK_FIELDS);
  		
  			if (i === 0 && leftFields === 'school_program_name,course_title,course_justification' && rightFields === 'opened_for,subject_person,course_start_date,course_end_date,course_cost') {
  				gr.setValue(this.LEFT_TASK_FIELDS, 'opened_for,school_program_name,course_title,course_justification');
  				gr.setValue(this.RIGHT_TASK_FIELDS, 'subject_person,course_start_date,course_end_date,course_cost');
  				if (gr.update() === null)
  					throw errorUtil.throwGrError(gr);
  			}
  			else if (i === 1 && leftFields === 'ref_sn_hr_er_case.locked' && rightFields === 'opened_for,subject_person') {
  				gr.setValue(this.LEFT_TASK_FIELDS, 'opened_for,locked');
  				gr.setValue(this.RIGHT_TASK_FIELDS, 'subject_person');
  				if (gr.update() === null)
  					throw errorUtil.throwGrError(gr);
  			}
  			else if (i === 2 && leftFields === null && rightFields === 'opened_for,locked') {
  				gr.setValue(this.LEFT_TASK_FIELDS, 'opened_for');
  				gr.setValue(this.RIGHT_TASK_FIELDS, 'locked');
  				if (gr.update() === null)
  					throw errorUtil.throwGrError(gr);
  			}
  		}
  		catch (error) {
  			throw errorUtil.handleExceptionMethod('_updateCaseCreationServiceConfig', error);
  		}
  		i++;
  	}
  	
  },
  
  
  type: 'hr_CaseCreation'
};

Sys ID

687d7d8deb6f3200a9e7e26ac106fee0

Offical Documentation

Official Docs: