- Rename DestinataerEmailEingang → EmailEingang with category support (destinataer, rechnung, land_pacht, stiftungsgeschichte, allgemein) - Add invoice capture workflow: create Verwaltungskosten from email, link DMS documents as invoice attachments, track payment status - Add Stiftungsgeschichte email category with auto-detection patterns (Ahnenforschung, Genealogie, Chronik, etc.) and DMS integration - Update poll_emails task with category detection and DMS context mapping - Show available history documents in Geschichte editor sidebar - Consolidate DMS views, remove legacy dokument templates - Update all detail/form templates for DMS document linking - Add deploy.sh script and streamline compose.yml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
237 lines
8.1 KiB
Python
237 lines
8.1 KiB
Python
# views/foerderung.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,
|
||
DokumentDatei, 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 foerderung_list(request):
|
||
"""List all funding grants with filtering and pagination"""
|
||
foerderungen = Foerderung.objects.select_related(
|
||
"destinataer", "verwendungsnachweis"
|
||
).all()
|
||
|
||
# Check for export request - handle both GET and POST
|
||
export_format = (
|
||
request.POST.get("format")
|
||
if request.method == "POST"
|
||
else request.GET.get("format", "")
|
||
)
|
||
selected_ids_param = (
|
||
request.POST.get("selected_entries", "")
|
||
if request.method == "POST"
|
||
else request.GET.get("selected_entries", "")
|
||
)
|
||
selected_ids = (
|
||
[id for id in selected_ids_param.split(",") if id] if selected_ids_param else []
|
||
)
|
||
|
||
# Filtering
|
||
jahr = request.GET.get("jahr")
|
||
kategorie = request.GET.get("kategorie")
|
||
status = request.GET.get("status")
|
||
destinataer = request.GET.get("destinataer")
|
||
|
||
if jahr:
|
||
foerderungen = foerderungen.filter(jahr=int(jahr))
|
||
if kategorie:
|
||
foerderungen = foerderungen.filter(kategorie=kategorie)
|
||
if status:
|
||
foerderungen = foerderungen.filter(status=status)
|
||
if destinataer:
|
||
foerderungen = foerderungen.filter(destinataer__nachname__icontains=destinataer)
|
||
|
||
# Handle exports
|
||
if export_format == "csv":
|
||
return export_foerderungen_csv(request, foerderungen, selected_ids)
|
||
elif export_format == "pdf":
|
||
return export_foerderungen_pdf(request, foerderungen, selected_ids)
|
||
|
||
# Pagination
|
||
paginator = Paginator(foerderungen, 25)
|
||
page_number = request.GET.get("page")
|
||
page_obj = paginator.get_page(page_number)
|
||
|
||
# Statistics
|
||
total_betrag = foerderungen.aggregate(total=Sum("betrag"))["total"] or 0
|
||
avg_betrag = foerderungen.aggregate(avg=Avg("betrag"))["avg"] or 0
|
||
|
||
# Year choices for filters
|
||
jahre = sorted(
|
||
set(list(Foerderung.objects.values_list("jahr", flat=True))), reverse=True
|
||
)
|
||
|
||
context = {
|
||
"page_obj": page_obj,
|
||
"foerderungen": foerderungen, # Add for counting
|
||
"total_betrag": total_betrag,
|
||
"avg_betrag": avg_betrag,
|
||
"kategorien": Foerderung.KATEGORIE_CHOICES,
|
||
"status_choices": Foerderung.STATUS_CHOICES,
|
||
"filter_jahr": jahr,
|
||
"filter_kategorie": kategorie,
|
||
"filter_status": status,
|
||
"filter_person": destinataer,
|
||
"jahre": jahre,
|
||
}
|
||
return render(request, "stiftung/foerderung_list.html", context)
|
||
|
||
|
||
@login_required
|
||
def foerderung_detail(request, pk):
|
||
"""Show details of a specific funding grant"""
|
||
foerderung = get_object_or_404(
|
||
Foerderung.objects.select_related("person", "verwendungsnachweis"), pk=pk
|
||
)
|
||
|
||
# Alle mit dieser Förderung verknüpften Dokumente laden
|
||
verknuepfte_dokumente = DokumentDatei.objects.filter(
|
||
foerderung=foerderung
|
||
).order_by("kontext", "titel")
|
||
|
||
context = {
|
||
"foerderung": foerderung,
|
||
"verknuepfte_dokumente": verknuepfte_dokumente,
|
||
"title": f"Förderung: {foerderung}",
|
||
}
|
||
return render(request, "stiftung/foerderung_detail.html", context)
|
||
|
||
|
||
@login_required
|
||
def foerderung_create(request):
|
||
"""Create a new funding grant"""
|
||
# Get destinataer from URL parameter if provided
|
||
destinataer_id = request.GET.get("destinataer")
|
||
initial = {}
|
||
if destinataer_id:
|
||
initial["destinataer"] = destinataer_id
|
||
|
||
if request.method == "POST":
|
||
form = FoerderungForm(request.POST)
|
||
if form.is_valid():
|
||
foerderung = form.save()
|
||
messages.success(
|
||
request,
|
||
f"Förderung für {foerderung.destinataer} wurde erfolgreich erstellt.",
|
||
)
|
||
return redirect("stiftung:foerderung_detail", pk=foerderung.pk)
|
||
else:
|
||
form = FoerderungForm(initial=initial)
|
||
|
||
context = {
|
||
"form": form,
|
||
"title": "Neue Förderung erstellen",
|
||
}
|
||
return render(request, "stiftung/foerderung_form.html", context)
|
||
|
||
|
||
@login_required
|
||
def foerderung_update(request, pk):
|
||
"""Update an existing funding grant"""
|
||
foerderung = get_object_or_404(Foerderung, pk=pk)
|
||
|
||
if request.method == "POST":
|
||
form = FoerderungForm(request.POST, instance=foerderung)
|
||
if form.is_valid():
|
||
form.save()
|
||
messages.success(
|
||
request,
|
||
f"Förderung für {foerderung.person} wurde erfolgreich aktualisiert.",
|
||
)
|
||
return redirect("stiftung:foerderung_detail", pk=foerderung.pk)
|
||
else:
|
||
form = FoerderungForm(instance=foerderung)
|
||
|
||
context = {
|
||
"form": form,
|
||
"foerderung": foerderung,
|
||
"title": f"Förderung bearbeiten: {foerderung}",
|
||
}
|
||
return render(request, "stiftung/foerderung_form.html", context)
|
||
|
||
|
||
@login_required
|
||
def foerderung_delete(request, pk):
|
||
"""Delete a funding grant"""
|
||
foerderung = get_object_or_404(Foerderung, pk=pk)
|
||
|
||
if request.method == "POST":
|
||
# Get the recipient name before deletion
|
||
recipient_name = (
|
||
foerderung.destinataer.get_full_name()
|
||
if foerderung.destinataer
|
||
else (
|
||
foerderung.person.get_full_name()
|
||
if foerderung.person
|
||
else "Unbekannter Empfänger"
|
||
)
|
||
)
|
||
|
||
foerderung.delete()
|
||
messages.success(
|
||
request, f"Förderung für {recipient_name} wurde erfolgreich gelöscht."
|
||
)
|
||
return redirect("stiftung:foerderung_list")
|
||
|
||
context = {
|
||
"foerderung": foerderung,
|
||
"title": f"Förderung löschen: {foerderung}",
|
||
}
|
||
return render(request, "stiftung/foerderung_confirm_delete.html", context)
|
||
|
||
|
||
# DokumentLink Views
|