Name

sn_sla_brkdwn.SLABreakdownProcessorSNC

Description

No description available

Script

var SLABreakdownProcessorSNC = Class.create();
SLABreakdownProcessorSNC.prototype = {
  SLA_BREAKDOWN_PROCESSOR_LOG: 'com.snc.sla.breakdown_processor.log',

  initialize: function(currentTaskSLAGr, previousTaskSLAGr) {
  	this.start = new GlideDateTime();
  	this.currentTaskSLAGr = currentTaskSLAGr;
  	this.previousTaskSLAGr = previousTaskSLAGr;
  	this.slaGr = this._getSLADefinitionGr();
  	this.taskGr = null;
  	this.durationCalculator = null;
  	this.breakdownDefinitionsData = null;
  	this.updateTime = null;
  	this.slaDurationMS = 0;
  	this.retroactive = false;
  	this.taskSLAChanged = false;
  	this.operationOverride = "";

  	this.lu = new global.GSLog(this.SLA_BREAKDOWN_PROCESSOR_LOG, this.type);
  	this.lu.includeTimestamp();
  	this.slalogging = new global.TaskSLALogging();
  },

  processBreakdowns: function() {
  	var timingStart;

  	if (this.lu.atLevel(global.GSLog.INFO)) {
  		this.lu.logInfo("processBreakdowns: starts");
  		timingStart = new GlideDateTime();
  	}

  	if (!this.currentTaskSLAGr || !this.currentTaskSLAGr.getUniqueValue()) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("processBreakdowns: failed to process breakdowns - Task SLA record was not supplied");

  		if (this.lu.atLevel(global.GSLog.INFO))
  			this._logProcessingTime(timingStart);

  		return;
  	}

  	if (!this.slaGr || !this.slaGr.isValidRecord()) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("processBreakdowns: failed to process breakdowns - SLA Definition not found for sys_id=" + this.currentTaskSLAGr.sla + ", Task SLA sys_id=" + this.currentTaskSLAGr.getUniqueValue());

  		if (this.lu.atLevel(global.GSLog.INFO))
  			this._logProcessingTime(timingStart);

  		return;
  	}

  	// First get the breakdown definitions linked to this SLA's definition
  	this.breakdownDefinitionsData = this._getBreakdownDefinitionsData();
  	var breakdownDefinitionIds = Object.keys(this.breakdownDefinitionsData);
  	if (breakdownDefinitionIds.length === 0) {
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("processBreakdowns: no breakdown definitions are linked to this SLA");
  		this._logProcessingTime(timingStart);
  		return;
  	}

  	// if a Task record hasn't been set get it from the database
  	if (this.taskGr === null)
  		this.taskGr = this._getTaskGr();

  	if (!this.taskGr || !this.taskGr.getUniqueValue()) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("processBreakdowns: failed to process breakdowns - Task not found for SLA " + this.currentTaskSLAGr.sla.getDisplayValue() + " : " + this.currentTaskSLAGr.getUniqueValue());

  		if (this.lu.atLevel(global.GSLog.INFO))
  			this._logProcessingTime(timingStart);

  		return;
  	}

  	if (this.lu.atLevel(global.GSLog.DEBUG)) {
  		this.lu.logDebug("processBreakdowns: processing breakdown for SLA " + this.currentTaskSLAGr.sla.getDisplayValue() + "(" + this.currentTaskSLAGr.getUniqueValue() + ")" +
  						"on Task " + this.taskGr.getDisplayValue() + "\n");
  		this.lu.logDebug("processBreakdowns: taskGr:\n" +
  						this.slalogging.getRecordContentMsg(this.taskGr) + "\n");
  		this.lu.logDebug("processBreakdowns: previousTaskSLAGr:\n" +
  						this.slalogging.getRecordContentMsg(this.previousTaskSLAGr) + "\n");
  		this.lu.logDebug("processBreakdowns: currentTaskSLAGr:\n" +
  						this.slalogging.getRecordContentMsg(this.currentTaskSLAGr) + "\n");	
  		this.lu.logDebug("processBreakdowns: breakdown definitions data retrieved for this SLA - \n " + JSON.stringify(this.breakdownDefinitionsData));
  	}

  	this.slaDurationMS = this._getSLADurationMS();
  	this.retroactive = this._isRetroactive();
  	this.breached = this._isBreached();
  	this.breachedNow = this._changesToBreached();

  	/* if we don't have an update time use the "sys_updated_on" of the Task if an engine parameter on the Task
  	   indicates the update was made by TaskSLA/RepairTaskSLA.  Otherwise use "sys_updated_on" of the Task SLA */
  	if (this.updateTime === null)
  		this.updateTime = this._deriveUpdateTime();
  	this.updateTimeMS = this.getUpdateTime().getNumericValue();

  	if (this.lu.atLevel(global.GSLog.DEBUG)) {
  		this.lu.logDebug("processBreakdowns:\n" + 
  						"slaDurationMS = " + this._getSLADurationMS() + " (" + new GlideDuration(this.slaDurationMS).getDurationValue() + ")\n" +
  						"retroactive = " + this.retroactive + "\n" +
  						"breached = " + this.breached + "\n" +
  						"breachedNow = " + this.breachedNow + "\n" +
  						"updateTime = " + this.updateTime);
  	}

  	/* if this is a new Task SLA the creation of the breakdown records is straightforward as
  	   we just need to insert 1 or 2 (if the SLA is retroactive) records based on the current values
  	   in the Task SLA - no calculations needed */
  	if (this._isNewSLA()) {
  		this._processNewTaskSLA();
  		if (this.lu.atLevel(global.GSLog.INFO))
  			this._logProcessingTime(timingStart);

  		return;
  	}

  	/* Otherwise we need to perform the appropriate updates on existing active breakdown records
  	   and if necessary create a new one */

  	// Just double check this is an active Task SLA - if it isn't get out
  	if (!this._isTaskSLAActive()) {
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("processBreakdowns: this is an update to an inactive Task SLA so no processing is required");
  		this._logProcessingTime(timingStart);
  		return;
  	}

  	/* Check if fields that affect the breakdowns have changed on the Task SLA
  	   - even if this is false it doesn't mean we have no work as the breakdown
  	   values may have changed */
  	this.taskSLAChanged = this._isTaskSLAChanged();

  	for (var i = 0, n = breakdownDefinitionIds.length; i < n; i++)
  		this._processActiveBreakdown(breakdownDefinitionIds[i]);

  	this._logProcessingTime(timingStart);		
  },

  setUpdateTime: function(dateTime) {
  	if (!dateTime)
  		return;

  	// if argument is a GlideDateTime just use it
  	if (dateTime instanceof GlideDateTime) {
  		this.updateTime = new GlideDateTime(dateTime);
  		return;
  	}

  	/* otherwise treat what we've been passed as a string and use it to create a new GlideDateTime
  	   this will work if it'sa GlideElement as well */
  	this.updateTime = new GlideDateTime(dateTime + "");
  },

  getUpdateTime: function() {
  	return this.updateTime;
  },

  setTaskSLAOperationOverride: function(operation) {
  	if (!operation)
  		return;

  	this.operationOverride = operation + "";
  },

  getTaskSLAOperationOverride: function() {
  	return this.operationOverride;
  },

  setTaskGr: function(taskGr) {
  	if (!taskGr)
  		return;

  	this.taskGr = taskGr;
  },

  _processActiveBreakdown: function(breakdownDefinitionId) {
  	if (!breakdownDefinitionId)
  		return;

  	var activeBreakdownGr = this._getActiveBreakdownByDefinitionId(breakdownDefinitionId);
  	if (!activeBreakdownGr || !activeBreakdownGr.isValidRecord()) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("_processActiveBreakdown: failed to retrieve active breakdown record for breakdown definition id=" + breakdownDefinitionId);

  		return;
  	}

  	var newBreakdownNeeded = this._isNewBreakdownNeeded(breakdownDefinitionId, activeBreakdownGr);

  	if (!this.taskSLAChanged && !newBreakdownNeeded.result) {
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("_processActiveBreakdown: no processing required for breakdown definition id=" + breakdownDefinitionId +
  							"\n   No changes to Task SLA and breakdown values have not changed:\n   " +
  							JSON.stringify(newBreakdownNeeded));
  		return;
  	}

  	var breakdownValues = {};
  	breakdownValues.duration = activeBreakdownGr.duration.dateNumericValue();
  	breakdownValues.business_duration = activeBreakdownGr.business_duration.dateNumericValue();
  	breakdownValues.pause_duration = activeBreakdownGr.pause_duration.dateNumericValue();
  	breakdownValues.business_pause_duration = activeBreakdownGr.business_pause_duration.dateNumericValue();

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_processActiveBreakdown: current durations in active breakdown:\n" + JSON.stringify(breakdownValues));

  	var lastUpdateTime = new GlideDateTime(activeBreakdownGr.last_updated + "");

  	/* if a value that impacts the duration breakdown records has changed on the Task SLA
  	   then we need to update the active breakdown record */
  	if (this.taskSLAChanged) {
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("_processActiveBreakdown: task SLA has been updated:\n" + 
  							"_isPaused() = " + this._isPaused() + 
  							", _changesToPaused() = " + this._changesToPaused() + 
  							", _changesFromPause() = " + this._changesFromPaused());

  		// if we've left paused or the numbers have changed but we've stayed in progress update the duration fields
  		if (this._changesToPaused() || (!this._isPaused() && !this._changesFromPaused())) {
  			// if we're changing to Paused we need to set the pause time unless we're creating a new breakdown
  			if (this._changesToPaused() && !newBreakdownNeeded.result)
  				breakdownValues.pause_time = this.currentTaskSLAGr.pause_time.dateNumericValue();
  			// and update the duration fields
  			this._updateDurationFields(breakdownValues, lastUpdateTime);
  		} if (this._changesFromPaused()) {
  			// if we're changing from paused then we need to update the pause duration fields and clear out pause time
  			breakdownValues.pause_time = "";
  			this._updatePauseDurationFields(breakdownValues, lastUpdateTime);
  		}
  	} else if (newBreakdownNeeded.result) {
  		/* if we need a new breakdown record then we need to update the values in the existing breakdown record before we
  		   shut it down and create a new one */
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("_processActiveBreakdown: breakdown values have changed so new breakdown is needed, active breakdown lastUpdated = " +
  							activeBreakdownGr.last_updated + ", and _isPaused() = " + this._isPaused());

  		// if the SLA is not paused then just update the duration fields before this breakdown is ended
  		if (!this._isPaused())
  			this._updateDurationFields(breakdownValues, lastUpdateTime);
  		else {
  			// otherwise we need to clear out the pause time and update the pause duration fields
  			breakdownValues.pause_time = "";
  			this._updatePauseDurationFields(breakdownValues, lastUpdateTime);
  		}
  	}

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_processActiveBreakdown: durations in active breakdown after calculation:\n" + JSON.stringify(breakdownValues));

  	if (this._changesToBreached())
  		breakdownValues.breached = true;

  	if (newBreakdownNeeded.result || this._changesToInactive()) {	
  		breakdownValues.active = false;
  		breakdownValues.end = this.updateTimeMS;
  	}

  	breakdownValues.last_updated = this.updateTimeMS;
  	this._setBreakdownValues(activeBreakdownGr, breakdownValues);
  	activeBreakdownGr.update();

  	if (newBreakdownNeeded.result && this._isTaskSLAActive() && !this._changesToInactive())
  		this._createBreakdownRecord(breakdownDefinitionId);
  },

  _updateDurationFields: function(breakdownValues, lastUpdateTime) {
  	var lastUpdateTimeMS = lastUpdateTime.getNumericValue();

  	breakdownValues.duration += this.updateTimeMS - lastUpdateTimeMS;
  	if (this._hasSchedule())
  		breakdownValues.business_duration += this._calcDuration(lastUpdateTime, this.updateTime);
  	else
  		breakdownValues.business_duration += this.updateTimeMS - lastUpdateTimeMS;
  },

  _updatePauseDurationFields: function(breakdownValues, lastUpdateTime) {
  	var lastUpdateTimeMS = lastUpdateTime.getNumericValue();

  	breakdownValues.pause_duration += this.updateTimeMS - lastUpdateTimeMS;
  	if (this._hasSchedule())
  		breakdownValues.business_pause_duration += this._calcDuration(lastUpdateTime, this.updateTime);
  	else
  		breakdownValues.business_pause_duration += this.updateTimeMS - lastUpdateTimeMS;
  },

  _processNewTaskSLA: function() {
  	var breakdownValues;
  	var breakdownDefinitionIds = Object.keys(this.breakdownDefinitionsData);
  	var startTime = new GlideDateTime(this.currentTaskSLAGr.start_time + "");
  	var createRetroactiveBreakdown = this.retroactive && this.updateTime.after(startTime);

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_processNewTaskSLA: breakdownDefinitionIds=" + breakdownDefinitionIds + ", startTime=" + startTime.getValue() + ", createRetroactiveBreakdown=" + createRetroactiveBreakdown);

  	for (var i = 0, n = breakdownDefinitionIds.length; i < n; i++) {
  		if (createRetroactiveBreakdown)
  			this._createBreakdownRecord(breakdownDefinitionIds[i], this._calculateRetroactiveBreakdownValues());

  		this._createBreakdownRecord(breakdownDefinitionIds[i], this._calculateFirstBreakdownValues(createRetroactiveBreakdown));
  	}			
  },

  _calculateFirstBreakdownValues: function(retroactiveBreakdownCreated) {
  	var breakdownValues = {};
  	retroactiveBreakdownCreated = retroactiveBreakdownCreated + "" === "true";

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_calculateFirstBreakdownValues: retroactiveBreakdownCreated=" + retroactiveBreakdownCreated);
  	
  	if (!retroactiveBreakdownCreated)
  		breakdownValues.start = this.currentTaskSLAGr.start_time.dateNumericValue();
  	if (!this._isPaused())
  		breakdownValues.pause_time = this.currentTaskSLAGr.pause_time.dateNumericValue();
  	breakdownValues.duration = retroactiveBreakdownCreated ? 0 : this.currentTaskSLAGr.duration.dateNumericValue();
  	breakdownValues.business_duration = retroactiveBreakdownCreated ? 0 : this.currentTaskSLAGr.business_duration.dateNumericValue();
  	breakdownValues.pause_duration = retroactiveBreakdownCreated ? 0 : this.currentTaskSLAGr.pause_duration.dateNumericValue();
  	breakdownValues.business_pause_duration = retroactiveBreakdownCreated ? 0 : this.currentTaskSLAGr.business_pause_duration.dateNumericValue();
  	breakdownValues.breached = retroactiveBreakdownCreated ? false : this.breached;
  	/* if we've already breached then this can only be due to retroactive time so we can mark this period
  	   as post_breach as we have the retroactive period representing when we actually breached */
  	if (this.breached)
  		breakdownValues.post_breach = true;

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_calculateFirstBreakdownValues: creating first (non-retroactive) period with values;\n" + JSON.stringify(breakdownValues));

  	return breakdownValues;
  },	

  _calculateRetroactiveBreakdownValues: function() {
  	var breakdownValues = {};
  	breakdownValues.start = this.currentTaskSLAGr.start_time.dateNumericValue();
  	breakdownValues.end = this.updateTimeMS;
  	breakdownValues.duration = this.currentTaskSLAGr.duration.dateNumericValue();
  	breakdownValues.business_duration = this.currentTaskSLAGr.business_duration.dateNumericValue();
  	breakdownValues.pause_duration = this.currentTaskSLAGr.pause_duration.dateNumericValue();
  	breakdownValues.business_pause_duration = this.currentTaskSLAGr.business_pause_duration.dateNumericValue();
  	breakdownValues.breached = this.breached;
  	breakdownValues.retroactive = true;
  	breakdownValues.active = false;

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_calculateRetroactiveBreakdownValues: creating retroactive period with values:\n" + JSON.stringify(breakdownValues));

  	return breakdownValues;
  },

  _createBreakdownRecord: function(breakdownDefinitionId, breakdownValues) {
  	if (!breakdownDefinitionId)
  		return;

  	var breakdownDefinition = this.breakdownDefinitionsData[breakdownDefinitionId];
  	if (!breakdownDefinition)
  		return;

  	var breakdownFields = breakdownDefinition.breakdownFields;

  	var breakdownGr = new GlideRecord(breakdownDefinition.breakdownTable);
  	breakdownGr.setValue(SLABreakdown.SLA_BREAKDOWN_DEFINITION, breakdownDefinitionId);
  	breakdownGr.setValue("task", this.taskGr.getUniqueValue());
  	breakdownGr.setValue("task_sla", this.currentTaskSLAGr.getUniqueValue());
  	if (!breakdownValues || !breakdownValues.hasOwnProperty("start"))
  		breakdownGr.start.setDateNumericValue(this.updateTimeMS);
  	if (this._isPaused() && (!breakdownValues || !breakdownValues.hasOwnProperty("pause_time")))
  		breakdownGr.pause_time.setDateNumericValue(this.updateTimeMS);
  	if (!breakdownValues || !breakdownValues.hasOwnProperty("last_updated"))
  		breakdownGr.last_updated.setDateNumericValue(this.updateTimeMS);
  	// if we've breached and not during this period then set post_breach true
  	if (this.breached && !this._changesToBreached())
  		breakdownGr.post_breach = true;

  	breakdownFields.forEach(function(breakdownField) {
  		breakdownGr.setValue(breakdownField.breakdownFieldName, this.taskGr.getValue(breakdownField.sourceFieldName));
  	}, this);

  	if (breakdownValues)
  		this._setBreakdownValues(breakdownGr, breakdownValues);

  	breakdownGr.insert();
  },

  _setBreakdownValues: function(breakdownGr, breakdownValues) {
  	if (!breakdownGr || !(breakdownGr instanceof GlideRecord))
  		return;

  	if (breakdownValues.hasOwnProperty("duration"))
  		breakdownGr.duration.setDateNumericValue(breakdownValues.duration);

  	if (breakdownValues.hasOwnProperty("business_duration")) {
  		breakdownGr.business_duration.setDateNumericValue(breakdownValues.business_duration);
  		breakdownGr.sla_duration_percentage = (breakdownValues.business_duration / this.slaDurationMS) * 100;
  	}

  	if (breakdownValues.hasOwnProperty("pause_duration"))
  		breakdownGr.pause_duration.setDateNumericValue(breakdownValues.pause_duration);

  	if (breakdownValues.hasOwnProperty("business_pause_duration"))
  		breakdownGr.business_pause_duration.setDateNumericValue(breakdownValues.business_pause_duration);

  	if (breakdownValues.hasOwnProperty("start"))
  		breakdownGr.start.setDateNumericValue(breakdownValues.start);

  	// "pause_time" is a special case as we may be setting it empty so we need to check for this and handle appropriately
  	if (breakdownValues.hasOwnProperty("pause_time")) {
  		if (breakdownValues.pause_time)
  			breakdownGr.pause_time.setDateNumericValue(breakdownValues.pause_time);
  		else
  			breakdownGr.setValue("pause_time", "");
  	}

  	if (breakdownValues.hasOwnProperty("end"))
  		breakdownGr.end.setDateNumericValue(breakdownValues.end);

  	if (breakdownValues.hasOwnProperty("last_updated"))
  		breakdownGr.last_updated.setDateNumericValue(breakdownValues.last_updated);

  	if (breakdownValues.breached === true)
  		breakdownGr.breached = true;

  	if (breakdownValues.retroactive === true)
  		breakdownGr.retroactive = true;

  	if (breakdownValues.post_breach === true)
  		breakdownGr.post_breach = true;

  	if (breakdownValues.active === false)
  		breakdownGr.active = false;
  },

  _isUpdateFromTask: function() {
  	/* Check if the GlideRecord object for the current Task SLA has the update source engine parameter 
  	   set to "TaskSLA" as this indicates that this update was due to an update on the parent Task */
  	var slaUpdateSource = new global.SLAUtil().getSLAUpdateSourceEngineParameter(this.currentTaskSLAGr);

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_isUpdateFromTask: slaUpdateSource=" + slaUpdateSource + ", update from TaskSLAController" + (slaUpdateSource === "TaskSLAController") +
  						", update from RepairTaskSLAController" + (slaUpdateSource === "RepairTaskSLAController"));
  	
  	if (slaUpdateSource === "TaskSLAController" || slaUpdateSource === "RepairTaskSLAController")
  		return true;

  	return false;
  },

  _isNewSLA: function() {
  	if (!this.operationOverride) {
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("_isNewSLA: record operation = " + this.currentTaskSLAGr.operation() + ", result = " + (this.currentTaskSLAGr.operation() === "insert"));
  		return this.currentTaskSLAGr.operation() === "insert";
  	}

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_isNewSLA: operationOverride is set, operationOverride = " + this.operationOverride + ", result = " + (this.operationOverride === "insert"));

  	return this.operationOverride === "insert";
  },

  _hasSchedule: function() {
  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_hasSchedule: schedule = " + (this.currentTaskSLAGr.schedule.nil() ? "<No schedule>" : this.currentTaskSLAGr.schedule.getDisplayValue()));

  	return !this.currentTaskSLAGr.schedule.nil();
  },

  _isRetroactive: function() {
  	return this.slaGr.retroactive.toString() === "true";
  },

  _isPaused: function() {
  	return !this.currentTaskSLAGr.pause_time.nil();
  },

  _changesToPaused: function() {
  	if (this.previousTaskSLAGr === null)
  		return false;

  	return this._isPaused() && this.previousTaskSLAGr.pause_time.nil();
  },

  _changesFromPaused: function() {
  	if (this.previousTaskSLAGr === null)
  		return false;

  	return !this._isPaused() && !this.previousTaskSLAGr.pause_time.nil();
  },

  _isBreached: function() {
  	return this.currentTaskSLAGr.has_breached.toString() === "true";
  },

  _changesToBreached: function() {
  	if (this.previousTaskSLAGr === null)
  		return false;

  	return this.previousTaskSLAGr.has_breached.toString() === "false" && this._isBreached();		
  },

  _changesToInactive: function() {
  	if (this.previousTaskSLAGr === null)
  		return false;

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_changesToInactive: previous active = " + (this.previousTaskSLAGr.active.toString() === "true") +
  						",  current active = " + (this.currentTaskSLAGr.active.toString() === "false") + ", result = " +
  						(this.previousTaskSLAGr.active.toString() === "true" && this.currentTaskSLAGr.active.toString() === "false"));

  	return this.previousTaskSLAGr.active.toString() === "true" && this.currentTaskSLAGr.active.toString() === "false";
  },

  _isTaskSLAActive: function() {
  	/* If we don't have a previous version of the Task SLA then processing is occurring because the
  	   the task was updated so we can just test the active field in the current Task SLA */
  	if (this.previousTaskSLAGr === null)
  		return this.currentTaskSLAGr.active.toString() === "true";

  	/* Otherwise make sure the active is false in previous and current and then we know this was triggered
  	   by an update to an inactive Task SLA which we don't need to process */
  	return !(this.previousTaskSLAGr.active.toString() === "false" && this.currentTaskSLAGr.active.toString() === "false");
  },

  _isTaskSLAChanged: function() {
  	/* If we don't have a previous version of the Task SLA then processing is occurring because the
  	   the task was updated and we need to see if our breakdown values have changed */
  	if (this.previousTaskSLAGr === null)
  		return false;

  	var fieldsToCheck = ["duration", "pause_duration", "stage", "has_breached"];
  	for (var i = 0, n = fieldsToCheck.length; i < n; i++) {
  		if (this.currentTaskSLAGr.getValue(fieldsToCheck[i]) !== this.previousTaskSLAGr.getValue(fieldsToCheck[i]))
  			return true;
  	}

  	return false;
  },

  _isNewBreakdownNeeded: function(breakdownDefinitionId, currentBreakdownGr) {
  	var breakdownNeeded = {result: false, fieldValues: []};
  	if (!breakdownDefinitionId) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("_isNewBreakdownNeeded: no breakdown definition id supplied");

  		return breakdownNeeded;
  	}

  	if (!currentBreakdownGr || !(currentBreakdownGr instanceof GlideRecord)) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("_isNewBreakdownNeeded: no current breakdown record supplied");

  		return breakdownNeeded;
  	}

  	var breakdownData = this.breakdownDefinitionsData[breakdownDefinitionId];
  	if (!breakdownData) {
  		this.lu.logWarn("_isNewBreakdownNeeded: no breakdown definition data found for id=" + breakdownDefinitionId);
  		return breakdownNeeded;
  	}

  	var breakdownFields = breakdownData.breakdownFields;
  	if (!breakdownFields || breakdownFields.length === 0) {
  		this.lu.logWarn("_isNewBreakdownNeeded: no breakdown fields in the breakdown definition data: " + JSON.stringify(breakdownData));
  		return breakdownNeeded;
  	}

  	var breakdownField;
  	var taskValue;
  	var breakdownValue;
  	var fieldData;

  	for (var i = 0, n = breakdownFields.length; i < n; i++) {
  		breakdownField = breakdownFields[i];
  		taskValue = this.taskGr.getValue(breakdownField.sourceFieldName);
  		breakdownValue = currentBreakdownGr.getValue(breakdownField.breakdownFieldName);

  		fieldData = {sourceField: breakdownField.sourceFieldName,
  					sourceValue: taskValue,
  					breakdownField: breakdownField.breakdownFieldName,
  					breakdownValue: breakdownValue,
  					changed: false};

  		if (taskValue !== breakdownValue) {
  			breakdownNeeded.result = true;
  			fieldData.changed = true;
  		}

  		breakdownNeeded.fieldValues.push(fieldData);
  	}

  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_isNewBreakdownNeeded: " + JSON.stringify(breakdownNeeded));

  	return breakdownNeeded;
  },

  _getBreakdownDefinitionsData: function() {
  	return new global.SLACacheManager().getBreakdownDefinitionsForSLA(this.slaGr.getUniqueValue());
  },

  _getActiveBreakdownByDefinitionId: function(breakdownDefinitionId) {
  	var breakdownData = this.breakdownDefinitionsData[breakdownDefinitionId];
  	if (!breakdownData || !breakdownData.breakdownTable)
  		return null;

  	var breakdownGr = new GlideRecord(breakdownData.breakdownTable);
  	breakdownGr.addActiveQuery();
  	breakdownGr.addQuery(SLABreakdown.SLA_BREAKDOWN_DEFINITION, breakdownDefinitionId);
  	breakdownGr.addQuery("task_sla", this.currentTaskSLAGr.getUniqueValue());
  	breakdownGr.orderBy("sys_created_on");
  	breakdownGr.query();
  	breakdownGr.next();

  	return breakdownGr;
  },

  _getSLADefinitionGr: function() {
  	return new global.SLABreakdownUtils().getSLADefinitionFromTaskSLA(this.currentTaskSLAGr);
  },

  _getTaskGr: function() {
  	if (!this.slaGr || !(this.slaGr.isValidRecord()))
  		return null;

  	return new global.SLABreakdownUtils().getTaskFromTaskSLA(this.currentTaskSLAGr, this.slaGr.getValue("collection"));
  },

  _deriveUpdateTime: function() {
  	/* if the update to this Task SLA was triggered by an update to the Task use it's
  	   update time */
  	var updateTime;
  	if (this._isUpdateFromTask() && this.taskGr instanceof GlideRecord) {
  		updateTime = new GlideDateTime(this.taskGr.sys_updated_on + "");
  		if (this.lu.atLevel(global.GSLog.DEBUG))
  			this.lu.logDebug("_deriveUpdateTime:  using Task's sys_updated_on -> updateTime=" + updateTime.getValue());
  		return updateTime;
  	}

  	/* Otherwise something other than an update to the Task (via TaskSLAController) has updated the
  	   Task SLA so we can just use its own update time */
  	updateTime = new GlideDateTime(this.currentTaskSLAGr.sys_updated_on + "");
  	if (this.lu.atLevel(global.GSLog.DEBUG))
  		this.lu.logDebug("_deriveUpdateTime:  using Task SLA's sys_updated_on -> updateTime=" + updateTime.getValue());
  	
  	return updateTime;
  },

  _getSLADurationMS: function() {
  	if (this.slaGr.duration_type.nil())
  		return this.slaGr.duration.dateNumericValue();

  	var originalBreachTime;
  	if (!this.currentTaskSLAGr.original_breach_time.nil())
  		originalBreachTime = new GlideDateTime(this.currentTaskSLAGr.original_breach_time + "");
  	else
  		originalBreachTime = this._getSLARelativeDurationEndTime();

  	return this._calcDuration(new GlideDateTime(this.currentTaskSLAGr.start_time + ""), originalBreachTime);
  },

  _getSLARelativeDurationEndTime: function() {
  	// Save current
  	var ocurrent = null;
  	if (typeof current !== 'undefined')
  		ocurrent = current;

  	// Determine whether we want to set current to the "task sla"  or the "table" record associated with the "SLA Definition"
  	if (this.sla.getValue("relative_duration_works_on") === "SLA record")
  		current = this.currentTaskSLAGr;
  	else
  		current = this.taskGr;

  	var dc = this._getDurationCalculator();
  	// Perform relative duration calculation
  	try {
  		dc.calcRelativeDuration(this.sla.getValue("duration_type"));
  	} catch(err) {
  		if (this.lu.atLevel(global.GSLog.WARNING))
  			this.lu.logWarning("_getSLARelativeDurationMS: error while calculating relative duration\n\n" + err);
  	} finally {
  		// Reset current back to its old value
  		if (ocurrent)
  			current = ocurrent;
  	}

  	return dc.getEndDateTime();
  },

  _calcDuration: function(startGdt, endGdt) {
  	var durationCalculator = this._getDurationCalculator();
  	return durationCalculator.calcScheduleDuration(startGdt, endGdt) * 1000;
  },

  _getDurationCalculator: function() {
  	if (this.durationCalculator === null)
  		this.durationCalculator = new global.SLABreakdownUtils().getDurationCalculatorForSLA(this.currentTaskSLAGr);

  	return this.durationCalculator;
  },

  _getDateTimeWithoutMSecs: function(gdt) {
  	if (!gdt)
  		return;

  	if (!(gdt instanceof GlideDateTime))
  		return;

  	var newGdt = new GlideDateTime(gdt);
  	var msecs = newGdt.getNumericValue() % 1000;
  	newGdt.subtract(msecs);

  	return newGdt;
  },

  _logProcessingTime: function(startTime) {
  	if (!this.lu.atLevel(global.GSLog.INFO))
  		return;

  	this.lu.logInfo("processBreakdowns: ends - processing time = " + this._getMSBetweenDates(startTime, new GlideDateTime()) + "ms");
  },

  _getMSBetweenDates: function(startTime, endTime) {
  	if (!startTime || !(startTime instanceof GlideDateTime) ||
  		!endTime || !(endTime instanceof GlideDateTime))
  		return 0;

  	return endTime.getNumericValue() - startTime.getNumericValue();
  },

  type: 'SLABreakdownProcessorSNC'
};

SLABreakdownProcessorSNC.hasBreakdownDefinitions = function(slaId) {
  var breakdownDefinitions = new global.SLACacheManager().getBreakdownDefinitionsForSLA(slaId);
  if (!breakdownDefinitions)
  	return false;
  
  return Object.keys(breakdownDefinitions).length > 0;
};

Sys ID

c4b7f37b57520300491dac11ac94f935

Offical Documentation

Official Docs: