Modify

Opened 10 months ago

#11549 new enhancement

introduced fields.remain in tracpm.py

Reported by: falkb Owned by: ChrisNelson
Priority: normal Component: TracJsGanttPlugin
Severity: normal Keywords:
Cc: Trac Release:

Description

I use Trac with these additional custom ticket fields:

  1. estimatedhours.label = originally planned time
  2. remaininghours.label = currently estimated rest time
  3. totalhours.label = sum of worked time

I set in trac.ini

[TracPM]
fields.estimate = estimatedhours
fields.worked = totalhours

but this patch allows me setting also the remaininghours for the Gantt chart to consider:

[TracPM]
fields.estimate = estimatedhours
fields.worked = totalhours
fields.remain = remaininghours

The patch is here:

!diff
Index: tracpm.py
===================================================================
--- tracpm.py	(revision 13386)
+++ tracpm.py	(working copy)
@@ -150,6 +150,8 @@
            """Ticket field to use as the data source for estimated work""")
     Option(cfgSection, 'fields.worked', None,
            """Ticket field to use as the data source for completed work""")
+    Option(cfgSection, 'fields.remain', None,
+           """Ticket field to use as the data source for remaining work""")
     Option(cfgSection, 'fields.start', None, 
            """Ticket field to use as the data source for start date""")
     Option(cfgSection, 'fields.finish', None, 
@@ -187,7 +189,7 @@
         self.sources = {}
 
         # All the data sources that may come from custom fields.
-        fields = ('percent', 'estimate', 'worked', 'start', 'finish',
+        fields = ('percent', 'estimate', 'worked', 'remain', 'start', 'finish',
                   'pred', 'succ', 'parent')
 
         # All the data sources that may come from external relations.
@@ -501,11 +503,11 @@
             est = 0.0
         # Closed tickets took as long as they took
         elif ticket['status'] == 'closed' and work:
-            est = work
+            est = 0 # work
         # If the task is over its estimate, assume it will take
         # pad more time
-        elif work > est:
-            est = work + self.estPad
+        #elif work > est:
+        #    est = work + self.estPad
         # If unestimated, use the default
         elif not est or est == 0:
             est = self.dftEst
@@ -540,7 +542,9 @@
                     percent = 0
                 else:
                     worked = float(worked)
-                    percent = '%s/%s' % (worked, estimate)
+                    prcnt = worked * 100.0 / (float(estimate) + worked)
+                    percent = int(prcnt)
+                    #self.env.log.info('#### percent(%s) = (worked=%d, estimate=%d) : %d' % (ticket['id'], worked, estimate, percent))
         # Use percent if provided
         elif self.isCfg('percent'):
             try:
@@ -794,6 +798,9 @@
         if self.isCfg('worked'):
             ticket[self.fields['worked']] = 0
 
+        if self.isCfg('remain'):
+            ticket[self.fields['remain']] = 0
+
         # There is no percent complete for a pseudoticket
         if self.isCfg('percent'):
             ticket[self.fields['percent']] = 0
@@ -820,11 +827,11 @@
         else:
             milestones = []
 
-        for t in tickets:
-            if 'milestone' in t and \
-                    t['milestone'] != '' and \
-                    t['milestone'] not in milestones:
-                milestones.append(t['milestone'])
+            for t in tickets:
+                if 'milestone' in t and \
+                        t['milestone'] != '' and \
+                        t['milestone'] not in milestones:
+                    milestones.append(t['milestone'])
 
         # Need a unique ID for each task.
         if len(milestones) > 0:
@@ -1008,7 +1015,7 @@
             nullable = [ 'pred', 'succ', 
                          'start', 'finish', 
                          'parent', 
-                         'worked', 'estimate', 'percent' ]
+                         'worked', 'estimate', 'remain', 'percent' ]
             for field in nullable:
                 if self.isField(field):
                     fieldName = self.fields[self.sources[field]]
@@ -1841,7 +1848,12 @@
                 # Set the field
                 t['_calc_finish'] = finish
 
+            start_based_on_work = False
+            overruled_start = None
+            start = None
+
             if t.get('_calc_start') == None:
+                hours = 0
                 # If work has begun, the start is the actual start.
                 if t.get('_actual_start') and options.get('useActuals'):
                     start = [ to_datetime(t['_actual_start']), True ]
@@ -1856,6 +1868,7 @@
                 # Otherwise, the start is based on the finish and the
                 # work to be done before then.
                 else:
+                    start_based_on_work = True
                     hours = self.pm.workHours(t)
                     start = t['_calc_finish'][0] + \
                         _calendarOffset(t, 
@@ -1865,9 +1878,27 @@
 
                 t['_calc_start'] = start
 
+                if start_based_on_work:
+                    if self.pm.isCfg('remain') and t.get(self.pm.fields['remain']) != None:
+                        remain_str = t[self.pm.fields['remain']]
+                        if remain_str != '':
+                            remaininghours = float(remain_str)
+                        else:
+                            remaininghours = 0.0
+
+                        start = t['_calc_finish'][0] + \
+                            _calendarOffset(t, 
+                                            -1*(remaininghours), 
+                                            t['_calc_finish'][0])
+                        start = [start, t['_calc_finish'][1]]
+                        overruled_start = start
+                        #t['_calc_start'] = overruled_start
+
                 # Adjust implicit finish for explicit start
                 if _betterDate(start, finish):
                     hours = self.pm.workHours(t)
+                    if (overruled_start):
+                        hours = remaining_hours
                     finish[0] = start[0] + _calendarOffset(t,
                                                            hours,
                                                            start[0])
@@ -1876,13 +1907,19 @@
 
             # Remember the limit for open tickets
             if t['status'] != 'closed':
+                if start is None:
+                    start = t['_calc_start']
                 limit = self.limits.get(t['owner'])
-                if not limit or limit > t['_calc_start'][0]:
-                    self.limits[t['owner']] = t['_calc_start'][0]
+                if not limit or limit > start[0]:
+                    self.limits[t['owner']] = start[0]
 
             self.taskStack.pop()
 
-            return t['_calc_start']
+            if overruled_start:
+                #self.env.log.warning('ticket=%s' % t['id'])
+                return overruled_start
+            else:
+                return t['_calc_start']
 
         # Schedule a task As Soon As Possible
         # Return the finish of the task as a date object

Now the length of a ticket bar is equal to 'estimatedhours' but tickets can overlap because resource leveling will consider the 'remaininghours'.

Remaining issue: For proper ressource leveling, I don't get it work that closed tickets have the original bar length of 'estimatedhours'.

Attachments (0)

Change History (0)

Add Comment

Modify Ticket

Action
as new The owner will remain ChrisNelson.
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.