nummi/nummi/main/models.py

269 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pathlib
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 _
from django.core.files.storage import Storage
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}"
@property
def invoices(self):
return Invoice.objects.filter(transaction=self)
@property
def has_invoice(self):
return self.invoices.count() > 0
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",
]
def invoice_path(instance, filename):
return pathlib.Path("invoices", str(instance.id)).with_suffix(".pdf")
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=invoice_path,
validators=[FileExtensionValidator(["pdf"])],
verbose_name=_("File"),
max_length=128,
)
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"]
def snapshot_path(instance, filename):
return pathlib.Path("snapshots", str(instance.id)).with_suffix(".pdf")
class Snapshot(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
date = models.DateField(default=date.today, 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
)
file = models.FileField(
upload_to=snapshot_path,
validators=[FileExtensionValidator(["pdf"])],
verbose_name=_("File"),
max_length=256,
blank=True,
)
def __str__(self):
return f"{_('Snapshot')} {self.date}"
def save(self, *args, only_super=False, **kwargs):
if not only_super:
_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)
@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", "file"]