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:

- estimatedhours.label = originally planned time
- remaininghours.label = currently estimated rest time
- 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)

**Note:**See TracTickets for help on using tickets.