Files
fet2020/fet2020/finance/models.py
2025-03-05 17:51:23 +01:00

291 lines
8.0 KiB
Python

from pathlib import Path
from django.core.validators import FileExtensionValidator, ValidationError
from django.db import models
from django.urls import reverse
from members.models import Member
from .validators import validate_bill_file_extension
class BankData(models.Model):
# members can be deleted but never their bank datas
bankdata_creator = models.ForeignKey(
Member,
on_delete=models.SET_NULL,
null=True,
verbose_name="Verknüpfung zum Mitglied",
)
name = models.CharField(max_length=128, verbose_name="Kontoinhaber:in")
iban = models.CharField(max_length=34, verbose_name="IBAN")
bic = models.CharField(max_length=11, verbose_name="BIC")
address = models.TextField(blank=True, verbose_name="Adresse")
is_disabled = models.BooleanField(default=False, verbose_name="deaktiviert")
class Meta:
verbose_name = "Bankdaten"
verbose_name_plural = "Bankdaten"
def __str__(self):
return f"{self.name} - {self.iban}"
def clean(self):
if self.iban:
self.iban = self.iban.replace(" ", "")
if self.bic:
self.bic = self.bic.replace(" ", "")
class Fee(models.Model):
fee_creator = models.ForeignKey(
Member,
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Verantwortliche:r",
)
bankdata = models.ForeignKey(
BankData,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name="Bankdaten",
)
job = models.TextField(verbose_name="Tätigkeit")
date_start = models.DateField(verbose_name="Start der Tätigkeit")
date_end = models.DateField(verbose_name="Ende der Tätigkeit")
amount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name="Betrag (EUR)")
class Status(models.TextChoices):
SUBMITTED = "S", "Eingereicht"
APPROVED = "A", "Für Überweisung freigegeben"
PAYOUT = "P", "Ausbezahlt"
CLEARED = "C", "An Dekanat verrechnet"
status = models.CharField(
max_length=1,
choices=Status.choices,
default=Status.SUBMITTED,
verbose_name="Status",
)
date_created = models.DateTimeField(auto_now_add=True)
file_field = models.FileField(
upload_to="uploads/finance/fee/",
validators=[FileExtensionValidator(["pdf"])],
blank=True,
null=True,
verbose_name="Honorarnote",
)
comment = models.TextField(blank=True, default="", verbose_name="Kommentar")
class Meta:
verbose_name = "Honorar"
verbose_name_plural = "Honorare"
def __str__(self):
return f"Honorar #{self.id} / {self.job}"
def save(self, *args, **kwargs):
if not self.date_end:
self.date_end = self.date_start
super().save(*args, **kwargs)
@property
def filename(self):
return Path(self.file_field.name).name
class Resolution(models.Model):
id = models.CharField(primary_key=True, max_length=128, verbose_name="Beschlussnummer")
name = models.CharField(max_length=128, verbose_name="Bezeichnung")
is_visible = models.BooleanField(default=False, verbose_name="sichtbar")
class Option(models.TextChoices):
NORMAL = "B", "normaler Beschluss"
PERMANENT = "D", "Dauerbeschluss"
FINANCE = "F", "Finanzbeschluss"
FSREF = "R", "FsRef-Beschluss"
option = models.CharField(max_length=1, choices=Option.choices, verbose_name="Beschluss")
date = models.DateField(verbose_name="Datum")
voting = models.CharField(max_length=15, verbose_name="Abstimmungsverhalten")
voting_text = models.TextField(verbose_name="Abstimmungstext")
budget = models.DecimalField(
max_digits=7,
decimal_places=2,
default=0.00,
verbose_name="Budget (EUR)",
)
class Meta:
verbose_name = "Beschluss"
verbose_name_plural = "Beschlüsse"
def __str__(self):
return f"{self.id} / {self.name}"
def get_absolute_url(self):
return reverse("finance:resolution_detail", kwargs={"id": self.id})
def clean(self):
if self.id == "":
year = self.date.strftime("%y")
week = self.date.strftime("%W")
_id = ""
for nmb in range(99):
_id = f"{year}{week}-{nmb + 1:02d}"
if not Resolution.objects.filter(id=_id).exists():
break
else:
raise ValidationError(
f"Es wurden zu viele Beschlüsse in dieser Woche angelegt. (ID: {_id})"
)
self.id = _id
if self.name == "":
self.name = self.id
class Wiref(models.Model):
wiref_id = models.CharField(max_length=10, blank=True, default="")
file_field = models.FileField(
upload_to="uploads/finance/wiref/",
validators=[FileExtensionValidator(["pdf"])],
blank=True,
null=True,
verbose_name="Wiref Formular",
)
class Status(models.TextChoices):
OPENED = "O", "Offen"
SUBMITTED = "S", "Eingereicht"
TRANSFERRED = "T", "Überwiesen"
status = models.CharField(
max_length=1,
choices=Status.choices,
default=Status.OPENED,
verbose_name="Status",
)
class Meta:
verbose_name = "Wiref Formular"
verbose_name_plural = "Wiref Formulare"
def __str__(self):
return f"{self.wiref_id}"
class Bill(models.Model):
# members can be deleted but never their bills
bill_creator = models.ForeignKey(
Member,
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Verantwortliche:r",
)
bankdata = models.ForeignKey(
BankData,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name="Bankdaten",
)
resolution = models.ForeignKey(
Resolution,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name="Beschlussnummer",
)
date = models.DateField()
invoice = models.TextField()
amount = models.DecimalField(max_digits=7, decimal_places=2, verbose_name="Betrag (EUR)")
purpose = models.CharField(max_length=140, verbose_name="Verwendungszweck")
class Affiliation(models.TextChoices):
VEREIN = "V", "Vereinsbudget"
OFFICIAL = "B", "Wiref-Budget"
REPRESENTATION = "R", "Bundesvertretung"
affiliation = models.CharField(
max_length=1,
choices=Affiliation.choices,
verbose_name="Abrechnungsbudget",
)
class Payer(models.TextChoices):
ME = "M", "Privat"
VEREIN = "V", "Verein (Safe/Kreditkarte)"
payer = models.CharField(
max_length=1,
choices=Payer.choices,
verbose_name="Wie wurde die Rechnung bezahlt?",
)
only_digital = models.BooleanField(default=False, verbose_name="Digitale Rechnung")
file_field = models.FileField(
upload_to="uploads/finance/bills/",
validators=[validate_bill_file_extension],
blank=True,
null=True,
)
comment = models.TextField(blank=True, default="")
class Status(models.TextChoices):
SUBMITTED = "S", "Eingereicht"
INCOMPLETED = "I", "Unvollständig / Abgelehnt"
CLEARED = "C", "Für Überweisung freigegeben"
FINISHED = "F", "Abgeschlossen / Überwiesen"
status = models.CharField(
max_length=1,
choices=Status.choices,
default=Status.SUBMITTED,
verbose_name="Status",
)
wiref = models.ForeignKey(
Wiref,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name="Wiref",
)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Rechnung"
verbose_name_plural = "Rechnungen"
def __str__(self):
return f"{self.purpose}"
def clean(self):
if self.status is None:
self.status = Bill.Status.SUBMITTED