def _send_mail(self, subject, ctx): # Don't go to the work unless we have a place to send it emails = utils.get_setting('TIMEPIECE_ACCOUNTING_EMAILS') if not emails: return from_email = utils.get_setting('DEFAULT_FROM_EMAIL') msg = render_to_string('timepiece/contract/hours_email.txt', ctx) send_mail(subject=subject, message=msg, from_email=from_email, recipient_list=emails)
def _send_mail(self, subject, ctx): # Don't go to the work unless we have a place to send it emails = utils.get_setting('TIMEPIECE_ACCOUNTING_EMAILS') if not emails: return from_email = utils.get_setting('DEFAULT_FROM_EMAIL') msg = render_to_string('timepiece/contract/hours_email.txt', ctx) send_mail( subject=subject, message=msg, from_email=from_email, recipient_list=emails )
def delete(self, *args, **kwargs): # Note: this gets called when you delete a single item using the red # Delete button at the bottom while editing it in the admin - but not # when you delete one or more from the change list using the admin # action. super(ContractHour, self).delete(*args, **kwargs) # If we have an email address to send to, and this record was in # pending status, we'll send an email about the change. if ContractHour.PENDING_STATUS in (self.status, self._original['status']): domain = Site.objects.get_current().domain method = 'https' if utils.get_setting('TIMEPIECE_EMAILS_USE_HTTPS')\ else 'http' url = self.contract.get_absolute_url() ctx = { 'deleted': True, 'new': False, 'changed': False, 'previous': self._original, 'link': '%s://%s%s' % (method, domain, url) } contract = self._original['contract'] name = self._meta.verbose_name subject = "Deleted pending %s for %s" % (name, contract) self._send_mail(subject, ctx)
def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') self.active = kwargs.pop('active', None) initial = kwargs.get('initial', {}) default_loc = utils.get_setting('TIMEPIECE_DEFAULT_LOCATION_SLUG') if default_loc: try: loc = Location.objects.get(slug=default_loc) except Location.DoesNotExist: loc = None if loc: initial['location'] = loc.pk project = initial.get('project', None) try: last_project_entry = Entry.objects.filter( user=self.user, project=project).order_by('-end_time')[0] except IndexError: initial['activity'] = None else: initial['activity'] = last_project_entry.activity.pk super(ClockInForm, self).__init__(*args, **kwargs) self.fields['start_time'].initial = datetime.datetime.now() self.fields['project'].queryset = Project.trackable.filter( users=self.user) if not self.active: self.fields.pop('active_comment') else: self.fields['active_comment'].initial = self.active.comments self.instance.user = self.user
def quick_clock_in(request): user = request.user work_projects = [] leave_projects = [] if user.is_authenticated() and user.is_active: # Display all active paid leave projects that the user is assigned to. leave_ids = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS').values() lq = Q(users=user) & Q(id__in=leave_ids) leave_projects = Project.trackable.filter(lq).order_by('name') # Get all projects this user has clocked in to. entries = Entry.objects.filter(user=user) project_ids = list(entries.values_list('project', flat=True)) # Narrow to projects which can still be clocked in to. pq = Q(id__in=project_ids) valid_projects = Project.trackable.filter(pq).exclude(id__in=leave_ids) valid_ids = list(valid_projects.values_list('id', flat=True)) # Display the 10 projects this user most recently clocked into. work_ids = [] for i in project_ids: if len(work_ids) > 10: break if i in valid_ids and i not in work_ids: work_ids.append(i) work_projects = [valid_projects.get(pk=i) for i in work_ids] return { 'leave_projects': leave_projects, 'work_projects': work_projects, }
def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') self.active = kwargs.pop('active', None) initial = kwargs.get('initial', {}) default_loc = utils.get_setting('TIMEPIECE_DEFAULT_LOCATION_SLUG') if default_loc: try: loc = Location.objects.get(slug=default_loc) except Location.DoesNotExist: loc = None if loc: initial['location'] = loc.pk project = initial.get('project', None) try: last_project_entry = Entry.objects.filter( user=self.user, project=project).order_by('-end_time')[0] except IndexError: initial['activity'] = None else: initial['activity'] = last_project_entry.activity.pk super(ClockInForm, self).__init__(*args, **kwargs) self.fields['start_time'].required = False self.fields['start_time'].initial = datetime.datetime.now() self.fields['start_time'].widget = TimepieceSplitDateTimeWidget() self.fields['project'].queryset = Project.trackable.filter( users=self.user) if not self.active: self.fields.pop('active_comment') else: self.fields['active_comment'].initial = self.active.comments self.instance.user = self.user
def __init__(self, *args, **kwargs): self.user = kwargs.pop("user") self.active = kwargs.pop("active", None) initial = kwargs.get("initial", {}) default_loc = utils.get_setting("TIMEPIECE_DEFAULT_LOCATION_SLUG") if default_loc: try: loc = Location.objects.get(slug=default_loc) except Location.DoesNotExist: loc = None if loc: initial["location"] = loc.pk project = initial.get("project", None) try: last_project_entry = Entry.objects.filter(user=self.user, project=project).order_by("-end_time")[0] except IndexError: initial["activity"] = None else: initial["activity"] = last_project_entry.activity.pk super(ClockInForm, self).__init__(*args, **kwargs) self.fields["start_time"].required = False self.fields["start_time"].initial = datetime.datetime.now() self.fields["start_time"].widget = TimepieceSplitDateTimeWidget() self.fields["project"].queryset = Project.trackable.filter(users=self.user) if not self.active: self.fields.pop("active_comment") else: self.fields["active_comment"].initial = self.active.comments self.instance.user = self.user
def save(self, *args, **kwargs): # Let the date_approved default to today if it's been set approved # and doesn't have one if self.status == self.APPROVED_STATUS and not self.date_approved: self.date_approved = datetime.date.today() # If we have an email address to send to, and this record was # or is in pending status, we'll send an email about the change. if ContractHour.PENDING_STATUS in (self.status, self._original['status']): is_new = self.pk is None super(ContractHour, self).save(*args, **kwargs) if ContractHour.PENDING_STATUS in (self.status, self._original['status']): domain = Site.objects.get_current().domain method = 'https' if utils.get_setting('TIMEPIECE_EMAILS_USE_HTTPS')\ else 'http' url = self.contract.get_absolute_url() ctx = { 'new': is_new, 'changed': not is_new, 'deleted': False, 'current': self, 'previous': self._original, 'link': '%s://%s%s' % (method, domain, url) } prefix = "New" if is_new else "Changed" name = self._meta.verbose_name subject = "%s pending %s for %s" % (prefix, name, self.contract) self._send_mail(subject, ctx)
def get_entries_data(self): projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') # Account for the day added by the form query = Q(end_time__gt=utils.get_week_start(self.from_date), end_time__lt=self.to_date + relativedelta(days=1)) query &= ~Q(project__in=projects.values()) entries = timepiece.Entry.objects.date_trunc( 'week', extra_values=('activity', 'project__status')).filter(query) return entries
def get_entries_data(self): projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') # Account for the day added by the form query = Q(end_time__gt=utils.get_week_start(self.from_date), end_time__lt=self.to_date + relativedelta(days=1)) query &= ~Q(project__in=projects.values()) entries = timepiece.Entry.objects.date_trunc('week', extra_values=('activity', 'project__status')).filter(query) return entries
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 get_entry_query(self, start, end, data): """Builds Entry query from form data.""" # Entry types. incl_billable = data.get('billable', True) incl_nonbillable = data.get('non_billable', True) incl_leave = data.get('paid_leave', True) # If no types are selected, shortcut & return nothing. if not any((incl_billable, incl_nonbillable, incl_leave)): return None # All entries must meet time period requirements. basicQ = Q(end_time__gte=start, end_time__lt=end) # Filter by project for HourlyReport. projects = data.get('projects', None) basicQ &= Q(project__in=projects) if projects else Q() # Filter by user, activity, and project type for BillableReport. if 'users' in data: basicQ &= Q(user__in=data.get('users')) if 'activities' in data: basicQ &= Q(activity__in=data.get('activities')) if 'project_types' in data: basicQ &= Q(project__type__in=data.get('project_types')) # If all types are selected, no further filtering is required. if all((incl_billable, incl_nonbillable, incl_leave)): return basicQ # Filter by whether a project is billable or non-billable. billableQ = None if incl_billable and not incl_nonbillable: billableQ = Q(activity__billable=True, project__type__billable=True) if incl_nonbillable and not incl_billable: billableQ = Q(activity__billable=False) |\ Q(project__type__billable=False) # Filter by whether the entry is paid leave. leave_ids = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS').values() leaveQ = Q(project__in=leave_ids) if incl_leave: extraQ = (leaveQ | billableQ) if billableQ else leaveQ else: extraQ = (~leaveQ & billableQ) if billableQ else ~leaveQ return basicQ & extraQ
def summary(user, date, end_date): """ Returns a summary of hours worked in the given time frame, for this user. The setting TIMEPIECE_PAID_LEAVE_PROJECTS can be used to separate out hours for paid leave that should not be included in the total worked (e.g., sick time, vacation time, etc.). Those hours will be added to the summary separately using the dictionary key set in TIMEPIECE_PAID_LEAVE_PROJECTS. """ projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') entries = user.timepiece_entries.filter(end_time__gt=date, end_time__lt=end_date) data = { 'billable': Decimal('0'), 'non_billable': Decimal('0'), 'invoiced': Decimal('0'), 'uninvoiced': Decimal('0'), 'total': Decimal('0') } invoiced = entries.filter(status='invoiced').aggregate( i=Sum('hours'))['i'] uninvoiced = entries.exclude(status='invoiced').aggregate( uninv=Sum('hours'))['uninv'] total = entries.aggregate(s=Sum('hours'))['s'] if invoiced: data['invoiced'] = invoiced if uninvoiced: data['uninvoiced'] = uninvoiced if total: data['total'] = total billable = entries.exclude(project__in=projects.values()) billable = billable.values('billable', ).annotate(s=Sum('hours')) for row in billable: if row['billable']: data['billable'] += row['s'] else: data['non_billable'] += row['s'] data['total_worked'] = data['billable'] + data['non_billable'] data['paid_leave'] = {} for name, pk in projects.iteritems(): qs = entries.filter(project=projects[name]) data['paid_leave'][name] = qs.aggregate(s=Sum('hours'))['s'] return data
def summary(user, date, end_date): """ Returns a summary of hours worked in the given time frame, for this user. The setting TIMEPIECE_PAID_LEAVE_PROJECTS can be used to separate out hours for paid leave that should not be included in the total worked (e.g., sick time, vacation time, etc.). Those hours will be added to the summary separately using the dictionary key set in TIMEPIECE_PAID_LEAVE_PROJECTS. """ projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') entries = user.timepiece_entries.filter( end_time__gt=date, end_time__lt=end_date) data = { 'billable': Decimal('0'), 'non_billable': Decimal('0'), 'invoiced': Decimal('0'), 'uninvoiced': Decimal('0'), 'total': Decimal('0') } invoiced = entries.filter( status=Entry.INVOICED).aggregate(i=Sum('hours'))['i'] uninvoiced = entries.exclude( status=Entry.INVOICED).aggregate(uninv=Sum('hours'))['uninv'] total = entries.aggregate(s=Sum('hours'))['s'] if invoiced: data['invoiced'] = invoiced if uninvoiced: data['uninvoiced'] = uninvoiced if total: data['total'] = total billable = entries.exclude(project__in=projects.values()) billable = billable.values( 'billable', ).annotate(s=Sum('hours')) for row in billable: if row['billable']: data['billable'] += row['s'] else: data['non_billable'] += row['s'] data['total_worked'] = data['billable'] + data['non_billable'] data['paid_leave'] = {} for name, pk in projects.iteritems(): qs = entries.filter(project=projects[name]) data['paid_leave'][name] = qs.aggregate(s=Sum('hours'))['s'] return data
def summary(user, date, end_date): """ Returns a summary of hours worked in the given time frame, for this user. The setting TIMEPIECE_PAID_LEAVE_PROJECTS can be used to separate out hours for paid leave that should not be included in the total worked (e.g., sick time, vacation time, etc.). Those hours will be added to the summary separately using the dictionary key set in TIMEPIECE_PAID_LEAVE_PROJECTS. """ projects = utils.get_setting("TIMEPIECE_PAID_LEAVE_PROJECTS") entries = user.timepiece_entries.filter(end_time__gt=date, end_time__lt=end_date) data = { "billable": Decimal("0"), "non_billable": Decimal("0"), "invoiced": Decimal("0"), "uninvoiced": Decimal("0"), "total": Decimal("0"), } invoiced = entries.filter(status=Entry.INVOICED).aggregate(i=Sum("hours"))["i"] uninvoiced = entries.exclude(status=Entry.INVOICED).aggregate(uninv=Sum("hours"))["uninv"] total = entries.aggregate(s=Sum("hours"))["s"] if invoiced: data["invoiced"] = invoiced if uninvoiced: data["uninvoiced"] = uninvoiced if total: data["total"] = total billable = entries.exclude(project__in=projects.values()) billable = billable.values("billable").annotate(s=Sum("hours")) for row in billable: if row["billable"]: data["billable"] += row["s"] else: data["non_billable"] += row["s"] data["total_worked"] = data["billable"] + data["non_billable"] data["paid_leave"] = {} for name, pk in projects.iteritems(): qs = entries.filter(project=projects[name]) data["paid_leave"][name] = qs.aggregate(s=Sum("hours"))["s"] return data
class PermissionsRequiredMixin(object): # Required. permissions = None # Optional. raise_exception = False login_url = utils.get_setting('LOGIN_URL') redirect_field_name = REDIRECT_FIELD_NAME def dispatch(self, request, *args, **kwargs): if getattr(self, 'permissions', None) is None: raise ImproperlyConfigured('Class must define the permissions ' 'attribute') if not request.user.has_perms(self.permissions): if self.raise_exception: raise PermissionDenied return redirect_to_login(request.get_full_path(), self.login_url, self.redirect_field_name) return super(PermissionsRequiredMixin, self).dispatch(request, *args, **kwargs)
def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') self.active = kwargs.pop('active', None) initial = kwargs.get('initial', {}) default_loc = utils.get_setting('TIMEPIECE_DEFAULT_LOCATION_SLUG') if default_loc: try: loc = timepiece.Location.objects.get(slug=default_loc) except timepiece.Location.DoesNotExist: loc = None if loc: initial['location'] = loc.pk project = initial.get('project') try: last_project_entry = timepiece.Entry.objects.filter( user=self.user, project=project).order_by('-end_time')[0] except IndexError: initial['activity'] = None else: initial['activity'] = last_project_entry.activity.id super(ClockInForm, self).__init__(*args, **kwargs) self.fields['start_time'].required = False self.fields['start_time'].initial = datetime.now() self.fields['start_time'].widget = forms.SplitDateTimeWidget( attrs={'class': 'timepiece-time'}, date_format='%m/%d/%Y', ) self.fields['project'].queryset = timepiece.Project.objects.filter( users=self.user, status__enable_timetracking=True, type__enable_timetracking=True) if not self.active: self.fields.pop('active_comment') else: self.fields['active_comment'].initial = self.active.comments self.instance.user = self.user
def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') self.active = kwargs.pop('active', None) initial = kwargs.get('initial', {}) default_loc = utils.get_setting('TIMEPIECE_DEFAULT_LOCATION_SLUG') if default_loc: try: loc = timepiece.Location.objects.get(slug=default_loc) except timepiece.Location.DoesNotExist: loc = None if loc: initial['location'] = loc.pk project = initial.get('project') try: last_project_entry = timepiece.Entry.objects.filter( user=self.user, project=project).order_by('-end_time')[0] except IndexError: initial['activity'] = None else: initial['activity'] = last_project_entry.activity.id super(ClockInForm, self).__init__(*args, **kwargs) self.fields['start_time'].required = False self.fields['start_time'].initial = datetime.now() self.fields['start_time'].widget = forms.SplitDateTimeWidget( attrs={'class': 'timepiece-time'}, date_format='%m/%d/%Y', ) self.fields['project'].queryset = timepiece.Project.objects.filter( users=self.user, status__enable_timetracking=True, type__enable_timetracking=True ) if not self.active: self.fields.pop('active_comment') else: self.fields['active_comment'].initial = self.active.comments self.instance.user = self.user
def get_query_set(self): qs = EntryQuerySet(self.model) projects = utils.get_setting('TIMEPIECE_PAID_LEAVE_PROJECTS') return qs.exclude(project__in=projects.values())
def extra_nav(request): return { 'timepiece_extra_nav': utils.get_setting('TIMEPIECE_EXTRA_NAV'), }