Relative duration and pause condition

Sebastiaan de V
Kilo Guru

The wiki (Defining Relative Durations - ServiceNow Wiki) clearly states: "Pause conditions are not compatible with Relative Durations" I want to understand why as this is the most clear and straight forward way to implement the customer requirements on SLA. As far as I know, the relative duration is calculated only once to fill in the planned end time. When I update the TaskSLAController script (on a developer instance off course) to not consider the relative duration:

if (/*taskSLAgr.sla.duration_type != '' ||*/ !taskSLAgr.sla.pause_condition)

                // relative duration SLAs, and those without pause conditions, cannot pause

                return;

and

// a "relative-duration" SLA cannot pause, whatever conditions might be in the SLA Definition record

    /*   if (taskSLAgr.sla.duration_type != '')

                return; */

Pausing of the task sla seems to work fine, including the calculation of pause and elapsed times.

Can you explain me where this will/could go wrong? I am also looking for alternatives to implement relative duration SLAs with pause conditions.

15 REPLIES 15

Hi Sebastiaan,



I followed your instructions and seems like SLA on relative duration is pausing, but SLA is not breaching if planned end time is crossed.



If I select Duration type =   relative duration, SLA braches as expected, but doesnt PAUSE. If I select Duration type   = user specified and calculated duration = Relative duration, SLA pauses but never breach.


here is the whole function code in the TASKSLA, Can you please guide me if I missed someting?



// recalculate the new end-time at start, or after a pause


  // pre-req: pause_duration, and business_pause_duration, or totalPauseTimeMS includes the time spent in the pause state so far or just left.


  _recalculateEndTime: function(totalPauseTimeMS) {


  if (this.breachedFlag && !this.calcEndTimeAfterBreach)


  return;



  if (!totalPauseTimeMS)


  totalPauseTimeMS = this.taskSLAgr.business_pause_duration.dateNumericValue();



  var dc = new DurationCalculator();


  var tz = this.taskSLAgr.timezone;


  var ocurrent = null;


  if (!tz)


  tz = gs.getSysTimeZone();


  if (this.taskSLAgr.schedule)


  dc.setSchedule(this.taskSLAgr.schedule, tz);


  dc.setStartDateTime(this._glideDateTime(this.taskSLAgr.start_time)); // this can be set via retroactive start


  /*Removed this block for PAUSE - Start


  if (this.taskSLAgr.sla.duration_type == '') {


  // plain end-time extended by total (business) pause time


  var newDuration = this.taskSLAgr.sla.duration.dateNumericValue() + totalPauseTimeMS; // milliseconds


  dc.calcDuration(newDuration / 1000);


  }


  Removed this block for PAUSE - Start*/


  // START customizations



  if (this.taskSLAgr.sla.duration_type == '' ) {


  if (this.taskSLAgr.sla.u_calculated_duration) {


  // A user defined calculated duration is selected, so use that for calculated end time


  //copies the same functionality as for a "normal" relative duration



  // Store the current value of 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.taskSLAgr.sla.relative_duration_works_on == "SLA record") current = this.taskSLAgr;


  else current = this.taskSLAgr.task.getRefRecord();



  // Perform calculation using the revised value of current, uses the custom relative duration


  dc.calcRelativeDuration(this.taskSLAgr.sla.u_calculated_duration);



  // After the calculation, reset current back to its old value


  if (ocurrent) current = ocurrent;



  // Pause time needs to consider the schedule so we calculate a new end time


  // based on the end time provided by the realtive duration but considering the


  // schedule, which is ignored by the relative duration calculation.



  var schedule = new GlideSchedule();


  schedule.load(this.taskSLAgr.schedule, tz, null);


  var totalMS = this._getDurationMS(schedule.duration(this._glideDateTime(this.taskSLAgr.start_time), dc.getEndDateTime()));


  totalMS = totalMS + totalPauseTimeMS;


  dc.calcDuration(totalMS / 1000);


  }


  else {


  // plain end-time extended by total (business) pause time


  var newDuration = this.taskSLAgr.sla.duration.dateNumericValue() + totalPauseTimeMS; // milliseconds


  dc.calcDuration(newDuration / 1000);


  }


  }


  // END customization




  else {


  // scripted "absolute" end_time, does not need to account for pause time.



  // Store the current value of 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.taskSLAgr.sla.relative_duration_works_on == "SLA record")


  current = this.taskSLAgr;


  else


  current = this.taskSLAgr.task.getRefRecord();



  // Perform calculation using the revised value of current


  dc.calcRelativeDuration(this.taskSLAgr.sla.duration_type);



  // After the calculation, reset current back to its old value


  if (ocurrent)


  current = ocurrent;


  }


  this.taskSLAgr.planned_end_time = dc.getEndDateTime();


  },



I will appreciate your help on this.



Thanks


Ak


Hi faryan4,



Thank you for trying out my code and posting the results. You are right, the SLA is not breached and the time calculations are probably also not correct. I found this already earlier and decided to postpone this solution until resolved. I have a (partial) solution, that might help you and others. To start with, your code for the _recalculateEndTime looks correct. And this should allow you to pause the SLA and the breach time (planned end time) calculation should be correct. There are a number of modifications to do to make it work.



1. The first modification I did is _recalculateEndTime, by changing this line


if (this.taskSLAgr.sla.duration_type == '' ) {


to this


if (this.taskSLAgr.sla.duration_type == '' || this.taskSLAgr.sla.u_calculated_duration != '') {



2. The next update is in the TaskSLAController script in the function _pauseUnpause, change this line (on an Istanbul release, it's line 355)


if (taskSLAgr.sla.duration_type != '')


to this


if (taskSLAgr.sla.duration_type != '' /* CUSTOM */ && taskSLAgr.sla.u_calculated_duration == '' /* END CUSTOM */)



3. The last modification actually changes the calculation, because the main reason that the calculations are incorrect or empty, is because the SLA does not have an actual duration, although we assume it has in all the other scripts (as we fake a "user specified" duration).   This requires a modification in the script SLACalculatorNG adding a few customisations to the _createTaskSLA function ( I have added a few of the existing lines before and after, so you can find the part to update).



  newTaskSLA.start_time             = sla.start_time.dateNumericValue();


  newTaskSLA.pause_duration     = sla.pause_duration.dateNumericValue();


  newTaskSLA.pause_time             = sla.pause_time.dateNumericValue();


  // task_sla business_pause_duration always has a value


  newTaskSLA.business_pause_duration = sla.business_pause_duration.dateNumericValue();



  // the SLA definition's duration


  newTaskSLA.sla_duration = sla.sla.duration.dateNumericValue();



  /* CUSTOM */


  if (sla.sla.u_calculated_duration) {


  //duration for a calculated (relative) duration


  newTaskSLA.sla_duration = sla.planned_end_time.dateNumericValue() - newTaskSLA.start_time;


  }


  /* END CUSTOM */



  // what planned_end_time would be, if it were updated past the breach


  var dc = this._newDurationCalculator(sla, newTaskSLA.sla_duration + newTaskSLA.business_pause_duration);


  newTaskSLA.derived_end_time = dc.getEndDateTime().getNumericValue();



  /* CUSTOM */



  if (sla.sla.u_calculated_duration) {


  //duration for a calculated (relative) duration


  newTaskSLA.derived_end_time = sla.planned_end_time.dateNumericValue();


  }


  /* END CUSTOM */



  // if this is a relative duration we need to set our sla duration to the time between start_time and planned_end_time


  if (!JSUtil.nil(sla.sla.duration_type))


  newTaskSLA.sla_duration = dc.getSeconds() * 1000;



These little modifications will make sure the calculation for all time fields (actual elapsed, actual percentage, actual time left, business elapsed and business left) are correct, except for the business percentage, which I still need to figure out.



4. Since by default the "Has breached" is flagged on the business percentage, this is not yet working. At the moment, until I can find out how to do the business percentage correctly, you should set the property com.snc.sla.calculation.use_time_left to true (Configure SLA properties   and Use exact times in SLA calculations) This will make sure that the Has breached is flagged correctly, since the time calculation is good with the above modifications.



Please let me know any feedback or questions and if you have found this solution to be working correctly. Of course I'm also interested in any other solutions or workarounds and feedback from people trying this in their instance.



Regards,


Sebastiaan



P.S. Please provide any appropriate feedback on my community post (like, endorse, etc.)


So first of all thanks for all the info, I was recently also required to implement this. One thing I've found which is not mentioned already is that you will also have to implement the the changes in _recalculateEndTime in the script include "RepairTaskSLA". I would also like to note, this still works in Orlando.

knight202
Kilo Guru

Relative duration is a fixed date and time when a task is supposed to be completed. If it is not, then the SLA will breach, regardless of the record state.



The SLA Engine is designed to push the Breach Date back when a record meets the Pause conditions in the SLA Definition ONLY when using a Non-Relative Duration.



If your organization wants to track time paused, use a Non-Relative Duration option in your SLA Definition.


knight202 I understand your point of view and that is also how ServiceNow designed and build the SLA engine, I fully agree with that. Judging from the questions and responses on the community there is also a demand for this functionality, or at least curiosity on how to do it .



My implementation is based on a actual business requirement, and I can imagine there are more people who want the ability to pause SLAs with a dynamically calculated end time. You're absolutely right that pushing back the breach time is not always a logical choice and my implementation described in this post can be adjusted to remain with a fixed breach time, but still pause the SLA, which I personally can imagine being an actual business requirement.



My solution is just a way to do it, there might be others, probably even better ways to do it, e.g. by removing all the checks for the duration type.



For the people interested in the implementation, I believe I have found the last piece of the puzzle to make sure the business percentage is also correct. The following needs to be changed in the _calcTaskSLA function in the SLACalculatorNG Script Include:



// Business elapsed time, Business elapsed percentage, Business time left


  currentSLA.business_elapsed = Math.max(0, (busElapsedTimeMS - businessPauseTimeMS));



  /* CUSTOMIZED percentage calculation */


  if (currentSLA.custom_business_duration) {


  currentSLA.business_percentage = ((currentSLA.business_elapsed / this._getScheduleDurationTime(startGDT, derivedEndGDT, schedule)) * 100).toFixed('2');


  }


  else {


  //standard:


  currentSLA.business_percentage = ((currentSLA.business_elapsed / currentSLA.sla_duration) * 100).toFixed('2');


  }



  /* END CUSTOMIZED */


  currentSLA.business_time_left = (timeLeftMS > 0) ?