Name

global.OnCallContactPreferenceUtilSNC

Description

No description available

Script

var OnCallContactPreferenceUtilSNC = Class.create();
OnCallContactPreferenceUtilSNC.prototype = {
  TABLES: {
  	CMN_ROTA: 'cmn_rota',
  	CMN_ROTA_ESCALATION_SET: 'cmn_rota_escalation_set',
  	CMN_ROTA_ESCALATION_STEP_DEFINITION: 'cmn_rota_esc_step_def',
  	CMN_ROTA_ROSTER: 'cmn_rota_roster',
  	CMN_ROTA_MEMBER: 'cmn_rota_member',
  	SYS_USER: 'sys_user',
  	SYS_USER_GROUP: 'sys_user_group',
  	CMN_NOTIF_DEVICE: 'cmn_notif_device',
  	CMN_ROTA_CONTACT_PREFERENCE: 'cmn_rota_contact_preference',
  	ON_CALL_COMMUNICATION_TYPE: 'on_call_communication_type',
  	ON_CALL_USER_PREFERENCE: 'on_call_user_preference',
  	ON_CALL_USER_CONTACT_PREFERENCE: 'on_call_user_contact_preference',
  	SYS_ON_CALL_CONTACT_SOURCE: 'sys_on_call_contact_source',
  	CMN_SCHEDULE: 'cmn_schedule',
  	CMN_SCHEDULE_SPAN: 'cmn_schedule_span',
  	CMN_NOTIF_MESSAGE: 'cmn_notif_message'
  },
  ATTRS: {
  	CMN_ROTA: 'cmn_rota',
  	CMN_ROTA_ESCALATION_SET: 'cmn_rota_escalation_set',
  	CONTACT_ATTEMPT: 'contact_attempt',
  	COMMUNICATION_TYPES: 'communication_types'
  },
  COMMUNICATION_TYPES: OnCallCommonSNC.COMMUNICATION_TYPES,
  CMN_NOTIFY_DEVICE_TYPES: {
  	EMAIL: 'Email',
  	SMS: 'SMS',
  	VOICE: 'Voice',
  	SLACK: 'Slack',
  	PUSH: 'Push',
  	TEAMS: 'Teams'
  },
  PREFERENCE_SOURCE_MAP: {
  	USER: 'user',
  	CONTACT: 'contact',
  	DEFAULT: 'system',
  	DEVICE: 'device'
  },

  NOTIFICATION_SYS_ID: 'f317cc22535320100999ddeeff7b127c',

  initialize: function () {
  	this._log = new GSLog("com.snc.on_call_rotation.log.level", this.type);
  	this.onCallCommon = new OnCallCommon();
  	this.ocRotation = new OnCallRotation();
  	this.onCallCommon = new OnCallCommon();
  	this._onCallWorkflowUtils = new OnCallWorkflowUtils();

  	this.isNotifyInstalled = this.onCallCommon.isNotifyPluginInstalled();
  	this.isSlackInstalled = this.onCallCommon.isSlackPluginInstalled();
  	this.isTeamsInstalled = this.onCallCommon.isTeamsPluginInstalled();
  	this.isMobilePushInstalled = this.onCallCommon.isMobilePushInstalled();

  	if (this.isNotifyInstalled)
  		this.notifyUtils = new NotifyUtil();

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[initialize] isNotifyInstalled: ' + this.isNotifyInstalled + ' isSlackInstalled: ' + this.isSlackInstalled + ' isMobilePushInstalled: ' + this.isMobilePushInstalled + ' isTeamsInstalled: ' + this.isTeamsInstalled);
  },

  getCatchAllContacts: function (rotaId, gdt) {
  	var contacts = [];
  	if (!rotaId)
  		return contacts;
  	gdt = gdt || new GlideDateTime();
  	var rotaGr = new GlideRecord(this.TABLES.CMN_ROTA);
  	if (rotaGr.get(rotaId)) {
  		var isCustomEscalation = this._isCustomEscalation(rotaGr);
  		if (!isCustomEscalation) {
  			var catchAllType = this.ocRotation.getCatchAllType(rotaId);
  			var contactAttempt = 1; //contact attempt is 1 for catch all preferences
  			if (JSUtil.notNil(catchAllType)) {
  				var overrideUserContactPreference = rotaGr.override_user_contact_preference + '' === 'true';

  				if (this._log.atLevel(GSLog.DEBUG))
  					this._log.logDebug('[getCatchAllContacts] overrideUserContactPreference: ' + overrideUserContactPreference);

  				var contactPreferences = this._getRotaDefaultContactPreferences(rotaGr, contactAttempt);
  				var catchAllUsers = [];
  				if (catchAllType + '' === 'all') {
  					var catchAllRoster = this.ocRotation.getCatchAll(rotaId);
  					var rosterMemberGr = new GlideRecord('cmn_rota_member');
  					rosterMemberGr.addQuery('roster', catchAllRoster);
  					rosterMemberGr.query();
  					while (rosterMemberGr.next())
  						catchAllUsers.push(rosterMemberGr.member + '');
  				}
  				else {
  					var catchAllUser = this.ocRotation.getCatchAll(rotaId);
  					var userGr = new GlideRecord(this.TABLES.SYS_USER);
  					if (userGr.get(catchAllUser))
  						catchAllUsers.push(catchAllUser);
  				}
  				for (var i = 0; i < catchAllUsers.length; i++) {
  					var userId = catchAllUsers[i];
  					var userContact = this._getContactObject('user', userId, rotaId, '', '', '', overrideUserContactPreference, '', contactAttempt);
  					var communicationTypes = this._resolveCommunicationTypesByOverrides(contactPreferences, overrideUserContactPreference, userContact.sys_id, contactAttempt, gdt, userContact);
  					this._populateUserContactPreferencesByCommunicationTypes(userContact, contactAttempt, gdt, communicationTypes);
  					contacts.push(userContact);
  				}
  			}
  		}
  	}
  	return contacts;
  },
  /**
   * escalatee (mandatory) - escalatee at current position/escalation level
   * contactAttempt (optional) - The attempt you are trying to contact [ default value = 1 ]
   *                             1 - first time
   * 							   2 - reminder 1 and so on...
   * gdt (optional) - This is used incase of user preferences [default value = new GlideDateTime()]
   * rotaSysIds (optional) - comma seperated sys_id's of rota's [ default value = Rota's at current time ]
   *
   * Example:
   *
   * 	var serviceDeskSysId = 'd625dccec0a8016700a222a0f7900d06';
  	var escalationPlan = new OnCallRotation().getEscalationPlan(serviceDeskSysId);
  	var escalatee = escalationPlan[0];
  	var onContactPrefUtil = new OnCallContactPreferenceUtil();
  	var contacts = onContactPrefUtil.getContactsAtByEscalatee(escalatee);
   *
   *
   */
  getContactsAtByEscalatee: function (escalatee, contactAttempt, gdt, rotaSysIds) {
  	var contacts = [];
  	if (!escalatee)
  		return contacts;

  	if (!contactAttempt)
  		contactAttempt = 1;

  	if (!gdt)
  		gdt = new GlideDateTime();

  	if (!rotaSysIds) {
  		//fetch rotaSysIds from escalatee object as we dont have access to ocRotation instance
  		rotaSysIds = this._getRotasFromEscalatee(escalatee);
  	}

  	rotaSysIds = rotaSysIds.split(',');
  	for (var i = 0; i < rotaSysIds.length; i++) {
  		var rotaContacts = this._getRotaContacts(rotaSysIds[i], escalatee, contactAttempt, gdt);
  		contacts = contacts.concat(rotaContacts);
  	}

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getContactsAtByEscalatee] contacts: ' + JSON.stringify(contacts));

  	return contacts;
  },

  getPreferencesByContactAttempt: function(groupId, escalationLevel, gdt, rotaId, escalationSetSysId, taskTable, taskSysId, contactAttempt, escalatee) {
  	if (!contactAttempt)
  		return [];
  	
  	if (!groupId)
  		return contactPreferences;
  		
  	var rota = new OnCallRotation();
  	var escalationPlan;
  	if (!escalatee) {
  		if (escalationSetSysId)
  			escalationPlan = rota.getEscalationPlanByEscalationSet(escalationSetSysId, groupId, rotaId, gdt);
  		else {
  			var taskGr;
  			if (taskTable && taskSysId) {
  				taskGr = new GlideRecord(taskTable);
  				if (!taskGr.get(taskSysId))
  					taskGr = null;
  			}
  			escalationPlan = rota.getEscalationPlan(groupId, gdt, rotaId, taskGr);
  		}
  		var escalatees = escalationPlan[escalationLevel - 1];
  		if (!escalatees)
  			return [];				
  	}
  	return this.getContactsAtByEscalatee(escalatee, contactAttempt, gdt, rotaId);
  },
  
  getContactPreferenceAtEscalationLevel: function (groupId, escalationLevel, gdt, rotaId, escalationSetSysId, taskTable, taskSysId) {
  	var contactPreferences = [];
  	escalationLevel = !escalationLevel ? 0 : escalationLevel + 0;
  	if (!groupId)
  		return contactPreferences;
  	var rota = new OnCallRotation();
  	var escalationPlan;
  	if (escalationSetSysId)
  		escalationPlan = rota.getEscalationPlanByEscalationSet(escalationSetSysId, groupId, rotaId, gdt);
  	else {
  		var taskGr;
  		if (taskTable && taskSysId) {
  			taskGr = new GlideRecord(taskTable);
  			if (!taskGr.get(taskSysId))
  				taskGr = null;
  		}
  		escalationPlan = rota.getEscalationPlan(groupId, gdt, rotaId, taskGr);
  	}
  	// Handle catch-all scenario
  	if (escalationPlan && escalationPlan.length === escalationLevel) {
  		var rotaGr = new GlideRecord(this.TABLES.CMN_ROTA);
  		if (!rotaId || !rotaGr.get(rotaId))
  			return contactPreferences;
  		var isCustomEscalation = this._isCustomEscalation(rotaGr);
  		if (!isCustomEscalation)
  			return this.getCatchAllContacts(rotaId, gdt);
  	}
  	var escalatee = escalationPlan[escalationLevel];
  	if (!escalatee)
  		return contactPreferences;
  	var reminders = escalatee.reminderNum ? parseInt(escalatee.reminderNum) + 1 : 1;
  	for (var contactAttempt = 1; contactAttempt <= reminders; contactAttempt++) {
  		var preferenceAtAttempt = this.getContactsAtByEscalatee(escalatee, contactAttempt, gdt, rotaId);
  		contactPreferences = contactPreferences.concat(preferenceAtAttempt);
  	}
  	return contactPreferences;
  },
  _getRotasFromEscalatee: function (escalatee) {
  	if (!escalatee)
  		return '';
  	var rotaSysIds = escalatee.getRotaId() || '';
  	var additionalEscalatees = escalatee.additionalEscalatees;
  	if (additionalEscalatees && additionalEscalatees.length > 0) {
  		for (var i = 0; i < additionalEscalatees.length; i++) {
  			var additionalEscalatee = additionalEscalatees[i];
  			rotaSysIds = rotaSysIds + ',' + additionalEscalatee.getRotaId();
  		}
  	}
  	return rotaSysIds;
  },
  _getRotaContacts: function (rotaId, escalatee, contactAttempt, gdt) {
  	var rotaContacts = [];
  	var rotaGr = new GlideRecord(this.TABLES.CMN_ROTA);
  	if (!rotaGr.get(rotaId))
  		return rotaContacts;
  	var contactPreferences = this._getRotaContactPreferences(rotaGr, contactAttempt, escalatee);

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getRotaContacts] contactPreferences: ' + JSON.stringify(contactPreferences));

  	var currentRotaEscalatee;
  	if (rotaId + '' === escalatee.getRotaId() + '')
  		currentRotaEscalatee = escalatee;
  	else {
  		var additionalEscalatees = escalatee.additionalEscalatees;
  		if (additionalEscalatees && additionalEscalatees.length > 0) {
  			for (var j = 0; j < additionalEscalatees.length; j++) {
  				var additionalEscalatee = additionalEscalatees[j];
  				if (rotaId + '' === additionalEscalatee.getRotaId() + '') {
  					currentRotaEscalatee = additionalEscalatee;
  					break;
  				}
  			}
  		}
  	}
  	var userContacts = this._getEscalateeUserContacts(currentRotaEscalatee, contactPreferences, contactAttempt, gdt);

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getRotaContacts] userContacts: ' + JSON.stringify(userContacts));

  	rotaContacts = rotaContacts.concat(userContacts);
  	var deviceContacts = this._getEscalateeDeviceContacts(currentRotaEscalatee, gdt, contactAttempt);

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getRotaContacts] deviceContacts: ' + JSON.stringify(deviceContacts));

  	rotaContacts = rotaContacts.concat(deviceContacts);

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getRotaContacts] rotaContacts: ' + JSON.stringify(rotaContacts));

  	return rotaContacts;
  },
  _getEscalateeDeviceContacts: function (escalatee, gdt, contactAttempt) {
  	var deviceContacts = [];
  	var deviceContact, i;
  	var overrideUserContactPreference = escalatee.getOverrideUserContactPreference();
  	if (escalatee.getDeviceId()) {
  		deviceContact = this._getContactObject('device', escalatee.getDeviceId(), escalatee.getRotaId(), escalatee.getRosterId(), escalatee.getCmnRotaEscStepDefId(), escalatee.getForcedCommunicationChannel(), overrideUserContactPreference, this.PREFERENCE_SOURCE_MAP.DEVICE, contactAttempt);
  		deviceContact.contact_preferences = this._getDeviceContactDetails(deviceContact, gdt);
  		deviceContacts.push(deviceContact);
  	}
  	var deviceIds = escalatee.getDeviceIds();
  	if (deviceIds && deviceIds.length > 0) {
  		for (i = 0; i < deviceIds.length; i++) {
  			deviceContact = this._getContactObject('device', deviceIds[i], escalatee.getRotaId(), escalatee.getRosterId(), escalatee.getCmnRotaEscStepDefId(), escalatee.getForcedCommunicationChannel(), overrideUserContactPreference, this.PREFERENCE_SOURCE_MAP.DEVICE, contactAttempt);
  			deviceContact.contact_preferences = this._getDeviceContactDetails(deviceContact, gdt);
  			deviceContacts.push(deviceContact);
  		}
  	}
  	for (i = 0; i < deviceContacts.length; i++) {
  		deviceContact = deviceContacts[i];
  		deviceContact.device_has_user = false;
  		deviceContact.user_id = '';
  		deviceContact.user_preferences = [];
  		if (deviceContact.contact_preferences.length > 0) {
  			devicePreference = deviceContact.contact_preferences[0];
  			if (devicePreference.user) {
  				deviceContact.device_has_user = true;
  				deviceContact.user_id = devicePreference.user;
  				delete devicePreference.user;
  			}
  		}
  	}
  	return deviceContacts;
  },

  _resolveCommunicationTypesByOverrides: function (contactPreferences, isOverride, userSysId, contactAttempt, gdt, userContact) {
  	var managerPreferredCommunicationTypes = contactPreferences;
  	var userPreferredCommunicationTypes;
  	var resolvedCommunicationTypes = [];
  	if (isOverride) {
  		//if override is true - consider only shift preferences
  		resolvedCommunicationTypes = managerPreferredCommunicationTypes;
  		userContact.preference_source = this.PREFERENCE_SOURCE_MAP.CONTACT;
  	}
  	else {
  		//if override is false - consider only user preferences
  		userPreferredCommunicationTypes = this._getUserPreferredCommunicationTypes(userSysId, contactAttempt, gdt);
  		resolvedCommunicationTypes = userPreferredCommunicationTypes;
  		userContact.preference_source = this.PREFERENCE_SOURCE_MAP.USER;
  		if (resolvedCommunicationTypes.length === 0) {
  			//if there are no user prefrences - then fallback to shift preference even though override is false
  			resolvedCommunicationTypes = managerPreferredCommunicationTypes;
  			userContact.preference_source = this.PREFERENCE_SOURCE_MAP.CONTACT;
  		}
  	}
  	resolvedCommunicationTypes = resolvedCommunicationTypes.filter(function (item, pos) { return resolvedCommunicationTypes.indexOf(item) === pos; });
  	if (resolvedCommunicationTypes.length === 0) {
  		resolvedCommunicationTypes = this._getDefaultContactPreferenceForUser(userSysId);
  		userContact.preference_source = this.PREFERENCE_SOURCE_MAP.DEFAULT;
  	}
  	return resolvedCommunicationTypes;
  },

  _getUserPreferredCommunicationTypes: function (userSysId, contactAttempt, gdt) {
  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getUserPreferredCommunicationTypes] userSysId: ' + userSysId);

  	var preferredCommunicationTypes = [];
  	var userPrefs = this.getUserPreferences(userSysId, contactAttempt, gdt);
  	userPrefs.forEach(function(userPref) {
  		if (preferredCommunicationTypes.indexOf(userPref.type) === -1)
  			preferredCommunicationTypes.push(userPref.type);
  	});

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getUserPreferredCommunicationTypes] preferredCommunicationTypes: ' + preferredCommunicationTypes);

  	return preferredCommunicationTypes;
  },

  _populateUserContactPreferencesByCommunicationTypes: function(userContact, contactAttempt, gdt, communicationTypes) {
  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_populateUserContactPreferencesByCommunicationTypes] userContact: ' + JSON.stringify(userContact) + ' contactAttempt: ' + contactAttempt + ' communicationTypes: ' + JSON.stringify(communicationTypes));

  	userContact.user_preferences = this.getUserPreferences(userContact.sys_id, contactAttempt, gdt, communicationTypes.join(','));
  	var userPreferredCommunicationTypes = [];
  	userContact.user_preferences.forEach(function(userPref) {
  		if (userPreferredCommunicationTypes.indexOf(userPref.type) === -1)
  			userPreferredCommunicationTypes.push(userPref.type);
  	});

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_populateUserContactPreferencesByCommunicationTypes] userPreferredCommunicationTypes: ' + userPreferredCommunicationTypes);

  	communicationTypes = communicationTypes.filter(function(commType) {
  		return userPreferredCommunicationTypes.indexOf(commType) === -1;
  	});

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_populateUserContactPreferencesByCommunicationTypes] communicationTypes: ' + communicationTypes);

  	userContact.contact_preferences = [];
  	if (communicationTypes.length > 0)
  		userContact.contact_preferences = this._getUserContactDetailsByPreferences(userContact, communicationTypes);

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_populateUserContactPreferencesByCommunicationTypes] userContact: ' + JSON.stringify(userContact));
  },

  _getEscalateeUserContacts: function (escalatee, contactPreferences, contactAttempt, gdt) {
  	if (!contactPreferences)
  		contactPreferences = [];
  	var overrideUserContactPreference = escalatee.getOverrideUserContactPreference();
  	var forcedCommunicationChannel = escalatee.getForcedCommunicationChannel();
  	if (forcedCommunicationChannel && contactPreferences.indexOf(forcedCommunicationChannel) === -1)
  		contactPreferences.push(forcedCommunicationChannel);
  	var userContacts = [];
  	var userContact, communicationTypes;
  	if (escalatee.getUserId()) {
  		userContact = this._getContactObject('user', escalatee.getUserId(), escalatee.getRotaId(), escalatee.getRosterId(), escalatee.getCmnRotaEscStepDefId(), escalatee.getForcedCommunicationChannel(), overrideUserContactPreference, this.PREFERENCE_SOURCE_MAP.DEFAULT, contactAttempt);
  		communicationTypes = this._resolveCommunicationTypesByOverrides(contactPreferences, overrideUserContactPreference, userContact.sys_id, contactAttempt, gdt, userContact);
  		this._populateUserContactPreferencesByCommunicationTypes(userContact, contactAttempt, gdt, communicationTypes);
  		userContacts.push(userContact);
  	}
  	var userIds = escalatee.getUserIds();
  	if (userIds && userIds.length > 0) {
  		for (var i = 0; i < userIds.length; i++) {
  			userContact = this._getContactObject('user', userIds[i], escalatee.getRotaId(), escalatee.getRosterId(), escalatee.getCmnRotaEscStepDefId(), escalatee.getForcedCommunicationChannel(), overrideUserContactPreference, this.PREFERENCE_SOURCE_MAP.DEFAULT, contactAttempt);
  			communicationTypes = this._resolveCommunicationTypesByOverrides(contactPreferences, overrideUserContactPreference, userContact.sys_id, contactAttempt, gdt, userContact);
  			this._populateUserContactPreferencesByCommunicationTypes(userContact, contactAttempt, gdt, communicationTypes);
  			userContacts.push(userContact);
  		}
  	}
  	return userContacts;
  },
  getUserPreferences: function (userSysId, contactAttempt, gdt, communicationTypes) {
  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getUserPreferences] userSysId: ' + userSysId);

  	var userPreferences = [];
  	if (!userSysId)
  		return userPreferences;

  	if (!contactAttempt)
  		contactAttempt = 1;

  	if (!communicationTypes) {
  		communicationTypes = [];
  		Object.keys(this.COMMUNICATION_TYPES).forEach(function(key) {
  			communicationTypes.push(this.COMMUNICATION_TYPES[key]);
  		}, this);
  	} else
  		communicationTypes = communicationTypes.split(',');

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getUserPreferences] communicationTypes: ' + communicationTypes);

  	gdt = gdt || new GlideDateTime();
  	var userPreferenceGr = this._getUserPreferenceByGdt(userSysId, gdt);
  	if (userPreferenceGr) {
  		var userContactPreferenceGr = new GlideRecord(this.TABLES.ON_CALL_USER_CONTACT_PREFERENCE);
  		userContactPreferenceGr.addQuery('on_call_user_preference', userPreferenceGr.getUniqueValue());
  		userContactPreferenceGr.addQuery('contact_attempt', contactAttempt);
  		userContactPreferenceGr.query();

  		if (this._log.atLevel(GSLog.DEBUG))
  			this._log.logDebug('[getUserPreferences] userContactPreferenceGr: ' + userContactPreferenceGr.getTableName() + ' encodedQuery: ' + userContactPreferenceGr.getEncodedQuery());

  		if (userContactPreferenceGr.next()) {
  			var devicesPreferences = this._getDevicesPreferencesInUserContactPreference(userContactPreferenceGr, gdt, communicationTypes);
  			userPreferences = userPreferences.concat(devicesPreferences);

  			if (communicationTypes.indexOf(this.COMMUNICATION_TYPES.EMAIL) !== -1) {
  				var emailPreferences = this._getEmailPreferencesInUserContactPreference(userContactPreferenceGr, userSysId);
  				userPreferences = userPreferences.concat(emailPreferences);
  			}

  			if (this.isSlackInstalled && (communicationTypes.indexOf(this.COMMUNICATION_TYPES.SLACK) !== -1)) {
  				var slackPreferences = this._getSlackPreferencesInUserContactPreference(userContactPreferenceGr, userSysId);
  				userPreferences = userPreferences.concat(slackPreferences);
  			}
  			
  			if (this.isTeamsInstalled && (communicationTypes.indexOf(this.COMMUNICATION_TYPES.TEAMS) !== -1)) {
  				var teamsPreferences = this._getTeamsPreferencesInUserContactPreference(userContactPreferenceGr, userSysId);
  				userPreferences = userPreferences.concat(teamsPreferences);
  			}

  			if (this.isMobilePushInstalled && (communicationTypes.indexOf(this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION) !== -1) && this._isUserPrefMobilePushEnabled(userSysId))
  				userPreferences = userPreferences.concat(this._getMobilePushPreferencesInUserContactPreference(userContactPreferenceGr, userSysId));

  			if (this.isNotifyInstalled) {
  				if (communicationTypes.indexOf(this.COMMUNICATION_TYPES.SMS) !== -1) {
  					var smsPreferences = this._getSMSPreferencesInUserContactPreference(userContactPreferenceGr, userSysId);
  					userPreferences = userPreferences.concat(smsPreferences);
  				}
  				if (communicationTypes.indexOf(this.COMMUNICATION_TYPES.VOICE) !== -1) {
  					var voicePreferences = this._getVoicePreferencesInUserContactPreference(userContactPreferenceGr, userSysId);
  					userPreferences = userPreferences.concat(voicePreferences);
  				}
  			}
  		}
  	}

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getUserPreferences] userPreferences: ' + userPreferences);

  	return userPreferences;
  },

  _getEmailPreferencesInUserContactPreference: function (userContactPreferenceGr, userSysId) {
  	return this._getPreferencesFromSources(userContactPreferenceGr, this.COMMUNICATION_TYPES.EMAIL, userSysId);
  },
  _getSMSPreferencesInUserContactPreference: function (userContactPreferenceGr, userSysId) {
  	return this._getPreferencesFromSources(userContactPreferenceGr, this.COMMUNICATION_TYPES.SMS, userSysId);
  },
  _getVoicePreferencesInUserContactPreference: function (userContactPreferenceGr, userSysId) {
  	return this._getPreferencesFromSources(userContactPreferenceGr, this.COMMUNICATION_TYPES.VOICE, userSysId);
  },
  _getSlackPreferencesInUserContactPreference: function (userContactPreferenceGr, userSysId) {
  	return this._getPreferencesFromSources(userContactPreferenceGr, this.COMMUNICATION_TYPES.SLACK, userSysId);
  },
  _getTeamsPreferencesInUserContactPreference: function (userContactPreferenceGr, userSysId) {
  	return this._getPreferencesFromSources(userContactPreferenceGr, this.COMMUNICATION_TYPES.TEAMS, userSysId);
  },
  _getMobilePushPreferencesInUserContactPreference: function (userContactPreferenceGr, userSysId) {
  	return this._getPreferencesFromSources(userContactPreferenceGr, this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION, userSysId);
  },

  _getPreferencesFromSources: function (userContactPreferenceGr, type, userSysId) {
  	type = type + '';

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getPreferencesFromSources] type: ' + type);

  	var preferences = [];
  	var sourceSysIds = userContactPreferenceGr.getValue(type);
  	if (sourceSysIds) {
  		var contactSourceGr = new GlideRecord(this.TABLES.SYS_ON_CALL_CONTACT_SOURCE);
  		contactSourceGr.addQuery('sys_id', 'IN', sourceSysIds);
  		contactSourceGr.addActiveQuery();
  		contactSourceGr.query();

  		if (this._log.atLevel(GSLog.DEBUG))
  			this._log.logDebug('[_getPreferencesFromSources] contactSourceGr: ' + contactSourceGr.getTableName() + ' encodedQuery: ' + contactSourceGr.getEncodedQuery());

  		while (contactSourceGr.next()) {
  			var sourceTable = contactSourceGr.table + '';
  			var userField = 'sys_id';
  			if (sourceTable !== this.TABLES.SYS_USER)
  				userField = contactSourceGr.user_field + '';
  			var sourceTableGr = new GlideRecord(sourceTable);
  			sourceTableGr.addQuery(userField, userSysId);
  			sourceTableGr.setLimit(1);
  			sourceTableGr.query();

  			if (this._log.atLevel(GSLog.DEBUG))
  				this._log.logDebug('[_getPreferencesFromSources] sourceTableGr: ' + sourceTableGr.getTableName() + ' encodedQuery: ' + sourceTableGr.getEncodedQuery());

  			if (sourceTableGr.next()) {
  				var preference = {
  					type: type
  				};

  				var fieldName = null;

  				if (type === this.COMMUNICATION_TYPES.SMS || type === this.COMMUNICATION_TYPES.VOICE) {
  					fieldName = contactSourceGr.getValue('phone_number_source');
  					if (fieldName) {
  						if ((!this.checkAccess || sourceTableGr[fieldName] && sourceTableGr[fieldName].canRead())) {
  							preference['number'] =  sourceTableGr.getValue(fieldName);
  						} else {
  							preference['number'] = '';
  						}
  					} else
  						this._log.logError('[_getPreferencesFromSources] fieldName: ' + fieldName + ' must be valid field on table: ' +  sourceTableGr.getTableName());

  					preferences.push(preference);
  				} else if (type === this.COMMUNICATION_TYPES.EMAIL) {
  					fieldName = contactSourceGr.getValue('email_source');
  					if (fieldName) {
  						if ((!this.checkAccess || sourceTableGr[fieldName] && sourceTableGr[fieldName].canRead())) {
  							preference[this.COMMUNICATION_TYPES.EMAIL] = sourceTableGr.getValue(fieldName);
  						} else {
  							preference[this.COMMUNICATION_TYPES.EMAIL] = "";
  						}
  					} else
  						this._log.logError('[_getPreferencesFromSources] fieldName: ' + fieldName + ' must be valid field on table: ' +  sourceTableGr.getTableName());

  					preferences.push(preference);
  				} else if (type === this.COMMUNICATION_TYPES.SLACK) {
  					fieldName = contactSourceGr.getValue('email_source');
  					if (fieldName) {
  						if ((!this.checkAccess || sourceTableGr[fieldName] && sourceTableGr[fieldName].canRead())) {
  							preference[this.COMMUNICATION_TYPES.SLACK] = sourceTableGr.getValue(fieldName);
  						} else {
  							preference[this.COMMUNICATION_TYPES.SLACK] = "";
  						}
  					} else
  						this._log.logError('[_getPreferencesFromSources] fieldName: ' + fieldName + ' must be valid field on table: ' +  sourceTableGr.getTableName());

  					preferences.push(preference);
  				} else if (type === this.COMMUNICATION_TYPES.TEAMS) {
  					preference[this.COMMUNICATION_TYPES.TEAMS] = gs.getMessage("Microsoft Teams");
  					preferences.push(preference);
  				} else if (type === this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION) {
  					// Mobile Push Notification does not actually need a number or source so just sets true
  					preference[this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION] = true;
  					preferences.push(preference);
  				}

  				if (this._log.atLevel(GSLog.DEBUG))
  					this._log.logDebug('[_getPreferencesFromSources] preference: ' + JSON.stringify(preference));
  			}
  		}
  	}

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getPreferencesFromSources] preferences: ' + JSON.stringify(preferences));

  	return preferences;
  },
  _getDevicesPreferencesInUserContactPreference: function (userContactPreferenceGr, gdt, communicationTypes) {
  	if (!communicationTypes)
  		communicationTypes = [];
  	var devicesPreferences = [];
  	var deviceSysIds = userContactPreferenceGr.getValue('devices');
  	if (deviceSysIds) {
  		var notifyDeviceGr = new GlideRecord(this.TABLES.CMN_NOTIF_DEVICE);
  		notifyDeviceGr.addActiveQuery();
  		notifyDeviceGr.addQuery('sys_id', 'IN', deviceSysIds);
  		if (communicationTypes.length > 0) {
  			var deviceTypes = [];
  			for (var i = 0; i < communicationTypes.length; i++) {
  				var commType = communicationTypes[i] + '';
  				if (commType === this.COMMUNICATION_TYPES.EMAIL)
  					deviceTypes.push(this.CMN_NOTIFY_DEVICE_TYPES.EMAIL);
  				if (commType === this.COMMUNICATION_TYPES.SMS)
  					deviceTypes.push(this.CMN_NOTIFY_DEVICE_TYPES.SMS);
  				if (commType === this.COMMUNICATION_TYPES.VOICE)
  					deviceTypes.push(this.CMN_NOTIFY_DEVICE_TYPES.VOICE);
  				if (commType === this.COMMUNICATION_TYPES.SLACK)
  					deviceTypes.push(this.CMN_NOTIFY_DEVICE_TYPES.SLACK);
  				if (commType === this.COMMUNICATION_TYPES.TEAMS)
  					deviceTypes.push(this.CMN_NOTIFY_DEVICE_TYPES.TEAMS);
  				if (commType === this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION)
  					deviceTypes.push(this.CMN_NOTIFY_DEVICE_TYPES.PUSH);
  			}
  			notifyDeviceGr.addQuery('type', 'IN', deviceTypes.join(','));
  		}
  		notifyDeviceGr.query();

  		if (this._log.atLevel(GSLog.DEBUG))
  			this._log.logDebug('[_getDevicesPreferencesInUserContactPreference] cmn_notif_device encodedQuery: ' + notifyDeviceGr.getEncodedQuery());

  		while (notifyDeviceGr.next()) {
  			if (this._isDeviceInSchedule(notifyDeviceGr, gdt)) {
  				var preference = this.getPreferenceFromDevice(notifyDeviceGr);
  				devicesPreferences.push(preference);
  			}
  		}
  	}
  	return devicesPreferences;
  },
  _isDeviceInSchedule: function (notifyDeviceGr, gdt) {
  	var isDeviceInSchedule = true;
  	var deviceScheduleSysId = notifyDeviceGr.schedule + '';
  	if (deviceScheduleSysId) {
  		var userSysId = notifyDeviceGr.user + '';
  		var deviceSchedule = this._getCmnSchedule(deviceScheduleSysId, userSysId);
  		if (deviceSchedule.isValid())
  			isDeviceInSchedule = deviceSchedule.isInSchedule(gdt);
  	}
  	return isDeviceInSchedule;
  },
  
  _getGlideElementPhoneNumber: function(number) {
  	if (!number)
  		return '';
  	// fix number
  	number = number.replace(/^([0]{2})/, '');   // remove trailing 00
  	number = number.replace(/^([^\+])/, '+$1'); // prefix with + if needed

  	// get GlideElementPhoneNumber instance (if possible)
  	var gep = new GlideElementPhoneNumber();
  	if (gep.setPhoneNumber(number, true))
  		return gep;
  },

  getPreferenceFromDevice: function(notifyDeviceGr) {
  	var preference = {};
  	var notifyDeviceType = notifyDeviceGr.getValue('type') + '';

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getPreferenceFromDevice] notifyDeviceType: ' + notifyDeviceType);

  	if (notifyDeviceType === this.CMN_NOTIFY_DEVICE_TYPES.EMAIL) {
  		preference.type = this.COMMUNICATION_TYPES.EMAIL;
  		preference.email = (!this.checkAccess || notifyDeviceGr.email_address.canRead()) ? notifyDeviceGr.getValue('email_address') : '';
  	} else if (this.isNotifyInstalled && notifyDeviceType === this.CMN_NOTIFY_DEVICE_TYPES.SMS) {
  		preference.type = this.COMMUNICATION_TYPES.SMS;
  		var formattedSMSNumber = this._getGlideElementPhoneNumber(notifyDeviceGr.getValue('phone_number'));
  		preference.number = (!this.checkAccess || (notifyDeviceGr.phone_number.canRead() && formattedSMSNumber)) ? formattedSMSNumber.getValue() : '';
  	} else if (this.isNotifyInstalled && notifyDeviceType === this.CMN_NOTIFY_DEVICE_TYPES.VOICE) {
  		preference.type = this.COMMUNICATION_TYPES.VOICE;
  		var formattedVoiceNumber = this._getGlideElementPhoneNumber(notifyDeviceGr.getValue('phone_number'));
  		preference.number = (!this.checkAccess || (notifyDeviceGr.phone_number.canRead() && formattedVoiceNumber)) ? formattedVoiceNumber.getValue() : '';
  	} else if (notifyDeviceType === this.CMN_NOTIFY_DEVICE_TYPES.SLACK) {
  		preference.type = this.COMMUNICATION_TYPES.SLACK;
  		preference.slack = (!this.checkAccess || notifyDeviceGr.user.email.canRead()) ? notifyDeviceGr.user.email + '' : '';
  	} else if (notifyDeviceType === this.CMN_NOTIFY_DEVICE_TYPES.TEAMS) {
  		preference.type = this.COMMUNICATION_TYPES.TEAMS;
  		preference.teams = (!this.checkAccess || notifyDeviceGr.user.email.canRead()) ? notifyDeviceGr.user.email + '' : '';
  	} else if (this.isMobilePushInstalled && notifyDeviceType === this.CMN_NOTIFY_DEVICE_TYPES.PUSH && this._isUserPrefMobilePushEnabled(notifyDeviceGr.getValue('user'))) {
  		preference.type = this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION;
  		preference[this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION] = true;
  	}

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getPreferenceFromDevice] preference: ' + JSON.stringify(preference));

  	return preference;
  },
  _isUserPrefMobilePushEnabled: function(userSysId) {
  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_isUserPrefMobilePushEnabled] userSysId: ' + userSysId);

  	if (!userSysId)
  		return false;

  	// Does NOT check calendar, Platform notif does that
  	var gr = new GlideRecord(this.TABLES.CMN_NOTIF_MESSAGE);
  	gr.addQuery('user', userSysId);
  	gr.addQuery('device.type', this.CMN_NOTIFY_DEVICE_TYPES.PUSH);
  	gr.addQuery('notification', this.NOTIFICATION_SYS_ID);
  	gr.addNullQuery('notification_filter');
  	gr.query();
  	var isUserPrefMobilePushEnabled = gr.next();

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_isUserPrefMobilePushEnabled] cmn_notif_message encodedQuery: ' + gr.getEncodedQuery() + ' isUserPrefMobilePushEnabled: ' + isUserPrefMobilePushEnabled);

  	return isUserPrefMobilePushEnabled;
  },
  _isUserDeviceMobilePushEnabled: function(userSysId) {
  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_isUserDeviceMobilePushEnabled] userSysId: ' + userSysId);

  	if (!userSysId)
  		return false;

  	var gr = new GlideRecord(this.TABLES.CMN_NOTIF_DEVICE);
  	gr.addActiveQuery();
  	gr.addQuery('user', userSysId);
  	gr.addQuery('type', this.CMN_NOTIFY_DEVICE_TYPES.PUSH);
  	gr.query();
  	var isUserDeviceMobilePushEnabled = gr.next();

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_isUserDeviceMobilePushEnabled] cmn_notif_device encodedQuery: ' + gr.getEncodedQuery() + ' isUserDeviceMobilePushEnabled: ' + isUserDeviceMobilePushEnabled);

  	return isUserDeviceMobilePushEnabled;
  },
  _getUserPreferenceByGdt: function (userSysId, gdt) {
  	var userPreferenceGr = new GlideRecord(this.TABLES.ON_CALL_USER_PREFERENCE);
  	userPreferenceGr.addActiveQuery();
  	userPreferenceGr.addQuery('user', userSysId);
  	userPreferenceGr.query();
  	while (userPreferenceGr.next()) {
  		if (this._isGdtinUserPreferenceSchedule(userPreferenceGr, gdt))
  			return userPreferenceGr;
  	}
  	return null;
  },
  _getUserLocalDate: function (userSysId, gdt) {
  	var userTZ = this._getUserTZ(userSysId);
  	var userGdt = gdt ? new GlideDateTime(gdt.getValue()) : new GlideDateTime();
  	userGdt.setTZ(userTZ);
  	return userGdt.getLocalDate();
  },
  _isGdtinUserPreferenceSchedule: function (userPreferenceGr, gdt) {
  	gdt = gdt || new GlideDateTime();
  	var userSysId = userPreferenceGr.user + '';
  	var userTZ = this._getUserTZ(userSysId);
  	if (userPreferenceGr.schedule_type + '' === 'simple') {
  		var isInSchedule = false;
  		var localDate = this._getUserLocalDate(userSysId, gdt);
  		var preferedStartTime = new GlideDateTime(userPreferenceGr.start_time);
  		preferedStartTime.setTZ(userTZ);
  		var startTime = preferedStartTime.getLocalTime();
  		var startTimeGdt = new GlideDateTime();
  		startTimeGdt.setTZ(userTZ);
  		startTimeGdt.setDisplayValueInternal(localDate + ' 00:00:00');
  		startTimeGdt.add(startTime);
  		var preferedEndTime = new GlideDateTime(userPreferenceGr.end_time);
  		preferedEndTime.setTZ(userTZ);
  		var endTime = preferedEndTime.getLocalTime();
  		var endTimeGdt = new GlideDateTime();
  		endTimeGdt.setTZ(userTZ);
  		endTimeGdt.setDisplayValueInternal(localDate + ' 00:00:00');
  		endTimeGdt.add(endTime);
  		gdt.setTZ(userTZ);
  		var isEndTimeOnNextDay = endTimeGdt.onOrBefore(startTimeGdt);
  		if (isEndTimeOnNextDay) {
  			startTimeGdt.addDaysLocalTime(-1);//decrease startTimeGdt by 1 day
  			isInSchedule = gdt.onOrAfter(startTimeGdt) && gdt.onOrBefore(endTimeGdt);
  			if (!isInSchedule) {
  				startTimeGdt.addDaysLocalTime(1);//reset the startTimeGdt happened in above step
  				endTimeGdt.addDaysLocalTime(1);//increase endTimeGdt by 1 day
  				isInSchedule = gdt.onOrAfter(startTimeGdt) && gdt.onOrBefore(endTimeGdt);
  			}
  		}
  		else
  			isInSchedule = gdt.onOrAfter(startTimeGdt) && gdt.before(endTimeGdt);
  		var userPreferredDaysOfWeek = userPreferenceGr.days_of_week + '';
  		var isStartTimeOnUserPreferredDays = userPreferredDaysOfWeek.indexOf(startTimeGdt.getDayOfWeek()) !== -1;
  		return isInSchedule && isStartTimeOnUserPreferredDays;
  	}
  	else {
  		var schedule = this._getCmnSchedule(userPreferenceGr.schedule + '', userSysId);
  		return schedule.isValid() && schedule.isInSchedule(gdt);
  	}
  },
  _getCmnSchedule: function (scheduleSysId, userSysId) {
  	var schedule = new GlideSchedule(scheduleSysId);
  	var cmnScheduleGr = new GlideRecord(this.TABLES.CMN_SCHEDULE);
  	if (cmnScheduleGr.get(scheduleSysId)) {
  		var scheduleTimeZone = cmnScheduleGr.getValue('time_zone');
  		if (!scheduleTimeZone) {
  			//schedule has floating timezone, fallback to user timezone
  			var userTimeZone = this._getUserTimeZone(userSysId);
  			schedule.setTimeZone(userTimeZone);
  		}
  	}
  	return schedule;
  },
  _getUserTimeZone: function (userSysId) {
  	if (!userSysId)
  		return new GlideUser().getSysTimeZone();
  	var userGr = new GlideRecord(this.TABLES.SYS_USER);
  	userGr.get(userSysId);
  	var userTimeZone = userGr.time_zone;
  	if (userTimeZone)
  		return userTimeZone;
  	//user doesn't have timezone, fallback to system timezone
  	return new GlideUser().getSysTimeZone();
  },
  _getUserTZ: function (userSysId) {
  	var userTimeZone = this._getUserTimeZone(userSysId);
  	var schedule = new GlideSchedule();
  	schedule.setTimeZone(userTimeZone);
  	var userTZ = schedule.getTZ();
  	return userTZ;
  },
  _getContactObject: function (type, sysId, rotaId, rosterId, escalationStepId, forcedCommunicationChannel, overrideUserContactPreference, preferenceSource, contactAttempt) {
  	return {
  		type: type,
  		sys_id: sysId,
  		rota_id: rotaId,
  		roster_id: rosterId,
  		escalation_step_id: escalationStepId,
  		forced_communication_channel: forcedCommunicationChannel,
  		override_user_contact_preference: overrideUserContactPreference,
  		preference_source: preferenceSource,
  		contact_attempt: contactAttempt,
  		teams_chat_link: this.onCallCommon.isTeamsPluginInstalled() ? new sn_tcm_collab_hook.MSTeamsChatUtil().getUserChatDeeplink(sysId + '') : ''
  	};
  },
  _getDeviceContactDetails: function (deviceContact, gdt) {
  	gdt = gdt || new GlideDateTime();
  	deviceContactDetails = [];
  	var notifyDeviceGr = new GlideRecord(this.TABLES.CMN_NOTIF_DEVICE);
  	if (notifyDeviceGr.get(deviceContact.sys_id)) {
  		var isDeviceInSchedule = this._isDeviceInSchedule(notifyDeviceGr, gdt);
  		if (isDeviceInSchedule) {
  			var preference = this.getPreferenceFromDevice(notifyDeviceGr);
  			preference.user = notifyDeviceGr.user + '';
  			deviceContactDetails.push(preference);
  		}
  	}
  	return deviceContactDetails;
  },
  _checkIfUserHasNumber: function (userSysId, communicationType) {
  	var numbers = this.notifyUtils.getUniquePhoneNumbersForUsersAndGroups([], [userSysId], [], communicationType);
  	return numbers.length > 0;
  },

  _getDefaultContactPreferenceForUser: function(userSysId) {
  	// No contact preferences or forced communication channel, fallback to default communication channel in below order
  	// Voice - if notify is installed and has a device which supports voice
  	// SMS - if notify is installed and has a device which supports SMS
  	// Slack - if slack integration is installed and user has configured Slack as a preference
  	// Mobile - if mobile is installed and user has enabled both device and preference as Mobile
  	// Email - the final default
  	if (this.isNotifyInstalled) {
  		if (this._checkIfUserHasNumber(userSysId, this.COMMUNICATION_TYPES.VOICE))
  			return [this.COMMUNICATION_TYPES.VOICE];
  		if (this._checkIfUserHasNumber(userSysId, this.COMMUNICATION_TYPES.SMS))
  			return [this.COMMUNICATION_TYPES.SMS];
  	}

  	if (this.isSlackInstalled && new OnCallSlackUtils().isSlackConfigured() && new OnCallSlackUtils().getUserInfo(userSysId).user_id)
  		return [this.COMMUNICATION_TYPES.SLACK];
  	
  	if (this.isTeamsInstalled && new OnCallTeamsUtils().isTeamsNotificationConfigured())
  		return [this.COMMUNICATION_TYPES.TEAMS];

  	if (this.isMobilePushInstalled && this._isUserDeviceMobilePushEnabled(userSysId) && this._isUserPrefMobilePushEnabled(userSysId))
  		return [this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION];

  	return [this.COMMUNICATION_TYPES.EMAIL];
  },
  
  _getDefaultEscalateePreferences: function(escalateeSysId, communicationTypes) {
  	if (!escalateeSysId)
  		return [];
  	
  	if (!communicationTypes) {
  		communicationTypes = [];
  		Object.keys(this.COMMUNICATION_TYPES).forEach(function(key) {
  			communicationTypes.push(this.COMMUNICATION_TYPES[key]);
  		}, this);
  	}
  	var preferences = [];
  	for (var i = 0; i < communicationTypes.length; i++) {
  		var commType = communicationTypes[i] + '';
  		if ((commType === this.COMMUNICATION_TYPES.SMS || commType === this.COMMUNICATION_TYPES.VOICE) && this.isNotifyInstalled) {
  			var numbers = this.notifyUtils.getUniquePhoneNumbersForUsersAndGroups([], [escalateeSysId], [], commType);
  			preferences.push({
  				type: commType,
  				number: numbers[0] || ''
  			});
  		} else if (commType === this.COMMUNICATION_TYPES.EMAIL) {
  			preferences.push({
  				type: commType,
  				email: this._getUserEmailAddress(escalateeSysId)
  			});
  		}
  	}
  	return preferences;
  },
  
  _getUserContactDetailsByPreferences: function (userContact, contactPreferences) {
  	if (!contactPreferences)
  		contactPreferences = [];
  	if (contactPreferences.length === 0)
  		contactPreferences = this._getDefaultContactPreferenceForUser(userContact.sys_id);
  	var userContactDetails = [];
  	for (var i = 0; i < contactPreferences.length; i++) {
  		var preference = contactPreferences[i] + '';

  		if (this.isNotifyInstalled && (preference === this.COMMUNICATION_TYPES.SMS || preference === this.COMMUNICATION_TYPES.VOICE)) {
  			var numbers = this.notifyUtils.getUniquePhoneNumbersForUsersAndGroups([], [userContact.sys_id], [], preference);
  			userContactDetails.push({
  				type: preference,
  				number: numbers[0] || ''
  			});
  		} else if (preference === this.COMMUNICATION_TYPES.EMAIL) {
  			userContactDetails.push({
  				type: preference,
  				email: this._getUserEmailAddress(userContact.sys_id)
  			});
  		} else if (preference === this.COMMUNICATION_TYPES.SLACK) {
  			userContactDetails.push({
  				type: preference,
  				slack: this._getUserEmailAddress(userContact.sys_id)
  			});
  		} else if (preference === this.COMMUNICATION_TYPES.TEAMS) {
  			userContactDetails.push({
  				type: preference,
  				teams: gs.getMessage('Microsoft Teams')
  			});
  		} else if (preference === this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION) {
  			var contactPref = { type: preference };
  			contactPref[this.COMMUNICATION_TYPES.MOBILE_NOTIFICATION] = true;
  			userContactDetails.push(contactPref);
  		}
  	}

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[_getUserContactDetailsByPreferences] userContactDetails: ' + JSON.stringify(userContactDetails));

  	return userContactDetails;
  },
  _getUserEmailAddress: function (userSysId) {
  	var cmnNotifyDeviceGr = new GlideRecord(this.TABLES.CMN_NOTIF_DEVICE);
  	cmnNotifyDeviceGr.addQuery('user', userSysId);
  	cmnNotifyDeviceGr.addQuery('type', 'Email');
  	cmnNotifyDeviceGr.addActiveQuery();
  	cmnNotifyDeviceGr.orderBy('order');
  	cmnNotifyDeviceGr.query();
  	while (cmnNotifyDeviceGr.next()) {
  		if (!this.checkAccess || cmnNotifyDeviceGr.email_address.canRead()) {
  			var emailAddress = cmnNotifyDeviceGr.getValue('email_address');
  			if (emailAddress)
  				return emailAddress;
  		}
  	}
  	//return email address on sys_user record if no notify devices found
  	var userGr = new GlideRecord(this.TABLES.SYS_USER);
  	if (userGr.get(userSysId)) {
  		if (!this.checkAccess || userGr.email.canRead())
  			return userGr.getValue('email');
  	}
  	return '';
  },
  _getRotaContactPreferences: function (rotaGr, contactAttempt, escalatee) {
  	var isCustomEscalation = this._isCustomEscalation(rotaGr);
  	var contactPreferences;
  	if (isCustomEscalation)
  		contactPreferences = this._getCustomEscalationContactPreferences(rotaGr, contactAttempt, escalatee.cmnRotaEscStepDefId);
  	else
  		contactPreferences = this._getRotaDefaultContactPreferences(rotaGr, contactAttempt);
  	return contactPreferences;
  },
  _getCustomEscalationContactPreferences: function (rotaGr, contactAttempt, escStepId) {
  	var contactPreferences = [];
  	var escalationStepGr = new GlideRecord(this.TABLES.CMN_ROTA_ESCALATION_STEP_DEFINITION);
  	escalationStepGr.get(escStepId);
  	var escalationSetSysId = escalationStepGr.escalation_set + '';
  	var escalationSetGr = new GlideRecord(this.TABLES.CMN_ROTA_ESCALATION_SET);
  	if (!escalationSetGr.get(escalationSetSysId))
  		return contactPreferences;
  	var contactPreferenceGr = new GlideRecord(this.TABLES.CMN_ROTA_CONTACT_PREFERENCE);
  	contactPreferenceGr.addQuery('type', 'escalation_set');
  	contactPreferenceGr.addQuery('cmn_rota_escalation_set', escalationSetGr.getUniqueValue());
  	contactPreferenceGr.addQuery('contact_attempt', contactAttempt);
  	contactPreferenceGr.query();
  	if (contactPreferenceGr.next())
  		contactPreferences = this._getCommunicationTypes(contactPreferenceGr.getValue('communication_types'));
  	return contactPreferences;
  },
  _getRotaDefaultContactPreferences: function (rotaGr, contactAttempt) {
  	var contactPreferences = [];
  	var contactPreferenceGr = new GlideRecord(this.TABLES.CMN_ROTA_CONTACT_PREFERENCE);
  	contactPreferenceGr.addQuery('type', 'rota');
  	contactPreferenceGr.addQuery('cmn_rota', rotaGr.getUniqueValue());
  	contactPreferenceGr.addQuery('contact_attempt', contactAttempt);
  	contactPreferenceGr.query();
  	if (contactPreferenceGr.next())
  		contactPreferences = this._getCommunicationTypes(contactPreferenceGr.getValue('communication_types'));
  	return contactPreferences;
  },
  _getCommunicationTypes: function (communicationTypesSysIds) {
  	var communicationTypes = [];
  	if (!communicationTypesSysIds)
  		return communicationTypes;
  	var communicationTypeGr = new GlideRecord(this.TABLES.ON_CALL_COMMUNICATION_TYPE);
  	communicationTypeGr.addQuery('sys_id', 'IN', communicationTypesSysIds);
  	communicationTypeGr.query();
  	while (communicationTypeGr.next())
  		communicationTypes.push(communicationTypeGr.getValue('type'));
  	return communicationTypes;
  },
  _isCustomEscalation: function (rotaGr) {
  	var isGroupEscalation = new OnCallRotation().isGroupEscalationApplied(rotaGr.getUniqueValue());
  	return isGroupEscalation || rotaGr.getValue('use_custom_escalation') + '' === '1';
  },
  isScheduleOverlappingWithOtherPreferences: function (userPreferenceGr) {
  	var isOverlapping = false;
  	var currentPreferenceSchedule = this._getGlideScheduleFromPreference(userPreferenceGr);
  	if (!currentPreferenceSchedule.isValid())
  		return isOverlapping;
  	var otherPreferencesGr = new GlideRecord(this.TABLES.ON_CALL_USER_PREFERENCE);
  	otherPreferencesGr.addActiveQuery();
  	otherPreferencesGr.addQuery('user', userPreferenceGr.user + '');
  	otherPreferencesGr.addQuery('sys_id', '!=', userPreferenceGr.getUniqueValue());
  	otherPreferencesGr.query();
  	while (otherPreferencesGr.next()) {
  		var otherPreferenceSchedule = this._getGlideScheduleFromPreference(otherPreferencesGr);
  		isOverlapping = this._checkForScheduleOverlap(currentPreferenceSchedule, otherPreferenceSchedule);
  		if (otherPreferencesGr.schedule_type + '' === 'simple')
  			this._deleteCmnSchedule(otherPreferenceSchedule.getID());
  		if (isOverlapping)
  			break;
  	}
  	if (userPreferenceGr.schedule_type + '' === 'simple')
  		this._deleteCmnSchedule(currentPreferenceSchedule.getID());
  	return isOverlapping;
  },
  _deleteCmnSchedule: function (sysId) {
  	var cmnScheduleGr = new GlideRecord(this.TABLES.CMN_SCHEDULE);
  	if (cmnScheduleGr.get(sysId)) {
  		cmnScheduleGr.deleteRecord();
  	}
  },
  _checkForScheduleOverlap: function (currentSchedule, otherSchedule) {
  	if (!otherSchedule)
  		return false;
  	var otherScheduleSpanGr = this._getScheduleSpans(otherSchedule.getID());
  	while (otherScheduleSpanGr.next()) {
  		var overlaps = currentSchedule.overlapsWith(otherScheduleSpanGr, currentSchedule.getTimeZone());
  		var isOverlapping = !overlaps.isEmpty();
  		if (isOverlapping)
  			return true;
  	}
  	return false;
  },
  _getScheduleSpans: function (scheduleSysId) {
  	var scheduleSpanGr = new GlideRecord(this.TABLES.CMN_SCHEDULE_SPAN);
  	scheduleSpanGr.addQuery('schedule', scheduleSysId);
  	scheduleSpanGr.query();
  	return scheduleSpanGr;
  },
  _getGlideScheduleFromPreference: function (userPreferenceGr) {
  	if (userPreferenceGr.schedule_type + '' === 'simple')
  		return this._createCmnScheduleFromSimplePreference(userPreferenceGr);
  	else if (userPreferenceGr.schedule_type + '' === 'schedule') {
  		var scheduleSysId = userPreferenceGr.schedule + '';
  		var userSysId = userPreferenceGr.user + '';
  		return this._getCmnSchedule(scheduleSysId, userSysId);
  	}
  },
  getLocalGdtFromGlideTime: function (time, userSysId) {
  	var localDate = this._getUserLocalDate(userSysId);
  	var userTZ = this._getUserTZ(userSysId);
  	var gdt = new GlideDateTime(time);
  	gdt.setTZ(userTZ);
  	var localTime = gdt.getLocalTime();
  	var timeGdt = new GlideDateTime();
  	timeGdt.setTZ(userTZ);
  	timeGdt.setDisplayValueInternal(localDate + ' 00:00:00');
  	timeGdt.add(localTime);
  	return timeGdt;
  },
  
  /*
   * @param number        String
   * @returns {*}         sys_id of the user associated
   */
  getUserFromContactSource: function(number, getUserRecord) {
  	if (!number)
  		return;
  	if (typeof getUserRecord !== 'boolean')
  		getUserRecord = false;
  	var csGr = new GlideRecord(this.TABLES.SYS_ON_CALL_CONTACT_SOURCE);
  	csGr.addQuery('source_type', 'phone_number');
  	csGr.addQuery('active', true);
  	csGr.query();
  	while (csGr.next()) {
  		var gr = new GlideRecord(csGr.table + '');
  		gr.addQuery(csGr.phone_number_source, number);
  		gr.query();
  		if (gr.next()) {
  			var user;
  			if (csGr.table + '' === 'sys_user')
  				user = gr.getValue('sys_id');
  			else
  				user = gr.getValue(csGr.user_field);
  			return (getUserRecord) ? this._getUserByID(user) : user;
  		}
  	}
  },
  
  /**
  * Do a reverse lookup by number to get the associated user. Note that
  * this is currently error prone as the phone number fields for the user
  * and its notification devices (sms, voice) are not E164 phone number
  * fields. Also, when multiple users have the same number configured the
  * first matching user will be returned. To improve the behaviour of this
  * method, upgrade the phone numbers  fields to the E164 compliant phone
  * number field. Checks users, devices and contact sources.
  *
  * @param number        String
  * @returns {*}         sys_id of the user associated
  */
  getUserByNumber: function(number) {
  	if (GlidePluginManager.isActive('com.snc.notify')) {
  		var user = null;
  		// try to lookup any device matching this number
  		user = this.notifyUtils.getUserFromCMNNotifDevice(number);
  		if (user)
  			return user;
  		// check if any sys_user record matched this number
  		user = this.notifyUtils.getSysUserByNumber(number);
  		if (user)
  			return user;
  		// check further in contact sources if none of the above match
  		user = this.getUserFromContactSource(number);
  		if (user)
  			return user;
  	}
  },
  
  _createCmnScheduleFromSimplePreference: function (userPreferenceGr) {
  	var userSysId = userPreferenceGr.user + '';
  	var userTimeZone = this._getUserTimeZone(userSysId);
  	var userTz = this._getUserTZ(userSysId);
  	var cmnScheduleGr = new GlideRecord(this.TABLES.CMN_SCHEDULE);
  	cmnScheduleGr.initialize();
  	cmnScheduleGr.name = userPreferenceGr.user.getDisplayValue();
  	cmnScheduleGr.time_zone = userTimeZone;
  	cmnScheduleGr.type = 'oc_user_preference_simple_schedule';
  	cmnScheduleGr.read_only = false;
  	var cmnScheduleId = cmnScheduleGr.insert();
  	var startTimeGdt = this.getLocalGdtFromGlideTime(userPreferenceGr.start_time, userSysId);
  	var endTimeGdt = this.getLocalGdtFromGlideTime(userPreferenceGr.end_time, userSysId);
  	startTimeGdt.addDaysLocalTime(-4);
  	endTimeGdt.addDaysLocalTime(-4);
  	var isEndTimeOnNextDay = endTimeGdt.before(startTimeGdt) || endTimeGdt.equals(startTimeGdt);
  	if (isEndTimeOnNextDay)
  		endTimeGdt.addDaysLocalTime(1);
  	var start = new GlideScheduleDateTime(startTimeGdt.getNumericValue(), userTz);
  	var end = new GlideScheduleDateTime(endTimeGdt.getNumericValue(), userTz);
  	// create and insert the span for the new schedule
  	var spanGr = new GlideRecord(this.TABLES.CMN_SCHEDULE_SPAN);
  	spanGr.initialize();
  	spanGr.name = userPreferenceGr.user.getDisplayValue();
  	spanGr.schedule = cmnScheduleId;
  	spanGr.setValue('start_date_time', start.getValue());
  	spanGr.setValue('end_date_time', end.getValue());
  	spanGr.repeat_type = 'weekly';
  	spanGr.repeat_count = 1;
  	spanGr.days_of_week = userPreferenceGr.days_of_week + '';
  	spanGr.insert();
  	var schedule = new GlideSchedule(cmnScheduleId);
  	return schedule;
  },
  getPreferencesByContact: function (contact, eliminateDuplicates) {
  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getPreferencesByContact] contact: ' + JSON.stringify(contact));

  	var allPreferences;
  	var contactPreferences = contact.contact_preferences;
  	var userPreferences = contact.user_preferences;
  	if (!contactPreferences)
  		contactPreferences = [];
  	if (!userPreferences)
  		userPreferences = [];
  	allPreferences = contactPreferences.concat(userPreferences);
  	if (eliminateDuplicates)
  		this._removeDuplicatePreferences(allPreferences);

  	if (this._log.atLevel(GSLog.DEBUG))
  		this._log.logDebug('[getPreferencesByContact] allPreferences: ' + JSON.stringify(allPreferences));

  	return allPreferences;
  },
  
  /**
   * Get a user record by sys id
   *
   * @param sysID
   * @returns {GlideRecord}
   * @private
   */
  _getUserByID: function(sysID) {
  	var user = new GlideRecord('sys_user');
  	if (user.get(sysID))
  		return user;
  },

  _removeDuplicatePreferences: function (preferences) {
  	if (!preferences)
  		return;

  	for (var i = 0; i < preferences.length; ++i) {
  		for (var j = i + 1; j < preferences.length; ++j) {
  			if (preferences[i].type + '' === preferences[j].type + '') {
  				if ((preferences[i].type + '' === 'email' && preferences[i].email + '' === preferences[j].email + '') || ((preferences[i].type + '' === 'sms' || preferences[i].type + '' === 'voice') && preferences[i].number + '' === preferences[j].number + ''))
  					preferences.splice(j--, 1);
  			}
  		}
  	}
  },

  getContactPreferences: function(type, sysId) {
  	if (!type || !sysId)
  		return [];
  	var contactPreferenceGr = [];
  	if (type + '' === 'escalation_set')
  		contactPreferenceGr = this._getContactPreferenceByEscalationSet(sysId);
  	else if (type + ''  === 'rota')
  		contactPreferenceGr = this._getContactPreferenceByRota(sysId, type);
  	
  	var contactPreferences = [];
  	var allCommTypes = this.getAllCommunicationTypes();
  	while (contactPreferenceGr.next()) {
  		if (!contactPreferenceGr.canRead()) {
  			throw {
  				message: gs.getMessage('Does not have read permission'),
  				securityError: true
  			};
  		}
  		var contactAttempts = this.onCallCommon.toJS(contactPreferenceGr, [this.ATTRS.CONTACT_ATTEMPT, 'type']);
  		contactAttempts.communication_types = this._filterCommunicationTypes(allCommTypes, contactPreferenceGr.getValue(this.ATTRS.COMMUNICATION_TYPES).split(','));
  		contactPreferences.push(contactAttempts);
  	}
  	return contactPreferences;
  },
  
  _getContactPreferenceByEscalationSet: function (escalationSetSysId) {
  	var contactPreferenceGr = new GlideRecord(this.TABLES.CMN_ROTA_CONTACT_PREFERENCE);
  	contactPreferenceGr.addQuery(this.ATTRS.CMN_ROTA_ESCALATION_SET, escalationSetSysId);
  	contactPreferenceGr.orderBy(this.ATTRS.CONTACT_ATTEMPT);
  	contactPreferenceGr.query();
  	return contactPreferenceGr;
  },
  
  _getContactPreferenceByRota: function (rotaSysId, type) {
  	var contactPreferenceGr = new GlideRecord(this.TABLES.CMN_ROTA_CONTACT_PREFERENCE);
  	contactPreferenceGr.addQuery(this.ATTRS.CMN_ROTA, rotaSysId);
  	type && contactPreferenceGr.addQuery('type', type);
  	contactPreferenceGr.orderBy(this.ATTRS.CONTACT_ATTEMPT);
  	contactPreferenceGr.query();
  	return contactPreferenceGr;
  },
  
  deleteContactPreference: function (contactPrefSysId) {
  	var contactPrefGr = new GlideRecord(this.TABLES.CMN_ROTA_CONTACT_PREFERENCE);
  	if (contactPrefGr.get(contactPrefSysId)) {
  		if (contactPrefGr.canDelete())
  			return contactPrefGr.deleteRecord();
  		else {
  			throw {
  				message: gs.getMessage('Does not have delete permission'),
  				securityError: true
  			};
  		}
  	}
  	else {
  		throw {
  			message: gs.getMessage('Record not found'),
  		};
  	}
  },
  
  _filterCommunicationTypes: function(allCommTypes, filterCommTypes) {
  	var result = allCommTypes.filter(function(item) {
  		if (filterCommTypes.indexOf(item.sys_id.value) === -1)
  			return false;
  		
  		return true;
  	});
  	return result;
  },

  queryAllCommunicationTypeRecords: function() {
  	// get all the records from on_call_communication_type table
  	var commTypesGr = new GlideRecord(this.TABLES.ON_CALL_COMMUNICATION_TYPE);
  	commTypesGr.query();
  	var resp = [];
  	if (commTypesGr.canRead()) {
  		while(commTypesGr.next())
  			resp.push(this.onCallCommon.toJS(commTypesGr, ['type']));
  	}
  	return resp;
  },

  getAllCommunicationTypes: function() {
  	var filteredChoices = [];
  	var choices = this.onCallCommon.getChoiceList(this.TABLES.ON_CALL_COMMUNICATION_TYPE, 'type');
  	var rotaChoices = this.queryAllCommunicationTypeRecords();
  	for(var i = 0; i < choices.length; i++){
  		rotaChoices.forEach(function(choice) {
  			if(choice.type.value + '' === choices[i].value + '') {
  				// adding rotaChoices values to the array
  				filteredChoices.push(choice);
  			}
  		});
  	}
  	return filteredChoices;
  },
  
  saveContactPreference: function(record) {
  	var map = {
  		rota: 'cmn_rota',
  		escalation_set: 'cmn_rota_escalation_set'
  	};
  	
  	var contactPreferenceGr = new GlideRecord(this.TABLES.CMN_ROTA_CONTACT_PREFERENCE);
  	if (!record['sys_id'] && contactPreferenceGr.canCreate()) {
  		if (!record['contact_attempt']) {
  			var maxAttempt =  new OCEscDesignerContactPrefUtilAjax().getHighestOrderContactAttempt(record.type, record[map[record.type]]);
  			record.contact_attempt = maxAttempt + 1;
  		}
  		contactPreferenceGr.initialize();
  		this._setSaveRecordProperties(record, contactPreferenceGr);
  		contactPreferenceGr.insert();
  	}
  	else if(record['sys_id']) {
  		contactPreferenceGr.get(record['sys_id']);
  		this._setSaveRecordProperties(record, contactPreferenceGr);
  		contactPreferenceGr.update();
  	}
  },
  _setSaveRecordProperties: function(record, gr) {
  	for (var key in record) {
  		if (record.hasOwnProperty(key))
  			gr.setValue(key, record[key]);
  	}
  },
  
  getUserFromDeviceOrContactSource: function (contactSourceValue, type) {
  	var notifDeviceSourceMap = {
  		'Voice': 'phone_number',
  		'SMS': 'phone_number',
  		'Email': 'email_address'
  	};
  	
  	var contactSourceMap = {
  		'Voice': {
  			sourceTypeValue: 'phone_number',
  			sourceField: 'phone_number_source'
  		},
  		'SMS': {
  			sourceTypeValue: 'phone_number',
  			sourceField: 'phone_number_source'
  		},
  		'Email': {
  			sourceTypeValue: 'email',
  			sourceField: 'email_source'
  		}
  	};
  	
  	var voiceDeviceGr = new GlideRecord(this.TABLES.CMN_NOTIF_DEVICE);
  	voiceDeviceGr.addQuery(notifDeviceSourceMap[type], contactSourceValue);
  	voiceDeviceGr.addNotNullQuery('user');
  	voiceDeviceGr.addQuery('type', type);
  	voiceDeviceGr.query();
  	if (voiceDeviceGr.next())
  		return voiceDeviceGr.user;

  	var csGr = new GlideRecord(this.TABLES.SYS_ON_CALL_CONTACT_SOURCE);
  	csGr.addQuery('source_type', contactSourceMap[type].sourceTypeValue);
  	csGr.addQuery('active', true);
  	csGr.query();

  	while (csGr.next()) {
  		var gr = new GlideRecord(csGr.table + '');
  		gr.addQuery(csGr[contactSourceMap[type].sourceField], contactSourceValue);
  		gr.query();
  		if (gr.next()) {
  			if (csGr.table + '' === 'sys_user')
  				return gr.getValue('sys_id');
  			else
  				return gr.getValue(csGr.user_field);
  		}
  	}
  },

  canCreateContactOverrides: function() {
  	return this.isNotifyInstalled || this.isSlackInstalled || this.isMobilePushInstalled || this.isTeamsInstalled;
  },

  setCheckAccess: function(checkAccess) {
  	this.checkAccess = checkAccess;
  	this.notifyUtils.setCheckAccess(checkAccess);
  	return this;
  },
  
  type: 'OnCallContactPreferenceUtilSNC'
};

Sys ID

e493a64f57322300fe7cac27cf94f974

Offical Documentation

Official Docs: