- 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>
744 lines
24 KiB
Python
744 lines
24 KiB
Python
# 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
|
||
# =============================================================================
|
||
|
||
|