Name

global.OCRotaICalendarSNC

Description

No description available

Script

var OCRotaICalendarSNC = Class.create();
OCRotaICalendarSNC.prototype = {

  SCHEDULE_TYPE: {
  	TIME_OFF: "time_off",
  	TIMEOFF: "timeoff"
  },

  MULTIPLIER: {
  	"daily": 1,
  	"weekly": 7
  },

  initialize: function() {
  	this.log = new GSLog("com.snc.on_call_rotation.log.level", this.type);
  },
  
  getCalendarEvents: function(groupId, rotaId, userId, dateRangeObj, useCache) {
  	var result = "";

  	if (JSUtil.nil(groupId) || JSUtil.nil(rotaId) || JSUtil.nil(userId))
  		return result;

  	// Get the cmn_rota record as it will be referenced throughout
  	var rotaGR = new GlideRecord("cmn_rota");
  	if (!rotaGR.get(rotaId))
  		return result;

  	// from and to dates, used for getItems, series repeatUntil and, storing response in cache
  	if (!dateRangeObj)
  		dateRangeObj = this._getCalculationDates(groupId, rotaGR);

  	// Gets response from cache if it exists
  	if (useCache)
  		result = this.getEventsFromTable(groupId, rotaId, userId, dateRangeObj);

  	// Already have the response
  	if (result)
  		return result;

  	// Get link to calendar to add to individual events
  	var calendarLink = this.getOnCallCalendarURL(rotaGR);

  	var items = new OCRotationV2(null, null)
  		.setStartDate(dateRangeObj.from)
  		.setEndDate(dateRangeObj.to, false)
  		.setGroupIds(groupId)
  		.setRotaIds(rotaId)
  		.setUserIds(userId)
  		.getItems();
  	
  	var calEvents = this.getCalendarEventsByItems(items, rotaGR, calendarLink);

  	if (calEvents.length == 0) {
  		result = this.createPlaceholderCalendar(rotaGR, dateRangeObj, calendarLink);
  		if (useCache)
  			this.saveCalendarEvents(groupId, rotaId, userId, dateRangeObj, result);
  		return result;
  	}
  	
  	result = new ICalUtil().formatICalComponent(calEvents).join("\n");

  	// Store the response so we can do a lookup for subsequent requests
  	if (useCache)
  		this.saveCalendarEvents(groupId, rotaId, userId, dateRangeObj, result);

  	return result;
  },
  
  getCalendarEventsByItems: function (items, rotaGR, calendarLink) {
  	var calEvents = [];
  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[getCalendarEventsByItems] items.size()=" + items.size());
  	for (var i = 0; i < items.size(); i++){
  		calEvents = calEvents.concat(this.convertItemToCalendarEvents(items.get(i), rotaGR.group.getDisplayValue() + "", calendarLink, rotaGR.getUniqueValue()));
  	}
  	return calEvents;
  },

  convertItemToCalendarEvents: function (item, groupName, calendarLink, rotaId) {
  	var spans = item.getTimeSpans();
  	var result = [];
  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[convertItemToCalendarEvents] spans.size()=" + spans.size());
  	for (var i = 0; i < spans.size(); i++)
  		if (item.getType() != 'rota' && item.getType() != 'timeoff' && item.getType() != 'exclude' && item.rotaId + "" === rotaId) {
  			result.push(this.formatSpanEvent(item, spans.get(i), groupName, calendarLink));
  		}
  	return result;
  },

  formatSpanEvent: function (item, span, groupName, calendarLink) {
  	var startSDT = span.getStart();
  	startSDT.setTimeZone("Etc/UTC");
  	var start = startSDT.getValue();
  	var endSDT = span.getEnd();
  	endSDT.setTimeZone("Etc/UTC");

  	var name = item.getName();
  	var userName = item.getUserName();
  	if (!JSUtil.nil(userName))
  		name = groupName + ": " + userName;
  	var rosterName = item.getRosterName();
  	if (!JSUtil.nil(rosterName))
  		name += ": " + rosterName;

  	return new ICalUtil().formatICalEvent([
  		"UID:" + item.getSysId() + start + endSDT.getValue(),
  		"DTSTAMP:" + start,
  		"DTSTART:" + start,
  		"DTEND:" + endSDT.getValue(),
  		"SUMMARY:" + name,
  		"DESCRIPTION:" + name,
  		"URL:" + calendarLink
  	], true);
  },

  /**
   * Returns iCal formatted events for the given users rotation
   *
   * @return result String - text/calendar formatted string.
  **/
  getCalendarEventsDeprecated: function(groupId, rotaId, userId, dateRangeObj, useCache) {
  	var result = "";

  	if (JSUtil.nil(groupId) || JSUtil.nil(rotaId) || JSUtil.nil(userId))
  		return result;

  	// Get the cmn_rota record as it will be referenced throughout
  	var rotaGR = new GlideRecord("cmn_rota");
  	if (!rotaGR.get(rotaId))
  		return result;

  	// from and to dates, used for getItems, series repeatUntil and, storing response in cache
  	if (!dateRangeObj)
  		dateRangeObj = this._getCalculationDates(groupId, rotaGR);

  	// Gets response from cache if it exists
  	if (useCache)
  		result = this.getEventsFromTable(groupId, rotaId, userId, dateRangeObj);

  	// Already have the response
  	if (result)
  		return result;

  	// Get link to calendar to add to individual events
  	var calendarLink = this.getOnCallCalendarURL(rotaGR);

  	// Get all events we are concerned about
  	var scheduleItems = this.createCalendarEvents(groupId, rotaId, userId, dateRangeObj);
  	var repeatRotaSpanIdArr = this._getMonthlyAndYearlyRepeatRotaSpans(rotaGR);
  	var memberSchedules = this.processSeriesEvent(scheduleItems, repeatRotaSpanIdArr, userId);
  	var seriesEvents = new OCSeriesEventGenerator().getMemberCalendar(groupId, rotaId, userId, dateRangeObj, memberSchedules, calendarLink);

  	var overrideEvents = [];
  	for (var rosterItem in memberSchedules)
  		for (var i = 0; i < memberSchedules[rosterItem].includeItems.length; i++)
  			overrideEvents.push(this.createCustomEvent(rotaGR.group.getDisplayValue() + "", memberSchedules[rosterItem].includeItems[i], calendarLink));

  	if (seriesEvents.length == 0 && overrideEvents.length == 0) {
  		result = this.createPlaceholderCalendar(rotaGR, dateRangeObj, calendarLink);
  		if (useCache)
  			this.saveCalendarEvents(groupId, rotaId, userId, dateRangeObj, result);
  		return result;
  	}

  	// Combine seriesEvents and overrideEvents as they are the result
  	var arrEvents = [];
  	arrEvents = arrEvents.concat(seriesEvents);
  	arrEvents = arrEvents.concat(overrideEvents);
  	result = new ICalUtil().formatICalComponent(arrEvents).join("\n");

  	// Store the response so we can do a lookup for subsequent requests
  	if (useCache)
  		this.saveCalendarEvents(groupId, rotaId, userId, dateRangeObj, result);

  	return result;
  },

  createPlaceholderCalendar: function(rotaGR, dateRangeObj, calendarLink) {
  	var gdt = new GlideDateTime(dateRangeObj.from);
  	var startSDT = new GlideScheduleDateTime(gdt);
  	gdt.addSeconds(3600);
  	var endSDT = new GlideScheduleDateTime(gdt);
  	var arrDetails = [
  		rotaGR.name + "",
  		rotaGR.group.getDisplayValue() + ""
  	];
  	var i18nSummary = gs.getMessage("Placeholder for {0} rotation in the {1} group", arrDetails);
  	var i18nDescription = gs.getMessage("You have no upcoming on-call commitments for {0} rotation in the {1} group. This is a placeholder event.", arrDetails);
  	var event = [new ICalUtil().formatICalEvent([
  		"UID:placeholder",
  		"DTSTAMP:" + startSDT.getValue(),
  		"DTSTART:" + startSDT.getValue(),
  		"DTEND:" + endSDT.getValue(),
  		"SUMMARY:" + i18nSummary,
  		"DESCRIPTION:" + i18nDescription,
  		"URL:" + calendarLink
  	], false)];

  	return new ICalUtil().formatICalComponent(event).join("\n");
  },

  /**
   * Retrieves iCalendar from cmn_rota_cal_event table if response for the request exists.
   *
   * @param dateRangeObj Object
   * @return String - response if match found for the request, else NULL
  **/
  getEventsFromTable: function(groupId, rotaId, userId, dateRangeObj) {
  	// Check if we have already fulfilled this request, return stored response if so
  	var gr = new GlideRecord("cmn_rota_resp_cache");
  	gr.addQuery("from", dateRangeObj.from);
  	gr.addQuery("to", dateRangeObj.to);
  	gr.addQuery("group", groupId);
  	gr.addQuery("rota", rotaId);
  	gr.addQuery("user", userId);
  	gr.query();

  	// Response exists
  	if (gr.next())
  		return gr.getValue("payload");

  	return null;
  },

  /**
   * Calculates AJAXScheduleItems for specified users rotation.
   *
   * @param String groupId
   * @param String rotaId
   * @param String userId
   * @param dateRangeObj {from: yyyy-mm-dd, to: yyyy-mm-dd}
   * @return AJAXScheduleItem[]
  **/
  createCalendarEvents: function(groupId, rotaId, userId, dateRangeObj) {
  	var scheduleItems = new OCRotationV2(null, null)
  		.setCompressTimeMap(false)
  		.setStartDate(dateRangeObj.from)
  		.setEndDate(dateRangeObj.to, true)
  		.setGroupIds(groupId)
  		.setRotaIds(rotaId)
  		.setRosterIds("")
  		.setUserIds(userId)
  		.getItems();

  	return scheduleItems;
  },

  processSeriesEvent: function(scheduleItems, repeatRotaSpanIdArr, userId) {
  	var rotaSpanItems = []; // Derived from rota schedule spans
  	var memberSpanItems = []; // Derived from member schedule spans
  	var rosterSpanItems = []; // Derived from roster_schedule_span; overrides and extra coverage spans
  	var definitionItems = []; // Combination of rotaSpanItems and rosterSpanItems that are extra coverage spans

  	for (var i = 0; i < scheduleItems.size(); i++) {
  		var scheduleItem = scheduleItems.get(i);
  		var scheduleItemTable = scheduleItem.getTable();

  		if ("cmn_schedule_span" == scheduleItemTable)
  			rotaSpanItems.push(scheduleItem);

  		if ("cmn_rota_member" == scheduleItemTable)
  			memberSpanItems.push(scheduleItem);

  		if ("roster_schedule_span" == scheduleItemTable && this.SCHEDULE_TYPE.TIMEOFF != scheduleItem.getType()) {				
  			rosterSpanItems.push(scheduleItem);

  			if (JSUtil.nil(scheduleItem.getUserId()))
  				definitionItems.push(scheduleItem);
  		}
  	}
  	definitionItems = definitionItems.concat(rotaSpanItems);

  	// Need to consider multiple member span items individually
  	var memberSchedules = {};

  	for (var j = 0; j < memberSpanItems.length; j++) {
  		var rotaMemberItems = this.handleRotaMember(memberSpanItems[j], rotaSpanItems, definitionItems, repeatRotaSpanIdArr);
  		memberSchedules[memberSpanItems[j].getRosterId()] = {
  			"includeItems": rotaMemberItems.includeItems,
  			"excludeItems": rotaMemberItems.excludeItems,
  			"startTimes": rotaMemberItems.seriesStartTimes
  		};
  	}

  	for (var k = 0; k < rosterSpanItems.length; k++) {
  		var rosterId = rosterSpanItems[k].getRosterId();

  		if (!memberSchedules[rosterId])
  			memberSchedules[rosterId] = {
  				"includeItems": [],
  				"excludeItems": []
  			};

  		if (userId == rosterSpanItems[k].getUserId()) {
  			var overrideItems = this.handleOverrideMember(rosterSpanItems[k]);
  			memberSchedules[rosterId].includeItems = memberSchedules[rosterId].includeItems.concat(overrideItems);
  			continue;
  		}

  		var rosterItems = this.updateExceptionList(rosterSpanItems[k], null, rotaSpanItems);
  		memberSchedules[rosterId].excludeItems = memberSchedules[rosterId].excludeItems.concat(rosterItems);
  	}

  	return memberSchedules;
  },

  handleRotaMember: function(scheduleItem, rotaSpanItems, definitionItems, repeatRotaSpanIdArr) {
  	var customScheduleItems = [];
  	var excludeScheduleItems = [];
  	var seriesStartTimes = {};
  	var timeSpans = scheduleItem.getTimeSpans();

  	for (var i = 0; i < timeSpans.size(); i++) {
  		var timeSpan = timeSpans.get(i);
  		if (this.matchRotaSpanRule(timeSpan, rotaSpanItems, repeatRotaSpanIdArr, seriesStartTimes))
  			continue;
  		customScheduleItems.push({item: scheduleItem, span: timeSpan});
  		var excludeScheduleItem = this.updateExceptionList(scheduleItem, timeSpan, rotaSpanItems);
  		if (excludeScheduleItem)
  			excludeScheduleItems.push(excludeScheduleItem);
  	}

  	return {
  		"includeItems": customScheduleItems,
  		"excludeItems": excludeScheduleItems,
  		"seriesStartTimes": seriesStartTimes
  	};
  },

  handleOverrideMember: function(scheduleItem) {
  	var includeItems = [];
  	var timeSpans = scheduleItem.getTimeSpans();
  	for (var i = 0; i < timeSpans.size(); i++)
  		includeItems.push({
  			item: scheduleItem,
  			span: timeSpans.get(i)
  		});
  	return includeItems;
  },

  matchRotaSpanRule: function(timeSpan, rotaSpanItems, repeatRotaSpanIdArr, seriesStartTimes) {
  	for (var i = 0; i < rotaSpanItems.length; i++) {
  		var timeSpans = rotaSpanItems[i].getTimeSpans();
  		if (repeatRotaSpanIdArr.indexOf(rotaSpanItems[i]) != -1)
  			return false;
  		for (var j = 0; j < timeSpans.size(); j++)
  			if (timeSpan.compareTo(timeSpans.get(j)) == 0) {
  				var rotaSpanSysId = rotaSpanItems[i].getScheduleSpanId();
  				if (!seriesStartTimes[rotaSpanSysId])
  					seriesStartTimes[rotaSpanSysId] = timeSpan.getStart();
  				return true;
  			}
  	}

  	return false;
  },

  getIntersectRotaSpanItem: function(timeSpan, rotaSpanItems) {
  	for (var i = 0; i < rotaSpanItems.length; i++) {
  		var timeSpans = rotaSpanItems[i].getTimeSpans();
  		for (var j = 0; j < timeSpans.size(); j++) {
  			var rotaTimeSpan = timeSpans.get(j);
  			if (timeSpan.overlapWith(rotaTimeSpan)) {
  				// Normalize span start and end to UTC
  				rotaTimeSpan.getStart().setTimeZone("Etc/UTC");
  				rotaTimeSpan.getEnd().setTimeZone("Etc/UTC");
  				return {
  					"item": rotaSpanItems[i],
  					"span": rotaTimeSpan
  				};
  			}
  		}
  	}

  	return false;
  },

  createCustomEvent: function(groupName, scheduleItemSpan, calendarLink) {
  	var startSDT = scheduleItemSpan.span.getStart();
  	startSDT.setTimeZone("Etc/UTC");
  	var start = startSDT.getValue();
  	var endSDT = scheduleItemSpan.span.getEnd();
  	endSDT.setTimeZone("Etc/UTC");

  	var name = scheduleItemSpan.item.getName();
  	var userName = scheduleItemSpan.item.getUserName();
  	if (!JSUtil.nil(userName))
  		name = groupName + ": " + userName;
  	var rosterName = scheduleItemSpan.item.getRosterName();
  	if (!JSUtil.nil(rosterName))
  		name += ": " + rosterName;

  	return new ICalUtil().formatICalEvent([
  		"UID:" + start + name,
  		"DTSTAMP:" + start,
  		"DTSTART:" + start,
  		"DTEND:" + endSDT.getValue(),
  		"SUMMARY:" + name,
  		"DESCRIPTION:" + name,
  		"URL:" + calendarLink
  	], true);
  },

  updateExceptionList: function(scheduleItem, timeSpan, rotaSpanItems) {
  	var includeItems = [];
  	var excludeItems = [];
  	var items = {
  		"includeItems" : includeItems,
  		"excludeItems" : excludeItems
  	};

  	if (JSUtil.nil(scheduleItem.getUserId()))
  		return excludeItems;

  	var excludeItem;

  	if (timeSpan) {
  		for (var i=0; i<rotaSpanItems.length; i++) {
  			var rotaTimeSpans = rotaSpanItems[i].getTimeSpans();
  			for (var j = 0; j < rotaTimeSpans.size(); j++)
  				if (timeSpan.compareTo(rotaTimeSpans.get(j)) == 0)
  					return excludeItems;
  		}
  		excludeItem = this.getIntersectRotaSpanItem(timeSpan, rotaSpanItems);
  		if (excludeItem)
  			items.excludeItems.push({item: excludeItem.item, span: excludeItem.span});
  	} else {
  		var timeSpans = scheduleItem.getTimeSpans();
  		for (var k = 0; k < timeSpans.size(); k++) {
  			excludeItem = this.getIntersectRotaSpanItem(timeSpans.get(k), rotaSpanItems);
  			excludeItems.push({item: excludeItem.item, span: excludeItem.span});
  		}
  	}

  	return excludeItems;
  },

  /**
   * Stores calculated iCalendar for subsequent requests.
   *
   * @param dateRangeObj Object
   * @param result String
  **/
  saveCalendarEvents: function(groupId, rotaId, userId, dateRangeObj, result) {
  	var rotaRespCacheGR = new GlideRecord("cmn_rota_resp_cache");
  	rotaRespCacheGR.setValue("payload", result);
  	rotaRespCacheGR.setValue("from", dateRangeObj.from + "");
  	rotaRespCacheGR.setValue("to", dateRangeObj.to + "");
  	rotaRespCacheGR.setValue("group", groupId);
  	rotaRespCacheGR.setValue("rota", rotaId);
  	rotaRespCacheGR.setValue("user", userId);
  	rotaRespCacheGR.insert();
  },

  invalidateRotaRespCache: function(gr) {
  	var iCalUtil = new ICalUtil();
  	var rotaRespCacheGR = null;
  	var tableName = gr.getTableName();

  	if (tableName == "cmn_rota_member")
  		rotaRespCacheGR = this._getRotaRespCache(null, null, gr.roster.rota.group + "", gr.roster.rota + "", null,
  			gr.from + "", gr.to + "");

  	if (tableName == "cmn_schedule_span" && (gr.schedule.document + "") == "cmn_rota") {
  		var rotaGR = new GlideRecord("cmn_rota");
  		rotaGR.get(gr.schedule.document_key + "");
  		rotaRespCacheGR = this._getRotaRespCache(null, gr.repeat_type + "", rotaGR.group + "", rotaGR.sys_id + "", null, null, null);
  	}

  	if (tableName == "roster_schedule_span" && gr.getValue("type") == "on_call")
  		rotaRespCacheGR = this._getRotaRespCache(null, gr.repeat_type + "", gr.group + "", gr.roster.rota + "", null,
  			iCalUtil.getSDT(gr.start_date_time + "").getGlideDateTime(),
  			iCalUtil.getSDT(gr.end_date_time + "").getGlideDateTime());

  	if (tableName == "roster_schedule_span" && gr.getValue("type") == this.SCHEDULE_TYPE.TIME_OFF)
  		rotaRespCacheGR = this._getRotaRespCache(this.SCHEDULE_TYPE.TIME_OFF, gr.repeat_type + "", null, null, gr.schedule.document_key + "",
  			iCalUtil.getSDT(gr.start_date_time + "").getGlideDateTime(),
  			iCalUtil.getSDT(gr.end_date_time + "").getGlideDateTime());

  	if (rotaRespCacheGR)
  		rotaRespCacheGR.deleteMultiple();
  },

  cleanExpiredCache: function() {
  	var today = new GlideDate();
  	var rotaRespCacheGR = new GlideRecord("cmn_rota_resp_cache");
  	rotaRespCacheGR.addQuery("from", "<", today);
  	rotaRespCacheGR.query();
  	rotaRespCacheGR.deleteMultiple();
  },

  getOnCallCalendarURL: function(rotaGR) {
  	var instanceName = GlideProperties.get("instance_name");
  	var fallbackURL = "https://" + instanceName + ".service-now.com";
  	var baseURL = gs.getProperty("glide.servlet.uri", fallbackURL);
  	var linkToCalendar = new OnCallRotationProcessor().getScheduleUrl(rotaGR.schedule + "").getUrl();

  	return baseURL + linkToCalendar;
  },

  /**
	 * Constructs a subscribable URL that provides an iCalendar for a users rotation
	**/
  getMemberCalendarURL: function (groupId, rotaId, userId) {
  	var instanceName = GlideProperties.get("instance_name");
  	var fallbackURL = "https://" + instanceName + ".service-now.com";
  	var baseURL = gs.getProperty("glide.servlet.uri", fallbackURL);
  	var service = "api/now/on_call_rota/rotaUserICalendar/";

  	return baseURL + service + groupId + "/"
  		+ rotaId + "/"
  		+ userId;
  },

  sendCalendarURL: function (rotaGR) {
  	var rotaMemberGA = new GlideAggregate("cmn_rota_member");
  	rotaMemberGA.addQuery("roster.rota", rotaGR.getUniqueValue());
  	rotaMemberGA.groupBy("member");
  	rotaMemberGA.query();

  	while (rotaMemberGA.next()) {
  		var memberLink = this.getMemberCalendarURL(rotaGR.group + "", rotaGR.sys_id + "", rotaMemberGA.member + "");
  		gs.eventQueue("rota.on_call.subscription", rotaGR, rotaMemberGA.member + "", memberLink);
  	}
  },

  populateCalendarSubscriptionSettings: function() {
  	var rotaGR = new GlideRecord("cmn_rota");
  	rotaGR.addNullQuery("coverage_lead_type");
  	rotaGR.addNullQuery("coverage_interval");
  	rotaGR.setValue("coverage_lead_type", "weekly");
  	rotaGR.setValue("coverage_interval", "12");
  	rotaGR.updateMultiple();
  },
  
  coverageLimitExceeded: function(rotaGR) {
  	var intervalType = rotaGR.getValue("coverage_lead_type");
  	var interval = rotaGR.getValue("coverage_interval");
  	if (JSUtil.nil(intervalType) || JSUtil.nil(interval))
  		return false;

  	var maximum = parseInt(gs.getProperty("com.snc.on_call_rotation.max_subscription_interval", 364), 10);
  	var days = parseInt(interval, 10) * this.MULTIPLIER[intervalType];
  	
  	if (days <= maximum)
  		return false;
  	
  	if ("weekly" === intervalType)
  			gs.addErrorMessage(gs.getMessage("Calendar subscription 'Get coverage for' has exceeded the limit of {0} weeks ({1} days)", [Math.floor(maximum / 7) + "", maximum + ""]));
  		else
  			gs.addErrorMessage(gs.getMessage("Calendar subscription 'Get coverage for' has exceeded the limit of {0} days ({1} weeks)", [maximum + "", Math.floor(maximum / 7) + ""]));
  	
  	return true;
  },

  _getRotaRespCache: function(type, repeat, groupSysId, rotaSysId, userSysId, from, to) {

  	// Ensure arguments are not null per request type
  	if (type === this.SCHEDULE_TYPE.TIME_OFF && JSUtil.nil(userSysId))
  		return null;
  	if (type !== this.SCHEDULE_TYPE.TIME_OFF && (JSUtil.nil(groupSysId) || JSUtil.nil(rotaSysId)))
  		return null;

  	var rotaRespCacheGR = new GlideRecord("cmn_rota_resp_cache");
  	if (!JSUtil.nil(from) && JSUtil.nil(repeat))
  		if (!JSUtil.nil(to)) {
  			rotaRespCacheGR.addQuery("from", "<=", to);
  			rotaRespCacheGR.addQuery("to", ">=", from);
  		} else
  			rotaRespCacheGR.addQuery("to", ">=", from);
  	if (!JSUtil.nil(userSysId))
  		rotaRespCacheGR.addQuery("user", userSysId);
  	if (!JSUtil.nil(rotaSysId))
  		rotaRespCacheGR.addQuery("rota", rotaSysId);
  	if (!JSUtil.nil(groupSysId))
  		rotaRespCacheGR.addQuery("group", groupSysId);
  	rotaRespCacheGR.query();

  	return rotaRespCacheGR;
  },

  /**
   * Creates the date range for the on-call rota data to be calculated.
   *
   * @return Object - {from, to}
  **/
  _getCalculationDates: function(groupId, rotaGR) {
  	var intervalType = rotaGR.getValue("coverage_lead_type");
  	var interval = rotaGR.getValue("coverage_interval");
  	if (JSUtil.nil(intervalType))
  		intervalType = "weekly";
  	if (JSUtil.nil(interval))
  		interval = 12;
  	var maximum = parseInt(gs.getProperty("com.snc.on_call_rotation.max_subscription_interval", 364), 10);
  	var days = parseInt(interval, 10) * this.MULTIPLIER[intervalType];
  	days = days > maximum ? maximum : days;

  	var from = new GlideDate();
  	var to = new GlideDate();
  	to.addDaysUTC(days);

  	return {
  		from : from,
  		to : to
  	};
  },

  /**
   * Get rotation spans that cannot be translated into a series iCal event.
   * Will make use of the AJAXScheduleItem object representation for these spans.
   *
   * @param rotaId
   * @return GlideRecord - rota spans that require individual events
  **/
  _getMonthlyAndYearlyRepeatRotaSpans: function(rotaGR) {
  	var repeatRotaSpanIdArr = [];

  	var rotaScheduleSpansGR = new GlideRecord("cmn_schedule_span");
  	rotaScheduleSpansGR.addQuery("schedule", rotaGR.schedule + "");
  	var condition = rotaScheduleSpansGR.addQuery("repeat_type", "monthly");
  	rotaScheduleSpansGR.appendOrQuery(condition, "repeat_type", "yearly");
  	rotaScheduleSpansGR.query();

  	while (rotaScheduleSpansGR.next())
  		repeatRotaSpanIdArr.push(rotaScheduleSpansGR.getUniqueValue());

  	return repeatRotaSpanIdArr;
  },

  type: 'OCRotaICalendarSNC'
};

Sys ID

50e95df6c3231200b6dcdfdc64d3ae79

Offical Documentation

Official Docs: