add new feature finance (submit bill)
This commit is contained in:
@@ -59,6 +59,7 @@ INSTALLED_APPS = [
|
|||||||
"tasks.apps.TasksConfig",
|
"tasks.apps.TasksConfig",
|
||||||
"gallery.apps.GalleryConfig",
|
"gallery.apps.GalleryConfig",
|
||||||
"intern.apps.InternConfig",
|
"intern.apps.InternConfig",
|
||||||
|
"finance.apps.FinanceConfig",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ urlpatterns = [
|
|||||||
path("auth/", include("authentications.urls")),
|
path("auth/", include("authentications.urls")),
|
||||||
path("api/", include(router.urls)),
|
path("api/", include(router.urls)),
|
||||||
path("ckeditor/", include("ckeditor_uploader.urls")),
|
path("ckeditor/", include("ckeditor_uploader.urls")),
|
||||||
|
path("finance/", include("finance.urls")),
|
||||||
path("gallery/", include("gallery.urls")),
|
path("gallery/", include("gallery.urls")),
|
||||||
path("intern/", include("intern.urls")),
|
path("intern/", include("intern.urls")),
|
||||||
path("jobs/", include("blackboard.urls")),
|
path("jobs/", include("blackboard.urls")),
|
||||||
|
|||||||
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")
|
||||||
24
fet2020/templates/baseform/text_with_suggestions.html
Normal file
24
fet2020/templates/baseform/text_with_suggestions.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">{{ field.label }}</span>
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<div class="alert-body">{{ field.errors }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="{{ field.name }}"
|
||||||
|
{% if field.value %}value="{{ field.value }}"{% endif %}
|
||||||
|
{% if field.field.required %}required{% endif %}
|
||||||
|
{% if field.field.disabled %}disabled{% endif %}
|
||||||
|
class="mt-1 block w-full disabled:bg-gray-200 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50"
|
||||||
|
>
|
||||||
|
<a href="" class="block btn btn-primary md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square my-2"></i></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if field.help_text %}
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">{{ field.help_text }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
67
fet2020/templates/finance/bill_create.html
Normal file
67
fet2020/templates/finance/bill_create.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Neue Rechnung einreichen{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="container mx-auto w-full px-4 my-8 flex-1">
|
||||||
|
<h1 class="page-title">Neue Rechnung einreichen</h1>
|
||||||
|
<div class="w-full h-full flex-1 flex justify-center items-center">
|
||||||
|
<form action="" enctype="multipart/form-data" method="POST" class="w-full max-w-xs sm:max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include "baseform/non_field_errors.html" %}
|
||||||
|
|
||||||
|
{% include "baseform/select.html" with field=form.bill_creator %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Bankdaten</span>
|
||||||
|
|
||||||
|
{% include "baseform/select.html" with field=form.payer %}
|
||||||
|
{% include "baseform/text_with_suggestions.html" with field=form.name_text %}
|
||||||
|
{% include "baseform/text.html" with field=form.iban_text %}
|
||||||
|
{% include "baseform/text.html" with field=form.bic_text %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Rechnung</span>
|
||||||
|
|
||||||
|
{% include "baseform/date.html" with field=form.date %}
|
||||||
|
{% include "baseform/textarea.html" with field=form.invoice %}
|
||||||
|
{% include "baseform/text.html" with field=form.purpose %}
|
||||||
|
|
||||||
|
<label class="block">
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">{{ form.amount.label }}</span>
|
||||||
|
{% if form.amount.errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<div class="alert-body">{{ form.amount.errors }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={{ form.amount.value }}
|
||||||
|
min="0.00"
|
||||||
|
step="0.01"
|
||||||
|
name="amount"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{% include "baseform/checkbox.html" with field=form.only_digital %}
|
||||||
|
{% include "baseform/file.html" with field=form.file_field %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Beschlusslage</span>
|
||||||
|
|
||||||
|
{% include "baseform/select.html" with field=form.affiliation %}
|
||||||
|
{% include "baseform/text.html" with field=form.resolution_text %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
{% include "baseform/textarea.html" with field=form.comment %}
|
||||||
|
|
||||||
|
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
|
||||||
|
<input type="submit" class="block btn btn-primary" value="Einreichen">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
70
fet2020/templates/finance/bill_update.html
Normal file
70
fet2020/templates/finance/bill_update.html
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Rechnung {{ object.pk }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="container mx-auto w-full px-4 my-8 flex-1">
|
||||||
|
<h1 class="page-title">Rechnung {{ object.pk }}</h1>
|
||||||
|
<div class="w-full h-full flex-1 flex justify-center items-center">
|
||||||
|
<form action="" enctype="multipart/form-data" method="POST" class="w-full max-w-xs sm:max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include "baseform/non_field_errors.html" %}
|
||||||
|
|
||||||
|
{% include "baseform/select.html" with field=form.bill_creator %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Bankdaten</span>
|
||||||
|
|
||||||
|
{% include "baseform/select.html" with field=form.payer %}
|
||||||
|
{% if form.name_text.value %}
|
||||||
|
{% include "baseform/text.html" with field=form.name_text %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.iban_text.value %}
|
||||||
|
{% include "baseform/text.html" with field=form.iban_text %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.bic_text.value %}
|
||||||
|
{% include "baseform/text.html" with field=form.bic_text %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Rechnung</span>
|
||||||
|
|
||||||
|
{% include "baseform/text.html" with field=form.date %}
|
||||||
|
{% include "baseform/textarea.html" with field=form.invoice %}
|
||||||
|
{% include "baseform/text.html" with field=form.purpose %}
|
||||||
|
{% include "baseform/text.html" with field=form.amount %}
|
||||||
|
|
||||||
|
{% if form.file_field.value %}
|
||||||
|
{% include "baseform/checkbox.html" with field=form.only_digital %}
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Derzeit:</span>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="{{ form.file_field.value.url }}"
|
||||||
|
class="text-gray-700 dark:text-gray-200 block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50"
|
||||||
|
>{{ form.file_field.value }}</a>
|
||||||
|
</label>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Keine digitale Rechnung eingereicht.</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200">Beschlusslage</span>
|
||||||
|
|
||||||
|
{% include "baseform/select.html" with field=form.affiliation %}
|
||||||
|
{% if form.resolution_text.value %}
|
||||||
|
{% include "baseform/text.html" with field=form.resolution_text %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
{% include "baseform/textarea.html" with field=form.comment %}
|
||||||
|
|
||||||
|
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
|
||||||
|
<input type="submit" class="block btn btn-primary" value="Speichern">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
50
fet2020/templates/finance/index.html
Normal file
50
fet2020/templates/finance/index.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Übersicht über die Rechnungen{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="container mx-auto w-full px-4 my-8 flex-1">
|
||||||
|
<h1 class="page-title">Übersicht über alle von dir eingereichten Rechnungen</h1>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="mx-auto max-w-prose flex flex-col gap-4">
|
||||||
|
|
||||||
|
{% for result in object_list %}
|
||||||
|
<a class="flex gap-x-4 group" href="{% url 'finance:bill_update' result.id %}">
|
||||||
|
<article class="flex-grow-0 self-center">
|
||||||
|
<h2 class="line-clamp-1 hover:underline decoration-1 text-gray-800 dark:text-gray-200 font-medium">Verwendungszweck: {{ result.purpose }}</h2>
|
||||||
|
<ul class="text-gray-700 dark:text-gray-300 text-sm sm:text-base">
|
||||||
|
<li><i class="fa-fw text-gray-600 dark:text-gray-400 mr-1"></i>{{ result.date }}</li>
|
||||||
|
<li><i class="fa-fw text-gray-600 dark:text-gray-400 mr-1"></i>{{ result.amount }}€</li>
|
||||||
|
<li><i class="fa-fw text-gray-600 dark:text-gray-400 mr-1"></i>Status: {{ result.get_status_display }}</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% if not object_list %}
|
||||||
|
<section>
|
||||||
|
<div class="mx-auto max-w-prose flex flex-col gap-4">
|
||||||
|
<h2 class="mb-1 text-gray-700 dark:text-gray-200">Keine Rechnungen für dich in dieser Liste.</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 max-w-prose mx-auto text-gray-700 dark:text-gray-300">
|
||||||
|
<section>
|
||||||
|
<div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4">
|
||||||
|
<a
|
||||||
|
href="{% url 'finance:bill_create' %}"
|
||||||
|
class="btn btn-primary block md:flex-grow lg:flex-grow-0"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-plus-square mr-2"></i>Rechnung einreichen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
@@ -42,6 +42,18 @@
|
|||||||
<span class="text-sm font-medium">Neue Fachschaftssitzung</span>
|
<span class="text-sm font-medium">Neue Fachschaftssitzung</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'finance:bill_create' %}" class="flex items-center py-2 px-5 hover:bg-gray-100 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white">
|
||||||
|
<i class="fa-solid fa-plus mr-2"></i>
|
||||||
|
<span class="text-sm font-medium">Neue Rechnung einreichen</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'finance:bill_list' %}" class="flex items-center py-2 px-5 hover:bg-gray-100 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white">
|
||||||
|
<i class="fa-solid fa-list mr-2"></i>
|
||||||
|
<span class="text-sm font-medium">Übersicht über alle von dir eingereichten Rechnungen</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" data-dial-toggle="speed-dial-menu-dropdown" aria-controls="speed-dial-menu-dropdown" aria-expanded="false" class="flex justify-center items-center ml-auto w-14 h-14 text-white bg-blue-700 rounded-full hover:bg-blue-800 dark:bg-blue-600 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 focus:outline-none dark:focus:ring-blue-800">
|
<button type="button" data-dial-toggle="speed-dial-menu-dropdown" aria-controls="speed-dial-menu-dropdown" aria-expanded="false" class="flex justify-center items-center ml-auto w-14 h-14 text-white bg-blue-700 rounded-full hover:bg-blue-800 dark:bg-blue-600 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 focus:outline-none dark:focus:ring-blue-800">
|
||||||
|
|||||||
Reference in New Issue
Block a user