Phase 0: forms.py, admin.py und views.py in Domain-Packages aufteilen
- forms.py → forms/ Package (8 Domänen: destinataere, land, finanzen, foerderung, dokumente, veranstaltung, system, geschichte) - admin.py → admin/ Package (7 Domänen, alle 22 @admin.register dekoriert) - views.py (8845 Zeilen) → views/ Package (10 Domänen: dashboard, destinataere, land, paechter, finanzen, foerderung, dokumente, unterstuetzungen, veranstaltung, geschichte, system) - __init__.py in jedem Package re-exportiert alle Symbole für Rückwärtskompatibilität - urls.py bleibt unverändert (funktioniert durch Re-Exports) - Django system check: 0 Fehler, alle URL-Auflösungen funktionieren Keine funktionalen Änderungen – reine Strukturverbesserung für Vision 2026. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
743
app/stiftung/views/finanzen.py
Normal file
743
app/stiftung/views/finanzen.py
Normal file
@@ -0,0 +1,743 @@
|
||||
# views/finanzen.py
|
||||
# Phase 0: Vision 2026 – Code-Refactoring
|
||||
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta, date
|
||||
from decimal import Decimal
|
||||
|
||||
import qrcode
|
||||
import qrcode.image.svg
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import (Avg, Count, DecimalField, F, IntegerField, Q,
|
||||
Sum, Value)
|
||||
from django.db.models.functions import Cast, Coalesce, NullIf, Replace
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django_otp.decorators import otp_required
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||
from django_otp.util import random_hex
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransaction,
|
||||
BriefVorlage, CSVImport, Destinataer,
|
||||
DestinataerEmailEingang, DestinataerNotiz,
|
||||
DestinataerUnterstuetzung,
|
||||
DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
Land, LandAbrechnung, LandVerpachtung, Paechter, Person,
|
||||
Rentmeister, StiftungsKalenderEintrag, StiftungsKonto,
|
||||
UnterstuetzungWiederkehrend, Veranstaltung,
|
||||
Veranstaltungsteilnehmer, Verwaltungskosten,
|
||||
VierteljahresNachweis)
|
||||
from stiftung.forms import (
|
||||
DestinataerForm, DestinataerUnterstuetzungForm, DestinataerNotizForm,
|
||||
FoerderungForm, GeschichteBildForm, GeschichteSeiteForm,
|
||||
LandForm, LandVerpachtungForm, LandAbrechnungForm,
|
||||
PaechterForm, DokumentLinkForm,
|
||||
RentmeisterForm, StiftungsKontoForm, VerwaltungskostenForm,
|
||||
BankTransactionForm, BankImportForm,
|
||||
UnterstuetzungForm, UnterstuetzungWiederkehrendForm,
|
||||
UnterstuetzungMarkAsPaidForm, VierteljahresNachweisForm,
|
||||
UserCreationForm, UserUpdateForm, PasswordChangeForm, UserPermissionForm,
|
||||
TwoFactorSetupForm, TwoFactorVerifyForm, TwoFactorDisableForm,
|
||||
BackupTokenRegenerateForm, PersonForm,
|
||||
VeranstaltungForm, VeranstaltungsteilnehmerForm,
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def bericht_list(request):
|
||||
"""List available reports"""
|
||||
# Get available years from data
|
||||
jahre = sorted(
|
||||
set(
|
||||
list(Foerderung.objects.values_list("jahr", flat=True))
|
||||
+ list(LandVerpachtung.objects.values_list("pachtbeginn__year", flat=True))
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Statistics for overview tiles (removed legacy Person and Verpachtung)
|
||||
total_destinataere = Destinataer.objects.count()
|
||||
total_laendereien = Land.objects.count()
|
||||
total_verpachtungen = LandVerpachtung.objects.count()
|
||||
total_foerderungen = Foerderung.objects.count()
|
||||
|
||||
context = {
|
||||
"jahre": jahre,
|
||||
"title": "Berichte",
|
||||
"total_destinataere": total_destinataere,
|
||||
"total_laendereien": total_laendereien,
|
||||
"total_verpachtungen": total_verpachtungen,
|
||||
"total_foerderungen": total_foerderungen,
|
||||
}
|
||||
return render(request, "stiftung/bericht_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def jahresbericht_generate(request, jahr):
|
||||
"""Generate annual report for a specific year"""
|
||||
# Get data for the year
|
||||
foerderungen = Foerderung.objects.filter(jahr=jahr).select_related("person")
|
||||
verpachtungen = LandVerpachtung.objects.filter(
|
||||
pachtbeginn__year__lte=jahr, pachtende__year__gte=jahr
|
||||
).select_related("land", "paechter")
|
||||
|
||||
# Calculate statistics
|
||||
total_foerderungen = foerderungen.aggregate(total=Sum("betrag"))["total"] or 0
|
||||
total_pachtzins = (
|
||||
verpachtungen.aggregate(total=Sum("pachtzins_pauschal"))["total"] or 0
|
||||
)
|
||||
|
||||
context = {
|
||||
"jahr": jahr,
|
||||
"foerderungen": foerderungen,
|
||||
"verpachtungen": verpachtungen,
|
||||
"total_foerderungen": total_foerderungen,
|
||||
"total_pachtzins": total_pachtzins,
|
||||
"title": f"Jahresbericht {jahr}",
|
||||
}
|
||||
return render(request, "stiftung/jahresbericht.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def jahresbericht_generate_redirect(request):
|
||||
"""Redirects the GET form without path param to the proper URL using the provided query param 'jahr'."""
|
||||
jahr = request.GET.get("jahr")
|
||||
if jahr and str(jahr).isdigit():
|
||||
return redirect("stiftung:jahresbericht_generate", jahr=int(jahr))
|
||||
messages.error(request, "Bitte wählen Sie ein gültiges Jahr aus.")
|
||||
return redirect("stiftung:bericht_list")
|
||||
|
||||
|
||||
@login_required
|
||||
def jahresbericht_pdf(request, jahr):
|
||||
"""Generate PDF version of annual report"""
|
||||
from django.http import HttpResponse
|
||||
from django.template.loader import render_to_string
|
||||
from weasyprint import HTML
|
||||
|
||||
# Get data for the year
|
||||
foerderungen = Foerderung.objects.filter(jahr=jahr).select_related("person")
|
||||
verpachtungen = LandVerpachtung.objects.filter(
|
||||
pachtbeginn__year__lte=jahr, pachtende__year__gte=jahr
|
||||
).select_related("land", "paechter")
|
||||
|
||||
# Calculate statistics
|
||||
total_foerderungen = foerderungen.aggregate(total=Sum("betrag"))["total"] or 0
|
||||
total_pachtzins = (
|
||||
verpachtungen.aggregate(total=Sum("pachtzins_pauschal"))["total"] or 0
|
||||
)
|
||||
|
||||
context = {
|
||||
"jahr": jahr,
|
||||
"foerderungen": foerderungen,
|
||||
"verpachtungen": verpachtungen,
|
||||
"total_foerderungen": total_foerderungen,
|
||||
"total_pachtzins": total_pachtzins,
|
||||
}
|
||||
|
||||
# Render HTML
|
||||
html_string = render_to_string("stiftung/jahresbericht.html", context)
|
||||
|
||||
# Generate PDF
|
||||
pdf = HTML(string=html_string).write_pdf()
|
||||
|
||||
# Create response
|
||||
response = HttpResponse(pdf, content_type="application/pdf")
|
||||
response["Content-Disposition"] = f'attachment; filename="jahresbericht_{jahr}.pdf"'
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# API Views for AJAX
|
||||
@login_required
|
||||
def geschaeftsfuehrung(request):
|
||||
"""Hauptansicht für die Geschäftsführung mit Übersicht"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db.models import Count, Sum
|
||||
|
||||
from stiftung.models import Rentmeister, StiftungsKonto, Verwaltungskosten
|
||||
|
||||
# Rentmeister-Übersicht
|
||||
rentmeister = Rentmeister.objects.filter(aktiv=True).order_by("nachname", "vorname")
|
||||
|
||||
# Konten-Übersicht
|
||||
konten = StiftungsKonto.objects.filter(aktiv=True).order_by(
|
||||
"bank_name", "kontoname"
|
||||
)
|
||||
gesamtsaldo = konten.aggregate(total=Sum("saldo"))["total"] or 0
|
||||
|
||||
# Aktuelle Kosten (letzten 30 Tage)
|
||||
heute = datetime.now().date()
|
||||
vor_30_tagen = heute - timedelta(days=30)
|
||||
|
||||
aktuelle_kosten = Verwaltungskosten.objects.filter(
|
||||
datum__gte=vor_30_tagen
|
||||
).order_by("-datum")[:10]
|
||||
|
||||
# Statistiken
|
||||
kosten_summe_monat = (
|
||||
Verwaltungskosten.objects.filter(datum__gte=vor_30_tagen).aggregate(
|
||||
total=Sum("betrag")
|
||||
)["total"]
|
||||
or 0
|
||||
)
|
||||
|
||||
kosten_statistik = (
|
||||
Verwaltungskosten.objects.filter(datum__gte=vor_30_tagen)
|
||||
.values("kategorie")
|
||||
.annotate(summe=Sum("betrag"), anzahl=Count("id"))
|
||||
.order_by("-summe")
|
||||
)
|
||||
|
||||
context = {
|
||||
"rentmeister": rentmeister,
|
||||
"konten": konten,
|
||||
"gesamtsaldo": gesamtsaldo,
|
||||
"aktuelle_kosten": aktuelle_kosten,
|
||||
"kosten_summe_monat": kosten_summe_monat,
|
||||
"kosten_statistik": kosten_statistik,
|
||||
}
|
||||
|
||||
return render(request, "stiftung/geschaeftsfuehrung.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def konto_list(request):
|
||||
"""Liste aller Stiftungskonten"""
|
||||
from django.db.models import Sum
|
||||
|
||||
from stiftung.models import StiftungsKonto
|
||||
|
||||
konten = StiftungsKonto.objects.all().order_by("bank_name", "kontoname")
|
||||
gesamtsaldo = konten.aggregate(total=Sum("saldo"))["total"] or 0
|
||||
|
||||
context = {
|
||||
"konten": konten,
|
||||
"gesamtsaldo": gesamtsaldo,
|
||||
}
|
||||
|
||||
return render(request, "stiftung/konto_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def verwaltungskosten_list(request):
|
||||
"""Liste aller Verwaltungskosten"""
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from stiftung.models import Verwaltungskosten
|
||||
|
||||
kosten = Verwaltungskosten.objects.all().order_by("-datum", "-erstellt_am")
|
||||
|
||||
# Filter nach Kategorie
|
||||
kategorie_filter = request.GET.get("kategorie")
|
||||
if kategorie_filter:
|
||||
kosten = kosten.filter(kategorie=kategorie_filter)
|
||||
|
||||
# Filter nach Status
|
||||
status_filter = request.GET.get("status")
|
||||
if status_filter:
|
||||
kosten = kosten.filter(status=status_filter)
|
||||
|
||||
# Pagination
|
||||
paginator = Paginator(kosten, 25)
|
||||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
# Für Filter-Dropdowns
|
||||
kategorien = Verwaltungskosten.KATEGORIE_CHOICES
|
||||
status_choices = Verwaltungskosten.STATUS_CHOICES
|
||||
|
||||
context = {
|
||||
"page_obj": page_obj,
|
||||
"kategorien": kategorien,
|
||||
"status_choices": status_choices,
|
||||
"kategorie_filter": kategorie_filter,
|
||||
"status_filter": status_filter,
|
||||
}
|
||||
|
||||
return render(request, "stiftung/verwaltungskosten_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def rentmeister_list(request):
|
||||
"""Liste aller Rentmeister"""
|
||||
from stiftung.models import Rentmeister
|
||||
|
||||
rentmeister = Rentmeister.objects.all().order_by("nachname", "vorname")
|
||||
|
||||
# Aktive/Inaktive aufteilen
|
||||
aktive_rentmeister = rentmeister.filter(aktiv=True)
|
||||
ehemalige_rentmeister = rentmeister.filter(aktiv=False)
|
||||
|
||||
context = {
|
||||
"aktive_rentmeister": aktive_rentmeister,
|
||||
"ehemalige_rentmeister": ehemalige_rentmeister,
|
||||
"total_count": rentmeister.count(),
|
||||
}
|
||||
|
||||
return render(request, "stiftung/rentmeister_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def rentmeister_detail(request, pk):
|
||||
"""Detailansicht eines Rentmeisters mit seinen Ausgaben"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db.models import Count, Q, Sum
|
||||
|
||||
from stiftung.models import Rentmeister, Verwaltungskosten
|
||||
|
||||
rentmeister = get_object_or_404(Rentmeister, pk=pk)
|
||||
|
||||
# Ausgaben des Rentmeisters
|
||||
ausgaben = Verwaltungskosten.objects.filter(rentmeister=rentmeister).order_by(
|
||||
"-datum"
|
||||
)
|
||||
|
||||
# Statistiken
|
||||
heute = datetime.now().date()
|
||||
aktueller_monat = heute.replace(day=1)
|
||||
aktuelles_jahr = heute.replace(month=1, day=1)
|
||||
|
||||
stats = {
|
||||
"gesamt_ausgaben": ausgaben.aggregate(total=Sum("betrag"))["total"] or 0,
|
||||
"monat_ausgaben": ausgaben.filter(datum__gte=aktueller_monat).aggregate(
|
||||
total=Sum("betrag")
|
||||
)["total"]
|
||||
or 0,
|
||||
"jahr_ausgaben": ausgaben.filter(datum__gte=aktuelles_jahr).aggregate(
|
||||
total=Sum("betrag")
|
||||
)["total"]
|
||||
or 0,
|
||||
"anzahl_ausgaben": ausgaben.count(),
|
||||
"offene_ausgaben": ausgaben.exclude(status="bezahlt").count(),
|
||||
}
|
||||
|
||||
# Kategorie-Aufschlüsselung
|
||||
kategorie_stats = (
|
||||
ausgaben.values("kategorie")
|
||||
.annotate(summe=Sum("betrag"), anzahl=Count("id"))
|
||||
.order_by("-summe")
|
||||
)
|
||||
|
||||
# Aktuelle Ausgaben (letzten 30 Tage)
|
||||
vor_30_tagen = heute - timedelta(days=30)
|
||||
aktuelle_ausgaben = ausgaben.filter(datum__gte=vor_30_tagen)[:10]
|
||||
|
||||
# Verknüpfte Dokumente laden
|
||||
from stiftung.models import DokumentLink
|
||||
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
rentmeister_id=rentmeister.id
|
||||
).order_by("-id")[
|
||||
:10
|
||||
] # Neueste 10 Dokumente
|
||||
|
||||
context = {
|
||||
"rentmeister": rentmeister,
|
||||
"ausgaben": ausgaben[:20], # Nur erste 20 für Übersicht
|
||||
"stats": stats,
|
||||
"kategorie_stats": kategorie_stats,
|
||||
"aktuelle_ausgaben": aktuelle_ausgaben,
|
||||
"verknuepfte_dokumente": verknuepfte_dokumente,
|
||||
}
|
||||
|
||||
return render(request, "stiftung/rentmeister_detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def rentmeister_ausgaben(request, pk):
|
||||
"""Vollständige Ausgabenliste eines Rentmeisters mit PDF Export"""
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import models
|
||||
from django.db.models import Count, Q, Sum
|
||||
|
||||
from stiftung.models import Rentmeister, Verwaltungskosten
|
||||
|
||||
rentmeister = get_object_or_404(Rentmeister, pk=pk)
|
||||
|
||||
# Handle PDF export request
|
||||
if request.method == "POST" and "export_pdf" in request.POST:
|
||||
selected_ids = request.POST.getlist("selected_expenses")
|
||||
if selected_ids:
|
||||
# Update status to 'in_bearbeitung' and log each change
|
||||
from stiftung.audit import log_action
|
||||
|
||||
expenses_to_update = Verwaltungskosten.objects.filter(
|
||||
id__in=selected_ids, rentmeister=rentmeister
|
||||
)
|
||||
|
||||
updated_count = 0
|
||||
for expense in expenses_to_update:
|
||||
old_status = expense.status
|
||||
expense.status = "in_bearbeitung"
|
||||
expense.save()
|
||||
updated_count += 1
|
||||
|
||||
# Log the status change
|
||||
log_action(
|
||||
request=request,
|
||||
action="update",
|
||||
entity_type="verwaltungskosten",
|
||||
entity_id=str(expense.pk),
|
||||
entity_name=expense.bezeichnung,
|
||||
description=f'Ausgaben-Status für PDF-Export geändert von "{old_status}" zu "in_bearbeitung"',
|
||||
changes={"status": {"old": old_status, "new": "in_bearbeitung"}},
|
||||
)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f"{updated_count} Ausgaben wurden zur Bearbeitung markiert und sind bereit für PDF Export.",
|
||||
)
|
||||
return redirect(
|
||||
"stiftung:rentmeister_ausgaben_pdf",
|
||||
pk=pk,
|
||||
expense_ids=",".join(selected_ids),
|
||||
)
|
||||
|
||||
# Get expenses grouped by status
|
||||
ausgaben_by_status = {}
|
||||
for status_code, status_name in Verwaltungskosten.STATUS_CHOICES:
|
||||
ausgaben_by_status[status_code] = {
|
||||
"name": status_name,
|
||||
"ausgaben": Verwaltungskosten.objects.filter(
|
||||
rentmeister=rentmeister, status=status_code
|
||||
).order_by("-datum", "-erstellt_am"),
|
||||
"total": Verwaltungskosten.objects.filter(
|
||||
rentmeister=rentmeister, status=status_code
|
||||
).aggregate(total=Sum("betrag"))["total"]
|
||||
or 0,
|
||||
}
|
||||
|
||||
# Get statistics
|
||||
stats = Verwaltungskosten.objects.filter(rentmeister=rentmeister).aggregate(
|
||||
total_count=Count("id"),
|
||||
total_amount=Sum("betrag"),
|
||||
geplant_count=Count("id", filter=Q(status="geplant")),
|
||||
geplant_amount=Sum("betrag", filter=Q(status="geplant")),
|
||||
in_bearbeitung_count=Count("id", filter=Q(status="in_bearbeitung")),
|
||||
in_bearbeitung_amount=Sum("betrag", filter=Q(status="in_bearbeitung")),
|
||||
bezahlt_count=Count("id", filter=Q(status="bezahlt")),
|
||||
bezahlt_amount=Sum("betrag", filter=Q(status="bezahlt")),
|
||||
)
|
||||
|
||||
context = {
|
||||
"rentmeister": rentmeister,
|
||||
"ausgaben_by_status": ausgaben_by_status,
|
||||
"stats": stats,
|
||||
"kategorien": Verwaltungskosten.KATEGORIE_CHOICES,
|
||||
"status_choices": Verwaltungskosten.STATUS_CHOICES,
|
||||
}
|
||||
|
||||
return render(request, "stiftung/rentmeister_ausgaben.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def rentmeister_create(request):
|
||||
"""Erstelle einen neuen Rentmeister"""
|
||||
from stiftung.forms import RentmeisterForm
|
||||
|
||||
if request.method == "POST":
|
||||
form = RentmeisterForm(request.POST)
|
||||
if form.is_valid():
|
||||
rentmeister = form.save()
|
||||
messages.success(
|
||||
request,
|
||||
f"Rentmeister {rentmeister.get_full_name()} wurde erfolgreich angelegt.",
|
||||
)
|
||||
return redirect("stiftung:rentmeister_detail", pk=rentmeister.pk)
|
||||
else:
|
||||
form = RentmeisterForm()
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"title": "Neuen Rentmeister anlegen",
|
||||
"submit_text": "Rentmeister anlegen",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/rentmeister_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def rentmeister_edit(request, pk):
|
||||
"""Bearbeite einen bestehenden Rentmeister"""
|
||||
from stiftung.forms import RentmeisterForm
|
||||
from stiftung.models import Rentmeister
|
||||
|
||||
rentmeister = get_object_or_404(Rentmeister, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
form = RentmeisterForm(request.POST, instance=rentmeister)
|
||||
if form.is_valid():
|
||||
rentmeister = form.save()
|
||||
messages.success(
|
||||
request,
|
||||
f"Rentmeister {rentmeister.get_full_name()} wurde erfolgreich aktualisiert.",
|
||||
)
|
||||
return redirect("stiftung:rentmeister_detail", pk=rentmeister.pk)
|
||||
else:
|
||||
form = RentmeisterForm(instance=rentmeister)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"rentmeister": rentmeister,
|
||||
"title": f"{rentmeister.get_full_name()} bearbeiten",
|
||||
"submit_text": "Änderungen speichern",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/rentmeister_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def konto_create(request):
|
||||
"""Erstelle ein neues Stiftungskonto"""
|
||||
from stiftung.forms import StiftungsKontoForm
|
||||
|
||||
if request.method == "POST":
|
||||
form = StiftungsKontoForm(request.POST)
|
||||
if form.is_valid():
|
||||
konto = form.save()
|
||||
messages.success(
|
||||
request, f"Konto {konto.kontoname} wurde erfolgreich angelegt."
|
||||
)
|
||||
return redirect("stiftung:konto_list")
|
||||
else:
|
||||
form = StiftungsKontoForm()
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"title": "Neues Konto anlegen",
|
||||
"submit_text": "Konto anlegen",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/konto_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def konto_edit(request, pk):
|
||||
"""Bearbeite ein bestehendes Stiftungskonto"""
|
||||
from stiftung.forms import StiftungsKontoForm
|
||||
from stiftung.models import StiftungsKonto
|
||||
|
||||
konto = get_object_or_404(StiftungsKonto, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
form = StiftungsKontoForm(request.POST, instance=konto)
|
||||
if form.is_valid():
|
||||
konto = form.save()
|
||||
messages.success(
|
||||
request, f"Konto {konto.kontoname} wurde erfolgreich aktualisiert."
|
||||
)
|
||||
return redirect("stiftung:konto_list")
|
||||
else:
|
||||
form = StiftungsKontoForm(instance=konto)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"konto": konto,
|
||||
"title": f"Konto {konto.kontoname} bearbeiten",
|
||||
"submit_text": "Änderungen speichern",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/konto_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def konto_detail(request, pk):
|
||||
"""Zeige Details eines Stiftungskontos"""
|
||||
from django.db import models
|
||||
from django.db.models import Count, Max, Q, Sum
|
||||
|
||||
from stiftung.models import BankTransaction, StiftungsKonto
|
||||
|
||||
konto = get_object_or_404(StiftungsKonto, pk=pk)
|
||||
|
||||
# Get transaction statistics
|
||||
transactions = BankTransaction.objects.filter(konto=konto)
|
||||
transaction_stats = transactions.aggregate(
|
||||
total_count=Count("id"),
|
||||
total_eingang=Sum("betrag", filter=Q(betrag__gt=0)),
|
||||
total_ausgang=Sum("betrag", filter=Q(betrag__lt=0)),
|
||||
last_transaction_date=Max("datum"),
|
||||
)
|
||||
|
||||
# Recent transactions
|
||||
recent_transactions = transactions.order_by("-datum", "-importiert_am")[:10]
|
||||
|
||||
context = {
|
||||
"konto": konto,
|
||||
"transaction_stats": transaction_stats,
|
||||
"recent_transactions": recent_transactions,
|
||||
}
|
||||
|
||||
return render(request, "stiftung/konto_detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def verwaltungskosten_create(request):
|
||||
"""Erstelle neue Verwaltungskosten"""
|
||||
from stiftung.forms import VerwaltungskostenForm
|
||||
from stiftung.models import Rentmeister
|
||||
|
||||
# Check if we're coming from a specific Rentmeister
|
||||
rentmeister_id = request.GET.get("rentmeister")
|
||||
initial_data = {}
|
||||
redirect_url = "stiftung:verwaltungskosten_list"
|
||||
|
||||
if rentmeister_id:
|
||||
try:
|
||||
rentmeister = Rentmeister.objects.get(pk=rentmeister_id)
|
||||
initial_data["rentmeister"] = rentmeister
|
||||
redirect_url = "stiftung:rentmeister_detail"
|
||||
except Rentmeister.DoesNotExist:
|
||||
pass
|
||||
|
||||
if request.method == "POST":
|
||||
form = VerwaltungskostenForm(request.POST)
|
||||
if form.is_valid():
|
||||
kosten = form.save()
|
||||
messages.success(
|
||||
request,
|
||||
f'Verwaltungskosten "{kosten.bezeichnung}" wurden erfolgreich angelegt.',
|
||||
)
|
||||
if rentmeister_id:
|
||||
return redirect(redirect_url, pk=rentmeister_id)
|
||||
return redirect("stiftung:verwaltungskosten_list")
|
||||
else:
|
||||
form = VerwaltungskostenForm(initial=initial_data)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"title": "Neue Verwaltungskosten anlegen",
|
||||
"submit_text": "Kosten anlegen",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/verwaltungskosten_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def verwaltungskosten_edit(request, pk):
|
||||
"""Bearbeite bestehende Verwaltungskosten"""
|
||||
from stiftung.forms import VerwaltungskostenForm
|
||||
from stiftung.models import Verwaltungskosten
|
||||
|
||||
verwaltungskosten = get_object_or_404(Verwaltungskosten, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
form = VerwaltungskostenForm(request.POST, instance=verwaltungskosten)
|
||||
if form.is_valid():
|
||||
verwaltungskosten = form.save()
|
||||
messages.success(
|
||||
request,
|
||||
f'Verwaltungskosten "{verwaltungskosten.bezeichnung}" wurden erfolgreich aktualisiert.',
|
||||
)
|
||||
return redirect("stiftung:verwaltungskosten_list")
|
||||
else:
|
||||
form = VerwaltungskostenForm(instance=verwaltungskosten)
|
||||
|
||||
context = {
|
||||
"form": form,
|
||||
"verwaltungskosten": verwaltungskosten,
|
||||
"title": f"Verwaltungskosten bearbeiten: {verwaltungskosten.bezeichnung}",
|
||||
"submit_text": "Änderungen speichern",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/verwaltungskosten_form.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def verwaltungskosten_delete(request, pk):
|
||||
"""Lösche Verwaltungskosten"""
|
||||
from stiftung.models import Verwaltungskosten
|
||||
|
||||
verwaltungskosten = get_object_or_404(Verwaltungskosten, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
bezeichnung = verwaltungskosten.bezeichnung
|
||||
|
||||
# Log the deletion
|
||||
from stiftung.audit import log_action
|
||||
log_action(
|
||||
request=request,
|
||||
action="delete",
|
||||
entity_type="verwaltungskosten",
|
||||
entity_id=str(verwaltungskosten.pk),
|
||||
entity_name=bezeichnung,
|
||||
description=f'Verwaltungskosten "{bezeichnung}" wurden gelöscht',
|
||||
)
|
||||
|
||||
verwaltungskosten.delete()
|
||||
messages.success(
|
||||
request,
|
||||
f'Verwaltungskosten "{bezeichnung}" wurden erfolgreich gelöscht.',
|
||||
)
|
||||
return redirect("stiftung:verwaltungskosten_list")
|
||||
|
||||
context = {
|
||||
"verwaltungskosten": verwaltungskosten,
|
||||
"title": f"Verwaltungskosten löschen: {verwaltungskosten.bezeichnung}",
|
||||
}
|
||||
|
||||
return render(request, "stiftung/verwaltungskosten_delete.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def mark_expense_paid(request):
|
||||
"""Markiere eine Ausgabe als bezahlt"""
|
||||
if request.method == "POST":
|
||||
expense_id = request.POST.get("expense_id")
|
||||
if expense_id:
|
||||
try:
|
||||
from stiftung.models import Verwaltungskosten
|
||||
|
||||
expense = Verwaltungskosten.objects.get(pk=expense_id)
|
||||
old_status = expense.status
|
||||
expense.status = "bezahlt"
|
||||
expense.save()
|
||||
|
||||
# Log the status change
|
||||
from stiftung.audit import log_action
|
||||
|
||||
log_action(
|
||||
request=request,
|
||||
action="update",
|
||||
entity_type="verwaltungskosten",
|
||||
entity_id=str(expense.pk),
|
||||
entity_name=expense.bezeichnung,
|
||||
description=f'Ausgaben-Status geändert von "{old_status}" zu "bezahlt"',
|
||||
changes={"status": {"old": old_status, "new": "bezahlt"}},
|
||||
)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f'Ausgabe "{expense.bezeichnung}" wurde als bezahlt markiert.',
|
||||
)
|
||||
return redirect(
|
||||
"stiftung:rentmeister_ausgaben", pk=expense.rentmeister.pk
|
||||
)
|
||||
except Verwaltungskosten.DoesNotExist:
|
||||
messages.error(request, "Ausgabe nicht gefunden.")
|
||||
|
||||
return redirect("stiftung:verwaltungskosten_list")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ADMINISTRATION VIEWS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user