repos
/ timestrap-django master

timestrap-django

mirror archived upstream

Time tracking you can host anywhere, built on Django. Full export support in multiple formats and easily extensible.

djangodockerhandcodedpythonself-hostedtime-trackingtimetracker

4.1 KB · 137 lines · Python Raw History
  1from datetime import date
  2from decimal import Decimal, ROUND_DOWN
  3
  4from django.contrib.sites.models import Site
  5from django.db import models
  6from django.db.models import Sum
  7
  8from conf.utils import current_site_id
  9from conf.managers import CurrentSiteManager
 10
 11from .utils import duration_string, duration_decimal
 12
 13
 14class Client(models.Model):
 15    name = models.CharField(max_length=255)
 16    archive = models.BooleanField(default=False)
 17    payment_id = models.CharField(max_length=255, blank=True, null=True)
 18    sites = models.ManyToManyField(Site)
 19
 20    objects = models.Manager()
 21    on_site = CurrentSiteManager()
 22
 23    class Meta:
 24        default_permissions = ("view", "add", "change", "delete")
 25        ordering = ["-id"]
 26
 27    def __str__(self):
 28        return "Client: " + self.name
 29
 30    def get_total_projects(self):
 31        return self.projects.count()
 32
 33    def get_total_duration(self):
 34        return duration_string(
 35            self.projects.aggregate(Sum("entries__duration"))["entries__duration__sum"]
 36        )
 37
 38
 39class Project(models.Model):
 40    client = models.ForeignKey(
 41        "Client", related_name="projects", on_delete=models.CASCADE
 42    )
 43    name = models.CharField(max_length=255)
 44    archive = models.BooleanField(default=False)
 45    estimate = models.DecimalField(
 46        max_digits=10, decimal_places=2, blank=True, null=True
 47    )
 48
 49    class Meta:
 50        default_permissions = ("view", "add", "change", "delete")
 51        ordering = ["client", "-id"]
 52
 53    def __str__(self):
 54        return "Project: " + self.name
 55
 56    def get_total_entries(self):
 57        return self.entries.count()
 58
 59    def get_total_cost(self):
 60        total_cost = Decimal()
 61        for entry in self.entries.iterator():
 62            try:
 63                if entry.task.hourly_rate:
 64                    total_cost += (
 65                        duration_decimal(entry.duration) * entry.task.hourly_rate
 66                    )
 67            except:  # noqa: E722
 68                continue
 69        return total_cost.quantize(Decimal(".01"), rounding=ROUND_DOWN)
 70
 71    def get_total_duration(self):
 72        return duration_string(self.entries.aggregate(Sum("duration"))["duration__sum"])
 73
 74    def get_percent_done(self):
 75        if self.estimate is not None:
 76            total_cost = Decimal(self.get_total_cost())
 77            total_estimate = Decimal(self.estimate)
 78            return int(100 * (total_cost / total_estimate))
 79        return None
 80
 81
 82class Task(models.Model):
 83    name = models.CharField(max_length=255)
 84    hourly_rate = models.DecimalField(
 85        max_digits=10, decimal_places=2, blank=True, null=True
 86    )
 87    sites = models.ManyToManyField(Site)
 88
 89    objects = models.Manager()
 90    on_site = CurrentSiteManager()
 91
 92    class Meta:
 93        default_permissions = ("view", "add", "change", "delete")
 94
 95    def __str__(self):
 96        return "Task: " + self.name
 97
 98
 99class Entry(models.Model):
100    project = models.ForeignKey(
101        "Project", related_name="entries", on_delete=models.CASCADE
102    )
103    task = models.ForeignKey(
104        "core.Task",
105        related_name="entries",
106        blank=True,
107        null=True,
108        on_delete=models.SET_NULL,
109    )
110    user = models.ForeignKey(
111        "auth.User", related_name="entries", on_delete=models.CASCADE
112    )
113    date = models.DateField(blank=True)
114    duration = models.DurationField(blank=True)
115    datetime_start = models.DateTimeField(blank=True, null=True)
116    datetime_end = models.DateTimeField(blank=True, null=True)
117    note = models.TextField(blank=True, null=True)
118    site = models.ForeignKey(Site, default=current_site_id(), on_delete=models.CASCADE)
119
120    objects = models.Manager()
121    on_site = CurrentSiteManager()
122
123    class Meta:
124        default_permissions = ("view", "add", "change", "delete")
125        ordering = ["-date", "-id"]
126        verbose_name_plural = "Entries"
127
128    def save(self, *args, **kwargs):
129        if not self.date:
130            self.date = date.today()
131        if not self.site:
132            self.site = Site.objects.get(id=current_site_id())
133        super().save(*args, **kwargs)
134
135    def __str__(self):
136        return "Entry for " + self.project.name + " by " + self.user.username