Name

global.DeliveryPlan

Description

A javascript wrapper over our execution plan code to wrap a few common calls into one thing

Script

gs.include("PrototypeServer");
gs.include("DeliveryPlanTaskGenerator");
var DeliveryPlan = Class.create();

DeliveryPlan.prototype = {
  initialize: function( /*GlideRecord*/ gr) {
      this.parentRecord = gr;
      this.planID = gr.delivery_plan + '';
      this.plan = GlideAbstractExecutionPlan.get(this.planID);
  },

  createChildren: function() {
      if (!this.plan)
          return;

      var gen = new DeliveryPlanTaskGenerator(this.plan, this.parentRecord);
      gen.createTasks();

  },

  rejectTask: function( /* GlideRecord */ rejectedTask) {
      if (!this.plan)
          return;

      var action = rejectedTask.upon_reject;
      var reject_goto = rejectedTask.rejection_goto;

      if (action == 'goto')
          this._rollBackTo(reject_goto, rejectedTask);
      else
          this._cancelPendingOrOpen(rejectedTask);
  },

  initTasks: function() {
      var kids = new GlideRecord('task');
      kids.addQuery('parent', this.parentRecord.sys_id);
      kids.query();
      while (kids.next()) {
          var canStart = this._canStart(kids.sys_id);
          if (canStart) {
              var className = kids.sys_class_name;
              var gr = new GlideRecord(className);
              gr.get(kids.sys_id);
              if (this._shouldSkip(gr))
                  this._skip.call(this, gr);
              else
                  this._start.call(this, gr);
              gr.update();
          }
      }
  },

  startTasks: function( /* GlideRecord */ closed) {
      var depends = new GlideRecord('execution_plan_local');
      depends.addQuery('predecessor', closed.sys_id);
      depends.query();
      while (depends.next()) {
          // depends might conceivably start if there are no other dependencies
          if (this._canStart(depends.successor)) {
              var className = depends.successor.sys_class_name;
              var realThing = new GlideRecord(className);
              realThing.get(depends.successor);
              if (this._shouldSkip(realThing))
                  this._skip.call(this, realThing);
              else
                  this._start.call(this, realThing);
              realThing.update();
          }
      }
  },

  canStartTasks: function( /* GlideRecord */ task) {
      var id = task.delivery_task;
      var tt = this.plan.getToken(id);
      if (!tt)
          return false;
      var approvalTasks = tt.getPredecessorApprovalIds();

      // we have all predeccesor approval Ids', just check any of them are rejected. 
      // if any of them is rejected we should not start the next task in sequence.
      var parent = new GlideRecord(current.getTableName());
      parent.addQuery('parent', current.parent);
      parent.addQuery('delivery_task', approvalTasks);
      parent.addQuery('approval', 'rejected');
      parent.query();
      if (parent.getRowCount() > 0)
          return false;
      return true;

  },

  _canStart: function( /* String */
      task_id) {
      // look for a predecessor which is still running
      // still running is defined as work_end is not null
      var depends = new GlideRecord('execution_plan_local');
      depends.addQuery('successor', task_id);
      depends.addQuery('predecessor.work_end', '=', '');
      depends.query();
      if (depends.next())
          return false;

      return true;
  },

  _shouldSkip: function( /* GlideRecord */ task) {
      if (!this.plan)
          return false;

      var id = task.delivery_task;
      var tt = this.plan.getToken(id);
      if (!tt)
          return false;

      return tt.shouldSkip(task);
  },

  _calcBasis: function() {
      var gr = new GlideRecord(this.plan.getTaskTable());
      var basis = this.parentRecord.sys_updated_on.getGlideObject();
      gr.addQuery('parent', this.parentRecord.sys_id);
      gr.query();
      while (gr.next()) {
          if (gr.delivery_task.nil())
              continue;

          var tt = this.plan.getToken(gr.delivery_task);
          if (!tt)
              continue;

          var due_date = tt.getDueDate(basis);
          var expected_start = tt.getExpectedStart(basis);

          gr.due_date.setValue(due_date);
          gr.expected_start.setValue(expected_start);
          if (gr.due_date.changes() || gr.expected_start.changes())
              gr.update();
      }
  },

  _cancelPendingOrOpen: function( /* GlideRecord */ rejectedTask) {
      this._stop.call(this, rejectedTask);
      if (!this.plan)
          return;

      var gr = new GlideRecord(this.plan.getTaskTable());
      gr.addQuery('parent', this.parentRecord.sys_id);
      gr.addQuery('sys_id', '!=', rejectedTask.sys_id);
      var qc = gr.addQuery('state', '1'); // open
      qc.addOrCondition('state', '-5'); // pending
      gr.query();
      while (gr.next()) {
          this._cancel.call(this, gr);
          gr.update();
      }
  },

  _rollBackTo: function( /* String */ reject_goto, /* GlideRecord */ rejectedTask) {
      if (!this.plan)
          return;
      var gr = new GlideRecord(this.plan.getTaskTable());
      gr.addQuery('parent', this.parentRecord.sys_id);
      gr.query();
      while (gr.next()) {

          gs.print('<<<<< Testing ' + gr.number);
          // this is the task we want to roll to
          if (gr.sys_id == reject_goto) {
              this._start.call(this, gr);
              gr.update();
              continue;
          }

          if (this._hasPredecessor(gr.sys_id, reject_goto)) {

              gs.print('PENDING ' + gr.number);
              if (gr.sys_id != rejectedTask.sys_id) {
                  this._pend.call(this, gr);
                  gr.update();
              } else {
                  this._pendSelf.call(this, rejectedTask);
              }
          }
      }
  },

  _hasPredecessor: function( /* String */ test_id, /* String */ pred_id) {

      gs.print('TEST ID = ' + test_id);
      var gr = new GlideRecord('execution_plan_local');
      gr.addQuery('successor', test_id);
      gr.query();
      while (gr.next()) {
          gs.print('PRED = ' + gr.predecessor + ' PRED_ID = ' + pred_id);
          if (gr.predecessor == pred_id)
              return true;

          if (this._hasPredecessor(gr.predecessor, pred_id))
              return true;
      }
      return false;
  },

  _start: function( /* GlideRecord */ gr) {
      gr.work_start = gs.nowDateTime();
      gr.work_end = null;
      gr.state = '1';
  },

  _stop: function( /* GlideRecord */ gr) {
      gr.work_end = gs.nowDateTime();
      gr.state = '4';
  },

  _skip: function( /* GlideRecord */ gr) {
      gr.work_start = gs.nowDateTime();
      gr.work_end = gs.nowDateTime();
      gr.state = '7';
  },

  _pend: function( /* GlideRecord */ gr) {
      gr.work_start = null;
      gr.work_end = null;
      gr.work_notes = "Task set back to pending due to a subsequent task being rejected";
      gr.state = '-5';
  },

  _pendSelf: function( /* GlideRecord */ gr) {
      gr.work_start = null;
      gr.work_end = null;
      gr.work_notes = "Task set back to pending due its being rejected and a previous task being restarted";
      gr.state = '-5';
  },



  _cancel: function( /* GlideRecord */ gr) {
      gr.work_start = gs.nowDateTime();
      gr.work_end = gs.nowDateTime();
      gr.state = '4';
  }
}

Sys ID

8c1748dc0a0a0b010064958d7b896b72

Offical Documentation

Official Docs: