diff --git a/nummi/main/migrations/0015_alter_snapshot_options_remove_snapshot_diff_and_more.py b/nummi/main/migrations/0015_alter_snapshot_options_remove_snapshot_diff_and_more.py new file mode 100644 index 0000000..b19e22b --- /dev/null +++ b/nummi/main/migrations/0015_alter_snapshot_options_remove_snapshot_diff_and_more.py @@ -0,0 +1,71 @@ +# Generated by Django 4.1.4 on 2022-12-29 20:26 + +import datetime + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0014_alter_account_icon"), + ] + + operations = [ + migrations.AlterModelOptions( + name="snapshot", + options={ + "ordering": ["-date"], + "verbose_name": "Statement", + "verbose_name_plural": "Statements", + }, + ), + migrations.RemoveField( + model_name="snapshot", + name="diff", + ), + migrations.RemoveField( + model_name="snapshot", + name="previous", + ), + migrations.AddField( + model_name="snapshot", + name="start_date", + field=models.DateField( + default=datetime.date.today, verbose_name="Start date" + ), + ), + migrations.AddField( + model_name="snapshot", + name="start_value", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=12, verbose_name="Start value" + ), + ), + migrations.AddField( + model_name="transaction", + name="snapshot", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="main.snapshot", + verbose_name="Statement", + ), + ), + migrations.AlterField( + model_name="snapshot", + name="date", + field=models.DateField( + default=datetime.date.today, verbose_name="End date" + ), + ), + migrations.AlterField( + model_name="snapshot", + name="value", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=12, verbose_name="End value" + ), + ), + ] diff --git a/nummi/main/migrations/0016_alter_transaction_account_alter_transaction_snapshot.py b/nummi/main/migrations/0016_alter_transaction_account_alter_transaction_snapshot.py new file mode 100644 index 0000000..b2e7a10 --- /dev/null +++ b/nummi/main/migrations/0016_alter_transaction_account_alter_transaction_snapshot.py @@ -0,0 +1,35 @@ +# Generated by Django 4.1.4 on 2022-12-29 20:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0015_alter_snapshot_options_remove_snapshot_diff_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="transaction", + name="account", + field=models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="main.account", + verbose_name="Account", + ), + ), + migrations.AlterField( + model_name="transaction", + name="snapshot", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="main.snapshot", + verbose_name="Statement", + ), + ), + ] diff --git a/nummi/main/migrations/0017_alter_transaction_account.py b/nummi/main/migrations/0017_alter_transaction_account.py new file mode 100644 index 0000000..bbe985d --- /dev/null +++ b/nummi/main/migrations/0017_alter_transaction_account.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.4 on 2022-12-29 20:34 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0016_alter_transaction_account_alter_transaction_snapshot"), + ] + + operations = [ + migrations.AlterField( + model_name="transaction", + name="account", + field=models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to="main.account", + verbose_name="Account", + ), + ), + ] diff --git a/nummi/main/models.py b/nummi/main/models.py index 043b7d2..3694261 100644 --- a/nummi/main/models.py +++ b/nummi/main/models.py @@ -1,6 +1,6 @@ +import datetime import pathlib import uuid -from datetime import date from django.conf import settings from django.core.validators import FileExtensionValidator @@ -101,7 +101,77 @@ class Category(CustomModel): verbose_name_plural = _("Categories") -class Transaction(AccountModel): +def snapshot_path(instance, filename): + return pathlib.Path("snapshots", str(instance.id)).with_suffix(".pdf") + + +class Snapshot(AccountModel): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + date = models.DateField(default=datetime.date.today, verbose_name=_("End date")) + start_date = models.DateField( + default=datetime.date.today, verbose_name=_("Start date") + ) + value = models.DecimalField( + max_digits=12, decimal_places=2, default=0, verbose_name=_("End value") + ) + start_value = models.DecimalField( + max_digits=12, decimal_places=2, default=0, verbose_name=_("Start value") + ) + file = models.FileField( + upload_to=snapshot_path, + validators=[FileExtensionValidator(["pdf"])], + verbose_name=_("File"), + max_length=256, + blank=True, + default="", + ) + + def __str__(self): + return _("%(date)s statement") % {"date": self.date} + + def save(self, *args, **kwargs): + if Snapshot.objects.filter(id=self.id).exists(): + _prever = Snapshot.objects.get(id=self.id) + if _prever.file and _prever.file != self.file: + pathlib.Path(_prever.file.path).unlink(missing_ok=True) + + super().save(*args, **kwargs) + + def delete(self, *args, only_super=False, **kwargs): + if self.file: + self.file.delete() + + def get_absolute_url(self): + return reverse("snapshot", kwargs={"pk": self.pk}) + + def get_delete_url(self): + return reverse("del_snapshot", kwargs={"pk": self.pk}) + + @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 = _("Statement") + verbose_name_plural = _("Statements") + + +class Transaction(CustomModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField( max_length=256, default=_("Transaction"), verbose_name=_("Name") @@ -110,7 +180,7 @@ class Transaction(AccountModel): value = models.DecimalField( max_digits=12, decimal_places=2, default=0, verbose_name=_("Value") ) - date = models.DateField(default=date.today, verbose_name=_("Date")) + date = models.DateField(default=datetime.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") @@ -125,6 +195,21 @@ class Transaction(AccountModel): null=True, verbose_name=_("Category"), ) + snapshot = models.ForeignKey( + Snapshot, + on_delete=models.CASCADE, + verbose_name=_("Statement"), + ) + account = models.ForeignKey( + Account, + on_delete=models.CASCADE, + verbose_name=_("Account"), + editable=False, + ) + + def save(self, *args, **kwargs): + self.account = self.snapshot.account + super().save(*args, **kwargs) def __str__(self): return f"{self.date} – {self.name}" @@ -192,146 +277,6 @@ class Invoice(CustomModel): verbose_name_plural = _("Invoices") -def snapshot_path(instance, filename): - return pathlib.Path("snapshots", str(instance.id)).with_suffix(".pdf") - - -class Snapshot(AccountModel): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - date = models.DateField(default=date.today, 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 - ) - file = models.FileField( - upload_to=snapshot_path, - validators=[FileExtensionValidator(["pdf"])], - verbose_name=_("File"), - max_length=256, - blank=True, - default="", - ) - - def __str__(self): - return _("%(date)s snapshot") % {"date": self.date} - - def save(self, *args, only_super=False, **kwargs): - if not only_super: - if Snapshot.objects.filter(id=self.id).exists(): - _prever = Snapshot.objects.get(id=self.id) - if _prever.file and _prever.file != self.file: - pathlib.Path(_prever.file.path).unlink(missing_ok=True) - - _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): - self.file.delete() - 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) - - def get_absolute_url(self): - return reverse("snapshot", kwargs={"pk": self.pk}) - - def get_delete_url(self): - return reverse("del_snapshot", kwargs={"pk": self.pk}) - - @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 NummiForm(ModelForm): template_name = "main/form/base.html" @@ -361,7 +306,7 @@ class TransactionForm(NummiForm): _user = kwargs.pop("user") super().__init__(*args, **kwargs) self.fields["category"].queryset = Category.objects.filter(user=_user) - self.fields["account"].queryset = Account.objects.filter(user=_user) + self.fields["snapshot"].queryset = Snapshot.objects.filter(user=_user) class InvoiceForm(NummiForm):