Name

global.SLATimeLineV2SNC

Description

API to provide TimeLine details of task_sla records. This API works out the break downs of task_sla records from the task history record. Public methods which can be reused for use cases of extracting task_sla timeline details - getTimeLineDetails - getTaskSLABreakDowns See comments in the function for details

Script

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

  /*Tables*/
  TABLE_TASK: 'task',
  TABLE_TASK_SLA: 'task_sla',
  TABLE_CONTRACT_SLA: 'contract_sla',
  TABLE_SYS_HISTORY_LINE: 'sys_history_line',
  TABLE_CMN_SCHEDULE: 'cmn_schedule',
  TABLE_ITEM_OPTION_NEW: 'item_option_new',
  TABLE_SC_ITEM_OPTION_MTOM: 'sc_item_option_mtom',
  TABLE_SC_ITEM_OPTION: 'sc_item_option',

  /*Common Attributes*/
  ATTR_SYS_ID: 'sys_id',
  ATTR_SYS_CREATED_ON: 'sys_created_on',
  ATTR_SYS_UPDATED_ON: 'sys_updated_on',
  ATTR_SYS_MOD_COUNT: 'sys_mod_count',
  /*End common attributes*/

  /*Attributes - task_sla*/
  ATTR_TASK: 'task',
  ATTR_SLA: 'sla',
  ATTR_SCHEDULE: 'schedule',
  ATTR_STAGE: 'stage',
  ATTR_BUSINESS_PERCENTAGE: 'business_percentage',
  ATTR_BUSINESS_DURATION: 'business_duration',
  ATTR_BUSINESS_TIME_LEFT: 'business_time_left',
  ATTR_BUSINESS_PAUSE_DURATION: 'business_pause_duration',
  ATTR_PERCENTAGE: 'percentage',
  ATTR_DURATION: 'duration',
  ATTR_TIME_LEFT: 'time_left',
  ATTR_PAUSE_DURATION: 'pause_duration',
  ATTR_ACTIVE: 'active',
  ATTR_START_TIME: 'start_time',
  ATTR_ORIGINAL_BREACH_TIME: 'original_breach_time',
  ATTR_HAS_BREACHED: 'has_breached',
  ATTR_PLANNED_END_TIME: 'planned_end_time',
  /*End Attributes - task_sla*/

  /*Attributes - contract_sla*/
  ATTR_TABLE: 'table',
  ATTR_CANCEL_CONDITION: 'cancel_condition',
  ATTR_PAUSE_CONDITION: 'pause_condition',
  ATTR_RESET_CONDITION: 'reset_condition',
  ATTR_RESUME_CONDITION: 'resume_condition',
  ATTR_START_CONDITION: 'start_condition',
  ATTR_STOP_CONDITION: 'stop_condition',
  /*End attributes*/

  /*Attributes - sys_history_line*/
  ATTR_SET: 'set',
  ATTR_TYPE: 'type',
  ATTR_UPDATE: 'update',
  ATTR_UPDATE_TIME: 'update_time',
  ATTR_NEW_VALUE: 'new_value',
  ATTR_NEW: 'new',
  ATTR_FIELD: 'field',
  ATTR_LABEL: 'label',
  /*End Attributes - sys_history_line*/

  /*Attributes - contract_sla*/
  ATTR_NAME: 'name',
  ATTR_COLLECTION: 'collection',
  ATTR_URL: 'url',
  /*End Attributes - contract_sla*/

  /*Field types*/
  FIELD_TYPE_WF: 'workflow',
  FIELD_TYPE_JOURNAL_INPUT: 'journal_input',
  /*Field types

  /*Attributes - task variables*/
  ATTR_REQUEST_ITEM: 'request_item',
  ATTR_QUESTION_TEXT: 'question_text',
  /*Attributes - task variables*/

  /*Operators*/
  OPR_IN: 'IN',
  /*End Operators*/

  /*Configuration*/
  SLA_TIME_LINE_LOG: 'com.snc.sla.time_line.log',
  SLA_ENGINE_VERSION: 'com.snc.sla.engine.version',
  LAND_MARK_PERCENTAGES: [50, 75, 100], //These percentage stages are inserted if updates do not happen at this exact stage
  /*End Configuration*/

  /*TASK_SLA STAGES*/
  SLA_STAGE_COMPLETED: 'completed',
  SLA_STAGE_CANCELLED: 'cancelled',
  SLA_STAGE_IN_PROGRESS: 'in_progress',
  SLA_STAGE_BREACHED: 'breached',
  /*End TASK_SLA STAGES*/

  /*Properties*/
  PROP_2010_BACK_COMPATIBILITY: 'com.snc.sla.compatibility.breach',
  PROP_TASK_UPDATE_LIMIT: 'com.snc.sla.timeline.task_update_limit',
  PROP_TASK_UPDATE_THRESHOLD: 'com.snc.sla.timeline.task_update_threshold',
  /*End Properties*/

  /*User Preference*/
  PREF_SHOW_ALL_TASK_UPDATES: 'com.snc.sla.timeline.show_all_task_updates',
  /*End User Preference*/

  initialize: function(params) {
  	this.arrayUtil = new ArrayUtil();
  	this.slaUtil = new SLAUtil();
  	this.hwWithJournals = false;
  	this.hwWithSysFields = false;
  	this.hwWithVariables = false;

  	this.conditionFields = [
  		this.ATTR_CANCEL_CONDITION,
  		this.ATTR_PAUSE_CONDITION,
  		this.ATTR_RESET_CONDITION,
  		this.ATTR_RESUME_CONDITION,
  		this.ATTR_START_CONDITION,
  		this.ATTR_STOP_CONDITION
  	];

  	this.log = new GSLog(this.SLA_TIME_LINE_LOG, this.type);
              this.log.includeTimestamp();

  	if (gs.getProperty(this.PROP_2010_BACK_COMPATIBILITY, 'false') === 'true')
  		this.isBackwardCompatibility2010Enabled = true;
  	else
  		this.isBackwardCompatibility2010Enabled = false;

  	this.TASK_UPDATE_LIMIT = parseInt(gs.getProperty(this.PROP_TASK_UPDATE_LIMIT, 12500));
  	this.TASK_UPDATE_THRESHOLD = parseInt(gs.getProperty(this.PROP_TASK_UPDATE_THRESHOLD, 1000));

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[initialize] params: " + JSON.stringify(params, null, 2));

  	if (params) {
  		if (params.getTaskSlaGroupedBySla)
  			this.GET_TASK_SLA_GROUPED_BY_SLA = true;
  		if (params.getDBTaskSlasAsStored)
  			this.GET_DB_TASK_SLAS_AS_STORED = true;
  		if (params.getTaskFullDetails)
  			this.GET_TASK_FULL_DETAILS = true;
  		if (params.getAllStages)
  			this.GET_ALL_STAGES = true;
  	}
  },

  /*
   ** This will be the function primarily responsible for preparing results of timeline
   ** details for SLA with query starting from a Task.
   **
   ** From REST API we might always end up hitting this API with a single task and the
   ** filtering on SLAs will be done on client side. However, we still provide a support to have the filter
   ** at server side for which we very well may have the need for other cases.
   **
   */
  getTimeLineDetails: function(taskId /*String - Mandatory*/ , contractSLAIds /*Array*/ ) {
  	var taskGr = new GlideRecord(this.TABLE_TASK);

  	if (!taskId || !taskGr.get(taskId))
  		return this._returnApiError(gs.getMessage('A valid task record is necessary to get timeline details'));

  	taskGr = this.getRecordWithClass(taskGr, taskId); //Fetching record with it's own class
  	var preConditions = this._apiPreConditionsCheck(taskGr);
  	if (preConditions.apiError || preConditions.apiSecurityError)
  		return preConditions;

  	return this._getTimeLineDetails(taskGr, contractSLAIds.filter(function(el) { return el; }));
  },

  getRecordWithClass: function(taskGr, taskId) {
  	var recordClassName = taskGr.getRecordClassName();
  	taskGr = new GlideRecord(recordClassName);
  	taskGr.get(taskId);
  	return taskGr;
  },

  _apiPreConditionsCheck: function(taskGr) {
  	if (gs.getProperty(this.SLA_ENGINE_VERSION, '2010') !== '2011')
  		return this._returnApiError(gs.getMessage("This feature is only supported while running the 2011 Engine."));

  	if (!(new GlideAuditor(taskGr.getRecordClassName(), null).auditTable()))
  		return this._returnApiError(gs.getMessage("Audit is not enabled on this table. Timeline details can't be extracted."));

  	if (!taskGr.canRead())
  		return this._returnApiSecurityError(gs.getMessage("Security constraints don't allow to view this timeline as read access is not available for this {0}", taskGr.getRecordClassName()));
  },

  _returnApiError: function(message) {
  	return { apiError: message };
  },

  _returnApiSecurityError: function(message) {
  	return {
  		apiSecurityError: message
  	};
  },

  _getTaskObj: function(taskGr) {
  	var taskObj = {};
  	if (this.GET_TASK_FULL_DETAILS)
  		taskObj = this.slaUtil.grToJsObj(taskGr);

  	if ((taskObj.hasOwnProperty('read_allowed') && taskObj.read_allowed) || !taskObj.hasOwnProperty('read_allowed')) {
  		taskObj.sys_id = taskGr.getUniqueValue();
  		if (taskGr.getDisplayName() && taskGr.getElement(taskGr.getDisplayName()).canRead())
  			taskObj.display_value = taskGr.getDisplayValue();
  		else
  			taskObj.display_value = '';
  		taskObj.url = taskGr.getLink(true);
  		taskObj.sys_class_name = taskGr.getRecordClassName();
  	}
  	return taskObj;
  },

  // Check SLA Definition conditions use Journal Conditions or contain variables
  _setHistoryWalkerParms: function(contractSLAData) {
  	if (!contractSLAData)
  		return;

  	if (contractSLAData.gr.hasNext()) {
  		while (contractSLAData.gr.next()) {
  			if (!this.hwWithJournals && this.slaUtil.hasAdvancedJournalCondition(contractSLAData.gr))
  				this.hwWithJournals = true;

  			if (!this.hwWithVariables && this.slaUtil.hasVariablesCondition(contractSLAData.gr))
  				this.hwWithVariables = true;

  			if (!this.hwWithSysFields && this.slaUtil.hasAdvancedSysFieldCondition(contractSLAData.gr))
  				this.hwWithSysFields = true;

  			if (this.hwWithJournals && this.hwWithVariables && this.hwWithSysFields)
  				break;
  		}
  		contractSLAData.gr.restoreLocation();
  	}

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_setHistoryWalkerParms] hwWithJournals: " + this.hwWithJournals + " hwWithVariables: " + this.hwWithVariables + " hwWithSysFields: " + this.hwWithSysFields);
  },

  _getTimeLineDetails: function(taskGr, contractSLAIds) {

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_getTimeLineDetails] contractSLAIds: " + contractSLAIds);

  	var taskSLAGr = new GlideRecord(this.TABLE_TASK_SLA);
  	taskSLAGr.addQuery(this.ATTR_TASK, taskGr.getUniqueValue());
  	if (Array.isArray(contractSLAIds) && contractSLAIds.length > 0)
  		taskSLAGr.addQuery(this.ATTR_SLA, this.OPR_IN, contractSLAIds.join(','));
  	taskSLAGr.query();

  	var taskSLAsCurrentStateInDb = this.slaUtil.grToJsArr(taskSLAGr);

  	var contractSLAData = this._getContractSLARecs(taskSLAsCurrentStateInDb, contractSLAIds, taskGr);

  	this._setHistoryWalkerParms(contractSLAData);

  	var taskSlasWorkedOut = this._prepareTaskSLAsForContractSLAs(contractSLAData, taskGr);

  	var timeLineDetails = {
  		task_delta_changes: taskSlasWorkedOut.taskDeltaChanges,
  		task_full_snap_at_updates: taskSlasWorkedOut.taskFullSnapAtUpdates,
  		task_slas_worked_out_attached_contracts: taskSlasWorkedOut.attached,
  		task_slas_worked_out_simulated_contracts: taskSlasWorkedOut.simulated,
  		contract_slas: taskSlasWorkedOut.contractSlas,
  		task_details: this._getTaskObj(taskGr),
  		task_reference_field_value_map: this._getTaskRefFieldValueMap(taskSlasWorkedOut.contractSlas, taskGr),
  		taskThresholdReached: taskSlasWorkedOut.taskThresholdReached,
  		taskHistoryLimitExceeded: taskSlasWorkedOut.taskHistoryLimitExceeded
  	};

  	if (this.GET_DB_TASK_SLAS_AS_STORED)
  		timeLineDetails.task_slas_current_state_db = taskSLAsCurrentStateInDb;

  	return timeLineDetails;
  },

  _getTaskRefFieldValueMap: function(contractSlas, taskGr) {
  	var taskRefFieldValueMap = {};
  	var variablesReadOnly = !taskGr.variables.canRead();
  	var variablesPrefix = "variables.";

  	for (var i = 0; i < contractSlas.length; i++) {
  		var contractSla = contractSlas[i];
  		var relatedFieldsForContractSla = [];

  		this.conditionFields.forEach(function(conditionField) {
  			relatedFieldsForContractSla = this.arrayUtil.concat(relatedFieldsForContractSla, contractSla[conditionField].related_fields);
  		}, this);

  		for (var j = 0; j < relatedFieldsForContractSla.length; j++) {
  			if (relatedFieldsForContractSla[j].indexOf('.') > -1 && !taskRefFieldValueMap.hasOwnProperty(relatedFieldsForContractSla[j])) {
  				var queryPart = relatedFieldsForContractSla[j];
  				var splitField = queryPart.split('.');
  				var dottedElement;
  				var dottedElementLabel;
  				var relatedFieldMap = {};
  				if (relatedFieldsForContractSla[j].startsWith(variablesPrefix)) {
  					var variableId = relatedFieldsForContractSla[j].substr(variablesPrefix.length);
  					var variableElement = taskGr.variables[variableId];
  					if (!variableElement)
  						continue;

  					relatedFieldMap.variable_name = variableElement.getName();
  					relatedFieldMap.read_allowed = variablesReadOnly;
  					relatedFieldMap.label = variableElement.getQuestion().getLabel();
  					relatedFieldMap.value = variablesReadOnly ? '' : variableElement.getValue();
  					relatedFieldMap.display_value = variablesReadOnly ? gs.getMessage('(restricted)') : variableElement.getDisplayValue();
  				} else {
  					for (var k = 0; k < splitField.length; k++) {//Go n level deep to find value
  						if (k === 0)
  							dottedElement = taskGr[splitField[k]];
  						else
  							dottedElement = dottedElement[splitField[k]];
  					}
  					//To form a valid query append '=' and then use the first part to get label of the query. Done to rely on platform API
  					var readableQuery = new GlideQueryBreadcrumbs().getReadableQuery(taskGr.getTableName(), queryPart + '=');
  					if (readableQuery)
  						dottedElementLabel = readableQuery.split('=')[0].trim();
  					else
  						dottedElementLabel = '';
  					if (dottedElement.canRead()) {
  						relatedFieldMap.read_allowed = true;
  						relatedFieldMap.label = dottedElementLabel;
  						relatedFieldMap.value = dottedElement.getValue();
  						relatedFieldMap.display_value = dottedElement.getDisplayValue();
  					} else {
  						relatedFieldMap.read_allowed = false;
  						relatedFieldMap.label = dottedElementLabel;
  						relatedFieldMap.value = '';
  						relatedFieldMap.display_value = gs.getMessage('(restricted)');
  					}
  				}
  				taskRefFieldValueMap[relatedFieldsForContractSla[j]] = relatedFieldMap;
  			}
  		}
  	}
  	return taskRefFieldValueMap;
  },

  _getAllRelatedFields: function(contractSlas) {
  	var relatedFieldsForContractSla = [];

  	contractSlas.forEach(function(contractSla) {
  		this.conditionFields.forEach(function(conditionField) {
  			relatedFieldsForContractSla = this.arrayUtil.concat(relatedFieldsForContractSla, contractSla[conditionField].related_fields);
  		}, this);
  	}, this);

  	relatedFieldsForContractSla = this.arrayUtil.unique(relatedFieldsForContractSla);

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_getAllRelatedFields] relatedFieldsForContractSla: " + JSON.stringify(relatedFieldsForContractSla, null, 2));

  	return relatedFieldsForContractSla;
  },

  _getContractSlaData: function(contractSlaGr) {
  	var contractSlas = [];

  	if (!contractSlaGr)
  		return contractSlas;

  	while (contractSlaGr.next())
  			contractSlas.push(this._prepareContractSLAData(contractSlaGr));

  	// Needs to be traversed again
  	contractSlaGr.restoreLocation();

  	return contractSlas;
  },

  _prepareTaskSLAsForContractSLAs: function(contractSla, taskGr) {

  	var taskSlasWorkedOut = {
  		contractSlas: [],
  		attached: this.GET_TASK_SLA_GROUPED_BY_SLA ? {} : [],
  		simulated: this.GET_TASK_SLA_GROUPED_BY_SLA ? {} : [],
  		taskDeltaChanges: [],
  		taskFullSnapAtUpdates: [],
  		taskThresholdReached: false,
  		taskHistoryLimitExceeded: false
  	};

  	if (!contractSla || !contractSla.gr)
  		return taskSlasWorkedOut;

  	if (this._exceedsHistoryLimit(taskGr)) {
  		taskSlasWorkedOut.taskHistoryLimitExceeded = true;
  		return taskSlasWorkedOut;
  	}

  	taskSlasWorkedOut.contractSlas = this._getContractSlaData(contractSla.gr);
  	var contractSlaRelatedFields = this._getAllRelatedFields(taskSlasWorkedOut.contractSlas);
  	var taskSlaGroupedBySla = this.getTaskSLABreakDowns(taskGr, contractSla.gr, contractSlaRelatedFields, taskSlasWorkedOut);

  	if (!taskSlaGroupedBySla)
  		return taskSlasWorkedOut;

  	// Populate attached and simulated arrays/objects
  	Object.keys(taskSlaGroupedBySla).forEach(function(contractSlaSysId) {
  		var obj = contractSla.attached.indexOf(contractSlaSysId) !== -1 ? taskSlasWorkedOut.attached : taskSlasWorkedOut.simulated;
  		if (this.GET_TASK_SLA_GROUPED_BY_SLA)
  			obj[contractSlaSysId] = taskSlaGroupedBySla[contractSlaSysId].taskSLAs;
  		else
  			obj = this.arrayUtil.concat(obj, taskSlaGroupedBySla[contractSlaSysId].taskSLAs);
  	}, this);

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_getTimeLineDetails] taskSlasWorkedOut: " + JSON.stringify(taskSlasWorkedOut, null, 2));

  	return taskSlasWorkedOut;
  },

  _exceedsHistoryLimit: function(taskGr) {
  	var rowCount = 0;
  	var auditGa = new GlideAggregate("sys_audit");
  	auditGa.addQuery("documentkey", taskGr.getUniqueValue());
  	auditGa.groupBy("record_checkpoint");
  	auditGa.addAggregate("COUNT");
  	auditGa.query();
  	if (auditGa.next())
  		rowCount = auditGa.getAggregate("COUNT");

  	var exceedsHistoryLimit = rowCount > this.TASK_UPDATE_LIMIT;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_exceedsHistoryLimit] rowCount: " + rowCount + " exceedsHistoryLimit: " + exceedsHistoryLimit);

  	return exceedsHistoryLimit;
  },

  _prepareContractSLAData: function(contractSLAGr) {
  	var contractSLAObj = this.slaUtil.grToJsObj(contractSLAGr, true);
  	if (contractSLAObj.read_allowed) {
  		this.conditionFields.forEach(function(conditionField) {
  			if (contractSLAGr.getValue(conditionField))
  				contractSLAObj[conditionField].related_fields = this.slaUtil.getRelatedFieldsFromEncodedQuery(contractSLAGr.getValue(this.ATTR_COLLECTION), contractSLAGr.getValue(conditionField));
  		}, this);
  	}

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_prepareContractSLAData] contractSLAObj: " + JSON.stringify(contractSLAObj, null, 2));

  	return contractSLAObj;
  },

  _getTaskUpdate: function(walkedRecordGr) {
  	var taskUpdate = {};
  	taskUpdate.update = walkedRecordGr.getValue(this.ATTR_SYS_MOD_COUNT);
  	if (parseInt(taskUpdate.update) === 0)
  		taskUpdate[this.ATTR_SYS_CREATED_ON] = walkedRecordGr[this.ATTR_SYS_CREATED_ON].getGlideObject().getDisplayValueInternal();
  	taskUpdate[this.ATTR_SYS_UPDATED_ON] = walkedRecordGr[this.ATTR_SYS_UPDATED_ON].getGlideObject().getDisplayValueInternal();
  	this._addChangedFields(walkedRecordGr, taskUpdate);
  	this._addChangedVariables(walkedRecordGr, taskUpdate);
  	this._addDateMapNonGrFields(taskUpdate, [this.ATTR_SYS_UPDATED_ON, this.ATTR_SYS_CREATED_ON]);
  	return taskUpdate;
  },

  _getFullSnapshot: function(walkedRecordGr, contractSlaRelatedFields) {
  	var jsObj = this.slaUtil.grToJsObj(walkedRecordGr);
  	var fullSnap = {};
  	for (var fieldName in jsObj)
  		if (fieldName.startsWith('sys_') || fieldName.startsWith('variables') || this.arrayUtil.contains(contractSlaRelatedFields, fieldName))
  			fieldName.startsWith('variables.') ? fullSnap[fieldName] = jsObj["variables"] : fullSnap[fieldName] = jsObj[fieldName];

  	return fullSnap;
  },

  _getTaskSLABreakDowns: function(taskGr, walkedRecordGr, previousWalkedRecordGr, lastUpdate, params, finalHistoryUpdate) {
  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_getTaskSLABreakDowns] walkedRecordGr.original_sys_id: " + walkedRecordGr.original_sys_id + " modCount: " + walkedRecordGr.sys_mod_count);

  	if (!params.taskSLA)
  		params.taskSLA = params.taskSLAController.initNewTaskSLA(walkedRecordGr, params.contractSLAGr);
  	else if (params.taskSLA) {
  		params.prevTaskSLA = params.taskSLAController.copyTaskSLA(params.contractSLAGr, previousWalkedRecordGr, this.slaUtil.copyGlideRecord(params.taskSLA.taskSLAgr), params.taskSLA.retroactivePauseData);
  		params.taskSLA = params.taskSLAController.updateTaskSLA(walkedRecordGr, params.taskSLA, params.contractSLAGr);
  	}

  	if (params.taskSLA) {
  		var taskSLADummyGr = params.taskSLA.getGlideRecord();
  		if (taskSLADummyGr.getValue(this.ATTR_STAGE)) {

  			// This block is for when SLA has start time later than task update date
  			if (this._isSlaStartAfterTaskUpdate(taskSLADummyGr, walkedRecordGr)) {

  				if (params.preStartSLAStages.length === 0) {
  					params.attachedDate = walkedRecordGr.sys_updated_on.getGlideObject().getDisplayValueInternal();
  					params.preStartSLAStages.push(this._prepareTaskSLAStage(taskSLADummyGr, undefined, walkedRecordGr));
  				} else
  					params.preStartSLAStages.push(this._prepareTaskSLAStage(taskSLADummyGr, params.preStartSLAStages[params.preStartSLAStages.length - 1], walkedRecordGr));

  				if (this.log.atLevel(GSLog.DEBUG))
  					this.log.debug("[_getTaskSLABreakDowns] params.preStartSLAStages: " + JSON.stringify(params.preStartSLAStages, null, 2));

  				this._trackIfUpdateWasResponsibleForStageChange(params.preStartSLAStages, params.stateChangeOccuredDueToUpdates, walkedRecordGr, true);

  				params.isTerminalStageUpdate = parseInt(taskSLADummyGr.getValue(this.ATTR_ACTIVE)) === 0;
  				if (params.isTerminalStageUpdate || lastUpdate) {
  					params.isReset = params.taskSLA.isReset;
  					if (params.preStartSLAStages && params.preStartSLAStages.length > 0 && params.isReset)
  						params.preStartSLAStages[params.preStartSLAStages.length - 1].values_at_evaluation_date.is_reset = params.isReset;

  					this._prepareFutureTaskSLA(params.contractSLAGr, params.preStartSLAStages, params.stateChangeOccuredDueToUpdates, params.taskSLAs, params.attachedDate, params.countOfTaskSlas++);
  					params.taskSLAController = new ReadOnlyTaskSLAController();
  					params.taskSLA = null;
  					params.attachedDate = null;
  					params.taskSLAStages = [];
  					params.preStartSLAStages = [];
  					params.landMarkPercentageCount = 0;
  					params.stateChangeOccuredDueToUpdates = [];
  					params.prevTaskSLA = null;
  					params.startedFromReset = null;

  					if (this.log.atLevel(GSLog.DEBUG))
  						this.log.debug("[_getTaskSLABreakDowns] params.isReset: " + params.isReset);

  					if (params.isReset) {
  						params.taskSLA = params.taskSLAController.initNewTaskSLA(walkedRecordGr, params.contractSLAGr);
  						if (params.taskSLA) {
  							params.taskSLADummyGrReset = params.taskSLA.getGlideRecord();
  							params.preStartSLAStages.push(this._prepareTaskSLAStage(params.taskSLADummyGrReset, undefined, walkedRecordGr));
  							this._trackIfUpdateWasResponsibleForStageChange(params.preStartSLAStages, params.stateChangeOccuredDueToUpdates, walkedRecordGr, true);
  						}
  					}
  				}
  			}
  			// This block is for when start time is before or equal to task update date
  			else {
  				if (params.taskSLAStages.length === 0)
  					params.taskSLAStages.push(this._prepareTaskSLAStage(taskSLADummyGr, undefined, walkedRecordGr));
  				else
  					params.taskSLAStages.push(this._prepareTaskSLAStage(taskSLADummyGr, params.taskSLAStages[params.taskSLAStages.length - 1], walkedRecordGr));

  				if (this._trackIfUpdateWasResponsibleForStageChange(params.taskSLAStages, params.stateChangeOccuredDueToUpdates, walkedRecordGr))
  					params.taskSLAStages[params.taskSLAStages.length - 1].mustHaveStage = true;

  				if (this.log.atLevel(GSLog.DEBUG))
  					this.log.debug("[_getTaskSLABreakDowns] params.taskSLAStages: " + JSON.stringify(params.taskSLAStages, null, 2));

  				// Populate the date when the params.taskSLA was attached
  				if (params.taskSLAStages.length === 1) {
  					params.attachedDate = walkedRecordGr.sys_updated_on.getGlideObject().getDisplayValueInternal();
  					params.walkedRecordGr = walkedRecordGr;
  					if (params.attachedDate > taskSLADummyGr.start_time.getGlideObject().getDisplayValueInternal())
  						this._insertRetroactiveStages(params);
  				}

  				params.prevTaskGr = this.slaUtil.copyGlideRecord(previousWalkedRecordGr);

  				// Evaluate and insert the stages where land mark business percentages like 50, 75, 100 were crossed at correct positions.
  				this._insertLandMarkStages(params);

  				params.isTerminalStageUpdate = parseInt(taskSLADummyGr.getValue(this.ATTR_ACTIVE)) === 0;
  				if (params.isTerminalStageUpdate || lastUpdate) {
  					params.taskSLAStages[params.taskSLAStages.length - 1].mustHaveStage = true;
  					params.isReset = params.taskSLA.isReset;
  					if (params.taskSLAStages) {
  						if (params.isReset)
  							params.taskSLAStages[params.taskSLAStages.length - 1].values_at_end_date.is_reset = params.isReset;

  						this._prepareTaskSLA(params.contractSLAGr, params.taskSLAStages, params.stateChangeOccuredDueToUpdates, params.taskSLAs, params.attachedDate, walkedRecordGr, params.countOfTaskSlas++, null, params.startedFromReset);
  					}

  					//clear task_sla to evaluate if it gets reattached or restarted cause of future updates.
  					params.taskSLAController = new ReadOnlyTaskSLAController();
  					params.taskSLA = null;
  					params.attachedDate = null;
  					params.taskSLAStages = [];
  					params.preStartSLAStages = [];
  					params.landMarkPercentageCount = 0;
  					params.stateChangeOccuredDueToUpdates = [];
  					params.prevTaskSLA = null;
  					params.startedFromReset = null;

  					if (this.log.atLevel(GSLog.DEBUG))
  						this.log.debug("[_getTaskSLABreakDowns] params.isReset: " + params.isReset);

  					if (params.isReset) {
  						params.taskSLA = params.taskSLAController.initNewTaskSLA(walkedRecordGr, params.contractSLAGr);
  						if (params.taskSLA) {
  							params.taskSLADummyGrReset = params.taskSLA.getGlideRecord();
  							params.taskSLAStages.push(this._prepareTaskSLAStage(params.taskSLADummyGrReset, undefined, walkedRecordGr));
  							if (this._trackIfUpdateWasResponsibleForStageChange(params.taskSLAStages, params.stateChangeOccuredDueToUpdates, walkedRecordGr))
  								params.taskSLAStages[params.taskSLAStages.length - 1].mustHaveStage = true;
  							params.attachedDate = walkedRecordGr.sys_updated_on.getGlideObject().getDisplayValueInternal();
  							params.startedFromReset = {
  								taskSLA_text: params.taskSLAs[params.taskSLAs.length - 1].text,
  								taskSLA_id: params.taskSLAs[params.taskSLAs.length - 1].id
  							};

  							params.walkedRecordGr = walkedRecordGr;
  							if (params.attachedDate > params.taskSLADummyGrReset.start_time.getGlideObject().getDisplayValueInternal())
  								this._insertRetroactiveStages(params);

  							if (finalHistoryUpdate) {
  								//The update that caused the reset is the last update and so we now need to evaluate the SLA based on the current date/time
  								params.prevTaskSLA = params.taskSLAController.copyTaskSLA(params.contractSLAGr, walkedRecordGr, this.slaUtil.copyGlideRecord(params.taskSLA.taskSLAgr), params.taskSLA.retroactivePauseDataList);
  								params.taskSLA = params.taskSLAController.updateTaskSLA(params.pseudoTaskGr, params.taskSLA, params.contractSLAGr);
  								var taskSLADummyGrResetPseudoUpdate = params.taskSLA.getGlideRecord();
  								//At this stage the pseudo update is again going to complete the SLA as it has not moved out of Reset so just stamp to the stage it was before
  								this._revertAttributes(taskSLADummyGrResetPseudoUpdate, params.taskSLAStages[params.taskSLAStages.length - 1]);
  								params.taskSLAStages.push(this._prepareTaskSLAStage(taskSLADummyGrResetPseudoUpdate, params.taskSLAStages[params.taskSLAStages.length - 1], params.pseudoTaskGr));
  								params.taskSLAStages[params.taskSLAStages.length - 1].mustHaveStage = true;
  								params.landMarkPercentageCount = this._insertLandMarkStages(params, false);
  								this._prepareTaskSLA(params.contractSLAGr, params.taskSLAStages, params.stateChangeOccuredDueToUpdates, params.taskSLAs, params.attachedDate, taskGr, params.countOfTaskSlas++, null, params.startedFromReset);
  							}
  						}
  					}
  				}
  			}
  		}
  	}
  },

  getTaskSLABreakDowns: function(taskGr, contractSLAGr, contractSlaRelatedFields, taskSlasWorkedOut) {
  	var walkedRecordGr;
  	var previousWalkedRecordGr;
  	var lastUpdate = false;

  	var hw = this.slaUtil.getHistoryWalker(
  		taskGr.getRecordClassName(),
  		taskGr.getValue('sys_id'),
  		/*recordLevelSecurity*/ false,
  		/*fieldLevelSecurity*/ false,
  		/*withVariables*/ this.hwWithVariables,
  		/*walkToFirstUpdate*/ false,
  		/*withJournalFields*/ this.hwWithJournals,
  		/*withSysFields*/ this.hwWithSysFields
  	);

  	if (!hw)
  		return;

  	// Get the latest update for our task
  	hw.walkBackward();

  	// We may need this "pseudo" copy of the Task which is basically the current task but with the update time set to now
  	var pseudoTaskGr = hw.getWalkedRecordCopy();
  	pseudoTaskGr.setValue(this.ATTR_SYS_UPDATED_ON, new GlideDateTime().getValue());
  	pseudoTaskGr.original_sys_id = walkedRecordGr.getUniqueValue();

  	var taskSlaGroupedBySla = {};
  	while(contractSLAGr.next()) {
  		taskSlaGroupedBySla[contractSLAGr.getUniqueValue()] = {
  			contractSLAGr: this.slaUtil.copyGlideRecord(contractSLAGr),
  			taskSLAStages: [],
  			taskSLAs: [],
  			taskSLA: null,
  			taskSLAController: new ReadOnlyTaskSLAController(),
  			countOfTaskSlas: 1,
  			stateChangeOccuredDueToUpdates: [],
  			attachedDate: null,
  			landMarkPercentageCount: 0,
  			prevTaskSLA: null,
  			preStartSLAStages: [],
  			isTerminalStageUpdate: false,
  			startedFromReset: null,
  			isReset: null,
  			taskSLADummyGrReset: null,
  			taskSLACopy: null,
  			retroactiveStagesObj: null,
  			retroactiveTaskSLAStages: [],
  			taskSLADummyGrResetPseudoUpdate: null,
  			pseudoTaskGr: pseudoTaskGr
  		};
  	}

  	// Now go to update 0 so we can start walking through the updates
  	if (!hw.walkTo(0))
  		return;

  	var userPrefAllTaskUpdates = gs.getUser().getPreference(this.PREF_SHOW_ALL_TASK_UPDATES);
  	var taskUpdatesCounter = 0;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[getTaskSLABreakDowns] userPrefAllTaskUpdates: " + userPrefAllTaskUpdates);

  	// Checking the mod count is not sufficient as processes can update the record with setWorkflow(false)
  	var maxUpdateCount = this.slaUtil.getMaxUpdateCount(taskGr.getRecordClassName(), taskGr.getUniqueValue());

  	do {
  		// Process the taskSla by walking the task history
  		walkedRecordGr = hw.getWalkedRecord();
  		walkedRecordGr.original_sys_id = taskGr.getUniqueValue();

  		var slaConditionFieldChanged = this._hasSlaConditionFieldChanged(contractSlaRelatedFields, walkedRecordGr);

  		if (slaConditionFieldChanged || (userPrefAllTaskUpdates && taskUpdatesCounter < this.TASK_UPDATE_THRESHOLD)) {
  			taskSlasWorkedOut.taskDeltaChanges.push(this._getTaskUpdate(walkedRecordGr));
  			taskSlasWorkedOut.taskFullSnapAtUpdates.push(this._getFullSnapshot(walkedRecordGr, contractSlaRelatedFields));

  			// increment number of task updates that have no effect on SLA
  			if (userPrefAllTaskUpdates && !slaConditionFieldChanged) {
  				taskUpdatesCounter++;
  				taskSlasWorkedOut.taskThresholdReached = taskUpdatesCounter >= this.TASK_UPDATE_THRESHOLD;
  			}
  		}

  		var updateNumber = hw.getUpdateNumber();
  		Object.keys(taskSlaGroupedBySla).forEach(function(contractSlaSysId) {
  			this._getTaskSLABreakDowns(taskGr, walkedRecordGr, previousWalkedRecordGr, lastUpdate, taskSlaGroupedBySla[contractSlaSysId], maxUpdateCount === updateNumber);
  		}, this);

  		previousWalkedRecordGr = hw.getWalkedRecordCopy();
  	} while (hw.walkForward());

  	// Process the pseudo record
  	if (pseudoTaskGr === null)
  		return taskSlaGroupedBySla;

  	Object.keys(taskSlaGroupedBySla).forEach(function(contractSlaSysId) {
  		if (!taskSlaGroupedBySla[contractSlaSysId].isTerminalStageUpdate)
  			this._getTaskSLABreakDowns(taskGr, pseudoTaskGr, previousWalkedRecordGr, true, taskSlaGroupedBySla[contractSlaSysId]);
  	}, this);

  	return taskSlaGroupedBySla;
  },

  _hasSlaConditionFieldChanged: function(contractSlaRelatedFields, walkedRecordGr) {
  	var changedFieldNames = GlideScriptRecordUtil.get(walkedRecordGr).getChangedFieldNames();
  	for (var i = 0; i < contractSlaRelatedFields.length; i++)
  		if (changedFieldNames.indexOf(contractSlaRelatedFields[i]) > -1)
  			return true;
  	return false;
  },

  //function to revert reset/retroactive stage to before for pseudo updates as re-evaluation of reset will complete the sla or retroactive could stamp false stage
  //while this update is not a real one
  _revertAttributes: function(taskSLADummyGrPseudoUpdate, prevStage) {
  	//retroactives can occur before creation date. So at that time the TaskSLA API could return false stage of cancelled or any other stage as per the Task value at that time. So ignore them.
  	if (this.isBackwardCompatibility2010Enabled && parseInt(taskSLADummyGrPseudoUpdate.getValue(this.ATTR_HAS_BREACHED)) === 1)
  		taskSLADummyGrPseudoUpdate.setValue(this.ATTR_STAGE, this.SLA_STAGE_BREACHED);
  	else
  		taskSLADummyGrPseudoUpdate.setValue(this.ATTR_STAGE, prevStage.values_at_end_date[this.ATTR_STAGE].value);
  	taskSLADummyGrPseudoUpdate.setValue(this.ATTR_ACTIVE, prevStage.values_at_end_date[this.ATTR_ACTIVE].value);
  },

  _isSlaStartAfterTaskUpdate: function(taskSLAGr, taskGr) {
  	var taskUpdateTime = taskGr.sys_updated_on.getGlideObject().getDisplayValueInternal();
  	var taskSlaStartTime = taskSLAGr.start_time.getGlideObject().getDisplayValueInternal();
  	var isSlaStartAfterTaskUpdate = taskUpdateTime < taskSlaStartTime;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_isSlaStartAfterTaskUpdate] taskSlaStartTime: " + taskSlaStartTime + " taskUpdateTime: " + taskUpdateTime + " isSlaStartAfterTaskUpdate: " + isSlaStartAfterTaskUpdate);

  	return isSlaStartAfterTaskUpdate;
  },

  _trackIfUpdateWasResponsibleForStageChange: function(taskSLAStages, stateChangeOccuredDueToUpdates, taskDummyGrCurrent, isFuture) {
  	var stageChanged = false;
  	//determine if this update was responsible for changing the state of this task_sla. For first stage always
  	//add to the stateChangeOccuredDueToUpdates as this update moved the task_sla to starting
  	if (taskSLAStages.length === 1)
  		stageChanged = true;

  	if (taskSLAStages.length > 1) {
  		var prevStage;
  		var currentStage;
  		if (isFuture)
  			prevStage = taskSLAStages[taskSLAStages.length - 2].values_at_evaluation_date.stage.value;
  		else
  			prevStage = taskSLAStages[taskSLAStages.length - 2].values_at_end_date.stage.value;
  		if (isFuture)
  			currentStage = taskSLAStages[taskSLAStages.length - 1].values_at_evaluation_date.stage.value;
  		else
  			currentStage = taskSLAStages[taskSLAStages.length - 1].values_at_end_date.stage.value;

  		var updateBasedStageChange = (prevStage !== currentStage && !(prevStage == this.SLA_STAGE_IN_PROGRESS && currentStage == this.SLA_STAGE_BREACHED));
  		var updateBasedTerminalStageChange;

  		if (isFuture)
  			updateBasedTerminalStageChange = (taskSLAStages[taskSLAStages.length - 1].values_at_evaluation_date.active.value !== taskSLAStages[taskSLAStages.length - 2].values_at_evaluation_date.active.value);
  		else
  			updateBasedTerminalStageChange = (taskSLAStages[taskSLAStages.length - 1].values_at_end_date.active.value !== taskSLAStages[taskSLAStages.length - 2].values_at_end_date.active.value);

  		if (updateBasedTerminalStageChange || updateBasedStageChange)
  			stageChanged = true;
  	}

  	if (stageChanged)
  		stateChangeOccuredDueToUpdates.push(taskDummyGrCurrent.getValue(this.ATTR_SYS_MOD_COUNT));

  	return stageChanged;
  },

  _insertRetroactiveStages: function(params) {
  	params.taskSLACopy = params.taskSLAController.copyTaskSLA(params.contractSLAGr, params.walkedRecordGr, this.slaUtil.copyGlideRecord(params.taskSLA.taskSLAgr), params.taskSLA.retroactivePauseDataList);

  	if (!params.taskSLAStages || !params.taskSLAStages[0])
  		return;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_insertRetroactiveStages] taskSLAStages: " + JSON.stringify(params.taskSLAStages, null, 2));

  	var attachedStageObj = params.taskSLAStages[0];
  	var landMarkPercentageCount = 0;
  	var attachedStage = attachedStageObj.values_at_end_date;
  	var schedule = this.slaUtil.getSchedule(params.contractSLAGr, params.walkedRecordGr);
  	var startTimeGdt = new GlideDateTime(attachedStage[this.ATTR_START_TIME].value);
  	var originalBreachTimeGdt = new GlideDateTime(attachedStage[this.ATTR_ORIGINAL_BREACH_TIME].value);
  	var totalDurMS = 0;
  	/*
  	 * Original breach time is the time when the SLA started and it was the 100% mark. So rely on it.
  	 */
  	if (schedule && schedule.getID()) {
  		var totalDur = schedule.duration(startTimeGdt, originalBreachTimeGdt);
  		totalDurMS = new GlideDateTime(totalDur.getValue()).getNumericValue();
  	} else
  		totalDurMS = originalBreachTimeGdt.getNumericValue() - startTimeGdt.getNumericValue();

  	var retroactivePauseDataList = params.taskSLACopy.retroactivePauseDataList;

  	//create a stage at 0 progress at start_time
  	var taskSLADummyGr = params.taskSLACopy.getGlideRecord();
  	taskSLADummyGr.setValue(this.ATTR_DURATION, new GlideDuration(0));
  	taskSLADummyGr.setValue(this.ATTR_BUSINESS_DURATION, new GlideDuration(0));
  	taskSLADummyGr.setValue(this.ATTR_PAUSE_DURATION, new GlideDuration(0));
  	taskSLADummyGr.setValue(this.ATTR_BUSINESS_PAUSE_DURATION, new GlideDuration(0));
  	taskSLADummyGr.setValue(this.ATTR_PAUSE_TIME, '');
  	taskSLADummyGr.setValue(this.ATTR_TIME_LEFT, new GlideDuration(totalDurMS));
  	taskSLADummyGr.setValue(this.ATTR_BUSINESS_TIME_LEFT, new GlideDuration(totalDurMS));
  	taskSLADummyGr.setValue(this.ATTR_PERCENTAGE, 0);
  	taskSLADummyGr.setValue(this.ATTR_BUSINESS_PERCENTAGE, 0);
  	taskSLADummyGr.setValue(this.ATTR_HAS_BREACHED, false);
  	taskSLADummyGr.setValue(this.ATTR_STAGE, this.SLA_STAGE_IN_PROGRESS);
  	taskSLADummyGr.setValue(this.ATTR_PLANNED_END_TIME, taskSLADummyGr.getValue(this.ATTR_ORIGINAL_BREACH_TIME));
  	params.taskSLACopy.taskSLAgr = taskSLADummyGr;

  	var dummyTaskGr = null;

  	var hw = this.slaUtil.getHistoryWalker(
  		params.walkedRecordGr.getRecordClassName(),
  		params.walkedRecordGr.getValue('sys_id'),
  		/*recordLevelSecurity*/ false,
  		/*fieldLevelSecurity*/ false,
  		/*withVariables*/ this.hwWithVariables,
  		/*walkToFirstUpdate*/ true,
  		/*withJournalFields*/ this.hwWithJournals,
  		/*withSysFields*/ this.hwWithSysFields
  	);

  	if (!hw)
  		return {};

  	do {
  		var walkedRecordGr = hw.getWalkedRecord();
  		walkedRecordGr.original_sys_id = params.walkedRecordGr.getUniqueValue();
  	} while (walkedRecordGr.sys_updated_on.getGlideObject().getDisplayValueInternal() < startTimeGdt.getDisplayValueInternal() && hw.walkForward());

  	dummyTaskGr = hw.getWalkedRecordCopy();
  	dummyTaskGr.setValue(this.ATTR_SYS_UPDATED_ON, startTimeGdt);
  	params.retroactiveTaskSLAStages.push(this._prepareTaskSLAStage(taskSLADummyGr, undefined, dummyTaskGr));
  	params.retroactiveTaskSLAStages[params.retroactiveTaskSLAStages.length - 1].mustHaveStage = true;
  	params.prevTaskGr = this.slaUtil.copyGlideRecord(dummyTaskGr);
  	params.prevTaskSLA = params.taskSLAController.copyTaskSLA(params.contractSLAGr, dummyTaskGr, taskSLADummyGr);
  	//inspect for known pauses (only applicable for retroactive pause ticked SLAs.)
  	for (var i = 0; i < retroactivePauseDataList.length; i++) {
  		var taskSLAGrExitingOrEnteringPause = retroactivePauseDataList[i].taskSLAGr;
  		var taskDummyGrCurrent = retroactivePauseDataList[i].responsibleTaskGr;
  		if (i !== 0) {
  			params.prevTaskGr = this.slaUtil.copyGlideRecord(retroactivePauseDataList[i - 1].responsibleTaskGr);
  			params.prevTaskSLA = params.taskSLAController.copyTaskSLA(params.contractSLAGr, retroactivePauseDataList[i - 1].responsibleTaskGr, retroactivePauseDataList[i - 1].taskSLAGr);
  		}
  		taskDummyGrCurrent.setValue(this.ATTR_SYS_UPDATED_ON, taskSLAGrExitingOrEnteringPause.getValue(this.ATTR_SYS_UPDATED_ON));

  		this._trackIfUpdateWasResponsibleForStageChange(params.retroactiveTaskSLAStages, params.stateChangeOccuredDueToUpdates, taskDummyGrCurrent);

  		params.retroactiveTaskSLAStages.push(this._prepareTaskSLAStage(taskSLAGrExitingOrEnteringPause, params.retroactiveTaskSLAStages[params.retroactiveTaskSLAStages.length - 1], taskDummyGrCurrent));

  		//insert landmarks as applicable at each stage
  		this._insertLandMarkStages(params, true);
  		if (i === retroactivePauseDataList.length - 1) {
  			params.prevTaskGr = this.slaUtil.copyGlideRecord(retroactivePauseDataList[i].responsibleTaskGr);
  			params.prevTaskSLA = params.taskSLAController.copyTaskSLA(params.contractSLAGr, retroactivePauseDataList[i].responsibleTaskGr, retroactivePauseDataList[i].taskSLAGr);
  		}
  		params.retroactiveTaskSLAStages[params.retroactiveTaskSLAStages.length - 1].mustHaveStage = true;
  	}
  	//join the retoractive stage and the stage at attached date
  	attachedStageObj.start_date = params.retroactiveTaskSLAStages[params.retroactiveTaskSLAStages.length - 1].end_date;
  	params.retroactiveTaskSLAStages.push(attachedStageObj);
  	params.taskSLAStages = params.retroactiveTaskSLAStages;
  	this._insertLandMarkStages(params, true);

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_insertRetroactiveStages] landMarkPercentageCount: " + params.landMarkPercentageCount + " taskSLAStages: " + JSON.stringify(params.taskSLAStages, null, 2));
  },

  _prepareFutureTaskSLA: function(contractSLAGr, preStartSLAStages, stateChangeOccuredDueToUpdates, taskSLAs, attachedDate, countOfTaskSlas) {
  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_prepareFutureTaskSLA] countOfTaskSlas: " + countOfTaskSlas);

  	if (preStartSLAStages && preStartSLAStages.length) {
  		var futureTaskSLA = {};
  		futureTaskSLA.read_allowed = true;
  		futureTaskSLA.is_future = true;
  		for (var i = 0; i < preStartSLAStages.length; i++) {
  			if (!preStartSLAStages[i].values_at_evaluation_date.read_allowed) {
  				futureTaskSLA.read_allowed = preStartSLAStages[i].values_at_evaluation_date.read_allowed;
  				futureTaskSLA.security_check_fail_message = preStartSLAStages[i].values_at_evaluation_date.security_check_fail_message;
  				break;
  			}
  		}

  		if (futureTaskSLA.read_allowed) {
  			futureTaskSLA.contract_sla_id = contractSLAGr.getUniqueValue();
  			futureTaskSLA.text = gs.getMessage('Task SLA - {0}', '' + countOfTaskSlas);
  			futureTaskSLA.attached_date = attachedDate;
  			futureTaskSLA.pre_start_sla_stages = preStartSLAStages;
  			futureTaskSLA.state_change_due_to_task_updates = stateChangeOccuredDueToUpdates;
  		}
  		taskSLAs.push(futureTaskSLA);
  	}

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_prepareFutureTaskSLA] taskSLAs: " + JSON.stringify(taskSLAs, null, 2));
  },

  _prepareTaskSLA: function(contractSLAGr, taskSLAStages, stateChangeOccuredDueToUpdates, taskSLAs, attachedDate, taskDummyGrCurrent, countOfTaskSlas, preStartStages, startedFromReset) {
  	var taskSLAObj = {};
  	taskSLAObj.read_allowed = true;
  	var taskSLAReturnStages = [];
  	if (taskSLAStages) {

  		if (this.log.atLevel(GSLog.DEBUG))
  			this.log.debug("[_prepareTaskSLA] taskSLAStages.length: " + taskSLAStages.length);

  		for (var i = 0; i < taskSLAStages.length; i++) {
  			if (!taskSLAStages[i].values_at_end_date.read_allowed) {
  				taskSLAObj.read_allowed = taskSLAStages[i].values_at_end_date.read_allowed;
  				taskSLAObj.security_check_fail_message = taskSLAStages[i].values_at_end_date.security_check_fail_message;
  				break;
  			}

  			if (taskSLAStages[i].mustHaveStage || this.GET_ALL_STAGES) {
  				var tempStage = {};
  				tempStage = taskSLAStages[i];

  				if (taskSLAReturnStages)
  					tempStage.start_date = taskSLAReturnStages[taskSLAReturnStages.length - 1].end_date;

  				this._addDateMapNonGrFields(tempStage, ['start_date', 'end_date']);
  				var stageStartDateGdt = new GlideDateTime();
  				stageStartDateGdt.setDisplayValueInternal(tempStage.start_date);
  				var stageEndDateGdt = new GlideDateTime();
  				stageEndDateGdt.setDisplayValueInternal(tempStage.end_date);
  				var stageDurationMS = stageEndDateGdt.getNumericValue() - stageStartDateGdt.getNumericValue();
  				tempStage.duration = new GlideDuration(stageDurationMS).getDisplayValue();
  				taskSLAReturnStages.push(tempStage);
  			}

  		}

  		if (taskSLAObj.read_allowed) {
  			taskSLAObj.start_date = taskSLAStages[0].start_date;
  			taskSLAObj.end_date = taskSLAStages[taskSLAStages.length - 1].end_date;
  		}
  	}

  	if (taskSLAObj.read_allowed) {
  		taskSLAObj.id = contractSLAGr.getUniqueValue() + '-' + attachedDate;
  		taskSLAObj.contract_sla_id = contractSLAGr.getUniqueValue();
  		taskSLAObj.text = gs.getMessage('Task SLA - {0}', '' + countOfTaskSlas);
  		taskSLAObj.state_change_due_to_task_updates = stateChangeOccuredDueToUpdates;
  		taskSLAObj.stages = taskSLAReturnStages;
  		taskSLAObj.pre_start_stages = preStartStages;
  		var taskSLAStartDate = new GlideDateTime();
  		taskSLAStartDate.setDisplayValueInternal(taskSLAObj.start_date);
  		var taskSLAEndDate = new GlideDateTime();
  		taskSLAEndDate.setDisplayValueInternal(taskSLAObj.end_date);
  		var slaSchedule = this.slaUtil.getSchedule(contractSLAGr, taskDummyGrCurrent);
  		taskSLAObj.used_timezone = {};
  		taskSLAObj.used_timezone.display_value = slaSchedule.getTimeZone();
  		taskSLAObj.attached_date = attachedDate;
  		taskSLAObj.business_schedule_spans = this._getBusinessScheduleSpans(taskSLAStartDate, taskSLAEndDate, slaSchedule);
  		taskSLAObj.out_of_business_schedule_spans = [];

  		if (taskSLAObj.start_date && taskSLAObj.end_date && slaSchedule.getID())
  			taskSLAObj.out_of_business_schedule_spans = this._getOutOfBusinessScheduleSpans(taskSLAObj.start_date, taskSLAObj.end_date, taskSLAObj.business_schedule_spans, slaSchedule);

  		this._addDateMapNonGrFields(taskSLAObj, ['start_date', 'end_date', 'attached_date']);
  		taskSLAObj.started_from_reset = startedFromReset;
  	}

  	taskSLAs.push(taskSLAObj);

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_prepareTaskSLA] taskSLAs: " + JSON.stringify(taskSLAs, null, 2));
  },

  _prepareTaskSLAStage: function(taskSLADummyGr, prevStage, taskDummyGrCurrent) {
  	var taskSLAStage = {};
  	var taskSLADummyObj = this.slaUtil.grToJsObj(taskSLADummyGr, true);
  	if (taskSLADummyObj.read_allowed) {
  		//We forcibly copy these values if a schedule is not present. This is done so that the REST API always guarantees business fields
  		if (!taskSLADummyObj[this.ATTR_SCHEDULE].value) {
  			var duration0 = new GlideDuration(0).getValue() + '';
  			if (!taskSLADummyObj[this.ATTR_BUSINESS_TIME_LEFT] || (taskSLADummyObj[this.ATTR_BUSINESS_TIME_LEFT] && (!taskSLADummyObj[this.ATTR_BUSINESS_TIME_LEFT].value || duration0 === taskSLADummyObj[this.ATTR_BUSINESS_TIME_LEFT].value) && taskSLADummyObj[this.ATTR_BUSINESS_TIME_LEFT].read_allowed))
  				taskSLADummyObj[this.ATTR_BUSINESS_TIME_LEFT] = taskSLADummyObj[this.ATTR_TIME_LEFT];
  			if (!taskSLADummyObj[this.ATTR_BUSINESS_PERCENTAGE] || (taskSLADummyObj[this.ATTR_BUSINESS_PERCENTAGE] && !taskSLADummyObj[this.ATTR_BUSINESS_PERCENTAGE].value && taskSLADummyObj[this.ATTR_BUSINESS_PERCENTAGE].read_allowed))
  				taskSLADummyObj[this.ATTR_BUSINESS_PERCENTAGE] = taskSLADummyObj[this.ATTR_PERCENTAGE];
  			if (!taskSLADummyObj[this.ATTR_BUSINESS_DURATION] || (taskSLADummyObj[this.ATTR_BUSINESS_DURATION] && (!taskSLADummyObj[this.ATTR_BUSINESS_DURATION].value || duration0 === taskSLADummyObj[this.ATTR_BUSINESS_DURATION].value) && taskSLADummyObj[this.ATTR_BUSINESS_DURATION].read_allowed))
  				taskSLADummyObj[this.ATTR_BUSINESS_DURATION] = taskSLADummyObj[this.ATTR_DURATION];
  			if (!taskSLADummyObj[this.ATTR_BUSINESS_PAUSE_DURATION] || (taskSLADummyObj[this.ATTR_BUSINESS_PAUSE_DURATION] && (!taskSLADummyObj[this.ATTR_BUSINESS_PAUSE_DURATION].value || duration0 === taskSLADummyObj[this.ATTR_BUSINESS_PAUSE_DURATION].value) && taskSLADummyObj[this.ATTR_BUSINESS_PAUSE_DURATION].read_allowed))
  				taskSLADummyObj[this.ATTR_BUSINESS_PAUSE_DURATION] = taskSLADummyObj[this.ATTR_PAUSE_DURATION];
  		}
  		//patch task values
  		taskSLADummyObj[this.ATTR_TASK] = {
  			value: taskDummyGrCurrent.original_sys_id, //This is stamped in history preparation method
  			display_value: taskDummyGrCurrent.getDisplayValue()
  		};
  		/**
  		 * Note: A SLA stage is evaluated at a particular point only. The elapsed time, percentages etc change with time but stage doesn't.
  		 * So we nest the stage with a value_at_end_date to denote that this is the exact value at end_date and not through out this stage.
  		 * The start_date is only a convenience attribute for drawing the timeline. The start_date is merely the last known stage.
  		 **/
  		var taskUpdateDate = taskDummyGrCurrent.sys_updated_on.getGlideObject().getDisplayValueInternal();
  		var startDate = taskSLADummyObj.start_time.display_value_internal;
  		if (startDate > taskUpdateDate) {
  			taskSLAStage.future_start_date = startDate;
  			taskSLAStage.evaluation_date = taskUpdateDate;
  		} else {
  			// If this is the first stage then start date is the SLA Rec's start time
  			if (!prevStage)
  				taskSLAStage.start_date = taskSLADummyObj.start_time.display_value_internal;
  			else
  				taskSLAStage.start_date = prevStage.end_date;

  			taskSLAStage.end_date = taskUpdateDate;
  		}
  	}

  	if (taskSLAStage.future_start_date)
  		taskSLAStage.values_at_evaluation_date = taskSLADummyObj;
  	else
  		taskSLAStage.values_at_end_date = taskSLADummyObj;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_prepareTaskSLAStage] taskSLAStage: " + JSON.stringify(taskSLAStage, null, 2));

  	return taskSLAStage;
  },

  _insertLandMarkStages: function(params, retroactiveContext) {
  	var taskSLAStages = retroactiveContext ? params.retroactiveTaskSLAStages : params.taskSLAStages;
  	var tempTaskSLA = params.prevTaskSLA;

  	if (params.landMarkPercentageCount === this.LAND_MARK_PERCENTAGES.length)
  		return;

  	if (taskSLAStages.length > 1) {

  		if (this.log.atLevel(GSLog.DEBUG)) {
  			this.log.logDebug('[_insertLandMarkStages] Previous percentage: ' + taskSLAStages[taskSLAStages.length - 2].values_at_end_date[this.ATTR_BUSINESS_PERCENTAGE].value);
  			this.log.logDebug('[_insertLandMarkStages] Land mark evaluated: ' + this.LAND_MARK_PERCENTAGES[params.landMarkPercentageCount]);
  			this.log.logDebug('[_insertLandMarkStages] Current percentage: ' + taskSLAStages[taskSLAStages.length - 1].values_at_end_date[this.ATTR_BUSINESS_PERCENTAGE].value);
  		}

  		//In a very remote case it is possible that the update has happened at landmark stage, so no calculation is required and we can move to next stage
  		if (taskSLAStages[taskSLAStages.length - 1].values_at_end_date[this.ATTR_BUSINESS_PERCENTAGE].value === this.LAND_MARK_PERCENTAGES[params.landMarkPercentageCount]) {
  			params.landMarkPercentageCount++;
  			return;
  		}
  	} else
  		return;

  	//For inserting landmarks we need minimum two stages. Even if the landmark value is same as the real update we still continue evaluation as in a out of schedule time
  	//it is possible that the percentage remains same but the breach could have happened earlier. This would be boundary condition when out of schedule start and landmark
  	//is reached at same time
  	while (taskSLAStages.length > 1 &&
  		this.LAND_MARK_PERCENTAGES[params.landMarkPercentageCount] <= taskSLAStages[taskSLAStages.length - 1].values_at_end_date[this.ATTR_BUSINESS_PERCENTAGE].value &&
  		taskSLAStages[taskSLAStages.length - 2].values_at_end_date[this.ATTR_BUSINESS_PERCENTAGE].value < this.LAND_MARK_PERCENTAGES[params.landMarkPercentageCount]) {

  		if (this.log.atLevel(GSLog.DEBUG))
  			this.log.logDebug('[_insertLandMarkStages] Landmark Percentage evaluation entered: ' + this.LAND_MARK_PERCENTAGES[params.landMarkPercentageCount]);

  		var schedule = this.slaUtil.getSchedule(params.contractSLAGr, params.prevTaskGr);
  		var hasSchedule = false;
  		if (schedule && schedule.getID())
  			hasSchedule = true;

  		//Since this is a post-evaluation where we have gone beyond the percentage we are interested in we go back one step
  		var prevStage = taskSLAStages[taskSLAStages.length - 2].values_at_end_date;

  		var prevStageElapsedDuration;
  		if (hasSchedule)
  			prevStageElapsedDuration = prevStage[this.ATTR_BUSINESS_DURATION].value;
  		else
  			prevStageElapsedDuration = prevStage[this.ATTR_DURATION].value;

  		var prevStageElapsedDurationMS = new GlideDateTime(prevStageElapsedDuration).getNumericValue();

  		var totalDurMS = 0;
  		if (prevStage[this.ATTR_ORIGINAL_BREACH_TIME].value) {
  			// Original breach time is the time when the SLA started and it was the 100% mark
  			var startTimeGdt = new GlideDateTime(prevStage[this.ATTR_START_TIME].value);
  			var originalBreachTimeGdt = new GlideDateTime(prevStage[this.ATTR_ORIGINAL_BREACH_TIME].value);
  			if (hasSchedule)
  				totalDurMS = schedule.duration(startTimeGdt, originalBreachTimeGdt).getNumericValue();
  			else
  				totalDurMS = originalBreachTimeGdt.getNumericValue() - startTimeGdt.getNumericValue();
  		} else // if original breach time is missing, we try to get the duration from the SLA Definition
  			totalDurMS = contractSLAGr.duration.dateNumericValue();

  		if (this.log.atLevel(GSLog.DEBUG))
  			this.log.logDebug('[_insertLandMarkStages] totalDurMS: ' + totalDurMS);

  		if (!totalDurMS)
  			return;

  		//Calculate in terms of milli-seconds the duration that is needed to be at the required percentage
  		var landMarkDurMS = (totalDurMS) * this.LAND_MARK_PERCENTAGES[params.landMarkPercentageCount] / 100;
  		var timeInMSToReachLandMark = landMarkDurMS - prevStageElapsedDurationMS;
  		var duration = new GlideDuration(timeInMSToReachLandMark);

  		if (this.log.atLevel(GSLog.DEBUG))
  			this.log.logDebug('[_insertLandMarkStages] Has prevTaskGr: ' + !!params.prevTaskGr);

  		if (!params.prevTaskGr)
  			return;

  		var prevTime = new GlideDateTime(params.prevTaskGr.getValue(this.ATTR_SYS_UPDATED_ON));

  		var targetTime;
  		if (hasSchedule) {
  			targetTime = schedule.add(prevTime, duration);

  			if (this.log.atLevel(GSLog.DEBUG))
  				this.log.logDebug('[_insertLandMarkStages] Previous Time: ' + prevTime.getDisplayValueInternal() + ' Duration to add: ' + duration.getDisplayValue() + ' Target time: ' + targetTime.getDisplayValueInternal());
  		} else {
  			targetTime /*GlideDateTime*/ = new GlideDateTime(prevTime.getValue());
  			targetTime.add(timeInMSToReachLandMark);

  			if (this.log.atLevel(GSLog.DEBUG))
  				this.log.logDebug('[_insertLandMarkStages] Previous Time: ' + prevTime.getDisplayValueInternal() + ' Target time: ' + targetTime.getDisplayValueInternal());
  		}
  		if (!targetTime)
  			return;

  		if (taskSLAStages[taskSLAStages.length - 1].end_date !== (targetTime.getDisplayValueInternal() + '')) {
  			params.prevTaskGr[this.ATTR_SYS_UPDATED_ON] = targetTime;
  			tempTaskSLA = params.taskSLAController.updateTaskSLA(params.prevTaskGr, tempTaskSLA, params.contractSLAGr);
  			var tempTaskSLAGr = tempTaskSLA.getGlideRecord();
  			// If we have a reset and no update in between, these pseudo updates will be treated like a true reset and set to previous stage value.
  			if (tempTaskSLA.isReset || retroactiveContext)
  				this._revertAttributes(tempTaskSLAGr, taskSLAStages[taskSLAStages.length - 2]);

  			// insert the task_sla snapshot for the land mark percentage
  			taskSLAStages.splice(taskSLAStages.length - 1, 0, this._prepareTaskSLAStage(tempTaskSLAGr, taskSLAStages[taskSLAStages.length - 2], params.prevTaskGr));
  			taskSLAStages[taskSLAStages.length - 2].mustHaveStage = true;
  			//readjust the highest snapshot's start date as we have more details available as a result of land mark insertions
  			taskSLAStages[taskSLAStages.length - 1].start_date = taskSLAStages[taskSLAStages.length - 2].end_date;
  		}
  		params.landMarkPercentageCount++;
  	}
  	return;
  },

  _getContractSLARecs: function(taskSLAsAttached, contractSLAIds, taskGr) {
  	var attached = [];
  	var simulated = [];

  	taskSLAsAttached.forEach(function(taskSla) {
  		if (!contractSLAIds || (Array.isArray(contractSLAIds) && contractSLAIds.indexOf(taskSla.sla.value) !== -1))
  			attached.push(taskSla.sla.value);
  	});

  	attached = this.arrayUtil.unique(attached);
  	simulated = this.arrayUtil.unique(this.arrayUtil.diff(contractSLAIds, attached));
  	var combined = this.arrayUtil.unique(this.arrayUtil.concat(attached, simulated));
  	var contractSlaGr = Array.isArray(combined) && combined.length > 0 ? this._getContractSLARecsByIds(combined, taskGr) : null;

  	var contractSlaRecs = {
  		attached: attached,
  		simulated: simulated,
  		gr: contractSlaGr
  	};

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_getContractSLARecs] contractSlaRecs: " + JSON.stringify(contractSlaRecs, null, 2));

  	return contractSlaRecs;
  },

  _getContractSLARecsByIds: function(contractSlaIds, taskGr) {
  	var contractSlaGr = new GlideRecord(this.TABLE_CONTRACT_SLA);
  	if (taskGr && taskGr.isValidRecord())
  		contractSlaGr.addDomainQuery(taskGr);
  	contractSlaGr.addQuery(this.ATTR_SYS_ID, this.OPR_IN, contractSlaIds.join(','));
  	contractSlaGr.query();

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_getContractSLARecsByIds] encodedQuery: " + contractSlaGr.getEncodedQuery());

  	return contractSlaGr;
  },

  _addChangedFields: function(walkedRecordGr, taskUpdate) {
  	var fields = this.slaUtil.getChangedFields(walkedRecordGr);
  	var field;
  	var fieldName;
  	for (var i = 0; i < fields.length; i++) {
  		field = fields[i];
  		fieldName = field.getName();

  		if (fieldName + "" == this.ATTR_SYS_CREATED_ON || fieldName + "" == this.ATTR_SYS_UPDATED_ON)
  			continue;

  		taskUpdate[fieldName] = {
  			label: field.getLabel()
  		};

  		if (field.canRead()) {
  			var internalType = field.getED().getInternalType() + "";
  			taskUpdate[fieldName].read_allowed = true;
  			taskUpdate[fieldName].new_value = field.getValue();

  			if (internalType === this.FIELD_TYPE_WF)
  				taskUpdate[fieldName].new_value_user = this._getWorkflowFieldDisplayValue(fieldName, walkedRecordGr);
  			else if (internalType === this.FIELD_TYPE_JOURNAL_INPUT)
  				taskUpdate[fieldName].new_value_user = field.getJournalEntry(0);
  			else
  				taskUpdate[fieldName].new_value_user = field.getDisplayValue();
  		} else {
  			taskUpdate[fieldName].read_allowed = false;
  			taskUpdate[fieldName].new_value = '';
  			taskUpdate[fieldName].new_value_user = gs.getMessage('(restricted)');
  		}
  	}
  },

  /* this method is to avoid a known issue with calling "getDisplayValue" on a field that is of type "workflow"
     as doing this overwrites the GlideRecord object that this field is attached to with the latest copy from the
     database */
  _getWorkflowFieldDisplayValue: function(fieldName, walkedRecordGr) {
  	var tempGr = new GlideRecord(walkedRecordGr.getRecordClassName());
  	tempGr.initialize();
  	tempGr.setValue(fieldName, walkedRecordGr.getValue(fieldName));

  	return tempGr[fieldName].getDisplayValue();
  },

  _addChangedVariables: function(walkedRecordGr, taskUpdate) {
  	var	variableNames = this.slaUtil.getChangedVariables(walkedRecordGr);
  	var variableName;
  	var variableElement;

  	var canRead = walkedRecordGr.variables.canRead();

  	for (var i = 0; i < variableNames.length; i++) {
  		variableName = variableNames[i];
  		if (canRead) {
  			variableElement = walkedRecordGr.variables[variableName];
  			taskUpdate[variableName] = {
  				label: variableName,
  				read_allowed: true,
  				new_value: variableElement.getValue(),
  				new_value_user: variableElement.getDisplayValue()
  			};
  		} else {
  			taskUpdate[variableName] = {
  				label: variableName,
  				read_allowed: false,
  				new_value: '',
  				new_value_user: gs.getMessage('(restricted)')
  			};
  		}
  	}
  },

  _addDateMapNonGrFields: function(obj, dateFieldsList) { /*These fields are expected to be in display_value_internal*/
  	obj.dates_map_non_gr_dates = {};
  	for (var i = 0; i < dateFieldsList.length; i++) {
  		obj.dates_map_non_gr_dates[dateFieldsList[i]] = this.slaUtil.populateDateInCommonFormatsAndConversions(obj[dateFieldsList[i]], this.slaUtil.DATE_FORMAT_DISPLAY_VALUE_INTERNAL);
  	}
  },

  _getBusinessScheduleSpans: function(startDate /*GlideDateTime*/ , endDate /*GlideDateTime*/ , schedule /*GlideSchedule*/ ) {
  	var it = schedule.getTimeMap(startDate, endDate);
  	var spans = [];
  	var span;
  	while (it.hasNext()) {
  		var timeMap = it.next();
  		span = {
  			start_date: timeMap.getStart().getGlideDateTime().getDisplayValueInternal(),
  			end_date: timeMap.getEnd().getGlideDateTime().getDisplayValueInternal()
  		};
  		this._addDateMapNonGrFields(span, ['start_date', 'end_date']);
  		spans.push(span);
  	}
  	return spans;
  },

  _getOutOfBusinessScheduleSpans: function(startDate /*String - Display value internal*/ , endDate, /*String - Display value internal*/
  	businessScheduleSpans /*o/p from _getBusinessScheduleSpans*/ , schedule /*GlideSchedule*/ ) {
  	var pointerDate = startDate;
  	var outOfBusinessScheduleSpans = [];
  	var outOfBusinessScheduleSpan;
  	var totalOutOfScheduleDurationMS = 0;
  	var outOfScheduleStartDateGdt;
  	var outOfScheduleEndDateGdt;
  	var outOfScheduleDurationMS;
  	var totalDurationAllIncludedMS;
  	var startDateGdt = new GlideDateTime();
  	startDateGdt.setDisplayValueInternal(startDate);
  	var endDateGdt = new GlideDateTime();
  	endDateGdt.setDisplayValueInternal(endDate);

  	for (var i = 0; businessScheduleSpans && i < businessScheduleSpans.length && pointerDate < endDate; i++) {
  		outOfScheduleStartDateGdt = new GlideDateTime();
  		outOfScheduleStartDateGdt.setDisplayValueInternal(pointerDate);

  		outOfScheduleEndDateGdt = new GlideDateTime();
  		outOfScheduleEndDateGdt.setDisplayValueInternal(businessScheduleSpans[i].start_date);

  		outOfScheduleDurationMS = outOfScheduleEndDateGdt.getNumericValue() - outOfScheduleStartDateGdt.getNumericValue();
  		totalDurationAllIncludedMS = outOfScheduleEndDateGdt.getNumericValue() - startDateGdt.getNumericValue();
  		totalOutOfScheduleDurationMS = totalOutOfScheduleDurationMS + outOfScheduleDurationMS;
  		if (pointerDate + '' !== businessScheduleSpans[i].start_date + '') {
  			outOfBusinessScheduleSpan = {
  				start_date: pointerDate,
  				end_date: businessScheduleSpans[i].start_date,
  				duration_out_of_schedule: new GlideDuration(outOfScheduleDurationMS).getDisplayValue(),
  				total_duration_out_of_schedule: new GlideDuration(totalOutOfScheduleDurationMS).getDisplayValue(),
  				total_duration_all_included: new GlideDuration(totalDurationAllIncludedMS).getDisplayValue()
  			};
  			this._addDateMapNonGrFields(outOfBusinessScheduleSpan, ['start_date', 'end_date']);
  			outOfBusinessScheduleSpans.push(outOfBusinessScheduleSpan);
  		}

  		pointerDate = businessScheduleSpans[i].end_date;
  		//when the task sla ending time is not within schedule
  		if (i === businessScheduleSpans.length - 1 && pointerDate < endDate) {
  			outOfScheduleStartDateGdt = new GlideDateTime();
  			outOfScheduleStartDateGdt.setDisplayValueInternal(pointerDate);

  			totalDurationAllIncludedMS = endDateGdt.getNumericValue() - startDateGdt.getNumericValue();

  			outOfScheduleDurationMS = endDateGdt.getNumericValue() - outOfScheduleStartDateGdt.getNumericValue();
  			totalOutOfScheduleDurationMS = totalOutOfScheduleDurationMS + outOfScheduleDurationMS;

  			outOfBusinessScheduleSpan = {
  				start_date: pointerDate,
  				end_date: endDate,
  				duration_out_of_schedule: new GlideDuration(outOfScheduleDurationMS).getDisplayValue(),
  				total_duration_out_of_schedule: new GlideDuration(totalOutOfScheduleDurationMS).getDisplayValue(),
  				total_duration_all_included: new GlideDuration(totalDurationAllIncludedMS).getDisplayValue()
  			};
  			this._addDateMapNonGrFields(outOfBusinessScheduleSpan, ['start_date', 'end_date']);
  			outOfBusinessScheduleSpans.push(outOfBusinessScheduleSpan);
  		}
  	}

  	if (schedule.getID() && !businessScheduleSpans) {
  		outOfScheduleDurationMS = endDateGdt.getNumericValue() - startDateGdt.getNumericValue();
  		totalOutOfScheduleDurationMS = outOfScheduleDurationMS;
  		totalDurationAllIncludedMS = outOfScheduleDurationMS;
  		outOfBusinessScheduleSpan = {
  			start_date: startDate,
  			end_date: endDate,
  			duration_out_of_schedule: new GlideDuration(outOfScheduleDurationMS).getDisplayValue(),
  			total_duration_out_of_schedule: new GlideDuration(totalOutOfScheduleDurationMS).getDisplayValue(),
  			total_duration_all_included: new GlideDuration(totalDurationAllIncludedMS).getDisplayValue()
  		};
  		this._addDateMapNonGrFields(outOfBusinessScheduleSpan, ['start_date', 'end_date']);
  		outOfBusinessScheduleSpans.push(outOfBusinessScheduleSpan);
  	}
  	return outOfBusinessScheduleSpans;
  },

  getSlaDefinitionDetail: function(contractSlaIds) {
  	var gr = new GlideRecord(this.TABLE_CONTRACT_SLA);
  	gr.addQuery(this.ATTR_SYS_ID, this.OPR_IN, contractSlaIds);
  	gr.query();
  	var contractSlaDetails = [];
  	while (gr.next()) {
  		var contractSlaObj = {};
  		contractSlaObj[this.ATTR_SYS_ID] = gr.getValue(this.ATTR_SYS_ID);
  		if (gr.canRead() && gr.getElement(this.ATTR_NAME).canRead())
  			contractSlaObj[this.ATTR_NAME] = gr.getValue(this.ATTR_NAME);
  		else
  			contractSlaObj[this.ATTR_NAME] = gs.getMessage('(restricted)');
  		contractSlaObj[this.ATTR_URL] = gr.getLink(true);
  		contractSlaObj[this.ATTR_COLLECTION] = gr.getValue(this.ATTR_COLLECTION);
  		contractSlaDetails.push(contractSlaObj);
  	}
  	return contractSlaDetails;
  },

  type: 'SLATimeLineV2SNC'
};

Sys ID

5176be939f2222002920bde8132e7006

Offical Documentation

Official Docs: