Name

sn_comm_management.CommunicationManagementDefaultHandlerSNC

Description

Default handler for Communication Management common tasks. This handler will be used if there is no specific script include shipped in comm_task_handler table. A handler can extend this definition.

Script

var context = this;
var CommunicationManagementDefaultHandlerSNC = Class.create();
CommunicationManagementDefaultHandlerSNC.prototype = {

  initialize: function() {
  	this.arrayUtil = new global.ArrayUtil();
  	this.communicationManagementUtil = new CommunicationManagementUtil();
  	this.contactManagementUtilsSNC = new global.ContactManagementUtilSNC();
  	this.incidentUtilsSNC = new global.IncidentUtilsSNC();
  	this.initializeChannelMap();
  	var startPage = '';
  	var endPage = '';
  	if (typeof global.NotifyConferenceUtils != 'function') {
  		startPage = 'sn_comm_management_conference_call_participants_comm_task';
  		endPage = 'sn_comm_management_conference_call_end_comm_task';
  	} else {
  		startPage = 'notify_conference_dialog';
  		endPage = 'notify_end_conference_dialog';
  	}
  	this.CONFERENCE_PAGES.MANAGE = startPage;
  	this.CONFERENCE_PAGES.END = endPage;
  },

  TABLES: {
  	COMM_CHANNEL: CommunicationManagementUtil.prototype.TABLES.COMM_CHANNEL,
  	COMM_CHANNEL_CONFERENCE: CommunicationManagementUtil.prototype.TABLES.COMM_CHANNEL_CONFERENCE,
  	COMM_PLAN_DEFINITION: CommunicationManagementUtil.prototype.TABLES.COMM_PLAN_DEFINITION,
  	COMM_TASK_DEFINITION: CommunicationManagementUtil.prototype.TABLES.COMM_TASK_DEFINITION,
  	COMM_CHANNEL_DEFINITION: CommunicationManagementUtil.prototype.TABLES.COMM_CHANNEL_DEFINITION,
  	COMM_CONTACT_DEFINITION: 'comm_contact_definition',
  	COMM_TASK_HANDLER: CommunicationManagementUtil.prototype.TABLES.COMM_TASK_HANDLER,
  	COMM_PLAN: CommunicationManagementUtil.prototype.TABLES.COMM_PLAN,
  	COMM_TASK: CommunicationManagementUtil.prototype.TABLES.COMM_TASK,
  	CONTACT: CommunicationManagementUtil.prototype.TABLES.CONTACT
  },

  COLUMNS: {
  	COMM_PLAN_DEFINITION: 'comm_plan_definition',
  	COMM_TASK_DEFINITION: 'comm_task_definition',
  	COMM_TASK_TABLE: 'comm_task_table',
  	SYS_CLASS_NAME: 'sys_class_name',
  	SOURCE: 'source',
  	SYS_ID: 'sys_id',
  	CONDITION: 'condition',
  	CONDITION_BASED: 'condition_based',
  	TABLE: 'table',
  	TYPE: 'type',
  	DOCUMENT: 'document',
  	ORDER: 'order',
  	COMMUNICATION_FREQUENCY: 'communication_frequency',
  	COMM_PLAN: 'comm_plan',
  	STATE: 'state',
  	HANDLER_SI: 'handler_si',
  	COMM_TASK: CommunicationManagementUtil.prototype.COLUMNS.COMM_TASK,
  	COMM_CHANNEL_CLASSIFICATION: CommunicationManagementUtil.prototype.COLUMNS.COMM_CHANNEL_CLASSIFICATION
  },

  CHANNEL_CLASSIFICATIONS: CommunicationManagementUtil.prototype.CHANNEL_CLASSIFICATIONS,

  CONTACT_TYPE: {
  	USER: 'sys_user',
  	GROUP: 'sys_user_group',
  	CONSUMER: 'csm_consumer',
  	RECIPIENT: 'sn_publications_recipients_list'
  },

  PROPERTIES: {
  	NOTIFY_NUMBER: 'com.snc.tcm.notify_number',
  	ENABLE_NOTIFY: 'com.snc.tcm.enable_notify',
  	ESCALATION_LEVEL: 'com.snc.tcm.on_call_escalation_level',
  	MAX_CONTACT_LIMIT_EMAIL: 'sn_comm_management.com.snc.tcm.max_contact_limit_email',
  	MAX_CONTACT_LIMIT_SMS: 'sn_comm_management.com.snc.tcm.max_contact_limit_sms',
  	CONFERENCE_CALL_ESCALATION_WORKFLOW: 'sn_comm_management.com.snc.tcm.conference_call_escalation_workflow',
  	ESCALATE_CONFERENCE: 'sn_comm_management.com.snc.tcm.conference_call_follow_on_call_escalation',
  	ADD_ALL_MEMBERS_ON_NO_ROTA_DEFINED: 'sn_comm_management.add_all_members_on_no_rota_defined'
  },

  COMMUNICATION_FREQUENCY_TYPE: {
  	RECURRING: 'recurring',
  	ONE_TIME: 'one_time'
  },

  COMM_TASK_STATES: {
  	PENDING: -5,
  	OPEN: 1,
  	WIP: 2,
  	COMPLETED: 3,
  	CANCELED: 4,
  	SKIPPED: 7
  },

  COMM_PLAN_STATES: {
  	NEW: 'new',
  	WIP: 'work_in_progress',
  	CLOSED: 'closed',
  	CANCELLED: 'cancelled'
  },

  COMM_TASK_ACTIONS: {
  	SKIP: 'skip',
  	SNOOZE: 'snooze',
  	CLOSE: 'close',
  	START: 'start'
  },

  CONFERENCE_PAGES: typeof global.NotifyConferenceUtils == 'function' ?
  	{
  		MANAGE: 'notify_conference_dialog',
  		END: 'notify_end_conference_dialog'
  	}:
  	{
  		MANAGE: 'add_to_conference',
  		END: 'sn_comm_management_conference_call_end_comm_task'
  	},

  DEFAULT_TEMPLATES: {
  	EMAIL: '5925caa5875013000e3dd61e36cb0b43',
  	SMS: '064a4265871013000e3dd61e36cb0b85'
  },

  /*Used to initialize channel related meta data*/
  initializeChannelMap: function() {
  	//This call is not going to populate 'channel.available' information as it is dependent on source table. Call explicitly with sourceTable if that info is needed.
  	this.channelMap = this.communicationManagementUtil.getChannelMap();
  },

  getCommTaskVariables: function(tableName) {
  	var commTaskVariables = {};
  	commTaskVariables.default_email_client_template = this.DEFAULT_TEMPLATES.EMAIL;
  	commTaskVariables.default_sms_template = this.DEFAULT_TEMPLATES.SMS;
  	commTaskVariables.default_recipient_field = this.communicationManagementUtil.getDefaultEmailRecipientField(tableName);
  	return commTaskVariables;
  },

  /*Override to set instance table of comm_plan for a Task record*/
  getCommPlanInstanceForTask: function() {
  	return this.TABLES.COMM_PLAN;
  },

  /*Override to set instance table of comm_task for a Task record*/
  getCommTaskInstanceForTask: function() {
  	return this.TABLES.COMM_TASK;
  },

  attachPlans: function(sourceRecordGr) {
  	var planDefsAlreadyAttached = [];
  	var planInstanceTable = this.getCommPlanInstanceForTask();
  	var cpmw = new global.CommunicationPlanMutexWrapper(sourceRecordGr.sys_id);
  	if (cpmw.getLock()) {
  		try {
  			//Piggy back on incidentUtils to use addDomainQuery
  			var planInstanceCheckGr = this.incidentUtilsSNC.getPlanInstanceGr(planInstanceTable, sourceRecordGr);
  			while (planInstanceCheckGr.next()) {
  				if (planDefsAlreadyAttached.indexOf(planInstanceCheckGr.getValue(this.COLUMNS.COMM_PLAN_DEFINITION)) == -1)
  					planDefsAlreadyAttached.push(planInstanceCheckGr.getValue(this.COLUMNS.COMM_PLAN_DEFINITION));
  			}
  			//Piggy back on incidentUtils to use addDomainQuery
  			var planDefGr = this.incidentUtilsSNC.getPlanDefinitionGr(sourceRecordGr, planDefsAlreadyAttached);
  			var planInstanceGr;
  			while(planDefGr.next()) {
  				var conditions = planDefGr.getValue(this.COLUMNS.CONDITION);
  				if (gs.nil(conditions) || GlideFilter.checkRecord(sourceRecordGr, conditions)) {
  					planInstanceGr = new GlideRecord(planInstanceTable);
  					planInstanceGr.newRecord();
  					this.peparePlanInstanceGr(planDefGr, planInstanceGr, sourceRecordGr);
  					this.updatePlanDetails(planDefGr, planInstanceGr, sourceRecordGr);
  				}
  			}
  		} finally {
  			cpmw.releaseLock();
  		}
  	}
  	else
  		gs.error("cannot get hold of the TCM lock for task" + sourceRecordGr.sys_id);
  },

  /*Override to set specific extension fields*/
  peparePlanInstanceGr: function(planDefGr, planInstanceGr, sourceRecordGr) {
  	planInstanceGr.source = sourceRecordGr.getUniqueValue();
  	planInstanceGr.comm_plan_definition = planDefGr.getUniqueValue();
  },

  updatePlanDetails: function(planDefGr, planInstanceGr, sourceRecordGr) {
  	var isDirty = false;
  	if (planInstanceGr.sys_domain != sourceRecordGr.sys_domain) {
  		planInstanceGr.sys_domain = sourceRecordGr.sys_domain;
  		isDirty = true;
  	}
  	if (gs.nil(planInstanceGr.short_description) && !gs.nil(planDefGr.name)) {
  		planInstanceGr.short_description = planDefGr.name;
  		isDirty = true;
  	}
  	if (gs.nil(planInstanceGr.description) && !gs.nil(planDefGr.description)) {
  		planInstanceGr.description = planDefGr.description;
  		if (!isDirty)
  			isDirty = true;
  	}
  	if (gs.nil(planInstanceGr.comm_plan_type) && !gs.nil(planDefGr.comm_plan_type)) {
  		planInstanceGr.comm_plan_type = planDefGr.comm_plan_type;
  		if (!isDirty)
  			isDirty = true;
  	}
  	if (gs.nil(planInstanceGr.order) && !gs.nil(planDefGr.order)) {
  		planInstanceGr.order = planDefGr.order;
  		if (!isDirty)
  			isDirty = true;
  	}
  	if (planInstanceGr.isNewRecord())
  		planInstanceGr.insert();
  	else if (isDirty)
  		planInstanceGr.update();
  },

  createCommunicationTasks: function(sourceRecordGr, planInstanceGr) {
  	//Piggy back on incidentUtils to use addDomainQuery
  	var planTaskDefGr = this.incidentUtilsSNC.getTaskDefinitionGr(planInstanceGr);
  	while (planTaskDefGr.next())
  		this.createCommTaskAndChannels(planTaskDefGr, planInstanceGr, sourceRecordGr);
  },

  prepareFieldForCommunicationTask: function(commTaskGr) {
  	var requiredFields = ['number', 'sys_class_name', 'state', 'last_communication', 'order', 'short_description', 'sys_created_on', 'due_date', 'description', 'sys_id', 'reminder_frequency', 'periodicity', 'closed_at', 'active', 'communication_frequency', 'comm_count', 'assigned_to', 'due_in', 'sys_updated_on'];
  	return this.communicationManagementUtil.toJS(commTaskGr, requiredFields);
  },

  prepareFieldForCommunicationChannel: function(commChannelGr) {
  	var configGr = commChannelGr.comm_channel_config.getRefRecord();
  	var typeValue = configGr.type + '';
  	var channelApi = this.channelMap[typeValue].api_name;
  	var channelApiInstance = this.communicationManagementUtil.getScriptInstanceFromApiName(context, channelApi);
  	var jsonOb = channelApiInstance.getCommunicationChannelDetails(commChannelGr, configGr);
  	return jsonOb;
  },

  prepareFieldForCommunicationPlan: function(commPlanGr) {
  	var requiredFields = ['sys_id', 'short_description', 'number', 'sys_class_name', 'order', 'sys_created_on', 'comm_plan_type'];
  	return this.communicationManagementUtil.toJS(commPlanGr, requiredFields);
  },

  getCommunicationDetails: function(sysId, filterCriteria) {
  	var respData = {
  		comm_plan: [],
  		contact_types: [],
  		permissions: {}
  	};

  	var hasValidPhoneNumber = false;
  	if(GlidePluginManager.isActive('com.snc.notify')) {
  		var notifyUtils = null;
  		if(typeof global.NotifyUtil != 'undefined')
  			notifyUtils = new global.NotifyUtil();
  		else
  			notifyUtils = new global.NotifyUtils();
  		var numbers = notifyUtils.getUniquePhoneNumbersForUsersAndGroups([], [gs.getUserID()], [], notifyUtils.numberType.voice);
  		hasValidPhoneNumber = numbers.length > 0;
  	}

  	var commPlanGr = new GlideRecord(this.getCommPlanInstanceForTask());
  	commPlanGr.addQuery("source", sysId);
  	if(filterCriteria.sysparm_query)
  		commPlanGr.addEncodedQuery(filterCriteria.sysparm_query);
  	if(filterCriteria && filterCriteria.orderBy){
  		if(filterCriteria.orderType == "desc"){
  			commPlanGr.orderByDesc(filterCriteria.orderBy);
  		} else
  			commPlanGr.orderBy(filterCriteria.orderBy);
  	}
  	if(filterCriteria && filterCriteria.maxRows)
  		commPlanGr.setLimit(filterCriteria.maxRows);
  	commPlanGr.query();
  	while (commPlanGr.next()) {
  		var jsCommPlan = this.prepareFieldForCommunicationPlan(commPlanGr);
  		if (jsCommPlan) {
  			jsCommPlan.comm_tasks = this.getCommunicationTaskForPlan(commPlanGr, hasValidPhoneNumber);
  			jsCommPlan.chatRecipient = this.communicationManagementUtil.getContactsForChat(commPlanGr.sys_id, commPlanGr.getRecordClassName());
  			respData.comm_plan.push(jsCommPlan);
  			jsCommPlan.recipients = [];
  			var recipientsGr = this.communicationManagementUtil.getCommPlanRecipients(commPlanGr);
  			while(recipientsGr.next()) {
  				if (recipientsGr.canRead()) {
  					var recipientJs = this.communicationManagementUtil.toJS(recipientsGr);
  					recipientJs.can_manage = this.contactManagementUtilsSNC.canManageRecipient(recipientsGr);
  					jsCommPlan.recipients.push(recipientJs);
  				}
  			}
  		}
  	}
  	respData.labels = {
  		comm_task: this.communicationManagementUtil.getTableFieldsMap(this.getCommTaskInstanceForTask())
  	};
  	respData.states = {
  		comm_task: this.communicationManagementUtil.getStates(this.getCommTaskInstanceForTask(), 'state')
  	};
  	respData.contact_types = this.communicationManagementUtil.getContactTypes();
  	respData.permissions.manage_recipients = this.contactManagementUtilsSNC.canManageRecipients();
  	var canCreateTask = this.incidentUtilsSNC.canCreateRecord(this.getCommTaskInstanceForTask());
  	respData.permissions.add_adhoc_communication = canCreateTask && this.incidentUtilsSNC.canCreateRecord(this.getCommPlanInstanceForTask());
  	respData.permissions.add_adhoc_task = canCreateTask;
  	// conference details
  	respData.activeCalls = this.getConferenceOrChatData(respData);
  	if(GlidePluginManager.isActive('com.snc.notify')) {
  		var notifyInfo = {
  			number: gs.getProperty(this.PROPERTIES.NOTIFY_NUMBER, ''),
  			enabled: gs.getProperty(this.PROPERTIES.ENABLE_NOTIFY, 'false'),
  			capabilities: {
  				speaking: false
  			},
  			serviceProviderDetails: {}
  		};
  		if (typeof global.NotifyConferenceUtils == 'function') {
  			var confUtils = new global.NotifyConferenceUtils();
  			notifyInfo.serviceProviderDetails = confUtils.getServiceProviderDetails();
  			notifyInfo.selfJoinStatusMap = confUtils.getSelfJoinStatusMap();
  		}
  		try {
  			notifyInfo.capabilities.speaking = new sn_notify.NotifyScoped().hasCapability(gs.getProperty(this.PROPERTIES.NOTIFY_NUMBER, ''), 'show_speakers');
  		} catch (e) {
  		}
  		respData.notifyInfo = notifyInfo;
  		respData.userInfo = {
  			userID: gs.getUserID()
  		};
  		respData.conf_pages = {};
  		respData.conf_pages.manage = this.getConferenceManagementPageName();
  		respData.conf_pages.end = this.getConferenceEndPageName();
  		respData.automation_urls = this.getAutomationUrls();
  	}
  	return respData;
  },
  
  getAutomationUrls: function() {
  	if(GlidePluginManager.isActive('sn_tcm_collab_hook') && GlidePluginManager.isActive('sn_now_teams')) {
  		var teamsConfigProvider = new sn_tcm_collab_hook.MSTeamsChatConfigProvider();
  		var teamsUrl = teamsConfigProvider.getTeamsUrl();
  		var teamsAppUrl = teamsConfigProvider.getTeamsAppUrl();
  		var azureLoginUrl = teamsConfigProvider.getAzureLoginUrl();
  		var msGraphUrl = teamsConfigProvider.getMSGraphUrl();
  		return {
  			"teamsUrl": teamsUrl,
  			"teamsAppUrl": teamsAppUrl,
  			"azureLoginUrl": azureLoginUrl,
  			"msGraphUrl": msGraphUrl
  		};
  	}
  	return {};
  },

  getConferenceCallJS: function(notifyGr) {
  	var jsFields = ['active', 'source', 'number', 'code', 'duration', 'sys_created_on', 'sys_updated_on', 'notify_number', 'service_provider', 'state', 'service_provider_data', 'conference_title'];

  	result =  this.communicationManagementUtil.toJS(notifyGr, jsFields);

  	if(result.notify_number.display_value == '')
  		result = this.communicationManagementUtil.getDialInNumber(notifyGr, result);

  	return result;
  },

  getConferenceCallParticipantJS: function(notifyParticipantGr) {
  	var jsFields = ['notify_conference_call', 'active', 'user', 'group', 'muted', 'sys_created_on', 'sys_updated_on', 'phone_number', 'speaking', 'email', 'initiator', 'service_provider_user_name', 'service_provider_data'];
  	return this.communicationManagementUtil.toJS(notifyParticipantGr, jsFields);
  },

  addRecordingUrl: function(confCallSysId) {
  	var obj = {};
  	var notifyGr = new GlideRecord('notify_recording');
  	notifyGr.addQuery('notify_conference_call', confCallSysId);
  	notifyGr.setLimit(1);
  	notifyGr.query();
  	if (notifyGr.next()) {
  		obj.display_value = "Recording";
  		obj.value = notifyGr.url + '';
  	}
  	return obj;
  },
  
  appendChatData: function(commTaskGr, channelTable) {
  	var data = {};
  	var teamsChatGr = new GlideRecord(channelTable.table);
  	teamsChatGr.addQuery(channelTable.source_table, commTaskGr.sys_class_name.value);
  	teamsChatGr.addQuery(channelTable.source, commTaskGr.sys_id.value);
  	teamsChatGr.setLimit(1);
  	teamsChatGr.query();
  	if (teamsChatGr.next()) {
  		var userGr = new GlideRecord('sys_user');
  		userGr.addActiveQuery();
  		if (userGr.get(teamsChatGr.getValue(channelTable.initiated_by))) {
  			data.display_value = userGr.getDisplayValue();
  			data.value = userGr.email.getDisplayValue();
  			data.sysId = userGr.getUniqueValue();
  			data.is_private = teamsChatGr.getValue('is_private');
  		}
  	}
  	return data;
  },
  
  getConferenceOrChatData: function(data) {
  	var confSysIds = [];
  	for (var i = 0; i < data.comm_plan.length; i++) {
  		for (var j = 0; j < data.comm_plan[i].comm_tasks.length; j++) {
  			var channels = data.comm_plan[i].comm_tasks[j].channels;
  			if(channels.length != 1)
  				continue;
  			if(channels[0].channelType.value != 'conference') {
  				if (channels[0].is_collaboration_chat)
  					channels[0].chatInitiatedBy = this.appendChatData(data.comm_plan[i].comm_tasks[j], channels[0].channelTable);
  				continue;	
  			}
  			if(GlidePluginManager.isActive('com.snc.notify')) {
  				if(!channels[0].notify_conference_call.value)
  					continue;
  				channels[0].notify_recording_url = this.addRecordingUrl(channels[0].notify_conference_call.value);
  				confSysIds.push(channels[0].notify_conference_call.value);
  			}
  		}
  	}
  	if(GlidePluginManager.isActive('com.snc.notify')) {
  		var notifyGr = new GlideRecord('notify_conference_call');
  		notifyGr.addQuery('sys_id', 'IN', confSysIds);
  		notifyGr.orderByDesc('sys_created_on');
  		notifyGr.query();
  		var activeCallsInfo = {
  		};
  		var activeCallSysIds = [];
  		while (notifyGr.next()) {
  			var sysId = notifyGr.sys_id + '';
  			activeCallsInfo[sysId] = this.getConferenceCallJS(notifyGr);
  			activeCallSysIds.push(sysId);
  		}

  		var notifyParticipantGr = new GlideRecord('notify_participant');
  		notifyParticipantGr.addQuery('notify_conference_call', 'IN', activeCallSysIds);
  		notifyParticipantGr.addQuery('notify_conference_call.active', 'true');
  		notifyParticipantGr.query();
  		while (notifyParticipantGr.next()) {
  			var participantInfo = this.getConferenceCallParticipantJS(notifyParticipantGr);
  			var callInfo = activeCallsInfo[participantInfo.notify_conference_call.value];
  			callInfo.participants = callInfo.participants || {};
  			callInfo.participants[participantInfo.sys_id.value] = participantInfo;
  		}
  		return activeCallsInfo;
  	}
  },
  doConferenceMutingAction: function (confId, muteAction) {
  	var muted;
  	if (muteAction + '' == 'multiMute')
  		muted = false;
  	else if (muteAction + '' == 'multiUnmute')
  		muted = true;

  	var confParticipantGr = new GlideRecord('notify_participant');
  	confParticipantGr.addActiveQuery();
  	confParticipantGr.addQuery('notify_conference_call', confId);
  	confParticipantGr.addQuery('muted', muted);
  	confParticipantGr.query();

  	while (confParticipantGr.next()) {
  		if (confParticipantGr.user + '' == gs.getUserID())
  			continue;

  		if (muteAction + '' == 'multiMute') {
  			new sn_notify.NotifyScoped().mute(confParticipantGr);
  		} else if (muteAction + '' == 'multiUnmute') {
  			new sn_notify.NotifyScoped().unmute(confParticipantGr);
  		}
  	}
  },

  doNotifyParticipantAction: function (action, actionInfo) {

  	var nutils = new global.NotifyUtils();
  	if (action === 'self_join' || action === 'anonymous_join' || action === 'join') {
  		var confGr = new GlideRecord('notify_conference_call');
  		if (!confGr.get(actionInfo.confId))
  			return false;

  		if (actionInfo.phNum && nutils.isParticipantSessionActiveForPhoneNumber(actionInfo.phNum, confGr.getUniqueValue()))
  			return false;

  		if (actionInfo.userId && nutils.isParticipantSessionActive(actionInfo.userId, confGr.getUniqueValue()))
  			return false;

  		var taskNotify = new sn_comm_management.CommTaskNotifyAjax();
  		taskNotify.call(actionInfo.userId || actionInfo.phNum, confGr);
  		return true;
  	}
  	if (!actionInfo.sysId)
  		return;

  	var gr = new GlideRecord('notify_participant');
  	if (!gr.get(actionInfo.sysId))
  		return new sn_ws_err.BadRequestError("Notify record not found");

  	var isLeader = false;
  	var sourceTask = gr.notify_conference_call.source;
  	if (gr.notify_conference_call.table == this.getCommTaskInstanceForTask()) {
  		var commTaskGr = new GlideRecord(this.getCommTaskInstanceForTask());
  		if (sourceTask && commTaskGr.get(sourceTask)) {
  			isLeader = new sn_comm_management.CommTaskNotifyUtils().isConferenceLeader(commTaskGr);
  		}
  	}
  	if (!isLeader)
  		return new sn_ws_err.BadRequestError("User doesn't have role to do this action");

  	if (action == 'kick') {
  		if (!gr.active) {
  			return {
  				action: 'change',
  				changes: ['active'],
  				record: {
  					active: {
  						value: 'false',
  						display_value: 'false'
  					}
  				}
  			};
  		}
  		new sn_notify.NotifyScoped().kick(gr);
  	} else if (action == 'mute') {
  		if (!!gr.muted) {
  			//return new sn_ws_err.BadRequestError("User is already in mute state");
  			return {
  				action: 'change',
  				changes: ['muted'],
  				record: {
  					muted: {
  						value: 'true',
  						display_value: 'true'
  					}
  				}
  			};
  		}
  		new sn_notify.NotifyScoped().mute(gr);
  	} else if (action == 'unmute') {
  		if (gr.muted + '' == 'false') {
  			return {
  				action: 'change',
  				changes: ['muted'],
  				record: {
  					muted: {
  						value: 'false',
  						display_value: 'false'
  					}
  				}
  			};
  		}
  		new sn_notify.NotifyScoped().unmute(gr);
  	} else if (action == 'join') {
  		if (!!gr.active) {
  			return {
  				action: 'change',
  				changes: ['active'],
  				record: {
  					active: {
  						value: 'true',
  						display_value: 'true'
  					}
  				}
  			};
  		}

  		if (!new global.NotifyUtils().isParticipantSessionActive(gr.user + '', actionInfo.confId)) {
  			var taskNotify = new sn_comm_management.CommTaskNotifyAjax();
  			taskNotify.call(gr.user + '', gr.notify_conference_call.getRefRecord());
  		}

  	}
  	return {
  		action: action,
  		actionInfo: actionInfo
  	};
  },


  getCommunicationTaskForPlan: function(commPlanGr, hasValidPhoneNumber) {
  	var comm_task = [];
  	var commTaskGr = new GlideRecord(this.getCommTaskInstanceForTask());
  	commTaskGr.addQuery("comm_plan", commPlanGr.getUniqueValue());
  	commTaskGr.orderBy("order");
  	commTaskGr.orderByDesc('sys_created_on');
  	commTaskGr.query();
  	while (commTaskGr.next()){
  		var jsCommTask = this.prepareFieldForCommunicationTask(commTaskGr);
  		jsCommTask.channels = this.getChannelsForTask(commTaskGr);
  		jsCommTask.canSendUpdates = this.canSendUpdates(commTaskGr);
  		jsCommTask.canStart = this.canStart(commTaskGr);
  		jsCommTask.canSnooze = this.canSnooze(commTaskGr);
  		jsCommTask.canClose = this.canClose(commTaskGr);
  		if (GlidePluginManager.isActive('com.snc.tcm_collab_hook') && this.isVersionSupported())
  			jsCommTask.isCollabLeader = this.isCollaborateLeader(commTaskGr);
  		if (GlidePluginManager.isActive('com.snc.notify')) {
  			jsCommTask.isLeader = new sn_comm_management.CommTaskNotifyUtils().isConferenceLeader(commTaskGr);
  			jsCommTask.hasValidPhoneNumber = hasValidPhoneNumber;
  		}
  		jsCommTask.canViewCollaborationActions = this.canViewCollaborationActions();
  		
  		comm_task.push(jsCommTask);
  	}
  	return comm_task;
  },
  
  isVersionSupported: function() {
  	if (sn_tcm_collab_hook.CollaborationUtils) {
  		if (new sn_tcm_collab_hook.CollaborationUtils().getVersion() >= 2)
  			return true;
  	}
  	return false;
  },
  
  isCollaborateLeader: function(gr) {
  	var userId = gs.getUserID();
  	var gUser = new global.IncidentUtils().getUserByIdObj(userId);
  	if (gr.getRecordClassName() == 'incident_alert_task') {
  		if (gr.active && gUser.hasRole('itil,sn_incident_write') && (gr.assigned_to == userId || gr.incident_alert.assigned_to == userId)) 
  			return true;
  	}
  	return !!(gr.active && (gUser.hasRole('sn_comm_management.comm_plan_manager') || gUser.hasRole('sn_comm_management.comm_plan_admin')));
  },
  
  canViewCollaborationActions: function() {
  	if (gs.getUser().hasRole('sn_comm_management.comm_plan_manager'))
  		return true;
  	return false;	
  },

  canSendUpdates: function(commTaskGR) {
  	var channelGR = new GlideRecord(this.TABLES.COMM_CHANNEL);
  	channelGR.addQuery(this.COLUMNS.COMM_TASK, commTaskGR.sys_id);
  	channelGR.addQuery(this.COLUMNS.COMM_CHANNEL_CLASSIFICATION, this.CHANNEL_CLASSIFICATIONS.UPDATE);
  	channelGR.query();
  	return (channelGR.hasNext() && commTaskGR.active == true && commTaskGR.state.canWrite());
  },

  canStart: function(commTaskGr) {
  	return commTaskGr.state == this.COMM_TASK_STATES.PENDING && commTaskGr.state.canWrite();
  },

  canSnooze: function(commTaskGr) {
  	return commTaskGr.state != this.COMM_TASK_STATES.PENDING && commTaskGr.communication_frequency == this.COMMUNICATION_FREQUENCY_TYPE.RECURRING && commTaskGr.state.canWrite();
  },

  canClose: function(commTaskGr) {
  	return commTaskGr.state != this.COMM_TASK_STATES.PENDING && commTaskGr.state.canWrite();
  },

  getCommTaskStates: function() {
  	return this.COMM_TASK_STATES;
  },

  getChannelsForTask: function(commTaskGr) {
  	var channels = [];
  	var channelMap = this.channelMap;
  	for (var channel in channelMap) {
  		var table = channelMap[channel].inst_table;
  		var commChannelGr = new GlideRecord(table);
  		commChannelGr.addQuery(this.COLUMNS.COMM_TASK, commTaskGr.getUniqueValue());
  		commChannelGr.query();
  		while(commChannelGr.next()){
  			channels.push(this.prepareFieldForCommunicationChannel(commChannelGr));
  		}
  	}
  	return channels;
  },

  getChannels: function(planTaskDef, planTaskGr, sourceRecordGr) {
  	if (!planTaskDef)
  		return;

  	var defChannels = [];
  	var channelMap = this.channelMap;
  	for (var channel in channelMap) {
  		var channelDefGr = this.incidentUtilsSNC.getChannelDefintionGr(channelMap[channel].def_table, planTaskDef, planTaskGr, channel);
  		if (!channelDefGr)
  			continue;
  		while (channelDefGr.next())
  			defChannels.push({channelMetaObj: channelMap[channel], channelDefGr: channelDefGr});

  	}
  	return defChannels;
  },

  updateTaskStateWBActions: function(commTaskSysId, action) {
  	/*Override to implement task state management from WorkBench - See CommunicationManagementIncidentHandlerSNC for implementation for Major Incident Management WB*/
  },

  createCommTaskAndChannels: function(planTaskDefGr, planInstanceGr, sourceRecordGr) {
  	var planTaskGr = new GlideRecord(this.getCommTaskInstanceForTask());
  	planTaskGr.newRecord();
  	this.prepareTaskInstanceGr(planTaskGr, planTaskDefGr, planInstanceGr);
  	planTaskGr.insert();

  	var taskChannels = this.getChannels(planTaskDefGr.getUniqueValue(), planTaskGr, sourceRecordGr);
  	for (var  j = 0; j < taskChannels.length;  j++) {
  		this.createChannelInstance(taskChannels[j].channelMetaObj, planTaskGr, taskChannels[j].channelDefGr);
  	}
  },

  /*Override to set specific extension fields*/
  prepareTaskInstanceGr: function(planTaskGr, planTaskDefGr, planInstanceGr) {
  	planTaskGr.comm_task_definition = planTaskDefGr.getUniqueValue();
  	planTaskGr.comm_plan = planInstanceGr.getUniqueValue();
  	planTaskGr.sys_domain = planInstanceGr.sys_domain;
  	planTaskGr.order = planTaskDefGr.order;
  	planTaskGr.short_description = planTaskDefGr.name;
  	planTaskGr.description = planTaskDefGr.description;
  	planTaskGr.comm_task_type = planTaskDefGr.comm_task_type;
  	planTaskGr.communication_frequency = planTaskDefGr.communication_frequency;
  	planTaskGr.due_in = planTaskDefGr.due_in;
  },

  createChannelInstance: function(channelObj, planTaskGr, channelDefGr) {
  	var channelInstance = new GlideRecord(channelObj.inst_table);
  	if (!channelInstance.isValid())
  		return;

  	channelInstance.initialize();
  	channelInstance.comm_task = planTaskGr.getUniqueValue();
  	channelInstance.sys_domain = planTaskGr.sys_domain;
  	channelInstance.comm_channel_definition = channelDefGr.getUniqueValue();
  	channelInstance.notify_group_selector = channelDefGr.notify_group_selector + '';
  	var channelApi = channelObj.api_name;
  	var channelApiInstance = this.communicationManagementUtil.getScriptInstanceFromApiName(context, channelApi);
  	var commTaskVariables = this.getCommTaskVariables(planTaskGr.comm_plan.source.getRefRecord().getRecordClassName());
  	channelApiInstance.updateChannelFromDef(channelInstance, channelDefGr, planTaskGr, commTaskVariables);
  	channelInstance.insert();
  },

  createContactsFromContactDefinition: function(planGr) {
  	if (planGr.comm_plan_definition) {
  		var existingContact = this.contactManagementUtilsSNC.getExistingContacts(planGr);
  		//Piggy back on incidentUtils to use addDomainQuery
  		var contactDef = this.incidentUtilsSNC.getContactDefinitionGr(planGr);
  		while (contactDef.next()) {
  			var contacts = new GlideRecord(this.TABLES.CONTACT);
  			contacts.initialize();
  			contacts.table = planGr.getRecordClassName();
  			contacts.document = planGr.sys_id;
  			contacts.responsibility = contactDef.contact_responsibility;
  			contacts.type = contactDef.type;
  			contacts.sys_domain = planGr.sys_domain;
  			contacts.classification = this.TABLES.COMM_CONTACT_DEFINITION;
  			var options = contacts.type + '';
  			switch (options) {
  				case this.CONTACT_TYPE.USER:
  					if (!this.arrayUtil.contains(existingContact, contactDef.sys_user)) {
  						contacts.user = contactDef.sys_user;
  						contacts.insert();
  					}
  					break;
  				case this.CONTACT_TYPE.GROUP:
  					if (!this.arrayUtil.contains(existingContact, contactDef.sys_user_group)) {
  						contacts.group = contactDef.sys_user_group;
  						contacts.insert();
  					}
  					break;
  				case this.CONTACT_TYPE.CONSUMER:
  					if (!this.arrayUtil.contains(existingContact, contactDef.csm_consumer)) {
  						contacts.consumer = contactDef.csm_consumer;
  						contacts.insert();
  					}
  					break;
  				case this.CONTACT_TYPE.RECIPIENT:
  					if (!this.arrayUtil.contains(existingContact, contactDef.recipients_list)) {
  						contacts.recipient_list = contactDef.recipients_list;
  						contacts.insert();
  					}
  					break;
  				default:
  					gs.log("Not a valid contact Type");
  			}
  		}
  	}
  },

  //Defines rules of when a add channel action can show up on commTask
  allowAddChannelOnCommTask: function(commTaskGr, ignoreACL, instanceTable) {
  	if(ignoreACL)
  		return commTaskGr.active;
  	else if(instanceTable) {
  		var commChannelGr = new GlideRecord(instanceTable);
  		commChannelGr.newRecord();
  		commChannelGr.setValue(this.COLUMNS.COMM_TASK, commTaskGr.getUniqueValue());
  		return commChannelGr.canCreate() && commTaskGr.active;
  	}
  	return false;
  },

  /* By default all channel types are supported. If a product wishes to limit the availability of channels
  they must add entry here.*/
  getDeniedChannelTypes: function() {
  	return [];
  },

  /* By default all provider types are supported for collaboration. If a product wishes to limit the availability of providers
  they must add entry here.*/
  getDeniedCollaborationProviderTypes: function() {
  	return [];
  },

  isNotifyNumberSet: function() {
  	return !gs.nil(gs.getProperty(this.PROPERTIES.NOTIFY_NUMBER, ''));
  },

  isNotifyEnabled: function() {
  	return (gs.getProperty(this.PROPERTIES.ENABLE_NOTIFY, '') === 'true');
  },

  onConferenceParticipantChange: function(commTaskGr, participants) {

  },

  /*override to use a product specific or custom conference page*/
  getConferenceManagementPageName: function() {
  	return this.CONFERENCE_PAGES.MANAGE;
  },

  /*override to use a product specific or custom conference page*/
  getConferenceEndPageName: function() {
  	return this.CONFERENCE_PAGES.END;
  },

  createAdhocCommTask: function(commTaskBody, channels, recipients, templateMap) {
  	if(!this.incidentUtilsSNC.canCreateRecord(this.getCommTaskInstanceForTask()))
  		return new sn_ws_err.BadRequestError("User doesn't have permissions");
  	var commPlanGr = new GlideRecord(this.getCommPlanInstanceForTask());
  	if (commPlanGr.get(commTaskBody.comm_plan)) {
  		var commTaskGr = new GlideRecord(this.getCommTaskInstanceForTask());
  		this.prepareAdhocCommTaskGr(commTaskGr, commPlanGr, commTaskBody);
  		commTaskGr.insert();
  		if (channels) {
  			for (var i = 0; i < channels.length; i++) {
  				var channel = channels[i];
  				var channelTable = '';
  				channelTable = this.channelMap[channel].inst_table;
  				if (channelTable) {
  					var channelGr = new GlideRecord(channelTable);
  					channelGr.newRecord();
  					channelGr.comm_task = commTaskGr.getUniqueValue();
  					var channelApi = this.channelMap[channel].api_name;
  					var commTaskVariables = this.getCommTaskVariables(commTaskGr.comm_plan.source.getRefRecord().getRecordClassName() + '');
  					commTaskVariables.templateMap = templateMap;
  					var channelApiInstance = this.communicationManagementUtil.getScriptInstanceFromApiName(context, channelApi);
  					channelApiInstance.updateChannelAdHoc(channelGr, commPlanGr, commTaskGr, commTaskVariables);
  					channelGr.insert();
  				}
  			}
  		}
  		if (recipients)
  			this.contactManagementUtilsSNC.addRecipients(this.getCommPlanInstanceForTask(), commPlanGr.getUniqueValue(), recipients);
  		return commTaskGr.sys_id + '';
  	}
  },

  createAdhocCommPlan: function(commPlanBody, commTaskBody, channels, sourceGr, recipients, templateMap) {
  	var canCreateTask = this.incidentUtilsSNC.canCreateRecord(this.getCommTaskInstanceForTask());
  	var canCreatePlan = canCreateTask && this.incidentUtilsSNC.canCreateRecord(this.getCommPlanInstanceForTask());
  	if(!canCreatePlan)
  		return new sn_ws_err.BadRequestError("User doesn't have permissions");
  	var commPlanGr = new GlideRecord(this.getCommPlanInstanceForTask());
  	this.prepareAdhocCommPlanGr(commPlanGr, commPlanBody, sourceGr);
  	commPlanGr.insert();
  	commTaskBody.comm_plan = commPlanGr.getUniqueValue();
  	var commTaskSysId = this.createAdhocCommTask(commTaskBody, channels, recipients, templateMap);
  	return commTaskSysId;
  },

  prepareAdhocCommPlanGr: function(commPlanGr, commPlanBody, sourceGr) {
  	commPlanGr.short_description = commPlanBody.short_description;
  	commPlanGr.comm_plan_type = commPlanBody.comm_plan_type;
  	commPlanGr.source = sourceGr.getUniqueValue();
  },

  prepareAdhocCommTaskGr: function(commTaskGr, commPlanGr, commTaskBody) {
  	commTaskGr.comm_plan = commTaskBody.comm_plan;
  	commTaskGr.comm_task_type = commTaskBody.comm_task_type;
  	commTaskGr.communication_frequency = commTaskBody.communication_frequency;
  	if (commTaskBody.due_in) {
  		// In tests found GlidDuration to support 2^62 + some X, so setting it to 2^62. Lesser that Java max limit for long
  		if ((commTaskBody.due_in * 6000) <= (Math.pow(2, 62)))
  			commTaskGr.due_in = new GlideDuration(commTaskBody.due_in * 60000);
  		else
  			gs.addErrorMessage(gs.getMessage('Duration provided is too long'));
  	}
  	commTaskGr.short_description = commTaskBody.short_description + '';
  },

  prepareTaskUpdatesPerChannelCommunication: function(commTaskGr, channelUpdates, currentTime) {
  	if (channelUpdates && channelUpdates.update_last_communication) {
  		commTaskGr.last_communication = currentTime;
  		var countTillNow = 0;
  		if (!gs.nil(commTaskGr.comm_count))
  			countTillNow = commTaskGr.comm_count * 1;
  		commTaskGr.comm_count = countTillNow + 1;

  		if (commTaskGr.communication_frequency == this.COMMUNICATION_FREQUENCY_TYPE.RECURRING && commTaskGr.due_in) {
  			var dueIn = new GlideDateTime(commTaskGr.due_in).getNumericValue() / 1000;
  			var nextDue = new GlideDateTime(commTaskGr.last_communication);
  			nextDue.addSeconds(dueIn);
  			commTaskGr.due_date = nextDue.getDisplayValue();
  		}
  	}
  },

  updateTaskPerChannelCommunication: function(commTaskGr, channelUpdates, currentTime) {
  	this.prepareTaskUpdatesPerChannelCommunication(commTaskGr, channelUpdates, currentTime);
  	commTaskGr.update();
  },

  addRecipients: function(commPlanSysId, recipients) {
  	if (!commPlanSysId || !recipients)
  		return;
  	return this.contactManagementUtilsSNC.addRecipients(this.getCommPlanInstanceForTask(), commPlanSysId, recipients);
  },

  prepareGroupsInvolved: function(sysId) {
  	//NoOp - Default implementation. See CommunicationManagementIncidentHandlerSNC for implementation for incident
  },


  /*Called from Calculate Next Due - Before BR on comm_task
  * WARNING - Hence don't use current.update()
  */
  calculateNextDue: function(current, previous) {
  	var openState = this.COMM_TASK_STATES.OPEN;
  	var pendingState = this.COMM_TASK_STATES.PENDING;
  	var secondsToAdd;
  	var nextDue;
  	if (current.state.changesTo(openState) && current.state.changesFrom(pendingState) && current.due_in) {
  		secondsToAdd = new GlideDateTime(current.due_in).getNumericValue() / 1000;
  		nextDue = new GlideDateTime();
  		nextDue.addSeconds(secondsToAdd);
  		current.due_date = nextDue.getDisplayValue();
  	} else if (current.state.changesTo(pendingState)) {
  		//May be user moved to open by mistake. So, allow moving to pending but clear due_date
  		current.due_date = null;
  	} else if (current.due_in && current.due_in.changes() && current.state != pendingState) {
  		if (gs.nil(previous.due_in))
  			secondsToAdd = new GlideDateTime(current.due_in).getNumericValue() / 1000;
  		else
  			secondsToAdd = (new GlideDateTime(current.due_in).getNumericValue()  - new GlideDateTime(previous.due_in).getNumericValue()) / 1000;

  		if (gs.nil(current.due_date))
  			nextDue = new GlideDateTime();
  		else
  			nextDue = new GlideDateTime(current.due_date);

  		nextDue.addSeconds(secondsToAdd);
  		current.due_date = nextDue.getDisplayValue();
  	} else if (!current.active) {
  		current.due_date = null;
  	}
  },

  /*Called from State Management - On Task Completion BR on comm_task. This is a after BR*/
  onCommTaskCompletion: function(current, previous) {
  	//NoOp - Default implementation. See CommunicationManagementIncidentHandlerSNC for implementation for incident_alert_task
  },

  /*Called from State Management-Decide state per order BR on comm_task.
  * WARNING - Hence don't use current.update()
  */
  decideStatePerOrder: function(current, previous) {
  	//NoOp - Default implementation. See CommunicationManagementIncidentHandlerSNC for implementation for incident_alert_task
  },

  /*returns sys_id of workflow to be attached while escalating conference call*/
  getConferenceCallEscalationWorkflow: function(groupSysId) {
  	var workflowSysId = gs.getProperty(this.PROPERTIES.CONFERENCE_CALL_ESCALATION_WORKFLOW);
  	if (!groupSysId || !this.conferenceEscalationGroupWorkflowMap)
  		return workflowSysId;
  	if (this.conferenceEscalationGroupWorkflowMap[groupSysId])
  		return this.conferenceEscalationGroupWorkflowMap[groupSysId];
  	return workflowSysId;
  },
  
  getClassNameForConferenceEscalation: function(conferenceCallGr) {
  	return conferenceCallGr.table;
  },
  
  getSysIdForWorkflow: function(conferenceCallGr) {
  	return conferenceCallGr.source + '';
  },
  
  getConferenceDefaultTitle: function(data) {
  	var gr = new GlideRecord(data.table);
  	if (gr.get(data.sysId))
  		return gr.number + ' - ' + gr.short_description;
  	return '';
  },
  
  getCommunicateExcludeList: function() {
  	return [];
  },

  type: 'CommunicationManagementDefaultHandlerSNC'
};

Sys ID

09b5281f533b030009170ef5d5dc344c

Offical Documentation

Official Docs: