Calendar or Schedule-based Incident Autoclose

///Calendar or Schedule-based Incident Autoclose

Calendar or Schedule-based Incident Autoclose

S

ervice-now provides the ability to automatically move incidents marked as ‘Resolved’ into a ‘Closed’ state after a certain number of days. In my experience I’ve found that this type of resolution/closure workflow is really the best way to configure your incident management setup because it allows end-users the ability to reopen incidents within a certain window (while they’re still marked as ‘Resolved’) but it also ensures that eventually all of the incident tickets move to a ‘Closed’ state where they won’t be reopened so that you can accurately calculate SLAs and reporting metrics.

The key piece to this auto close functionality is the ‘incident autoclose’ business rule on the ‘Incident’ table. It works in conjunction with the property shown here – that sets the number of days after which a resolved incident will be moved to a closed state. The ‘incident autoclose’ script works great but it is based off of a basic date calculation that doesn’t take into account any business hours or holidays. Shown below are some modified versions of the ‘incident autoclose’ script that take into account the default system calendar (in the case of calendar-based autoclose), or your choice of any system schedule set up in your system (in the case of schedule-based autoclose).

Calendar-based Incident Autoclose Script

NOTE: Calendars have been replaced in favor of schedules in Service-now. This calendar-based script is provided simply for reference purposes for those who may be using this script already. I would advise you to use the schedule-based script below if at all possible because it allows you to specify a specific schedule for the calculation (giving you much more flexibility and control) rather than simply using the default system calendar.
// This script automatically closes incidents that are resolved
// and haven't been updated in a chosen number of days. You
// can set this number as a property in System Properties.
// If you want a comment placed in the incident, uncomment the
// 'gr.comments' line below.

var ps = gs.getProperty('glide.ui.autoclose.time');
var pn = parseInt(ps);

if (pn > 0) {
   var gr = new GlideRecord('incident');
   gr.addQuery('state', 6);
   gr.query();
   while(gr.next()){
      //Close any records that have not been updated in 'pn' number of days
      //Date difference calculated based on default system calendar
      var dif = gs.calDateDiff(gs.nowDateTime(), gr.sys_updated_on, true);
      if(dif >= pn*86400){
         gr.state = 7;
         //gr.comments = 'Incident automatically closed after ' + pn + ' days in the Resolved state.';
         gr.active = false;
         gr.update();
      }
   }
}

Schedule-based Incident Autoclose Script

This schedule-based script is the BEST solution if you are setting up any kind of autoclose functionality in your system. The use of schedules gives you much more flexibility to determine exactly how much time should pass before closing out a resolved ticket. Schedules can be managed by navigating to ‘System Scheduler->Schedules’ in the left navigation pane of your Service-now instance. One important thing to note is that ‘Number of days’ is actually the number of 24 hour periods!
// This script automatically closes incidents that are resolved
// and haven't been updated in a chosen number of days. You
// can set this number as a property in System Properties.
// If you want a comment placed in the incident, uncomment the
// 'gr.comments' line below.

var ps = gs.getProperty('glide.ui.autoclose.time');
var pn = parseInt(ps);

if(pn > 0){
   //Get a schedule by name to calculate duration
   var schedRec = new GlideRecord('cmn_schedule');
   schedRec.get('name', '8-5 weekdays');
   if (typeof GlideSchedule != 'undefined')
      var sched = new GlideSchedule(schedRec.sys_id);
   else
      var sched = new Packages.com.glide.schedules.Schedule(schedRec.sys_id);

   //Get the current date/time in correct format for duration calculation
   var actualDateTime = new GlideDateTime();
   actualDateTime.setDisplayValue(gs.nowDateTime());

   //Query for resolved incident records
   var gr = new GlideRecord('incident');
   gr.addQuery('state', 6);
   gr.query();
   while(gr.next()){
      //Close any records that have not been updated in 'pn' number of days
      //Date difference calculated based on specified schedule
      var dif = sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getDayPart();
      if(dif >= pn){
         gr.state = 7;
         gr.active = false;
         //gr.comments = 'Incident automatically closed after ' + pn + ' days in the Resolved state.';
         gr.update();
      }
   }
}

Schedule-based Incident Autoclose Script (BY HOURS!)

The fact that ‘Number of days’ is actually the number of 24 hour periods ends up being pretty confusing for people sometimes. As I’ve dealt with this issue several times now, I think the best way to do incident autoclose based on a schedule is to do the autoclose on an hourly basis. This eliminates the ‘Number of days’ confusion and it also gives you the added benefit of a bit more granularity. If you use this script, you should also make sure to change the ‘glide.ui.autoclose.time’ property description to hours instead of days.
// This script automatically closes incidents that are resolved
// and haven't been updated in a chosen number of hours. You
// can set this number as a property in System Properties.
// If you want a comment placed in the incident, uncomment the
// 'gr.comments' line below.

var ps = gs.getProperty('glide.ui.autoclose.time');
var pn = parseInt(ps);

if(pn > 0){
   //Get a schedule by name to calculate duration
   var schedRec = new GlideRecord('cmn_schedule');
   schedRec.get('name', '8-5 weekdays excluding holidays');
   if (typeof GlideSchedule != 'undefined')
      var sched = new GlideSchedule(schedRec.sys_id);
   else
      var sched = new Packages.com.glide.schedules.Schedule(schedRec.sys_id);
   
   //Get the current date/time in correct format for duration calculation
   var actualDateTime = new GlideDateTime();
   actualDateTime.setDisplayValue(gs.nowDateTime());
   
   //Query for resolved incident records
   var gr = new GlideRecord('incident');
   gr.addQuery('state', 6);
   gr.query();
   while(gr.next()){
      //Close any records that have not been updated in 'pn' number of hours
      //Date difference calculated based on specified schedule
      var difDay = sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getDayPart()*24;
      var difHour = sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getDurationValue().split(':')[0].substr(-2);
      var dif = difDay + parseInt(difHour.replace(/^[0]+/g,""));
     
      if(dif >= pn){
         gr.state = 7;
         gr.active = false;
         //gr.comments = 'Incident automatically closed after ' + pn + ' hours in the Resolved state.';
         gr.update();
      }
   }
}
By | 2018-07-09T15:00:01-06:00 January 24th, 2011|Categories: Business rules|Tags: , , , |27 Comments

About the Author:

27 Comments

  1. Bill Collins January 24, 2011 at 6:32 am - Reply

    Thank you.

    • Veronica Hoard February 10, 2011 at 3:36 am - Reply

      We have installed the HR plugin but are not able to get the HR cases to auto-close after 5 days. We do have a similar HR autoclose script to reference the correct values for ‘Resolved’ on the HR case. Any thoughts how to get this triggered?

      • Mark Stanger February 10, 2011 at 3:48 am - Reply

        There is a scheduled job (‘System Scheduler -> Scheduled Jobs’) called ‘Autoclose Incidents’ that triggers the ‘incident autoclose’ business rule. You’ll need to duplicate this scheduled job and have it point to your HR business rule (in the ‘Job Context’ field of the scheduled job) in order to trigger the business rule script.

        Alternatively, you could simply set up a scheduled script execution from the ‘System Definition -> Scheduled Jobs’ module and paste your HR business rule script in directly there.

        • Veronica Hoard February 11, 2011 at 4:53 am - Reply

          I’m testing the scheduled script execution on our General cases (similar to INC and HR cases). I cannot test right now on the HR cases as I have none in my development instance that are in the Resolved state. I am not having any luck with getting them to the closed state. Here is the script (state 25 = Resolved and state 26 = Closed):

          autoCloseGEN();
           

           
          function autoCloseGEN() {
           
            var ps = gs.getProperty('glide.ui.autoclose.time');
           
            var pn = parseInt(ps);
           
           
           
            if (pn > 0) {
           
              var gr = new GlideRecord('hr');
           
              gr.addQuery('state', '25');
           
              gr.addQuery('sys_updated_on', '<', gs.daysAgoStart(pn));
           
              gr.query();
           
              while(gr.next()) {
           
                gr.state = '26';
           
            //  gr.comments = 'GENERAL case automatically closed after ' + pn + ' days in the Resolved state.';
           
                gr.active = false;
           
                gr.update();
           
              }
           
            }
           
          }
          • Mark Stanger February 11, 2011 at 5:09 am - Reply

            If you need additional help debugging custom scripts you may need to go to the Service-now forums for assistance. I can tell you that the script here is querying for HR records (which wouldn’t work if you’re trying to close records outside of that table). You are also querying for (and setting) state values and putting them in quotes. The only things that need to be in quotes when you’re using script are string or text values. Those state values are actually integers so you should remove the quotes around ’25’ and ’26’ in your script.
            A good troubleshooting tip is to add ‘gs.log’ commands at different places in your script to see where the script might be breaking.

  2. Tim March 1, 2011 at 5:50 am - Reply

    Mark, thank you very much for this!

    I’m curious whether the query and update should be for incident_state or for state or if it doesn’t matter (for Incidents, of course).

    Thanks again!

    • Mark Stanger March 1, 2011 at 5:55 am - Reply

      Good question. It does matter. If you use incident state for your incident table you need to change the script accordingly. I prefer to use the ‘state’ field from the task table for my clients so the scripts here will usually use ‘state’ rather than ‘incident_state’.

      • Tim March 1, 2011 at 5:58 am - Reply

        Great clarification, thanks! I’ll consider trying to use the task table more often as well, allowing the scripts to be more portable or global in nature.

  3. Troy December 7, 2011 at 11:50 am - Reply

    Performance Question — won’t this query all resolved incidents every time an incident is touched?

    Why not schedule an event to close the specific incident?
    Sounds like a nice idea but I don’t see how I can queue and event for the future.

    • Mark Stanger December 7, 2011 at 11:55 am - Reply

      Good question. It will query all resolved incidents but it’s certainly not anywhere close to a performance killer. The problem with queueing an event is that you then have to deal with the process of un-queueing that event (or ignoring it) if the incident moves back to an active status. That could be managed but you’re not really buying yourself much of anything in the way of performance and you’re adding a lot more scripting and process complexity.

      • Troy December 7, 2011 at 12:09 pm - Reply

        I thought this was running when any incident record was accessed, but it isn’t — my bad.

        I now see it is run on the hour by the system scheduler. That’s different.

  4. James Dundilling March 15, 2012 at 5:45 am - Reply

    Thanks for nice post regarding schedules. Do you know how to check current time is within a schedule? I tried to do the check with this kind of code, but it seems that the IF statement does not work 🙁

    ******
    gs.addInfoMessage(“test”);

    var schedRec = new GlideRecord(‘cmn_schedule’);
    schedRec.get(‘name’, ‘8-5 weekdays excluding holidays’);
    var sched = new Packages.com.glide.schedules.Schedule(schedRec.sys_id);

    gs.addInfoMessage(sched.name);

    if (sched.isInSchedule(new GlideDateTime()))
    {
    gs.addInfoMessage(“test2″);
    current.schedule = ”;
    }
    else
    {
    gs.addInfoMessage(“test3”);
    }

    ******

    • Mark Stanger March 15, 2012 at 7:12 am - Reply

      I don’t see any obvious issues with your code. You might try the forums on this one though as it’s not directly related to the solution in this post. Here’s a place you could start.

      http://community.service-now.com/forum/7180

  5. Simon W. June 14, 2012 at 11:00 pm - Reply

    Thanks for this! I made a small modification to the script to look for an auto close records of a different table. The “pending confirmation” State value is “-5” (long story but not going to change it unless I really need to). Using this code, a bunch of tickets that were last updated on “2012-05-30” is calculated as 4 days old. Not sure why. Would really appreciate it if you can ID something wrong with the code:

    // This script automatically closes Tickets that are resolved
    // and haven’t been updated in a chosen number of days.

    //var ps = gs.getProperty(‘glide.ui.autoclose.time’);
    gs.log(“Starting auto-close ticket job”);
    var ps = 5;
    var pn = parseInt(ps);

    if(pn > 0){
    //Get a schedule by name to calculate duration
    var schedRec = new GlideRecord(‘cmn_schedule’);
    schedRec.get(‘name’, ‘8-5 weekdays’);
    var sched = new Packages.com.glide.schedules.Schedule(schedRec.sys_id);

    //Get the current date/time in correct format for duration calculation
    var actualDateTime = new Packages.com.glide.glideobject.GlideDateTime();
    actualDateTime.setDisplayValue(gs.nowDateTime());

    //Query for resolved ticket records
    var gr = new GlideRecord(‘ticket’);
    gr.addQuery(‘state’, -5);
    gr.query();
    while(gr.next()){
    //Close any records that have not been updated in ‘pn’ number of days
    //Date difference calculated based on specified schedule
    var dif = sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getDayPart();
    gs.log(gr.number + ” is ” + dif + ” days old.”);
    if(dif >= pn){
    //gr.state = 3;
    //gr.active = false;
    //gr.comments = ‘Incident automatically closed after ‘ + pn + ‘ days in the Resolved state.’;
    //gr.update();
    }
    }
    }

    • Mark Stanger June 15, 2012 at 6:18 am - Reply

      I think that you’re probably getting the correct result. Remember that a day is a 24 hour period. If your schedule is an 8 hour time period each day, then that would put you about 15 calendar days back. I think 2 instead of 5 will get you a closer result.

  6. Brian Rowland April 30, 2013 at 2:59 pm - Reply

    Mark,
    Thanks for posting this code. Just curious if there is a reason you’re not using sched.duration().getNumericValue() to get the schedule duration in ms? By my testing these two blocks get the same result:

    Original:
    var difDay = sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getDayPart()*24;
    var difHour = sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getDurationValue().split(‘:’)[0].substr(-2);
    var dif = difDay + parseInt(difHour.replace(/^[0]+/g,””));

    getNumericValue:
    var dif = Math.floor((sched.duration(gr.sys_updated_on.getGlideObject(), actualDateTime).getNumericValue())/(60*60*1000)));

    Thanks again for sharing your code.

    -Brian

    • Mark Stanger April 30, 2013 at 8:22 pm - Reply

      No particular reason other than I found something that worked and went with it. Your solution may work just as well. If you use it with your modification, let me know how it’s working after it’s been in production for a few months. I hesitate to change a working solution without a lot of testing behind the replacement.

  7. Andreas June 6, 2013 at 11:40 am - Reply

    Hi,

    I was wondering, if there is/was a specific reason to use the Package call instead of the DurationCalculator. (Beside the fact that a working solution was found which works well)
    Looking at the wiki article (http://wiki.servicenow.com/index.php?title=DurationCalculator) also here it is possible to define a schedule for the calculation.

    Andreas

    • Mark Stanger June 6, 2013 at 11:42 am - Reply

      No reason other than the Packages call provided a working solution. I’ve updated all of the code above to work for both Packages calls and non-packages calls so it shouldn’t be an issue when Calgary comes along.

      • Andreas June 6, 2013 at 11:54 am - Reply

        Thx – yes, I saw that one 🙂 Looking at the code it works great. I am using your approach to prepare our own code for the upgrade to Calgary.

        I was thinking of tackling this similar but I am missing one piece to get it working.
        The idea is to perform the date and time calculation in the beginning (also based on a schedule) and then adding it to the query to find only those records which have been updated last on the calculated date and time or earlier.
        This would make the code less expensive for the server as the difference no longer needs to be calculated for each found record.
        Depending on the amount of found records it could make a difference.

        All functions and methods I found so far were about to “add” time but not to “subtract” it. Any idea if there is something out there?

  8. Adam June 2, 2014 at 3:58 am - Reply

    Hi,
    Do you have any idea what may be the reason that incident autoclose scheduled job causes performance problems? I used exactly the same code as in this article. Code is run as scripted scheduled job, once per hour. Did a test and closing 10 incidents takes approximately 14 – 18 seconds. That’s very bad because we have 40 000 incidents to be closed every day. After switching of the workflow (gr.setWorkflow(false)) before calling gr.update() – it works very fast. Is there any good way how to check what business rules, workflows and other kind of events are run on “incident_state” and “active” fields update? I found 15 custom or in-built business rules that are triggered but none of these is performance killer. Thanks in advance for any tip.

    – Adam

    • Mark Stanger June 2, 2014 at 6:50 am - Reply

      Sounds like the autoclose job isn’t the source of the performance issues if setting the ‘setWorkflow’ flag to false removes the performance issue. The only thing I can say is to review the business rules on the incident and task tables. You can use business rule debugging and manually change an incident from resolved to closed to see which business rules are being run.

  9. Sidarth Wangneo May 9, 2017 at 6:47 am - Reply

    Hi.
    The above script works fine except for the fact that it displays the time in GMT. I know that we can change that by adding a setDisplay() method but that converts the variables into string format and they can’t be used in Schedule.Duration function. Any idea how can I do it in system’s time zone?

    • Mark Stanger May 9, 2017 at 7:18 am - Reply

      I don’t think the scripts above display the time anywhere, do they? If you’re looking for help with the notifications or business rules that set field values associated with autoclose you should post that on the ServiceNow community. If I’m missing something please let me know and I’ll attempt to address that.

  10. JM May 18, 2017 at 12:50 am - Reply

    Hi Mark,

    Can we use this code in Istanbul version?

    • Jim Pisello July 14, 2017 at 10:23 am - Reply

      Hi JM,

      This code hasn’t been tested in Istanbul specifically. If you want to try it we recommend applying it to a sub-production instance and thoroughly testing it before moving it into production.

  11. Sidarth Wangneo May 18, 2017 at 3:46 am - Reply

    No they don’t.
    But if we try to print out the variables “actualDateTime.setDisplayValue(gs.nowDateTime())” and gr.sys_updated_on.getGlideObject() they give the time in GMT format.
    What if I wanted the time in system’s/user’s time zone?

Leave A Comment