Name

global.PercentCompleteRollupHandler

Description

No description available

Script

var PercentCompleteRollupHandler = Class.create();
PercentCompleteRollupHandler.prototype = {
  
  initialize: function() {
  },
  
  handle: function(childRecord, parentTable, parentNavigator, rollupColumn, isAverage) {
  	PPMDebug.log('PercentCompleteRollupHandler.handle: ' + parentTable + " | " + parentNavigator + " | " + rollupColumn);
  	var rollupStructureLoader = new RollupStructureLoader();
  	var parentSysId = childRecord.getValue(parentNavigator);
  	var childRecordTable = childRecord.instanceOf("rm_story")? childRecord.getValue("sys_class_name"): "planned_task";
  	if(childRecord && childRecord.operation() == "delete") {
  		this.updateParent(parentTable, parentSysId, parentNavigator, childRecordTable, childRecord.getValue("sys_id"));
  	} else {
  		this.updateParent(parentTable, parentSysId, parentNavigator, childRecordTable);
  	}
  },
  
  updateParent: function(parentTable, parentSysId, parentNavigator, childTableName, excludeSysId) {
  	PPMDebug.log('PercentCompleteRollupHandler.updateParent: ' + parentTable + " | " + parentSysId + " | " + parentNavigator +
  	" | " + childTableName + " | " + excludeSysId);
  	//Calculate durations from sibling tasks
  	var parent = new GlideRecord(parentTable);
  	if (parent.get(parentSysId)) {
  		var sibling = new GlideRecord(childTableName);
  		sibling.addQuery(parentNavigator, parentSysId);
  		sibling.addNullQuery('orig_sys_id');
  		var lookupTable = parentTable;
  		if(parent.instanceOf('pm_project') || parent.instanceOf('pm_project_task'))
  			lookupTable = parent.instanceOf('pm_project') ? 'pm_project' : 'pm_project_task';
  		
  		var ptRollupApi = new PlannedTaskRollupApi();
  		var sysClassExclusions = ptRollupApi.getRollupExclusions(lookupTable,'percent_complete');
  		if(sysClassExclusions.length >0 )
  			sibling.addQuery('sys_class_name', 'NOT IN', sysClassExclusions.join(','));
  		
  		if( excludeSysId ) {
  			sibling.addQuery("sys_id", "!=", excludeSysId);
  		}
  		sibling.query();
  		this.rollupFromChildrenAndUpdateParent(sibling, parent, parentTable, parentSysId, parentNavigator, childTableName);
  	}
  },
  
  
  rollupFromChildrenAndUpdateParent: function(childRecords, parentRecord, parentTable, parentSysId, parentNavigator, childTableName) {
      PPMDebug.log('PercentCompleteRollupHandler.rollupFromChildrenAndUpdateParent: ' + parentRecord.getValue("short_description") +
          " | Child Record Count -> " + childRecords.getRowCount() + " - " + childRecords.getEncodedQuery());
      var totalDur = 0;
      var workedDuration = 0;
      var percentCalc = 0;
      var totalRecords = childRecords.getRowCount();
      var schedule;
      var durationCalculator = new DurationCalculator();
  	var hoursPerDayBasedOnSchedule;
      var duration;
      if (totalRecords == 0) {
          parentRecord.percent_complete = percentCalc;
          parentRecord.update();
      } else {
          var mileStones = 0;
          var completedMileStones = 0;
          while (childRecords.next()) {
  			if(!schedule && childRecords.top_task)
  				schedule = childRecords.top_task.schedule;
  				
              if (schedule) {
  				var ptGlobalApi = new PTGlobalAPI();
  				if(JSUtil.nil(hoursPerDayBasedOnSchedule))
  					hoursPerDayBasedOnSchedule = ptGlobalApi.getHoursPerDay(childRecords.start_date, schedule, childRecords.getUniqueValue());
  				//Duration value is in format d HH:mm:ss, to get hours,minutes,values we are splitting on space. if duration only has hours it will give us HH:mm:ss
  				// durationCalculator._timeToSeconds will calculate seconds if we give value in the format HH:mm:ss
  				var durationArray = childRecords.duration.getGlideObject().getDurationValue().split(' ');
  				var durValueIn_HH_mm_ss_format = durationArray.length == 2 ? childRecords.duration.getGlideObject().getDurationValue().split(' ')[1] : childRecords.duration.getGlideObject().getDurationValue();

  				duration = hoursPerDayBasedOnSchedule * parseInt(childRecords.duration.getGlideObject().getDayPart()) * 60 * 60 + durationCalculator._timeToSeconds(durValueIn_HH_mm_ss_format);
              } else {
                  duration = childRecords.duration.getGlideObject().getNumericValue() / 1000;
              }
              totalDur += duration;
              if (JSUtil.notNil(childRecords.getValue("percent_complete")) && childRecords.getValue("percent_complete") != "0") {
                  workedDuration += duration * (childRecords.percent_complete / 100);
              }

              if (childRecords.milestone == true) {
                  mileStones++;
                  if (JSUtil.notNil(childRecords.getValue("percent_complete")) && childRecords.getValue("percent_complete") != "0") {
                      if ((childRecords.percent_complete / 100) === 1) {
                          completedMileStones++;
                      }
                  }
              }
          }
          percentCalc = (workedDuration / totalDur) * 100;

          /* case 1: when project contains all milestones
          then project %complete will be 100% ONLY if all the milestones are 100% complete.
          */
          if (totalRecords === mileStones) {
              //even if one is not completed then its 0%
              if (completedMileStones == mileStones) {
                  percentCalc = 100;
              } else {
                  percentCalc = 0;
              }
          } else {
              /* case 2: when project has milestones and regular tasks 
              Project %complete = 100% ONLY when all the milestones & regular tasks are 100% complete.
              */
              if (!isNaN(percentCalc) && parseInt(percentCalc) === 100 && completedMileStones != mileStones) {
                  percentCalc = 99;
              }
          }


          if (!isNaN(percentCalc) && parentRecord.percent_complete != percentCalc) {
              parentRecord.percent_complete = percentCalc;
              PPMDebug.log('PercentCompleteRollupHandler.rollupFromChildrenAndUpdateParent - Updating Parent Percent Complete -> ' +
                  parentRecord.getValue("short_description") + " - " + percentCalc);
              parentRecord.update();
          } else {
              /* parent percent complete and percentCalc are same, but there will be case where its parent 
                 percent complete will be different. this else condition handles this scenerio
                 
                 ex:
                 Project
                    T1 -   Closed Complete - 100 percent - 20 days
              	  T2    - Pending        - 0%          - 2  days
              	    T21 - Pending        - 0%          - 1  day
              		T22 - Pending        - 0%          - 1  day
              		
                 for above project structure, if T22 duration is updated to 20 days, T2 duration will get updated to 20 days and this will have impact of Project percent complete, though there is no change in T2 percent complete		
               */
              if (parentRecord.parent) {
                  var newParent = parentRecord.parent.getRefRecord();
                  if (newParent.instanceOf('pm_project') || newParent.instanceOf('pm_project_task'))
                      parentTable = newParent.instanceOf('pm_project') ? 'pm_project' : 'pm_project_task';

                  this.updateParent(parentTable, newParent.getValue('sys_id'), parentNavigator, childTableName)

              }

          }
      }

  },
  	
  type: 'PercentCompleteRollupHandler'
};

Sys ID

1e6e73619f001200598a5bb0657fcfc2

Offical Documentation

Official Docs: