Phase 4: SEPA-Validierung (schwifty), Globale Suche (Cmd+K) & Jahresbericht-Modul

- SEPA-Export: IBAN/BIC-Validierung via schwifty, Schuldner-Konto aus StiftungsKonto
- Globale Suche: Cmd+K Modal über Destinatäre, Pächter, Ländereien, Förderungen, Dokumente
- Jahresbericht: Vollständige Jahresbilanz mit Einnahmen/Ausgaben/Netto, Unterstützungen,
  Landabrechnungen, Verwaltungskosten nach Kategorie, PDF-Export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-11 12:57:36 +00:00
parent a79a0989d6
commit 2be72c3990
8 changed files with 695 additions and 160 deletions

View File

@@ -86,29 +86,86 @@ def bericht_list(request):
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
def _jahresbericht_context(jahr):
"""Phase 4: Aggregiert alle Daten für den Jahresbericht."""
from stiftung.models import (
DestinataerUnterstuetzung, LandAbrechnung, Verwaltungskosten,
)
context = {
# Förderungen (legacy)
foerderungen = Foerderung.objects.filter(jahr=jahr).select_related("destinataer", "person")
total_foerderungen_legacy = foerderungen.aggregate(total=Sum("betrag"))["total"] or 0
# Unterstützungen (Phase 2 neue Pipeline)
unterstuetzungen = DestinataerUnterstuetzung.objects.filter(
faellig_am__year=jahr
).exclude(status="storniert").select_related("destinataer", "konto")
unterstuetzungen_ausgezahlt = unterstuetzungen.filter(
status__in=["ausgezahlt", "abgeschlossen"]
)
total_unterstuetzungen = unterstuetzungen_ausgezahlt.aggregate(total=Sum("betrag"))["total"] or 0
# Gesamtausgaben Förderung
total_ausgaben_foerderung = total_foerderungen_legacy + total_unterstuetzungen
# Verpachtungen
verpachtungen = LandVerpachtung.objects.filter(
pachtbeginn__year__lte=jahr
).filter(
Q(pachtende__isnull=True) | Q(pachtende__year__gte=jahr)
).select_related("land", "paechter")
total_pachtzins = verpachtungen.aggregate(total=Sum("pachtzins_pauschal"))["total"] or 0
# Landabrechnungen für das Jahr
landabrechnungen = LandAbrechnung.objects.filter(
abrechnungsjahr=jahr
).select_related("land")
pacht_vereinnahmt = landabrechnungen.aggregate(total=Sum("pacht_vereinnahmt"))["total"] or 0
grundsteuer_gesamt = landabrechnungen.aggregate(total=Sum("grundsteuer_betrag"))["total"] or 0
# Verwaltungskosten
verwaltungskosten_qs = Verwaltungskosten.objects.filter(datum__year=jahr)
total_verwaltungskosten = verwaltungskosten_qs.aggregate(total=Sum("betrag"))["total"] or 0
verwaltungskosten_nach_kategorie = (
verwaltungskosten_qs
.values("kategorie")
.annotate(summe=Sum("betrag"), anzahl=Count("id"))
.order_by("-summe")
)
# Gesamtbilanz
total_einnahmen = pacht_vereinnahmt if pacht_vereinnahmt else total_pachtzins
total_ausgaben = total_ausgaben_foerderung + total_verwaltungskosten
netto = total_einnahmen - total_ausgaben
return {
"jahr": jahr,
"foerderungen": foerderungen,
"verpachtungen": verpachtungen,
"total_foerderungen": total_foerderungen,
"total_pachtzins": total_pachtzins,
"title": f"Jahresbericht {jahr}",
"foerderungen": foerderungen,
"total_foerderungen_legacy": total_foerderungen_legacy,
"unterstuetzungen": unterstuetzungen,
"unterstuetzungen_ausgezahlt": unterstuetzungen_ausgezahlt,
"total_unterstuetzungen": total_unterstuetzungen,
"total_ausgaben_foerderung": total_ausgaben_foerderung,
"verpachtungen": verpachtungen,
"total_pachtzins": total_pachtzins,
"landabrechnungen": landabrechnungen,
"pacht_vereinnahmt": pacht_vereinnahmt,
"grundsteuer_gesamt": grundsteuer_gesamt,
"verwaltungskosten_nach_kategorie": verwaltungskosten_nach_kategorie,
"total_verwaltungskosten": total_verwaltungskosten,
"total_einnahmen": total_einnahmen,
"total_ausgaben": total_ausgaben,
"netto": netto,
# Rückwärtskompatibilität
"total_foerderungen": total_ausgaben_foerderung,
}
@login_required
def jahresbericht_generate(request, jahr):
"""Phase 4: Jahresbericht mit aggregierten Finanzdaten."""
context = _jahresbericht_context(jahr)
return render(request, "stiftung/jahresbericht.html", context)
@@ -124,30 +181,12 @@ def jahresbericht_generate_redirect(request):
@login_required
def jahresbericht_pdf(request, jahr):
"""Generate PDF version of annual report"""
"""Phase 4: PDF-Export des Jahresberichts."""
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,
}
context = _jahresbericht_context(jahr)
# Render HTML
html_string = render_to_string("stiftung/jahresbericht.html", context)