def get_context_data(self, **kwargs): context = super(ProjectTimesheet, self).get_context_data(**kwargs) project = self.object year_month_form = YearMonthForm(self.request.GET or None) if self.request.GET and year_month_form.is_valid(): from_date, to_date = year_month_form.save() else: date = utils.add_timezone(datetime.datetime.today()) from_date = utils.get_month_start(date).date() to_date = from_date + relativedelta(months=1) entries_qs = Entry.objects entries_qs = entries_qs.timespan(from_date, span='month').filter(project=project) extra_values = ('start_time', 'end_time', 'comments', 'seconds_paused', 'id', 'location__name', 'project__name', 'activity__name', 'status') month_entries = entries_qs.date_trunc('month', extra_values) total = entries_qs.aggregate(hours=Sum('hours'))['hours'] user_entries = entries_qs.order_by().values( 'user__first_name', 'user__last_name').annotate(sum=Sum('hours')).order_by('-sum') activity_entries = entries_qs.order_by().values( 'activity__name').annotate(sum=Sum('hours')).order_by('-sum') context.update({ 'project': project, 'year_month_form': year_month_form, 'from_date': from_date, 'to_date': to_date - relativedelta(days=1), 'entries': month_entries, 'total': total, 'user_entries': user_entries, 'activity_entries': activity_entries, }) return context
def invoice_projects(request): to_date = utils.get_month_start(datetime.datetime.today()).date() from_date = None defaults = { 'to_date': (to_date - relativedelta(days=1)).strftime('%m/%d/%Y'), } date_form = timepiece_forms.DateForm(request.GET or defaults) if request.GET and date_form.is_valid(): from_date, to_date = date_form.save() datesQ = Q() datesQ &= Q(end_time__gte=from_date) if from_date else Q() datesQ &= Q(end_time__lt=to_date) if to_date else Q() entries = timepiece.Entry.objects.filter(datesQ) project_totals = entries.filter( status='approved', project__type__billable=True, project__status__billable=True).values( 'project__type__pk', 'project__type__label', 'project__name', 'hours', 'project__pk', 'status', 'project__status__label').annotate(s=Sum('hours')).order_by( 'project__type__label', 'project__name', 'status') return render_to_response( 'timepiece/time-sheet/invoice/make_invoice.html', { 'date_form': date_form, 'project_totals': project_totals if to_date else [], 'to_date': to_date - relativedelta(days=1) if to_date else '', 'from_date': from_date, }, context_instance=RequestContext(request))
def clean(self): """ If we're not editing the active entry, ensure that this entry doesn't conflict with or come after the active entry. """ active = utils.get_active_entry(self.user) start_time = self.cleaned_data.get('start_time', None) end = self.cleaned_data.get('end', None) if (end != None and active != None): end_time = datetime.datetime.combine(active.start_time.date(), end) elif (end != None and active == None): end_time = datetime.datetime.combine(start_time.date(), end) else: end_time = None if active and active.pk != self.instance.pk: if (start_time and start_time > active.start_time) or \ (end_time and end_time > active.start_time): raise forms.ValidationError( 'The start time or end time conflict with the active ' 'entry: {project} starting at ' '{start_time}.'.format( project=active.project, start_time=active.start_time.strftime('%H:%M:%S'), )) month_start = utils.get_month_start(start_time) next_month = month_start + relativedelta(months=1) entries = self.instance.user.timepiece_entries.filter( start_time__gte=month_start, end_time__lt=next_month) entry = self.instance return self.cleaned_data
def invoice_projects(request): to_date = utils.get_month_start(datetime.datetime.today()).date() from_date = None defaults = { 'to_date': (to_date - relativedelta(days=1)).strftime('%m/%d/%Y'), } date_form = timepiece_forms.DateForm(request.GET or defaults) if request.GET and date_form.is_valid(): from_date, to_date = date_form.save() datesQ = Q() datesQ &= Q(end_time__gte=from_date) if from_date else Q() datesQ &= Q(end_time__lt=to_date) if to_date else Q() entries = timepiece.Entry.objects.filter(datesQ) project_totals = entries.filter(status='approved', project__type__billable=True, project__status__billable=True).values( 'project__type__pk', 'project__type__label', 'project__name', 'hours', 'project__pk', 'status', 'project__status__label' ).annotate(s=Sum('hours')).order_by('project__type__label', 'project__name', 'status') return render_to_response( 'timepiece/time-sheet/invoice/make_invoice.html', { 'date_form': date_form, 'project_totals': project_totals if to_date else [], 'to_date': to_date - relativedelta(days=1) if to_date else '', 'from_date': from_date, }, context_instance=RequestContext(request))
def get_context_data(self, **kwargs): context = super(ProjectTimesheet, self).get_context_data(**kwargs) project = self.object year_month_form = timepiece_forms.YearMonthForm(self.request.GET or None) if self.request.GET and year_month_form.is_valid(): from_date, to_date, user = year_month_form.save() else: from_date = utils.get_month_start(datetime.datetime.today()).date() to_date = from_date + relativedelta(months=1) entries_qs = timepiece.Entry.objects entries_qs = entries_qs.timespan(from_date, span='month').filter(project=project) month_entries = entries_qs.date_trunc('month', True).order_by('start_time') total = entries_qs.aggregate(hours=Sum('hours'))['hours'] user_entries = entries_qs.order_by().values( 'user__first_name', 'user__last_name').annotate(sum=Sum('hours')).order_by('-sum') activity_entries = entries_qs.order_by().values( 'activity__name').annotate(sum=Sum('hours')).order_by('-sum') return { 'project': project, 'year_month_form': year_month_form, 'from_date': from_date, 'to_date': to_date - datetime.timedelta(days=1), 'entries': month_entries, 'total': total, 'user_entries': user_entries, 'activity_entries': activity_entries, }
def hourly_report(request, date_form, from_date, to_date, status, activity): if not from_date: from_date = utils.get_month_start(datetime.datetime.today()).date() if not to_date: to_date = from_date + relativedelta(months=1) header_to = to_date - relativedelta(days=1) trunc = timepiece_forms.ProjectFiltersForm.DEFAULT_TRUNC query = Q(end_time__gt=utils.get_week_start(from_date), end_time__lt=to_date) if 'ok' in request.GET or 'export' in request.GET: form = timepiece_forms.ProjectFiltersForm(request.GET) if form.is_valid(): trunc = form.cleaned_data['trunc'] if not form.cleaned_data['paid_leave']: projects = getattr(settings, 'TIMEPIECE_PROJECTS', {}) query &= ~Q(project__in=projects.values()) if form.cleaned_data['pj_select']: query &= Q(project__in=form.cleaned_data['pj_select']) else: form = timepiece_forms.ProjectFiltersForm() hour_type = form.get_hour_type() entries = timepiece.Entry.objects.date_trunc(trunc).filter(query) date_headers = utils.generate_dates(from_date, header_to, by=trunc) project_totals = utils.project_totals(entries, date_headers, hour_type, total_column=True) if entries else '' if not request.GET.get('export', False): return { 'date_form': date_form, 'from_date': from_date, 'date_headers': date_headers, 'pj_filters': form, 'trunc': trunc, 'project_totals': project_totals, } else: from_date_str = from_date.strftime('%m-%d') to_date_str = to_date.strftime('%m-%d') response = HttpResponse(mimetype='text/csv') response['Content-Disposition'] = \ 'attachment; filename="%s_hours_%s_to_%s_by_%s.csv"' % ( hour_type, from_date_str, to_date_str, trunc) writer = csv.writer(response) headers = ['Name'] headers.extend([date.strftime('%m/%d/%Y') for date in date_headers]) headers.append('Total') writer.writerow(headers) for rows, totals in project_totals: for name, hours in rows: data = [name] data.extend(hours) writer.writerow(data) total = ['Totals'] total.extend(totals) writer.writerow(total) return response
def hourly_report(request, date_form, from_date, to_date, status, activity): if not from_date: from_date = utils.get_month_start(datetime.datetime.today()).date() if not to_date: to_date = from_date + relativedelta(months=1) header_to = to_date - relativedelta(days=1) trunc = timepiece_forms.ProjectFiltersForm.DEFAULT_TRUNC query = Q(end_time__gt=utils.get_week_start(from_date), end_time__lt=to_date) if 'ok' in request.GET or 'export' in request.GET: form = timepiece_forms.ProjectFiltersForm(request.GET) if form.is_valid(): trunc = form.cleaned_data['trunc'] if not form.cleaned_data['paid_leave']: projects = getattr(settings, 'TIMEPIECE_PROJECTS', {}) query &= ~Q(project__in=projects.values()) if form.cleaned_data['pj_select']: query &= Q(project__in=form.cleaned_data['pj_select']) else: form = timepiece_forms.ProjectFiltersForm() hour_type = form.get_hour_type() entries = timepiece.Entry.objects.date_trunc(trunc).filter(query) date_headers = utils.generate_dates(from_date, header_to, by=trunc) project_totals = utils.project_totals( entries, date_headers, hour_type, total_column=True) if entries else '' if not request.GET.get('export', False): return { 'date_form': date_form, 'from_date': from_date, 'date_headers': date_headers, 'pj_filters': form, 'trunc': trunc, 'project_totals': project_totals, } else: from_date_str = from_date.strftime('%m-%d') to_date_str = to_date.strftime('%m-%d') response = HttpResponse(mimetype='text/csv') response['Content-Disposition'] = \ 'attachment; filename="%s_hours_%s_to_%s_by_%s.csv"' % ( hour_type, from_date_str, to_date_str, trunc) writer = csv.writer(response) headers = ['Name'] headers.extend([date.strftime('%m/%d/%Y') for date in date_headers]) headers.append('Total') writer.writerow(headers) for rows, totals in project_totals: for name, hours in rows: data = [name] data.extend(hours) writer.writerow(data) total = ['Totals'] total.extend(totals) writer.writerow(total) return response
def test_month_start(self): """ Test that any day returns the first day of the month""" days = [datetime.date(2011, 1, 1), datetime.date(2011, 1, 16), datetime.date(2011, 1, 17), datetime.date(2011, 1, 22), datetime.date(2011, 1, 31), ] for day in days: self.assertEqual(utils.get_month_start(day), datetime.date(2011, 1, 1))
def __init__(self, *args, **kwargs): super(OutstandingHoursFilterForm, self).__init__(*args, **kwargs) # Check all statuses by default. statuses = Attribute.statuses.all() self.fields['statuses'].queryset = statuses self.fields['statuses'].initial = statuses month_start = utils.get_month_start().date() self.fields['to_date'].required = True self.fields['to_date'].initial = month_start - relativedelta(days=1) self.fields['from_date'].initial = None
def test_month_start(self): """ Test that any day returns the first day of the month""" days = [ datetime.date(2011, 1, 1), datetime.date(2011, 1, 16), datetime.date(2011, 1, 17), datetime.date(2011, 1, 22), datetime.date(2011, 1, 31), ] date = datetime.datetime(2011, 1, 1, tzinfo=timezone.get_current_timezone()) for day in days: self.assertEqual(utils.get_month_start(day), date)
def test_month_start(self): """ Test that any day returns the first day of the month""" days = [ datetime.date(2011, 1, 1), datetime.date(2011, 1, 16), datetime.date(2011, 1, 17), datetime.date(2011, 1, 22), datetime.date(2011, 1, 31), ] for day in days: self.assertEqual(utils.get_month_start(day), datetime.date(2011, 1, 1))
def report_payroll_summary(request): date = timezone.now() - relativedelta(months=1) from_date = utils.get_month_start(date).date() to_date = from_date + relativedelta(months=1) year_month_form = PayrollSummaryReportForm(request.GET or None, initial={ 'month': from_date.month, 'year': from_date.year }) if year_month_form.is_valid(): from_date, to_date = year_month_form.save() last_billable = utils.get_last_billable_day(from_date) projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') weekQ = Q(end_time__gt=utils.get_week_start(from_date), end_time__lt=last_billable + relativedelta(days=1)) monthQ = Q(end_time__gt=from_date, end_time__lt=to_date) workQ = ~Q(project__in=projects.values()) statusQ = Q(status=Entry.INVOICED) | Q(status=Entry.APPROVED) # Weekly totals week_entries = Entry.objects.date_trunc('week').filter( weekQ, statusQ, workQ) date_headers = generate_dates(from_date, last_billable, by='week') weekly_totals = list( get_project_totals(week_entries, date_headers, 'total', overtime=True)) # Monthly totals leave = Entry.objects.filter(monthQ, ~workQ).values('user', 'hours', 'project__name') extra_values = ('project__type__label', ) month_entries = Entry.objects.date_trunc('month', extra_values) month_entries_valid = month_entries.filter(monthQ, statusQ, workQ) labels, monthly_totals = get_payroll_totals(month_entries_valid, leave) # Unapproved and unverified hours entries = Entry.objects.filter(monthQ).order_by() # No ordering user_values = ['user__pk', 'user__first_name', 'user__last_name'] unverified = entries.filter(status=Entry.UNVERIFIED, user__is_active=True) \ .values_list(*user_values).distinct() unapproved = entries.filter(status=Entry.VERIFIED) \ .values_list(*user_values).distinct() return render( request, 'timepiece/reports/payroll_summary.html', { 'from_date': from_date, 'year_month_form': year_month_form, 'date_headers': date_headers, 'weekly_totals': weekly_totals, 'monthly_totals': monthly_totals, 'unverified': unverified, 'unapproved': unapproved, 'labels': labels, })
def report_payroll_summary(request): date = timezone.now() - relativedelta(months=1) from_date = utils.get_month_start(date).date() to_date = from_date + relativedelta(months=1) year_month_form = PayrollSummaryReportForm(request.GET or None, initial={ 'month': from_date.month, 'year': from_date.year, }) if year_month_form.is_valid(): from_date, to_date = year_month_form.save() last_billable = utils.get_last_billable_day(from_date) projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') weekQ = Q(end_time__gt=utils.get_week_start(from_date), end_time__lt=last_billable + relativedelta(days=1)) monthQ = Q(end_time__gt=from_date, end_time__lt=to_date) workQ = ~Q(project__in=projects.values()) statusQ = Q(status=Entry.INVOICED) | Q(status=Entry.APPROVED) # Weekly totals week_entries = Entry.objects.date_trunc('week').filter( weekQ, statusQ, workQ ) date_headers = generate_dates(from_date, last_billable, by='week') weekly_totals = list(get_project_totals(week_entries, date_headers, 'total', overtime=True)) # Monthly totals leave = Entry.objects.filter(monthQ, ~workQ) leave = leave.values('user', 'hours', 'project__name') extra_values = ('project__type__label',) month_entries = Entry.objects.date_trunc('month', extra_values) month_entries_valid = month_entries.filter(monthQ, statusQ, workQ) labels, monthly_totals = get_payroll_totals(month_entries_valid, leave) # Unapproved and unverified hours entries = Entry.objects.filter(monthQ).order_by() # No ordering user_values = ['user__pk', 'user__first_name', 'user__last_name'] unverified = entries.filter(status=Entry.UNVERIFIED, user__is_active=True) \ .values_list(*user_values).distinct() unapproved = entries.filter(status=Entry.VERIFIED) \ .values_list(*user_values).distinct() return render(request, 'timepiece/reports/payroll_summary.html', { 'from_date': from_date, 'year_month_form': year_month_form, 'date_headers': date_headers, 'weekly_totals': weekly_totals, 'monthly_totals': monthly_totals, 'unverified': unverified, 'unapproved': unapproved, 'labels': labels, })
def generate_dates(start=None, end=None, by='week'): if start: start = add_timezone(start) if end: end = add_timezone(end) if by == 'year': start = get_year_start(start) return rrule.rrule(rrule.YEARLY, dtstart=start, until=end) if by == 'month': start = get_month_start(start) return rrule.rrule(rrule.MONTHLY, dtstart=start, until=end) if by == 'week': start = get_week_start(start) return rrule.rrule(rrule.WEEKLY, dtstart=start, until=end, byweekday=0) if by == 'day': return rrule.rrule(rrule.DAILY, dtstart=start, until=end)
def payroll_summary(request): year_month_form = timepiece_forms.YearMonthForm(request.GET or None) if request.GET and year_month_form.is_valid(): from_date, to_date, user = year_month_form.save() else: from_date = utils.get_month_start(datetime.datetime.today()).date() to_date = from_date + relativedelta(months=1) last_billable = utils.get_last_billable_day(from_date) projects = getattr(settings, 'TIMEPIECE_PROJECTS', {}) weekQ = Q(end_time__gt=utils.get_week_start(from_date), end_time__lt=last_billable + datetime.timedelta(days=1)) monthQ = Q(end_time__gt=from_date, end_time__lt=to_date) workQ = ~Q(project__in=projects.values()) statusQ = Q(status='invoiced') | Q(status='approved') # Weekly totals week_entries = timepiece.Entry.objects.date_trunc('week') week_entries = week_entries.filter(weekQ, statusQ, workQ) date_headers = utils.generate_dates(from_date, last_billable, by='week') weekly_totals = list( utils.project_totals(week_entries, date_headers, 'total', overtime=True)) # Monthly totals leave = timepiece.Entry.objects.filter(monthQ, ~workQ).values( 'user', 'hours', 'project__name') month_entries = timepiece.Entry.objects.date_trunc('month') month_entries_valid = month_entries.filter(monthQ, statusQ, workQ) monthly_totals = list( utils.payroll_totals(month_entries_valid, from_date, leave)) # Unapproved and unverified hours entries = timepiece.Entry.objects.filter(monthQ) user_values = ['user__pk', 'user__first_name', 'user__last_name'] unverified = entries.filter(monthQ, status='unverified', user__is_active=True) unapproved = entries.filter(monthQ, status='verified') return { 'from_date': from_date, 'year_month_form': year_month_form, 'date_headers': date_headers, 'weekly_totals': weekly_totals, 'monthly_totals': monthly_totals, 'unverified': unverified.values_list(*user_values).distinct(), 'unapproved': unapproved.values_list(*user_values).distinct(), }
def get_context_data(self, **kwargs): context = super(ProjectTimesheet, self).get_context_data(**kwargs) project = self.object year_month_form = YearMonthForm(self.request.GET or None) if self.request.GET and year_month_form.is_valid(): from_date, to_date = year_month_form.save() else: date = utils.add_timezone(datetime.datetime.today()) from_date = utils.get_month_start(date).date() to_date = from_date + relativedelta(months=1) entries_qs = Entry.objects entries_qs = entries_qs.timespan(from_date, span='month').filter( project=project ) extra_values = ('start_time', 'end_time', 'comments', 'seconds_paused', 'id', 'location__name', 'project__name', 'activity__name', 'status') month_entries = entries_qs.date_trunc('month', extra_values).order_by('start_time') if month_entries: format_totals(month_entries, "hours") total = entries_qs.aggregate(hours=Sum('hours'))['hours'] if total: total = "{0:.2f}".format(total) user_entries = entries_qs.order_by().values('user__first_name', 'user__last_name') user_entries = user_entries.annotate(sum=Sum('hours')).order_by('-sum') if user_entries: format_totals(user_entries) activity_entries = entries_qs.order_by().values('activity__name') activity_entries = activity_entries.annotate(sum=Sum('hours')).order_by('-sum') if activity_entries: format_totals(activity_entries) context.update({ 'project': project, 'year_month_form': year_month_form, 'from_date': from_date, 'to_date': to_date - relativedelta(days=1), 'entries': month_entries, 'total': total, 'user_entries': user_entries, 'activity_entries': activity_entries, }) return context
def payroll_summary(request): year_month_form = timepiece_forms.YearMonthForm(request.GET or None) if request.GET and year_month_form.is_valid(): from_date, to_date = year_month_form.save() else: from_date = utils.get_month_start(datetime.datetime.today()).date() to_date = from_date + relativedelta(months=1) last_billable = utils.get_last_billable_day(from_date) projects = getattr(settings, 'TIMEPIECE_PROJECTS', {}) weekQ = Q(end_time__gt=utils.get_week_start(from_date), end_time__lt=last_billable + datetime.timedelta(days=1)) monthQ = Q(end_time__gt=from_date, end_time__lt=to_date) workQ = ~Q(project__in=projects.values()) statusQ = Q(status='invoiced') | Q(status='approved') # Weekly totals week_entries = timepiece.Entry.objects.date_trunc('week') week_entries = week_entries.filter(weekQ, statusQ, workQ) date_headers = utils.generate_dates(from_date, last_billable, by='week') weekly_totals = list(utils.project_totals(week_entries, date_headers, 'total', overtime=True)) # Monthly totals leave = timepiece.Entry.objects.filter(monthQ, ~workQ ).values('user', 'hours', 'project__name') month_entries = timepiece.Entry.objects.date_trunc('month') month_entries_valid = month_entries.filter(monthQ, statusQ, workQ) monthly_totals = list(utils.payroll_totals(month_entries_valid, from_date, leave)) # Unapproved and unverified hours entries = timepiece.Entry.objects.filter(monthQ) user_values = ['user__pk', 'user__first_name', 'user__last_name'] unverified = entries.filter(monthQ, status='unverified', user__is_active=True) unapproved = entries.filter(monthQ, status='verified') return { 'from_date': from_date, 'year_month_form': year_month_form, 'date_headers': date_headers, 'weekly_totals': weekly_totals, 'monthly_totals': monthly_totals, 'unverified': unverified.values_list(*user_values).distinct(), 'unapproved': unapproved.values_list(*user_values).distinct(), }
def clean(self): """ If we're not editing the active entry, ensure that this entry doesn't conflict with or come after the active entry. """ active = utils.get_active_entry(self.user) start_time = self.cleaned_data.get('start_time', None) end_time = self.cleaned_data.get('end_time', None) if active and active.pk != self.instance.pk: if (start_time and start_time > active.start_time) or \ (end_time and end_time > active.start_time): raise forms.ValidationError( 'The start time or end time conflict with the active ' 'entry: {activity} on {project} starting at ' '{start_time}.'.format( project=active.project, activity=active.activity, start_time=active.start_time.strftime('%H:%M:%S'), )) month_start = utils.get_month_start(start_time) next_month = month_start + relativedelta(months=1) entries = self.instance.user.timepiece_entries.filter( Q(status=Entry.APPROVED) | Q(status=Entry.INVOICED), start_time__gte=month_start, end_time__lt=next_month) entry = self.instance if not self.acting_user.is_superuser: if (entries.exists() and not entry.id or entry.id and entry.status == Entry.INVOICED): message = 'You cannot add/edit entries after a timesheet has been ' \ 'approved or invoiced. Please correct the start and end times.' raise forms.ValidationError(message) return self.cleaned_data
def clean(self): """ If we're not editing the active entry, ensure that this entry doesn't conflict with or come after the active entry. """ active = utils.get_active_entry(self.user) start_time = self.cleaned_data.get('start_time', None) end_time = self.cleaned_data.get('end_time', None) if active and active.pk != self.instance.pk: if (start_time and start_time > active.start_time) or \ (end_time and end_time > active.start_time): raise forms.ValidationError( 'The start time or end time conflict with the active ' 'entry: {activity} on {project} starting at ' '{start_time}.'.format( project=active.project, activity=active.activity, start_time=active.start_time.strftime('%H:%M:%S'), )) month_start = utils.get_month_start(start_time) next_month = month_start + relativedelta(months=1) entries = self.instance.user.timepiece_entries.filter( Q(status=Entry.APPROVED) | Q(status=Entry.INVOICED), start_time__gte=month_start, end_time__lt=next_month ) entry = self.instance if not self.acting_user.is_superuser: if (entries.exists() and not entry.id or entry.id and entry.status == Entry.INVOICED): message = 'You cannot add/edit entries after a timesheet has been ' \ 'approved or invoiced. Please correct the start and end times.' raise forms.ValidationError(message) return self.cleaned_data
def get_context_data(self, **kwargs): context = super(ProjectTimesheet, self).get_context_data(**kwargs) project = self.object year_month_form = timepiece_forms.YearMonthForm(self.request.GET or None) if self.request.GET and year_month_form.is_valid(): from_date, to_date = year_month_form.save() else: from_date = utils.get_month_start(datetime.datetime.today()).date() to_date = from_date + relativedelta(months=1) entries_qs = timepiece.Entry.objects entries_qs = entries_qs.timespan(from_date, span='month').filter( project=project ) month_entries = entries_qs.date_trunc('month', True).order_by( 'start_time' ) total = entries_qs.aggregate(hours=Sum('hours'))['hours'] user_entries = entries_qs.order_by().values( 'user__first_name', 'user__last_name').annotate( sum=Sum('hours')).order_by('-sum' ) activity_entries = entries_qs.order_by().values( 'activity__name').annotate( sum=Sum('hours')).order_by('-sum' ) return { 'project': project, 'year_month_form': year_month_form, 'from_date': from_date, 'to_date': to_date - datetime.timedelta(days=1), 'entries': month_entries, 'total': total, 'user_entries': user_entries, 'activity_entries': activity_entries, }
def clean(self): if not self.user_id: raise ValidationError('An unexpected error has occured') if not self.start_time: raise ValidationError('Please enter a valid start time') start = self.start_time if self.end_time: end = self.end_time #Current entries have no end_time else: end = start + datetime.timedelta(seconds=1) entries = self.user.timepiece_entries.filter( Q(end_time__range=(start, end)) | \ Q(start_time__range=(start, end)) | \ Q(start_time__lte=start, end_time__gte=end)) #An entry can not conflict with itself so remove it from the list if self.id: entries = entries.exclude(pk=self.id) for entry in entries: entry_data = { 'project': entry.project, 'activity': entry.activity, 'start_time': entry.start_time, 'end_time': entry.end_time } #Conflicting saved entries if entry.end_time: if entry.start_time.date() == start.date() \ and entry.end_time.date() == end.date(): entry_data['start_time'] = entry.start_time.strftime( '%H:%M:%S') entry_data['end_time'] = entry.end_time.strftime( '%H:%M:%S') output = 'Start time overlaps with: ' + \ '%(project)s - %(activity)s - ' % entry_data + \ 'from %(start_time)s to %(end_time)s' % entry_data raise ValidationError(output) else: entry_data['start_time'] = entry.start_time.strftime( '%H:%M:%S on %m\%d\%Y') entry_data['end_time'] = entry.end_time.strftime( '%H:%M:%S on %m\%d\%Y') output = 'Start time overlaps with: ' + \ '%(project)s - %(activity)s - ' % entry_data + \ 'from %(start_time)s to %(end_time)s' % entry_data raise ValidationError(output) try: act_group = self.project.activity_group if act_group: activity = self.activity if not act_group.activities.filter(pk=activity.pk).exists(): name = activity.name err_msg = '%s is not allowed for this project. ' % name allowed = act_group.activities.filter() allowed = allowed.values_list('name', flat=True) allowed_names = ['among '] if len(allowed) > 1: for index, activity in enumerate(allowed): allowed_names += activity if index < len(allowed) - 2: allowed_names += ', ' elif index < len(allowed) - 1: allowed_names += ', and ' allowed_activities = ''.join(allowed_names) else: allowed_activities = allowed[0] err_msg += 'Please choose %s' % allowed_activities raise ValidationError(err_msg) except (Project.DoesNotExist, Activity.DoesNotExist): # Will be caught by field requirements pass if end <= start: raise ValidationError('Ending time must exceed the starting time') delta = (end - start) delta_secs = (delta.seconds + delta.days * 24 * 60 * 60) limit_secs = 60 * 60 * 12 if delta_secs > limit_secs or self.seconds_paused > limit_secs: err_msg = 'Ending time exceeds starting time by 12 hours or more '\ 'for {0} on {1} at {2} to {3} at {4}.'.format( self.project, start.strftime('%m/%d/%Y'), start.strftime('%H:%M:%S'), end.strftime('%m/%d/%Y'), end.strftime('%H:%M:%S') ) raise ValidationError(err_msg) month_start = utils.get_month_start(start) next_month = month_start + relativedelta(months=1) entries = self.user.timepiece_entries.filter( Q(status='approved') | Q(status='invoiced'), start_time__gte=month_start, end_time__lt=next_month) if (entries.exists() and not self.id or self.id and self.status == 'invoiced'): msg = 'You cannot add/edit entries after a timesheet has been ' \ 'approved or invoiced. Please correct the start and end times.' raise ValidationError(msg) return True
def view_user_timesheet(request, user_id, active_tab): # User can only view their own time sheet unless they have a permission. user = get_object_or_404(User, pk=user_id) has_perm = request.user.has_perm('entries.view_entry_summary') if not (has_perm or user.pk == request.user.pk): return HttpResponseForbidden('Forbidden') FormClass = UserYearMonthForm if has_perm else YearMonthForm form = FormClass(request.GET or None) if form.is_valid(): if has_perm: from_date, to_date, form_user = form.save() if form_user and request.GET.get('yearmonth', None): # Redirect to form_user's time sheet. # Do not use request.GET in urlencode to prevent redirect # loop caused by yearmonth parameter. url = reverse('view_user_timesheet', args=(form_user.pk,)) request_data = { 'month': from_date.month, 'year': from_date.year, 'user': form_user.pk, # Keep so that user appears in form. } url += '?{0}'.format(urllib.urlencode(request_data)) return HttpResponseRedirect(url) else: # User must be viewing their own time sheet; no redirect needed. from_date, to_date = form.save() from_date = utils.add_timezone(from_date) to_date = utils.add_timezone(to_date) else: # Default to showing this month. from_date = utils.get_month_start() to_date = from_date + relativedelta(months=1) entries_qs = Entry.objects.filter(user=user) month_qs = entries_qs.timespan(from_date, span='month') extra_values = ('start_time', 'end_time', 'comments', 'seconds_paused', 'id', 'location__name', 'project__name', 'activity__name', 'status') month_entries = month_qs.date_trunc('month', extra_values) # For grouped entries, back date up to the start of the week. first_week = utils.get_week_start(from_date) month_week = first_week + relativedelta(weeks=1) grouped_qs = entries_qs.timespan(first_week, to_date=to_date) intersection = grouped_qs.filter(start_time__lt=month_week, start_time__gte=from_date) # If the month of the first week starts in the previous # month and we dont have entries in that previous ISO # week, then update the first week to start at the first # of the actual month if not intersection and first_week.month < from_date.month: grouped_qs = entries_qs.timespan(from_date, to_date=to_date) totals =grouped_totals(grouped_qs) if month_entries else '' project_entries = month_qs.order_by().values( 'project__name').annotate(sum=Sum('hours')).order_by('-sum') summary = Entry.summary(user, from_date, to_date) show_approve = show_verify = False can_change = request.user.has_perm('entries.change_entry') can_approve = request.user.has_perm('entries.approve_timesheet') if can_change or can_approve or user == request.user: statuses = list(month_qs.values_list('status', flat=True)) total_statuses = len(statuses) unverified_count = statuses.count(Entry.UNVERIFIED) verified_count = statuses.count(Entry.VERIFIED) approved_count = statuses.count(Entry.APPROVED) if can_change or user == request.user: show_verify = unverified_count != 0 if can_approve: show_approve = verified_count + approved_count == total_statuses \ and verified_count > 0 and total_statuses != 0 return render(request, 'timepiece/user/timesheet/view.html', { 'active_tab': active_tab or 'overview', 'year_month_form': form, 'from_date': from_date, 'to_date': to_date - relativedelta(days=1), 'show_verify': show_verify, 'show_approve': show_approve, 'timesheet_user': user, 'entries': month_entries, 'grouped_totals': totals, 'project_entries': project_entries, 'summary': summary, })
def view_person_time_sheet(request, user_id): user = get_object_or_404(User, pk=user_id) if not (request.user.has_perm('timepiece.view_entry_summary') or \ user.pk == request.user.pk): return HttpResponseForbidden('Forbidden') from_date = utils.get_month_start(datetime.datetime.today()).date() to_date = from_date + relativedelta(months=1) initial = { 'request_user': request.user, 'user': request.GET.get('user', user_id) } year_month_form = timepiece_forms.YearMonthForm(request.GET or None, initial=initial) if year_month_form.is_valid(): from_date, to_date, form_user = year_month_form.save() if form_user: url = reverse('view_person_time_sheet', args=(form_user.pk, )) # Do not use request.GET in urlencode in case it has the # user parameter (redirect loop otherwise) request_data = { 'month': request.GET.get('month', from_date.month), 'year': request.GET.get('year', from_date.year) } url += '?{0}'.format(urllib.urlencode(request_data)) return HttpResponseRedirect(url) entries_qs = timepiece.Entry.objects.filter(user=user) month_qs = entries_qs.timespan(from_date, span='month') month_entries = month_qs.date_trunc('month', True) # For grouped entries, back date up to the start of the week. first_week = utils.get_week_start(from_date) month_week = first_week + datetime.timedelta(weeks=1) grouped_qs = entries_qs.timespan(first_week, to_date=to_date) intersection = grouped_qs.filter(start_time__lt=month_week, start_time__gte=from_date) # If the month of the first week starts in the previous # month and we dont have entries in that previous ISO # week, then update the first week to start at the first # of the actual month if not intersection and first_week.month < from_date.month: grouped_qs = entries_qs.timespan(from_date, to_date=to_date) grouped_totals = utils.grouped_totals(grouped_qs) if month_entries else '' project_entries = month_qs.order_by().values('project__name').annotate( sum=Sum('hours')).order_by('-sum') summary = timepiece.Entry.summary(user, from_date, to_date) show_approve = show_verify = False if request.user.has_perm('timepiece.change_entry') or \ user == request.user: statuses = list(entries_qs.values_list('status', flat=True)) total_statuses = len(statuses) unverified_count = statuses.count('unverified') verified_count = statuses.count('verified') approved_count = statuses.count('approved') show_verify = unverified_count != 0 if request.user.has_perm('timepiece.change_entry'): show_approve = verified_count + approved_count == total_statuses \ and verified_count > 0 and total_statuses != 0 context = { 'year_month_form': year_month_form, 'from_date': from_date, 'to_date': to_date - datetime.timedelta(days=1), 'show_verify': show_verify, 'show_approve': show_approve, 'timesheet_user': user, 'entries': month_entries, 'grouped_totals': grouped_totals, 'project_entries': project_entries, 'summary': summary, } return render_to_response('timepiece/time-sheet/people/view.html', context, context_instance=RequestContext(request))
def get_previous_month(self): """Returns date range for the previous full month.""" end = utils.get_month_start() - relativedelta(days=1) end = utils.to_datetime(end) start = utils.get_month_start(end) return start, end
def clean(self): if not self.user_id: raise ValidationError("An unexpected error has occured") if not self.start_time: raise ValidationError("Please enter a valid start time") start = self.start_time if self.end_time: end = self.end_time # Current entries have no end_time else: end = start + relativedelta(seconds=1) entries = self.user.timepiece_entries.filter( Q(end_time__range=(start, end)) | Q(start_time__range=(start, end)) | Q(start_time__lte=start, end_time__gte=end) ) # An entry can not conflict with itself so remove it from the list if self.id: entries = entries.exclude(pk=self.id) for entry in entries: entry_data = { "project": entry.project, "activity": entry.activity, "start_time": entry.start_time, "end_time": entry.end_time, } # Conflicting saved entries if entry.end_time: if entry.start_time.date() == start.date() and entry.end_time.date() == end.date(): entry_data["start_time"] = entry.start_time.strftime("%H:%M:%S") entry_data["end_time"] = entry.end_time.strftime("%H:%M:%S") raise ValidationError( "Start time overlaps with " "{activity} on {project} from {start_time} to " "{end_time}.".format(**entry_data) ) else: entry_data["start_time"] = entry.start_time.strftime("%H:%M:%S on %m\%d\%Y") entry_data["end_time"] = entry.end_time.strftime("%H:%M:%S on %m\%d\%Y") raise ValidationError( "Start time overlaps with " "{activity} on {project} from {start_time} to " "{end_time}.".format(**entry_data) ) try: act_group = self.project.activity_group if act_group: activity = self.activity if not act_group.activities.filter(pk=activity.pk).exists(): name = activity.name err_msg = "%s is not allowed for this project. " % name allowed = act_group.activities.filter() allowed = allowed.values_list("name", flat=True) allowed_names = ["among "] if len(allowed) > 1: for index, activity in enumerate(allowed): allowed_names += activity if index < len(allowed) - 2: allowed_names += ", " elif index < len(allowed) - 1: allowed_names += ", and " allowed_activities = "".join(allowed_names) else: allowed_activities = allowed[0] err_msg += "Please choose %s" % allowed_activities raise ValidationError(err_msg) except (Project.DoesNotExist, Activity.DoesNotExist): # Will be caught by field requirements pass if end <= start: raise ValidationError("Ending time must exceed the starting time") delta = end - start delta_secs = delta.seconds + delta.days * 24 * 60 * 60 limit_secs = 60 * 60 * 12 if delta_secs > limit_secs or self.seconds_paused > limit_secs: err_msg = ( "Ending time exceeds starting time by 12 hours or more " "for {0} on {1} at {2} to {3} at {4}.".format( self.project, start.strftime("%m/%d/%Y"), start.strftime("%H:%M:%S"), end.strftime("%m/%d/%Y"), end.strftime("%H:%M:%S"), ) ) raise ValidationError(err_msg) month_start = utils.get_month_start(start) next_month = month_start + relativedelta(months=1) entries = self.user.timepiece_entries.filter( Q(status=Entry.APPROVED) | Q(status=Entry.INVOICED), start_time__gte=month_start, end_time__lt=next_month ) if entries.exists() and not self.id or self.id and self.status == Entry.INVOICED: msg = ( "You cannot add/edit entries after a timesheet has been " "approved or invoiced. Please correct the start and end times." ) raise ValidationError(msg) return True
def show_cal(from_date, offset=0): date = get_month_start(from_date) date = date + relativedelta(months=offset) html_cal = calendar.HTMLCalendar(calendar.SUNDAY) return html_cal.formatmonth(date.year, date.month)
def view_person_time_sheet(request, user_id): user = get_object_or_404(User, pk=user_id) if not (request.user.has_perm('timepiece.view_entry_summary') or \ user.pk == request.user.pk): return HttpResponseForbidden('Forbidden') today_reset = datetime.datetime.today() today_reset = today_reset.replace(hour=0, minute=0, second=0, \ microsecond=0) from_date = utils.get_month_start(today_reset) to_date = from_date + relativedelta(months=1) can_view_summary = request.user and \ request.user.has_perm('timepiece.view_entry_summary') form = timepiece_forms.UserYearMonthForm if can_view_summary else \ timepiece_forms.YearMonthForm year_month_form = form(request.GET or None) if year_month_form.is_valid(): if can_view_summary: from_date, to_date, form_user = year_month_form.save() is_update = request.GET.get('yearmonth', None) if form_user and is_update: url = reverse('view_person_time_sheet', args=(form_user.pk,)) # Do not use request.GET in urlencode in case it has the # yearmonth parameter (redirect loop otherwise) request_data = { 'month': from_date.month, 'year': from_date.year, 'user': form_user.pk } url += '?{0}'.format(urllib.urlencode(request_data)) return HttpResponseRedirect(url) else: from_date, to_date = year_month_form.save() entries_qs = timepiece.Entry.objects.filter(user=user) month_qs = entries_qs.timespan(from_date, span='month') month_entries = month_qs.date_trunc('month', True) # For grouped entries, back date up to the start of the week. first_week = utils.get_week_start(from_date) month_week = first_week + datetime.timedelta(weeks=1) grouped_qs = entries_qs.timespan(first_week, to_date=to_date) intersection = grouped_qs.filter(start_time__lt=month_week, start_time__gte=from_date) # If the month of the first week starts in the previous # month and we dont have entries in that previous ISO # week, then update the first week to start at the first # of the actual month if not intersection and first_week.month < from_date.month: grouped_qs = entries_qs.timespan(from_date, to_date=to_date) grouped_totals = utils.grouped_totals(grouped_qs) if month_entries else '' project_entries = month_qs.order_by().values( 'project__name').annotate(sum=Sum('hours')).order_by('-sum') summary = timepiece.Entry.summary(user, from_date, to_date) show_approve = show_verify = False if request.user.has_perm('timepiece.change_entry') or \ user == request.user: statuses = list(month_qs.values_list('status', flat=True)) total_statuses = len(statuses) unverified_count = statuses.count('unverified') verified_count = statuses.count('verified') approved_count = statuses.count('approved') show_verify = unverified_count != 0 if request.user.has_perm('timepiece.change_entry'): show_approve = verified_count + approved_count == total_statuses \ and verified_count > 0 and total_statuses != 0 context = { 'year_month_form': year_month_form, 'from_date': from_date, 'to_date': to_date - datetime.timedelta(days=1), 'show_verify': show_verify, 'show_approve': show_approve, 'timesheet_user': user, 'entries': month_entries, 'grouped_totals': grouped_totals, 'project_entries': project_entries, 'summary': summary, } return render_to_response('timepiece/time-sheet/people/view.html', context, context_instance=RequestContext(request))
def clean(self): if not self.user_id: raise ValidationError('An unexpected error has occured') if not self.start_time: raise ValidationError('Please enter a valid start time') start = self.start_time if self.end_time: end = self.end_time #Current entries have no end_time else: end = start + datetime.timedelta(seconds=1) entries = self.user.timepiece_entries.filter( Q(end_time__range=(start, end)) | \ Q(start_time__range=(start, end)) | \ Q(start_time__lte=start, end_time__gte=end)) #An entry can not conflict with itself so remove it from the list if self.id: entries = entries.exclude(pk=self.id) for entry in entries: entry_data = { 'project': entry.project, 'activity': entry.activity, 'start_time': entry.start_time, 'end_time': entry.end_time } #Conflicting saved entries if entry.end_time: if entry.start_time.date() == start.date() \ and entry.end_time.date() == end.date(): entry_data['start_time'] = entry.start_time.strftime( '%H:%M:%S') entry_data['end_time'] = entry.end_time.strftime( '%H:%M:%S') output = 'Start time overlaps with: ' + \ '%(project)s - %(activity)s - ' % entry_data + \ 'from %(start_time)s to %(end_time)s' % entry_data raise ValidationError(output) else: entry_data['start_time'] = entry.start_time.strftime( '%H:%M:%S on %m\%d\%Y') entry_data['end_time'] = entry.end_time.strftime( '%H:%M:%S on %m\%d\%Y') output = 'Start time overlaps with: ' + \ '%(project)s - %(activity)s - ' % entry_data + \ 'from %(start_time)s to %(end_time)s' % entry_data raise ValidationError(output) try: act_group = self.project.activity_group if act_group: activity = self.activity if not act_group.activities.filter(pk=activity.pk).exists(): name = activity.name err_msg = '%s is not allowed for this project. ' % name allowed = act_group.activities.filter() allowed = allowed.values_list('name', flat=True) allowed_names = ['among '] if len(allowed) > 1: for index, activity in enumerate(allowed): allowed_names += activity if index < len(allowed) - 2: allowed_names += ', ' elif index < len(allowed) - 1: allowed_names += ', and ' allowed_activities = ''.join(allowed_names) else: allowed_activities = allowed[0] err_msg += 'Please choose %s' % allowed_activities raise ValidationError(err_msg) except (Project.DoesNotExist, Activity.DoesNotExist): # Will be caught by field requirements pass if end <= start: raise ValidationError('Ending time must exceed the starting time') delta = (end - start) delta_secs = (delta.seconds + delta.days * 24 * 60 * 60) limit_secs = 60 * 60 * 12 if delta_secs > limit_secs or self.seconds_paused > limit_secs: err_msg = 'Ending time exceeds starting time by 12 hours or more '\ 'for {0} on {1} at {2} to {3} at {4}.'.format( self.project.name, start.strftime('%m/%d/%Y'), start.strftime('%H:%M:%S'), end.strftime('%m/%d/%Y'), end.strftime('%H:%M:%S') ) raise ValidationError(err_msg) month_start = utils.get_month_start(start) next_month = month_start + relativedelta(months=1) entries = self.user.timepiece_entries.filter( Q(status='approved') | Q(status='invoiced'), start_time__gte=month_start, end_time__lt=next_month ) if (entries.exists() and not self.id or self.id and self.status == 'invoiced'): msg = 'You cannot add/edit entries after a timesheet has been ' \ 'approved or invoiced. Please correct the start and end times.' raise ValidationError(msg) return True
def view_user_timesheet(request, user_id, active_tab): # User can only view their own time sheet unless they have a permission. user = get_object_or_404(User, pk=user_id) has_perm = request.user.has_perm('entries.view_entry_summary') if not (has_perm or user.pk == request.user.pk): return HttpResponseForbidden('Forbidden') FormClass = UserYearMonthForm if has_perm else YearMonthForm form = FormClass(request.GET or None) if form.is_valid(): if has_perm: from_date, to_date, form_user = form.save() if form_user and request.GET.get('yearmonth', None): # Redirect to form_user's time sheet. # Do not use request.GET in urlencode to prevent redirect # loop caused by yearmonth parameter. url = reverse('view_user_timesheet', args=(form_user.pk, )) request_data = { 'month': from_date.month, 'year': from_date.year, 'user': form_user.pk, # Keep so that user appears in form. } url += '?{0}'.format(urllib.urlencode(request_data)) return HttpResponseRedirect(url) else: # User must be viewing their own time sheet; no redirect needed. from_date, to_date = form.save() from_date = utils.add_timezone(from_date) to_date = utils.add_timezone(to_date) else: # Default to showing this month. from_date = utils.get_month_start() to_date = from_date + relativedelta(months=1) entries_qs = Entry.objects.filter(user=user) month_qs = entries_qs.timespan(from_date, span='month') extra_values = ('start_time', 'end_time', 'comments', 'seconds_paused', 'id', 'location__name', 'project__name', 'activity__name', 'status') month_entries = month_qs.date_trunc('month', extra_values) # For grouped entries, back date up to the start of the week. first_week = utils.get_week_start(from_date) month_week = first_week + relativedelta(weeks=1) grouped_qs = entries_qs.timespan(first_week, to_date=to_date) intersection = grouped_qs.filter(start_time__lt=month_week, start_time__gte=from_date) # If the month of the first week starts in the previous # month and we dont have entries in that previous ISO # week, then update the first week to start at the first # of the actual month if not intersection and first_week.month < from_date.month: grouped_qs = entries_qs.timespan(from_date, to_date=to_date) totals = grouped_totals(grouped_qs) if month_entries else '' project_entries = month_qs.order_by().values('project__name').annotate( sum=Sum('hours')).order_by('-sum') summary = Entry.summary(user, from_date, to_date) show_approve = show_verify = False can_change = request.user.has_perm('entries.change_entry') can_approve = request.user.has_perm('entries.approve_timesheet') if can_change or can_approve or user == request.user: statuses = list(month_qs.values_list('status', flat=True)) total_statuses = len(statuses) unverified_count = statuses.count(Entry.UNVERIFIED) verified_count = statuses.count(Entry.VERIFIED) approved_count = statuses.count(Entry.APPROVED) if can_change or user == request.user: show_verify = unverified_count != 0 if can_approve: show_approve = verified_count + approved_count == total_statuses \ and verified_count > 0 and total_statuses != 0 return render( request, 'timepiece/user/timesheet/view.html', { 'active_tab': active_tab or 'overview', 'year_month_form': form, 'from_date': from_date, 'to_date': to_date - relativedelta(days=1), 'show_verify': show_verify, 'show_approve': show_approve, 'timesheet_user': user, 'entries': month_entries, 'grouped_totals': totals, 'project_entries': project_entries, 'summary': summary, })