def taskbrain2dict(self, brain): """Get a dict with info from this task brain. """ # Get info about parent Story try: obj = brain.getObject() except (AttributeError, KeyError): return {} story = obj.aq_parent estimate = brain.estimate actual = brain.actual_time returnvalue = dict( url=brain.getURL(), brain=brain, UID=brain.UID, title=brain.Title, story_url=story.absolute_url(), story_title=story.Title(), description=brain.Description, estimate=formatTime(estimate), actual=formatTime(actual), difference=formatTime(estimate - actual), assignees=brain.getAssignees, ) return returnvalue
def totals(self): context = aq_inner(self.context) searchpath = '/'.join(context.getPhysicalPath()) filter = dict(searchpath=searchpath) taskbrains = self.simple_tasklist(**filter) memberlist = [] members = context.getProject().getMembers() for memberid in members: tasks = [taskbrain for taskbrain in taskbrains if memberid in taskbrain.getAssignees] rawEstimate = sum([task.estimate / len(task.getAssignees) for task in tasks]) bookings = self.catalog.searchResults( portal_type='Booking', Creator=memberid, path=searchpath) rawActualHours = sum([booking.actual_time or 0.0 for booking in bookings]) if rawEstimate > 0 or rawActualHours > 0: rawDifference = rawEstimate - rawActualHours info = dict( memberid=memberid, estimate=formatTime(rawEstimate), actual=formatTime(rawActualHours), difference=formatTime(rawDifference), ) memberlist.append(info) return memberlist
def totals(self): context = aq_inner(self.context) searchpath = '/'.join(context.getPhysicalPath()) filter = dict(searchpath=searchpath) taskbrains = self.simple_tasklist(**filter) memberlist = [] members = context.getProject().getMembers() for memberid in members: tasks = [ taskbrain for taskbrain in taskbrains if memberid in taskbrain.getAssignees ] rawEstimate = sum( [task.estimate / len(task.getAssignees) for task in tasks]) bookings = self.catalog.searchResults(portal_type='Booking', Creator=memberid, path=searchpath) rawActualHours = sum( [booking.actual_time or 0.0 for booking in bookings]) if rawEstimate > 0 or rawActualHours > 0: rawDifference = rawEstimate - rawActualHours info = dict( memberid=memberid, estimate=formatTime(rawEstimate), actual=formatTime(rawActualHours), difference=formatTime(rawDifference), ) memberlist.append(info) return memberlist
def iterationbrain2dict(self, brain): """Get a dict with info from this iteration brain. """ review_state_id = brain.review_state estimate = brain.estimate actual = brain.actual_time obj = brain.getObject() history = self.workflow.getHistoryOf('eXtreme_Iteration_Workflow', obj) completion_date = None for item in history: if item['action'] == 'complete': completion_date = item['time'] returnvalue = dict( url=brain.getURL(), title=brain.Title, description=brain.Description, raw_estimate=estimate, estimate=formatTime(estimate), actual=formatTime(actual), review_state=review_state_id, review_state_title=self.workflow.getTitleForStateOnType( review_state_id, 'Iteration'), start_date=obj.getStartDate(), end_date=obj.getEndDate(), completion_date=completion_date, brain=brain, ) returnvalue.update(self.extra_dict(obj, brain)) return returnvalue
def storybrain2dict(self, brain, locked_status=False): """Get a dict with info from this story brain. """ context = aq_inner(self.context) review_state_id = brain.review_state # compute progress percentage is_completed = (review_state_id == 'completed') if is_completed: progress = 100 else: estimated = brain.estimate actual = brain.actual_time progress = self.get_progress_perc(actual, estimated) # Extract locked status if requested locked = False if locked_status: locked = wl_isLocked(brain.getObject()) # compute open task count searchpath = brain.getPath() filter = dict(portal_type=['Task', 'PoiTask'], path=searchpath) unfinished_states = ( 'open', 'to-do', ) filter['review_state'] = unfinished_states open_tasks = len(self.catalog.searchResults(**filter)) # compute completed task count finished_states = ('completed', ) filter['review_state'] = finished_states completed_tasks = len(self.catalog.searchResults(**filter)) estimate = brain.estimate actual = brain.actual_time returnvalue = dict( story_id=brain.getId, uid=brain.UID, url=brain.getURL(), title=brain.Title, description=brain.Description, raw_estimate=estimate, estimate=formatTime(estimate), size_estimate=brain.size_estimate, actual=formatTime(actual), difference=formatTime(estimate - actual), progress=progress, review_state=review_state_id, review_state_title=self.workflow.getTitleForStateOnType( review_state_id, 'Story'), is_completed=is_completed, open_tasks=open_tasks, completed_tasks=completed_tasks, locked=locked, ) return returnvalue
def storybrain2dict(self, brain, locked_status=False): """Get a dict with info from this story brain. """ context = aq_inner(self.context) review_state_id = brain.review_state # compute progress percentage is_completed = (review_state_id == 'completed') if is_completed: progress = 100 else: estimated = brain.estimate actual = brain.actual_time progress = self.get_progress_perc(actual, estimated) # Extract locked status if requested locked = False if locked_status: locked = wl_isLocked(brain.getObject()) # compute open task count searchpath = brain.getPath() filter = dict(portal_type=['Task', 'PoiTask'], path=searchpath) unfinished_states = ('open', 'to-do', ) filter['review_state'] = unfinished_states open_tasks = len(self.catalog.searchResults(**filter)) # compute completed task count finished_states = ('completed', ) filter['review_state'] = finished_states completed_tasks = len(self.catalog.searchResults(**filter)) estimate = brain.estimate actual = brain.actual_time returnvalue = dict( story_id=brain.getId, uid=brain.UID, url=brain.getURL(), title=brain.Title, description=brain.Description, raw_estimate=estimate, estimate=formatTime(estimate), size_estimate=brain.size_estimate, actual=formatTime(actual), difference=formatTime(estimate - actual), progress=progress, review_state=review_state_id, review_state_title=self.workflow.getTitleForStateOnType( review_state_id, 'Story'), is_completed=is_completed, open_tasks=open_tasks, completed_tasks=completed_tasks, locked=locked, ) return returnvalue
def iterationbrain2dict(self, brain): """Get a dict with info from this iteration brain. """ estimate = brain.estimate actual = brain.actual_time obj = brain.getObject() wf_id = 'eXtreme_Iteration_Workflow' # fallback wfs = self.workflow.getWorkflowsFor(obj) if len(wfs): wf_id = wfs[0].id history = self.workflow.getHistoryOf(wf_id, obj) completion_date = None for item in history: if item['action'] == 'complete': completion_date = item['time'] open_stories = len( self.catalog( path=brain.getPath(), portal_type='Story', review_state=['draft', 'estimated', 'in-progress', 'pending'])) open_tasks = len( self.catalog(path=brain.getPath(), portal_type=['Task', 'PoiTask'], review_state=['open', 'to-do'])) if open_stories or open_tasks: status_warning = _("msg_status_warning", default="${open_stories} open stories and " "${open_tasks} open tasks", mapping=dict(open_stories=open_stories, open_tasks=open_tasks)) else: status_warning = '' returnvalue = dict( iteration_url=brain.getURL(), iteration_title=brain.Title, iteration_description=brain.Description, icon=brain.getIcon, man_hours=brain.getManHours, raw_estimate=estimate, estimate=formatTime(estimate), raw_actual=actual, actual=formatTime(actual), start_date=obj.getStartDate(), end_date=obj.getEndDate(), completion_date=completion_date, brain=brain, open_stories=open_stories, open_tasks=open_tasks, status_warning=status_warning, ) returnvalue.update(self.extra_dict(obj, brain)) return returnvalue
def main(self): """Get a dict with info from this Context. """ context = aq_inner(self.context) anno = IActualHours(context, None) if anno is not None: actual = anno.actual_time else: # Should not happen (tm). actual = -99.0 est = IEstimate(context, None) if est is not None: estimate = est.estimate else: # Should not happen (tm). estimate = -99.0 # Size estimate. We may want to do this smarter. filter = dict(portal_type='Story') items = context.getFolderContents(filter) size_estimate = sum([ item.size_estimate for item in items if item.size_estimate is not None ]) review_state = self.workflow.getInfoFor(context, 'review_state') if review_state in ['completed', 'invoiced', 'own-account']: budget_left = None else: budget_left = self.actual_budget_left() if budget_left is not None: budget_left = formatTime(budget_left) ploneview = context.restrictedTraverse('@@plone') if hasattr(context, 'getManHours'): manhours = context.getManHours() else: manhours = None returnvalue = dict( title=context.Title(), description=context.Description(), man_hours=manhours, start_date=ploneview.toLocalizedTime(context.getStartDate()), end_date=ploneview.toLocalizedTime(context.getEndDate()), estimate=formatTime(estimate), size_estimate=size_estimate, actual=formatTime(actual), difference=formatTime(estimate - actual), review_state=review_state, budget_left=budget_left, ) return returnvalue
def iterationbrain2dict(self, brain): """Get a dict with info from this iteration brain. """ estimate = brain.estimate actual = brain.actual_time obj = brain.getObject() wf_id = 'eXtreme_Iteration_Workflow' # fallback wfs = self.workflow.getWorkflowsFor(obj) if len(wfs): wf_id = wfs[0].id history = self.workflow.getHistoryOf(wf_id, obj) completion_date = None for item in history: if item['action'] == 'complete': completion_date = item['time'] open_stories = len(self.catalog( path=brain.getPath(), portal_type='Story', review_state=['draft', 'estimated', 'in-progress', 'pending'])) open_tasks = len(self.catalog( path=brain.getPath(), portal_type=['Task', 'PoiTask'], review_state=['open', 'to-do'])) if open_stories or open_tasks: status_warning = _( u"msg_status_warning", default=u"${open_stories} open stories and " "${open_tasks} open tasks", mapping=dict(open_stories=open_stories, open_tasks=open_tasks)) else: status_warning = '' returnvalue = dict( iteration_url=brain.getURL(), iteration_title=brain.Title, iteration_description=brain.Description, icon=brain.getIcon, man_hours=brain.getManHours, raw_estimate=estimate, estimate=formatTime(estimate), raw_actual=actual, actual=formatTime(actual), start_date=obj.getStartDate(), end_date=obj.getEndDate(), completion_date=completion_date, brain=brain, open_stories=open_stories, open_tasks=open_tasks, status_warning=status_warning, ) returnvalue.update(self.extra_dict(obj, brain)) return returnvalue
def main(self): """Get a dict with info from this Context. """ context = aq_inner(self.context) anno = IActualHours(context, None) if anno is not None: actual = anno.actual_time else: # Should not happen (tm). actual = -99.0 est = IEstimate(context, None) if est is not None: estimate = est.estimate else: # Should not happen (tm). estimate = -99.0 # Size estimate. We may want to do this smarter. filter = dict(portal_type='Story') items = context.getFolderContents(filter) size_estimate = sum([item.size_estimate for item in items if item.size_estimate is not None]) review_state = self.workflow.getInfoFor(context, 'review_state') if review_state in ['completed', 'invoiced', 'own-account']: budget_left = None else: budget_left = self.actual_budget_left() if budget_left is not None: budget_left = formatTime(budget_left) ploneview = context.restrictedTraverse('@@plone') if hasattr(context, 'getManHours'): manhours = context.getManHours() else: manhours = None returnvalue = dict( title=context.Title(), description=context.Description(), man_hours=manhours, start_date=ploneview.toLocalizedTime(context.getStartDate()), end_date=ploneview.toLocalizedTime(context.getEndDate()), estimate=formatTime(estimate), size_estimate=size_estimate, actual=formatTime(actual), difference=formatTime(estimate - actual), review_state=review_state, budget_left=budget_left, ) return returnvalue
def getTaskTotals(self, tasks): """Get my portion of total estimate, etc for these tasks """ rawEstimate = sum([task.estimate * self.portion(task) for task in tasks]) rawActualHours = sum([task.actual_time * self.portion(task) for task in tasks]) rawDifference = sum([(task.estimate - task.actual_time) * self.portion(task) for task in tasks]) totals = dict( estimate=formatTime(rawEstimate), actual=formatTime(rawActualHours), difference=formatTime(rawDifference), ) return totals
def bookingbrain2dict(self, brain): """Get a dict with info from this booking brain. """ realDate = brain.getBookingDate context = aq_inner(self.context) ploneview = context.restrictedTraverse('@@plone') date = ploneview.toLocalizedTime(realDate, self.friendlyDateFormat) today = datetime.date.today() pyDate = datetime.date(realDate.year(), realDate.month(), realDate.day()) if today == pyDate: date = 'Today (%s)' % date elif (pyDate - today).days == -1: date = 'Yesterday (%s)' % date # Webintelligenttext for the description desc = self.portal_transforms('web_intelligent_plain_text_to_html', brain.Description) returnvalue = dict( date=date, # base_view of a booking gets redirected to the task view, # which we do not want here. url=brain.getURL() + '/base_edit', title=brain.Title, description=desc, actual=formatTime(brain.actual_time), creator=brain.Creator, billable=brain.getBillable, ) return returnvalue
def main(self): """Get a dict with info from this Booking. """ context = aq_inner(self.context) anno = IActualHours(context, None) if anno is not None: actual = anno.actual_time else: # What the??? actual = -99.0 ploneview = context.restrictedTraverse('@@plone') # Webintelligenttext for the description desc = context.Description() pt = getToolByName(context, 'portal_transforms') desc = pt('web_intelligent_plain_text_to_html', desc) returnvalue = dict( title=context.title_or_id(), description=desc, actual=formatTime(actual), booking_date=ploneview.toLocalizedTime(context.getBookingDate()), billable=context.getBillable(), creator=context.Creator(), # base_view of a booking gets redirected to the task view, # which we do not want here. url=context.absolute_url() + '/base_edit', ) return returnvalue
def getTaskTotals(self, tasks): """Get my portion of total estimate, etc for these tasks """ rawEstimate = sum( [task.estimate * self.portion(task) for task in tasks]) rawActualHours = sum( [task.actual_time * self.portion(task) for task in tasks]) rawDifference = sum([ (task.estimate - task.actual_time) * self.portion(task) for task in tasks ]) totals = dict( estimate=formatTime(rawEstimate), actual=formatTime(rawActualHours), difference=formatTime(rawDifference), ) return totals
def total(self): if self._total is None: # projectlist hasn't been called yet, so do it to # update the total. self.projectlist() if self._total is None: # total could still be none if there are no iterations. return '' return formatTime(self._total)
def bookingbrain2extended_dict(self, bookingbrain): """Get a dict with extended info from this booking brain. booking = bookingbrain.getObject() project = booking.getProject() # This would wake up all objects between the Booking and the Project... # So try it via the catalog instead: """ # Get info about grand grand (grand) parent Project # Booking is in Task is in Story. # Story can be in Iteration or directly in Project. bookingpath = bookingbrain.getPath().split('/') path = '/'.join(bookingpath[:-3]) search_filter = dict(portal_type='Project', path=path) results = self.catalog(**search_filter) if len(results) == 0: # Presumably we found an Iteration, so try one level up. path = '/'.join(bookingpath[:-4]) search_filter = dict(portal_type='Project', path=path) results = self.catalog(**search_filter) try: project_title = results[0].Title except: # If you see this as project title, then you have probably # changed eXtremeManagement in such a way that Bookings # can be added in places that this code does not expect. project_title = 'Unknown project' # Get info about parent Task path = '/'.join(bookingpath[:-1]) search_filter = dict(portal_type=['Task', 'PoiTask'], path=path) taskbrain = self.catalog(**search_filter)[0] # Webintelligenttext for the description desc = bookingbrain.Description pt = getToolByName(self.context, 'portal_transforms') desc = pt('web_intelligent_plain_text_to_html', desc) returnvalue = dict( booking_date=self.toLocalizedTime(bookingbrain.getBookingDate), day_of_week=bookingbrain.getBookingDate.Day(), project_title=project_title, task_url=taskbrain.getURL(), task_title=taskbrain.Title, # base_view of a booking gets redirected to the task view, # which we do not want here. booking_url=bookingbrain.getURL() + '/base_edit', booking_title=bookingbrain.Title, booking_description=desc, booking_hours=formatTime(bookingbrain.actual_time), creator=bookingbrain.Creator, ) return returnvalue
def iterationbrain2dict(self, brain): """Get a dict with info from this iteration brain. """ review_state_id = brain.review_state estimate = brain.estimate actual = brain.actual_time returnvalue = dict( url=brain.getURL(), title=brain.Title, description=brain.Description, icon=brain.getIcon, man_hours=brain.getManHours, estimate=formatTime(estimate), actual=formatTime(actual), difference=formatTime(estimate - actual), review_state=review_state_id, review_state_title=self.workflow.getTitleForStateOnType( review_state_id, 'Iteration'), brain=brain, ) return returnvalue
def totals(self): """Get a dict with totals for this Story. """ context = aq_inner(self.context) anno = IActualHours(context, None) if anno is not None: actual = anno.actual_time else: # Should not happen (tm). actual = -99.0 est = IEstimate(context, None) if est is not None: estimate = est.estimate else: # Should not happen (tm). estimate = -99.0 totals = dict( estimate=formatTime(estimate), actual=formatTime(actual), difference=formatTime(estimate - actual), ) return totals
def main(self): """Get a dict with info from this Task. """ context = aq_inner(self.context) anno = IActualHours(context, None) if anno is not None: actual = anno.actual_time else: # Should not happen (tm). actual = -99.0 est = IEstimate(context, None) if est is not None: estimate = est.estimate else: # Should not happen (tm). estimate = -99.0 pas_member = queryMultiAdapter((context, self.request), name='pas_member') if pas_member is None: # Plone 3, Poi 1.2 nice_namer = context.poi_niceName else: # Plone 4 nice_namer = lambda x: pas_member.info(x).get('name_or_id') # Get info for previous and next links. We only want tasks # here, not for example images. story = aq_parent(context) tasks = story.getFolderContents() num_tasks = len(tasks) pos = story.getObjectPosition(context.id) next = None next_pos = pos + 1 while next_pos < num_tasks: if tasks[next_pos].portal_type in ('Task', 'PoiTask'): next = tasks[next_pos] break next_pos += 1 prev = None prev_pos = pos - 1 while prev_pos >= 0: if tasks[prev_pos].portal_type in ('Task', 'PoiTask'): prev = tasks[prev_pos] break prev_pos -= 1 returnvalue = dict( title=context.Title(), description=context.Description(), cooked_body=context.CookedBody(), estimate=formatTime(estimate), actual=formatTime(actual), difference=formatTime(estimate - actual), review_state=self.workflow.getInfoFor(context, 'review_state'), assignees=[{ 'niceName': nice_namer(x), 'username': x, 'active': True } for x in context.getAssignees()], prev=prev, next=next, ) return returnvalue
def total(self, date=None): return formatTime(self.raw_total(date))
def main(self): """Get a dict with info from this Task. """ context = aq_inner(self.context) anno = IActualHours(context, None) if anno is not None: actual = anno.actual_time else: # Should not happen (tm). actual = -99.0 est = IEstimate(context, None) if est is not None: estimate = est.estimate else: # Should not happen (tm). estimate = -99.0 pas_member = queryMultiAdapter((context, self.request), name='pas_member') if pas_member is None: # Plone 3, Poi 1.2 nice_namer = context.poi_niceName else: # Plone 4 nice_namer = lambda x: pas_member.info(x).get('name_or_id') # Get info for previous and next links. We only want tasks # here, not for example images. story = aq_parent(context) tasks = story.getFolderContents() num_tasks = len(tasks) pos = story.getObjectPosition(context.id) next = None next_pos = pos + 1 while next_pos < num_tasks: if tasks[next_pos].portal_type in ('Task', 'PoiTask'): next = tasks[next_pos] break next_pos += 1 prev = None prev_pos = pos - 1 while prev_pos >= 0: if tasks[prev_pos].portal_type in ('Task', 'PoiTask'): prev = tasks[prev_pos] break prev_pos -= 1 returnvalue = dict( title=context.Title(), description=context.Description(), cooked_body=context.CookedBody(), estimate=formatTime(estimate), actual=formatTime(actual), difference=formatTime(estimate - actual), review_state=self.workflow.getInfoFor(context, 'review_state'), assignees=[{'niceName': nice_namer(x), 'username': x, 'active': True} for x in context.getAssignees()], prev=prev, next=next, ) return returnvalue
def total(self): return formatTime(self.raw_total)
def update(self): context = aq_inner(self.context) ptool = self.tools().properties() hours_per_day = ptool.xm_properties.getProperty('hours_per_day') request = self.request weeklist = [] # Start at first day of the week. Note: with the # DateTime.week() method Monday is considered the first day, # even though DateTime.dow() says Sunday is day zero. To make # things worse, if say Sunday is 1 October, we want to start # with the week of Monday 25 September. # Go to the beginning of the week that has the first day of # this month. How many days do we have to subtract for that? offset = self.startDate.dow() - 1 if offset < 0: # Only happens for Sunday offset += 7 if offset == 0: date = self.startDate year, month = self.year, self.month else: year, month = getPrevYearMonth(self.year, self.month) last_day = getEndOfMonth(year, month).day() date = DateTime(year, month, last_day - offset + 1) daynumber = date.day() # Assemble info for at most one month: ploneview = context.restrictedTraverse('@@plone') month_billable = 0.0 month_worked_days = 0 # When comparing dates, make sure December of previous year is # less than January of this year. while date.month() + 12 * date.year() <= self.month + 12 * self.year: weekinfo = dict( week_number=date.week(), week_start=ploneview.toLocalizedTime(date), ) # Start the week cleanly day_of_week = 0 daylist = [] week_total = 0.0 week_strict_total = 0.0 days_bookings = DayBookingOverview(context, request, memberid=self.memberid) week_billable = 0.0 week_worked_days = 0 # Strict billable means: only count days of this week that # are really in this month. week_strict_billable = 0.0 week_strict_worked_days = 0 while day_of_week < 7: day_total = days_bookings.raw_total(date=date) day_billable = days_bookings.raw_billable(date=date) ui_class = 'greyed' if day_total > 0: # Update week stats week_total += day_total if day_total != 0: # Only add the billable hours to the week when # some work (billable or not) has been done # today. week_billable += day_billable week_worked_days += 1 if date.month() == self.startDate.month(): # Update strict stats week_strict_total += day_total week_strict_billable += day_billable week_strict_worked_days += 1 # Update month stats self.raw_total += day_total if day_total != 0: # Only add the billable hours to the month # when some work (billable or not) has # been done today. month_billable += day_billable month_worked_days += 1 ui_class = 'good' else: ui_class = 'greyed' daylist.append( dict(total=formatTime(day_total), day_of_week=date.Day(), style=ui_class)) else: daylist.append( dict(total=None, day_of_week=date.Day(), style=ui_class)) day_of_week += 1 daynumber += 1 try: # We used to simply do date + 1, but that gave # problems with Daylight Savings Time. date = DateTime(year, month, daynumber) except DateTime.DateError: # End of month reached, so go to the next. daynumber = 1 year, month = getNextYearMonth(year, month) try: date = DateTime(year, month, daynumber) except DateTime.DateError: # This Should Not Happen (tm) break # Add the info to the dict for this week weekinfo['days'] = daylist weekinfo['week_total'] = formatTime(week_total) weekinfo['week_strict_total'] = formatTime(week_strict_total) # Normal week stats if week_worked_days: norm = week_worked_days * hours_per_day week_perc_billable = 100.0 * week_billable / norm else: week_perc_billable = 0.0 fmt_perc_billable = "%0.1f %%" % week_perc_billable # Strict week stats if week_strict_worked_days: norm = week_strict_worked_days * hours_per_day week_strict_perc_billable = 100.0 * week_strict_billable / norm else: week_strict_perc_billable = 0.0 fmt_strict_perc_billable = "%0.1f %%" % week_strict_perc_billable weekinfo['total_style'] = weekinfo['perc_style'] = 'greyed' if date < DateTime(): weekinfo['total_style'] = weekinfo['perc_style'] = 'good' if week_total < 40.0: weekinfo['total_style'] = 'not-enough' if week_perc_billable < 50: weekinfo['perc_style'] = 'not-enough' weekinfo['perc_billable'] = fmt_perc_billable weekinfo['strict_perc_billable'] = fmt_strict_perc_billable self.bookinglist.append(weekinfo) if month_worked_days > 0: norm = month_worked_days * hours_per_day self.perc_billable = 100.0 * month_billable / norm
def update(self): context = aq_inner(self.context) ptool = self.tools().properties() hours_per_day = ptool.xm_properties.getProperty('hours_per_day') request = self.request weeklist = [] # Start at first day of the week. Note: with the # DateTime.week() method Monday is considered the first day, # even though DateTime.dow() says Sunday is day zero. To make # things worse, if say Sunday is 1 October, we want to start # with the week of Monday 25 September. # Go to the beginning of the week that has the first day of # this month. How many days do we have to subtract for that? offset = self.startDate.dow() - 1 if offset < 0: # Only happens for Sunday offset += 7 if offset == 0: date = self.startDate year, month = self.year, self.month else: year, month = getPrevYearMonth( self.year, self.month) last_day = getEndOfMonth(year, month).day() date = DateTime(year, month, last_day - offset + 1) daynumber = date.day() # Assemble info for at most one month: ploneview = context.restrictedTraverse('@@plone') month_billable = 0.0 month_worked_days = 0 # When comparing dates, make sure December of previous year is # less than January of this year. while date.month() + 12 * date.year() <= self.month + 12 * self.year: weekinfo = dict( week_number=date.week(), week_start=ploneview.toLocalizedTime(date), ) # Start the week cleanly day_of_week = 0 daylist = [] week_total = 0.0 week_strict_total = 0.0 days_bookings = DayBookingOverview( context, request, memberid=self.memberid) week_billable = 0.0 week_worked_days = 0 # Strict billable means: only count days of this week that # are really in this month. week_strict_billable = 0.0 week_strict_worked_days = 0 while day_of_week < 7: day_total = days_bookings.raw_total(date=date) day_billable = days_bookings.raw_billable(date=date) ui_class = 'greyed' if day_total > 0: # Update week stats week_total += day_total if day_total != 0: # Only add the billable hours to the week when # some work (billable or not) has been done # today. week_billable += day_billable week_worked_days += 1 if date.month() == self.startDate.month(): # Update strict stats week_strict_total += day_total week_strict_billable += day_billable week_strict_worked_days += 1 # Update month stats self.raw_total += day_total if day_total != 0: # Only add the billable hours to the month # when some work (billable or not) has # been done today. month_billable += day_billable month_worked_days += 1 ui_class = 'good' else: ui_class = 'greyed' daylist.append(dict(total=formatTime(day_total), day_of_week=date.Day(), style=ui_class)) else: daylist.append(dict(total=None, day_of_week=date.Day(), style=ui_class)) day_of_week += 1 daynumber += 1 try: # We used to simply do date + 1, but that gave # problems with Daylight Savings Time. date = DateTime(year, month, daynumber) except DateTime.DateError: # End of month reached, so go to the next. daynumber = 1 year, month = getNextYearMonth( year, month) try: date = DateTime(year, month, daynumber) except DateTime.DateError: # This Should Not Happen (tm) break # Add the info to the dict for this week weekinfo['days'] = daylist weekinfo['week_total'] = formatTime(week_total) weekinfo['week_strict_total'] = formatTime(week_strict_total) # Normal week stats if week_worked_days: norm = week_worked_days * hours_per_day week_perc_billable = 100.0 * week_billable / norm else: week_perc_billable = 0.0 fmt_perc_billable = "%0.1f %%" % week_perc_billable # Strict week stats if week_strict_worked_days: norm = week_strict_worked_days * hours_per_day week_strict_perc_billable = 100.0 * week_strict_billable / norm else: week_strict_perc_billable = 0.0 fmt_strict_perc_billable = "%0.1f %%" % week_strict_perc_billable weekinfo['total_style'] = weekinfo['perc_style'] = 'greyed' if date < DateTime(): weekinfo['total_style'] = weekinfo['perc_style'] = 'good' if week_total < 40.0: weekinfo['total_style'] = 'not-enough' if week_perc_billable < 50: weekinfo['perc_style'] = 'not-enough' weekinfo['perc_billable'] = fmt_perc_billable weekinfo['strict_perc_billable'] = fmt_strict_perc_billable self.bookinglist.append(weekinfo) if month_worked_days > 0: norm = month_worked_days * hours_per_day self.perc_billable = 100.0 * month_billable / norm