add new feature finance (submit bill)

This commit is contained in:
2023-08-22 20:10:38 +00:00
parent eeeeaa50dc
commit 7d2091b0a9
17 changed files with 1079 additions and 0 deletions

View File

@@ -59,6 +59,7 @@ INSTALLED_APPS = [
"tasks.apps.TasksConfig",
"gallery.apps.GalleryConfig",
"intern.apps.InternConfig",
"finance.apps.FinanceConfig",
]

View File

@@ -33,6 +33,7 @@ urlpatterns = [
path("auth/", include("authentications.urls")),
path("api/", include(router.urls)),
path("ckeditor/", include("ckeditor_uploader.urls")),
path("finance/", include("finance.urls")),
path("gallery/", include("gallery.urls")),
path("intern/", include("intern.urls")),
path("jobs/", include("blackboard.urls")),

View File

183
fet2020/finance/admin.py Normal file
View 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
View 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
View 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__"

View 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",
},
),
]

View File

121
fet2020/finance/models.py Normal file
View 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
View File

16
fet2020/finance/urls.py Normal file
View 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
View 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")

View 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>

View 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 %}

View 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 %}

View 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 %}

View File

@@ -42,6 +42,18 @@
<span class="text-sm font-medium">Neue Fachschaftssitzung</span>
</a>
</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>
</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">