add new feature finance (submit bill)
This commit is contained in:
0
fet2020/finance/__init__.py
Normal file
0
fet2020/finance/__init__.py
Normal file
183
fet2020/finance/admin.py
Normal file
183
fet2020/finance/admin.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .forms import BankDataAdminForm, BillAdminForm, ResolutionAdminForm
|
||||
from .models import BankData, Bill, Resolution
|
||||
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
|
||||
class BankDataAdmin(admin.ModelAdmin):
|
||||
form = BankDataAdminForm
|
||||
model = BankData
|
||||
|
||||
list_display = [
|
||||
"name",
|
||||
"iban",
|
||||
]
|
||||
|
||||
|
||||
class BillAdmin(admin.ModelAdmin):
|
||||
form = BillAdminForm
|
||||
model = Bill
|
||||
|
||||
list_display = [
|
||||
"id",
|
||||
"only_digital",
|
||||
"amount",
|
||||
"purpose",
|
||||
"resolution",
|
||||
"status",
|
||||
"bill_creator",
|
||||
"affiliation",
|
||||
"wiref_id",
|
||||
]
|
||||
|
||||
actions = ["make_cleared", "make_finished"]
|
||||
list_filter = ["status", "affiliation"]
|
||||
search_fields = ["wiref_id", "purpose"]
|
||||
ordering = ["date_created"]
|
||||
|
||||
readonly_fields = ["get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic"]
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"bill_creator",
|
||||
"resolution",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Bankdaten",
|
||||
{
|
||||
"fields": (
|
||||
"payer",
|
||||
"bankdata",
|
||||
("get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic"),
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Rechnung",
|
||||
{
|
||||
"fields": (
|
||||
"affiliation",
|
||||
"date",
|
||||
"invoice",
|
||||
"purpose",
|
||||
"amount",
|
||||
"only_digital",
|
||||
"file_field",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Sonstiges",
|
||||
{
|
||||
"fields": (
|
||||
"comment",
|
||||
"wiref_id",
|
||||
"status",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
def get_bankdata_name(self, obj):
|
||||
try:
|
||||
tmp = BankData.objects.get(id=obj.bankdata.id)
|
||||
except Exception:
|
||||
return "-"
|
||||
|
||||
return f"{ tmp.name }"
|
||||
|
||||
get_bankdata_name.short_description = "Kontoinhaber:in"
|
||||
|
||||
def get_bankdata_iban(self, obj):
|
||||
try:
|
||||
tmp = BankData.objects.get(id=obj.bankdata.id)
|
||||
except Exception:
|
||||
return "-"
|
||||
|
||||
return f"{ tmp.iban }"
|
||||
|
||||
get_bankdata_iban.short_description = "IBAN"
|
||||
|
||||
def get_bankdata_bic(self, obj):
|
||||
try:
|
||||
tmp = BankData.objects.get(id=obj.bankdata.id)
|
||||
except Exception:
|
||||
return "-"
|
||||
|
||||
return f"{ tmp.bic }"
|
||||
|
||||
get_bankdata_bic.short_description = "BIC"
|
||||
|
||||
@admin.action(description="Als 'Abgerechnet' markieren.")
|
||||
def make_cleared(self, request, queryset):
|
||||
updated = queryset.update(status="C")
|
||||
self.message_user(
|
||||
request,
|
||||
ngettext(
|
||||
"%d Rechnung wurde als 'Abgerechnet' markiert.",
|
||||
"%d Rechnungen wurde als 'Abgerechnet' markiert.",
|
||||
updated,
|
||||
)
|
||||
% updated,
|
||||
messages.SUCCESS,
|
||||
)
|
||||
|
||||
@admin.action(description="Als 'Abgeschlossen' markieren.")
|
||||
def make_finished(self, request, queryset):
|
||||
updated = queryset.update(status="F")
|
||||
self.message_user(
|
||||
request,
|
||||
ngettext(
|
||||
"%d Rechnung wurde als 'Abgeschlossen' markiert.",
|
||||
"%d Rechnungen wurde als 'Abgeschlossen' markiert.",
|
||||
updated,
|
||||
)
|
||||
% updated,
|
||||
messages.SUCCESS,
|
||||
)
|
||||
|
||||
|
||||
class ResolutionAdmin(admin.ModelAdmin):
|
||||
form = ResolutionAdminForm
|
||||
model = Resolution
|
||||
|
||||
list_display = [
|
||||
"name",
|
||||
"id",
|
||||
"is_visible",
|
||||
]
|
||||
|
||||
|
||||
admin.site.register(BankData, BankDataAdmin)
|
||||
admin.site.register(Bill, BillAdmin)
|
||||
admin.site.register(Resolution, ResolutionAdmin)
|
||||
12
fet2020/finance/apps.py
Normal file
12
fet2020/finance/apps.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.apps import AppConfig
|
||||
from django.db.models.signals import post_migrate
|
||||
|
||||
from fet2020.utils import create_perms
|
||||
|
||||
|
||||
class FinanceConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "finance"
|
||||
|
||||
def ready(self):
|
||||
post_migrate.connect(create_perms, sender=self)
|
||||
264
fet2020/finance/forms.py
Normal file
264
fet2020/finance/forms.py
Normal file
@@ -0,0 +1,264 @@
|
||||
from django import forms
|
||||
from django.core.validators import ValidationError
|
||||
from django.forms import DateInput
|
||||
|
||||
from members.models import Member
|
||||
|
||||
from .models import BankData, Bill, Resolution
|
||||
|
||||
|
||||
class DateInput(DateInput):
|
||||
input_type = "date"
|
||||
|
||||
|
||||
class BankDataForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BankData
|
||||
|
||||
fields = ["iban", "bic", "name"]
|
||||
|
||||
labels = {"iban": "IBAN", "bic": "BIC", "name": "Kontoinhaber:in"}
|
||||
|
||||
|
||||
class BillCreateForm(forms.ModelForm):
|
||||
resolution_text = forms.CharField(max_length=128)
|
||||
name_text = forms.CharField(max_length=128)
|
||||
iban_text = forms.CharField(max_length=34)
|
||||
bic_text = forms.CharField(max_length=11)
|
||||
|
||||
class Meta:
|
||||
model = Bill
|
||||
|
||||
fields = [
|
||||
"bill_creator",
|
||||
"date",
|
||||
"invoice",
|
||||
"amount",
|
||||
"purpose",
|
||||
"affiliation",
|
||||
"payer",
|
||||
"only_digital",
|
||||
"file_field",
|
||||
"comment",
|
||||
"resolution",
|
||||
]
|
||||
|
||||
labels = {
|
||||
"bill_creator": "Verantwortliche:r für die Einreichung",
|
||||
"date": "Rechnungsdatum",
|
||||
"invoice": "Rechnungsaussteller",
|
||||
"amount": "Betrag (EUR)",
|
||||
"purpose": "Verwendungszweck",
|
||||
"affiliation": "Abrechnungsbudget",
|
||||
"payer": "Wie wurde die Rechnung bezahlt?",
|
||||
"only_digital": "Ich habe nur eine digitale Rechnung.",
|
||||
"file_field": "Rechnung hochladen (PDF)",
|
||||
"comment": "Kommentar",
|
||||
}
|
||||
|
||||
widgets = {
|
||||
"date": DateInput(format=("%Y-%m-%d")),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "user" in kwargs:
|
||||
user = kwargs.pop("user")
|
||||
else:
|
||||
user = None
|
||||
|
||||
super().__init__(*args, **kwargs) # to get the self.fields set
|
||||
|
||||
self.fields["bill_creator"].initial = Member.objects.get(username=user.username)
|
||||
self.fields["bill_creator"].disabled = True
|
||||
|
||||
self.fields["invoice"].placeholder = "Firmenname\nStraße\nPLZ Ort"
|
||||
self.fields["invoice"].rows = 3
|
||||
|
||||
# bank data fields
|
||||
self.fields["name_text"].label = "Kontoinhaber:in"
|
||||
self.fields["name_text"].required = False
|
||||
|
||||
self.fields["iban_text"].label = "IBAN"
|
||||
self.fields["iban_text"].required = False
|
||||
|
||||
self.fields["bic_text"].label = "BIC"
|
||||
self.fields["bic_text"].required = False
|
||||
|
||||
# resolution fields
|
||||
self.fields["resolution_text"].label = "Beschlussnummer"
|
||||
self.fields["resolution_text"].required = False
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
amount = cleaned_data.get("amount")
|
||||
resolution = cleaned_data.get("resolution_text")
|
||||
|
||||
payer = cleaned_data.get("payer")
|
||||
name = cleaned_data.get("name_text")
|
||||
iban = cleaned_data.get("iban_text")
|
||||
bic = cleaned_data.get("bic_text")
|
||||
|
||||
only_digital = cleaned_data.get("only_digital")
|
||||
file_field = cleaned_data.get("file_field")
|
||||
|
||||
# check that amount is valid because invalid amount is a NoneType.
|
||||
if amount:
|
||||
if amount > 30 and resolution == "":
|
||||
raise ValidationError(
|
||||
"Die Beschlussnummer fehlt, weil der Betrag über 30€ beträgt "
|
||||
f"(Betrag: {amount}€)."
|
||||
)
|
||||
|
||||
if payer == "M":
|
||||
if name == "" or iban == "":
|
||||
raise ValidationError(
|
||||
f"Bankdaten unvollständig (Kontoinhaber: {name}, IBAN: {iban})."
|
||||
)
|
||||
|
||||
if payer == "V":
|
||||
cleaned_data["name_text"] = ""
|
||||
cleaned_data["iban_text"] = ""
|
||||
cleaned_data["bic_text"] = ""
|
||||
|
||||
if only_digital and file_field is None:
|
||||
raise ValidationError(f"Digitale Rechnung fehlt.")
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class BillUpdateForm(forms.ModelForm):
|
||||
resolution_text = forms.CharField(max_length=128)
|
||||
name_text = forms.CharField(max_length=128)
|
||||
iban_text = forms.CharField(max_length=34)
|
||||
bic_text = forms.CharField(max_length=11)
|
||||
|
||||
class Meta:
|
||||
model = Bill
|
||||
|
||||
fields = [
|
||||
"bill_creator",
|
||||
"date",
|
||||
"invoice",
|
||||
"amount",
|
||||
"purpose",
|
||||
"affiliation",
|
||||
"payer",
|
||||
"only_digital",
|
||||
"file_field",
|
||||
"comment",
|
||||
"resolution",
|
||||
]
|
||||
|
||||
labels = {
|
||||
"bill_creator": "Verantwortliche:r für die Einreichung",
|
||||
"date": "Rechnungsdatum",
|
||||
"invoice": "Rechnungsaussteller",
|
||||
"amount": "Betrag (EUR)",
|
||||
"purpose": "Verwendungszweck",
|
||||
"affiliation": "Abrechnungsbudget",
|
||||
"payer": "Wie wurde die Rechnung bezahlt?",
|
||||
"only_digital": "Ich habe nur eine digitale Rechnung.",
|
||||
"file_field": "Rechnung hochladen (PDF)",
|
||||
"comment": "Kommentar",
|
||||
}
|
||||
|
||||
widgets = {
|
||||
"date": DateInput(format=("%Y-%m-%d")),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs) # to get the self.fields set
|
||||
|
||||
self.fields["bill_creator"].disabled = True
|
||||
self.fields["date"].disabled = True
|
||||
self.fields["invoice"].disabled = True
|
||||
self.fields["amount"].disabled = True
|
||||
self.fields["purpose"].disabled = True
|
||||
self.fields["affiliation"].disabled = True
|
||||
self.fields["payer"].disabled = True
|
||||
self.fields["only_digital"].disabled = True
|
||||
self.fields["file_field"].disabled = True
|
||||
self.fields["resolution"].disabled = True
|
||||
|
||||
# bank data fields
|
||||
if kwargs["instance"].bankdata:
|
||||
self.fields["name_text"].initial = kwargs["instance"].bankdata.name
|
||||
self.fields["iban_text"].initial = kwargs["instance"].bankdata.iban
|
||||
self.fields["bic_text"].initial = kwargs["instance"].bankdata.bic
|
||||
|
||||
self.fields["name_text"].disabled = True
|
||||
self.fields["name_text"].label = "Kontoinhaber:in"
|
||||
self.fields["name_text"].required = False
|
||||
|
||||
self.fields["iban_text"].disabled = True
|
||||
self.fields["iban_text"].label = "IBAN"
|
||||
self.fields["iban_text"].required = False
|
||||
|
||||
self.fields["bic_text"].disabled = True
|
||||
self.fields["bic_text"].label = "BIC"
|
||||
self.fields["bic_text"].required = False
|
||||
|
||||
# resolution fields
|
||||
if kwargs["instance"].resolution:
|
||||
self.fields["resolution_text"].initial = kwargs["instance"].resolution.name
|
||||
|
||||
self.fields["resolution_text"].disabled = True
|
||||
self.fields["resolution_text"].label = "Beschlussnummer"
|
||||
self.fields["resolution_text"].required = False
|
||||
|
||||
# comment disabled when bill is cleared or finished
|
||||
if kwargs["instance"].status != "S":
|
||||
self.fields["comment"].disabled = True
|
||||
|
||||
|
||||
class BankDataAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BankData
|
||||
|
||||
fields = "__all__"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs) # to get the self.fields set
|
||||
|
||||
self.fields["bankdata_creator"].widget.can_add_related = False
|
||||
self.fields["bankdata_creator"].widget.can_change_related = False
|
||||
self.fields["bankdata_creator"].widget.can_delete_related = False
|
||||
|
||||
|
||||
class BillAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Bill
|
||||
|
||||
fields = "__all__"
|
||||
|
||||
labels = {
|
||||
"affiliation": "Abrechnungsbudget",
|
||||
"amount": "Betrag (EUR)",
|
||||
"comment": "Kommentar",
|
||||
"date": "Rechnungsdatum",
|
||||
"file_field": "Rechnung hochladen (PDF)",
|
||||
"invoice": "Rechnungsaussteller",
|
||||
"only_digital": "Ich habe nur eine digitale Rechnung.",
|
||||
"payer": "Wie wurde die Rechnung bezahlt?",
|
||||
"purpose": "Verwendungszweck",
|
||||
"resolution": "Beschlussnummer",
|
||||
}
|
||||
|
||||
widgets = {
|
||||
"date": DateInput(format=("%Y-%m-%d")),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs) # to get the self.fields set
|
||||
|
||||
self.fields["bill_creator"].widget.can_add_related = False
|
||||
self.fields["bill_creator"].widget.can_change_related = False
|
||||
self.fields["bill_creator"].widget.can_delete_related = False
|
||||
|
||||
|
||||
class ResolutionAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Resolution
|
||||
|
||||
fields = "__all__"
|
||||
178
fet2020/finance/migrations/0001_initial.py
Normal file
178
fet2020/finance/migrations/0001_initial.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# Generated by Django 4.2.2 on 2023-08-22 16:57
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("members", "0007_alter_member_username"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BankData",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"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")),
|
||||
(
|
||||
"is_disabled",
|
||||
models.BooleanField(default=False, verbose_name="deaktiviert"),
|
||||
),
|
||||
(
|
||||
"bankdata_creator",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="members.member",
|
||||
verbose_name="Verknüpfung zum Mitglied",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Bankdaten",
|
||||
"verbose_name_plural": "Bankdaten",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Resolution",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.CharField(max_length=128, primary_key=True, serialize=False),
|
||||
),
|
||||
("name", models.CharField(max_length=128)),
|
||||
(
|
||||
"is_visible",
|
||||
models.BooleanField(default=False, verbose_name="sichtbar"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Beschluss",
|
||||
"verbose_name_plural": "Beschlüsse",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Bill",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("date", models.DateField()),
|
||||
("invoice", models.TextField()),
|
||||
(
|
||||
"amount",
|
||||
models.DecimalField(
|
||||
decimal_places=2, max_digits=7, verbose_name="Betrag (EUR)"
|
||||
),
|
||||
),
|
||||
(
|
||||
"purpose",
|
||||
models.CharField(max_length=140, verbose_name="Verwendungszweck"),
|
||||
),
|
||||
(
|
||||
"affiliation",
|
||||
models.CharField(
|
||||
choices=[("V", "Vereinsbudget"), ("B", "Offizielles Budget")],
|
||||
max_length=1,
|
||||
verbose_name="Abrechnungsbudget",
|
||||
),
|
||||
),
|
||||
(
|
||||
"payer",
|
||||
models.CharField(
|
||||
choices=[("M", "Privat"), ("V", "Verein (Safe/Kreditkarte)")],
|
||||
max_length=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"only_digital",
|
||||
models.BooleanField(
|
||||
default=False, verbose_name="Digitale Rechnung"
|
||||
),
|
||||
),
|
||||
(
|
||||
"file_field",
|
||||
models.FileField(
|
||||
blank=True,
|
||||
null=True,
|
||||
upload_to="uploads/finance/files/",
|
||||
validators=[
|
||||
django.core.validators.FileExtensionValidator(["pdf"])
|
||||
],
|
||||
),
|
||||
),
|
||||
("comment", models.TextField(blank=True, null=True)),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("S", "Eingereicht"),
|
||||
("C", "Abgerechnet"),
|
||||
("F", "Abgeschlossen"),
|
||||
],
|
||||
default="S",
|
||||
max_length=1,
|
||||
verbose_name="Status",
|
||||
),
|
||||
),
|
||||
("wiref_id", models.CharField(blank=True, max_length=10, null=True)),
|
||||
("date_created", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"bankdata",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="finance.bankdata",
|
||||
verbose_name="Kontodaten",
|
||||
),
|
||||
),
|
||||
(
|
||||
"bill_creator",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="members.member",
|
||||
verbose_name="Verantwortliche:r",
|
||||
),
|
||||
),
|
||||
(
|
||||
"resolution",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="finance.resolution",
|
||||
verbose_name="Beschlussnummer",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Rechnung",
|
||||
"verbose_name_plural": "Rechnungen",
|
||||
},
|
||||
),
|
||||
]
|
||||
0
fet2020/finance/migrations/__init__.py
Normal file
0
fet2020/finance/migrations/__init__.py
Normal file
121
fet2020/finance/models.py
Normal file
121
fet2020/finance/models.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from django.core.validators import FileExtensionValidator, ValidationError
|
||||
from django.db import models
|
||||
|
||||
from members.models import Member
|
||||
|
||||
|
||||
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")
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
class Resolution(models.Model):
|
||||
id = models.CharField(primary_key=True, max_length=128)
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
is_visible = models.BooleanField(default=False, verbose_name="sichtbar")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Beschluss"
|
||||
verbose_name_plural = "Beschlüsse"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
class Bill(models.Model):
|
||||
# members can be deleted but never their bills
|
||||
bill_creator = models.ForeignKey(
|
||||
Member, on_delete=models.PROTECT, verbose_name="Verantwortliche:r"
|
||||
)
|
||||
|
||||
bankdata = models.ForeignKey(
|
||||
BankData,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="Kontodaten",
|
||||
)
|
||||
|
||||
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", "Offizielles Budget"
|
||||
|
||||
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)
|
||||
|
||||
only_digital = models.BooleanField(default=False, verbose_name="Digitale Rechnung")
|
||||
|
||||
file_field = models.FileField(
|
||||
upload_to="uploads/finance/files/",
|
||||
validators=[FileExtensionValidator(["pdf"])],
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
comment = models.TextField(blank=True, null=True)
|
||||
|
||||
class Status(models.TextChoices):
|
||||
SUBMITTED = "S", "Eingereicht"
|
||||
CLEARED = "C", "Abgerechnet"
|
||||
FINISHED = "F", "Abgeschlossen"
|
||||
|
||||
status = models.CharField(
|
||||
max_length=1,
|
||||
choices=Status.choices,
|
||||
default=Status.SUBMITTED,
|
||||
verbose_name="Status",
|
||||
)
|
||||
wiref_id = models.CharField(max_length=10, blank=True, null=True)
|
||||
|
||||
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
|
||||
0
fet2020/finance/tests.py
Normal file
0
fet2020/finance/tests.py
Normal file
16
fet2020/finance/urls.py
Normal file
16
fet2020/finance/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import apps, views
|
||||
from .views import (
|
||||
BillCreateView,
|
||||
BillListView,
|
||||
BillUpdateView,
|
||||
)
|
||||
|
||||
app_name = apps.FinanceConfig.name
|
||||
|
||||
urlpatterns = [
|
||||
path("", BillListView.as_view(), name="bill_list"),
|
||||
path("<int:pk>/", BillUpdateView.as_view(), name="bill_update"),
|
||||
path("create-bill/", BillCreateView.as_view(), name="bill_create"),
|
||||
]
|
||||
80
fet2020/finance/views.py
Normal file
80
fet2020/finance/views.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
|
||||
from fet2020.utils import add_log_action, create_formsets
|
||||
from members.models import Member
|
||||
|
||||
from .forms import BankDataForm, BillCreateForm, BillUpdateForm
|
||||
from .models import Bill, BankData, Resolution
|
||||
|
||||
|
||||
class BillCreateView(LoginRequiredMixin, CreateView):
|
||||
form_class = BillCreateForm
|
||||
model = Bill
|
||||
success_url = reverse_lazy("finance:bill_list")
|
||||
template_name = "finance/bill_create.html"
|
||||
|
||||
def form_valid(self, form):
|
||||
# get or create resolution object
|
||||
resolution = form.cleaned_data["resolution_text"]
|
||||
if resolution != "":
|
||||
obj, created = Resolution.objects.get_or_create(
|
||||
id=resolution, defaults={"name": resolution}
|
||||
)
|
||||
form.instance.resolution = obj
|
||||
|
||||
# get or create bankdata object
|
||||
name = form.cleaned_data["name_text"]
|
||||
iban = form.cleaned_data["iban_text"]
|
||||
bic = form.cleaned_data["bic_text"]
|
||||
if name != "" and iban != "" and bic != "":
|
||||
obj, created = BankData.objects.get_or_create(
|
||||
name=name,
|
||||
iban=iban,
|
||||
bic=bic,
|
||||
defaults={"bankdata_creator": form.cleaned_data["bill_creator"]},
|
||||
)
|
||||
form.instance.bankdata = obj
|
||||
|
||||
add_log_action(self.request, form, "finance", "bill", True)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
|
||||
class BillListView(LoginRequiredMixin, ListView):
|
||||
model = Bill
|
||||
template_name = "finance/index.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return Bill.objects.filter(bill_creator__username=self.request.user)
|
||||
|
||||
|
||||
class BillUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
||||
form_class = BillUpdateForm
|
||||
model = Bill
|
||||
success_url = reverse_lazy("finance:bill_list")
|
||||
template_name = "finance/bill_update.html"
|
||||
|
||||
def form_valid(self, form):
|
||||
add_log_action(self.request, form, "finance", "bill", False)
|
||||
return super().form_valid(form)
|
||||
|
||||
# call bill if it's only yours
|
||||
def test_func(self):
|
||||
if self.get_object().bill_creator.username == self.request.user.username:
|
||||
return True
|
||||
|
||||
# call handle_no_permissions method
|
||||
return False
|
||||
|
||||
def handle_no_permission(self):
|
||||
return redirect("finance:bill_list")
|
||||
Reference in New Issue
Block a user