from datetime import date import uuid from django.db import models from django.forms import ModelForm from django.core.validators import FileExtensionValidator from django.utils.translation import gettext as _ class Category(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=64, default=_("Category"), verbose_name=_("Name") ) icon = models.CharField(max_length=64, default="folder", verbose_name=_("Icon")) budget = models.BooleanField(default=True, verbose_name=_("Budget")) def __str__(self): return self.name class Meta: ordering = ["name"] verbose_name = _("Category") verbose_name_plural = _("Categories") class CategoryForm(ModelForm): template_name = "main/form.html" class Meta: model = Category fields = ["name", "icon", "budget"] class Transaction(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Transaction"), verbose_name=_("Name") ) description = models.TextField(null=True, blank=True, verbose_name=_("Description")) value = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name=_("Value") ) date = models.DateField(default=date.today, verbose_name=_("Date")) real_date = models.DateField(blank=True, null=True, verbose_name=_("Real date")) trader = models.CharField( max_length=128, blank=True, null=True, verbose_name=_("Trader") ) payment = models.CharField( max_length=128, blank=True, null=True, verbose_name=_("Payment") ) category = models.ForeignKey( Category, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_("Category"), ) def __str__(self): return f"{self.date} – {self.name}" class Meta: ordering = ["-date"] verbose_name = _("Transaction") verbose_name_plural = _("Transactions") class TransactionForm(ModelForm): template_name = "main/form.html" class Meta: model = Transaction fields = [ "date", "name", "value", "trader", "category", "real_date", "payment", "description", ] class Invoice(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Invoice"), verbose_name=_("Name") ) file = models.FileField( upload_to="invoices/", validators=[FileExtensionValidator(["pdf"])], verbose_name=_("File"), ) transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE) def __str__(self): return f"{self.name}: {self.transaction}" def delete(self, *args, **kwargs): self.file.delete() super().delete(*args, **kwargs) class Meta: verbose_name = _("Invoice") verbose_name_plural = _("Invoices") class InvoiceForm(ModelForm): template_name = "main/form.html" prefix = "invoice" class Meta: model = Invoice fields = ["name", "file"] class Snapshot(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) date = models.DateField(unique=True, verbose_name=_("Date")) value = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name=_("Value") ) previous = models.OneToOneField( "self", on_delete=models.SET_NULL, blank=True, null=True, editable=False ) diff = models.DecimalField( max_digits=12, decimal_places=2, editable=False, blank=True, null=True ) def __str__(self): return f"{_('Snapshot')} {self.date}" def save(self, *args, only_super=False, **kwargs): if not only_super: _prev = ( self.__class__.objects.order_by("-date") .exclude(id=self.id) .filter(date__lt=self.date) .first() ) try: _next = self.__class__.objects.exclude(id=self.id).get(previous=_prev) except self.__class__.DoesNotExist: pass else: try: _prevnext = self.__class__.objects.exclude(id=self.id).get( previous=self ) except self.__class__.DoesNotExist: pass else: _prevnext.previous = ( self.__class__.objects.order_by("-date") .exclude(id=self.id) .filter(date__lt=_prevnext.date) .first() ) _prevnext.save(only_super=True) _next.previous = None super().save(*args, **kwargs) _next.previous = self _next.save(only_super=True) self.previous = _prev if self.previous is None: self.diff = None else: self.diff = self.value - self.previous.value super().save(*args, **kwargs) try: _next = self.__class__.objects.get(previous=self) except self.__class__.DoesNotExist: pass else: _next.save(only_super=True) def delete(self, *args, only_super=False, **kwargs): if not only_super: try: _next = self.__class__.objects.get(previous=self) except self.__class__.DoesNotExist: super().delete(*args, **kwargs) else: _next.previous = self.previous super().delete(*args, **kwargs) _next.save(only_super=True) else: super().delete(*args, **kwargs) @property def sum(self): if self.previous is None: return 0 trans = self.transactions.aggregate(sum=models.Sum("value")) return trans["sum"] or 0 @property def transactions(self): if self.previous is None: return Transaction.objects.none() return Transaction.objects.filter( date__lte=self.date, date__gt=self.previous.date ) @property def pos(self): return ( self.transactions.filter(value__gt=0).aggregate(sum=models.Sum("value"))[ "sum" ] or 0 ) @property def neg(self): return ( self.transactions.filter(value__lt=0).aggregate(sum=models.Sum("value"))[ "sum" ] or 0 ) class Meta: ordering = ["-date"] verbose_name = _("Snapshot") verbose_name_plural = _("Snapshots") class SnapshotForm(ModelForm): template_name = "main/form.html" class Meta: model = Snapshot fields = ["date", "value"]