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

@@ -2137,3 +2137,94 @@ from stiftung.models import GeschichteSeite, GeschichteBild
from stiftung.forms import GeschichteSeiteForm, GeschichteBildForm
# =============================================================================
# Phase 4: GLOBALE SUCHE (Cmd+K)
# =============================================================================
@login_required
def globale_suche_api(request):
"""Phase 4: AJAX-Endpunkt für globale Suche über alle Bereiche."""
from django.http import JsonResponse
from stiftung.models import (
Destinataer, Paechter, Land, LandVerpachtung, Foerderung,
)
from stiftung.models.dokumente import DokumentDatei
q = request.GET.get("q", "").strip()
if len(q) < 2:
return JsonResponse({"results": []})
results = []
# Destinatäre
for d in Destinataer.objects.filter(
Q(vorname__icontains=q) | Q(nachname__icontains=q) | Q(email__icontains=q)
)[:5]:
results.append({
"typ": "Destinatär",
"titel": d.get_full_name(),
"untertitel": d.email or "",
"url": f"/destinataere/{d.pk}/",
"icon": "fas fa-user",
})
# Pächter
for p in Paechter.objects.filter(
Q(vorname__icontains=q) | Q(nachname__icontains=q) | Q(email__icontains=q)
)[:5]:
results.append({
"typ": "Pächter",
"titel": p.get_full_name(),
"untertitel": p.email or "",
"url": f"/paechter/{p.pk}/",
"icon": "fas fa-user-tie",
})
# Ländereien
for l in Land.objects.filter(
Q(gemeinde__icontains=q) | Q(gemarkung__icontains=q) | Q(lfd_nr__icontains=q)
)[:5]:
results.append({
"typ": "Länderei",
"titel": str(l),
"untertitel": l.gemeinde or "",
"url": f"/laendereien/{l.pk}/",
"icon": "fas fa-map",
})
# Förderungen suche über Destinatär-Name oder Bemerkungen
for f in Foerderung.objects.filter(
Q(destinataer__vorname__icontains=q)
| Q(destinataer__nachname__icontains=q)
| Q(bemerkungen__icontains=q)
).select_related("destinataer", "person")[:5]:
empfaenger = (
f.destinataer.get_full_name() if f.destinataer
else (f.person.get_full_name() if f.person else "Unbekannt")
)
results.append({
"typ": "Förderung",
"titel": f"{empfaenger} ({f.jahr})",
"untertitel": f"{f.betrag} · {f.get_status_display()}",
"url": f"/foerderungen/{f.pk}/",
"icon": "fas fa-gift",
})
# Dokumente (DMS)
try:
for dok in DokumentDatei.objects.filter(
Q(titel__icontains=q) | Q(beschreibung__icontains=q)
)[:5]:
results.append({
"typ": "Dokument",
"titel": dok.titel,
"untertitel": dok.get_kontext_display(),
"url": f"/dms/{dok.pk}/",
"icon": "fas fa-file-alt",
})
except Exception:
pass
return JsonResponse({"results": results, "query": q})