From 4d751d861d4c5131a24d3b7786d38e3b9ff8a79f Mon Sep 17 00:00:00 2001
From: SysAdmin Agent
Date: Sat, 21 Mar 2026 22:43:01 +0000
Subject: [PATCH] =?UTF-8?q?DSGVO-Compliance:=20Einwilligung,=20Datenschutz?=
=?UTF-8?q?erkl=C3=A4rung=20&=20Consent-Logging=20im=20Upload-Portal=20(ST?=
=?UTF-8?q?I-89)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Datenschutzerklärung unter /portal/datenschutz/ öffentlich erreichbar
- Link zur Datenschutzerklärung in Nachweis-Aufforderungs-E-Mails (HTML + TXT)
- Einwilligungs-Checkbox vor Upload mit Server-Side-Validierung
- Consent-Logging: einwilligung_erteilt_am auf UploadToken (Art. 7 Abs. 1 DSGVO)
- Regelsatz-Korrektur: 449€→563€ in Onboarding-Template (Stand 01/2024)
Co-Authored-By: Claude Opus 4.6
---
..._einwilligung_erteilt_am_to_uploadtoken.py | 18 +++++++++++++++
app/stiftung/models/destinataere.py | 4 ++++
app/stiftung/portal_urls.py | 7 ++++++
app/stiftung/tasks.py | 2 ++
app/stiftung/views/portal.py | 22 +++++++++++++++++++
.../email/nachweis_aufforderung.html | 3 ++-
app/templates/email/nachweis_aufforderung.txt | 1 +
.../portal/einwilligung_onboarding.html | 2 +-
app/templates/portal/upload_formular.html | 15 +++++++++++++
9 files changed, 72 insertions(+), 2 deletions(-)
create mode 100644 app/stiftung/migrations/0064_add_einwilligung_erteilt_am_to_uploadtoken.py
diff --git a/app/stiftung/migrations/0064_add_einwilligung_erteilt_am_to_uploadtoken.py b/app/stiftung/migrations/0064_add_einwilligung_erteilt_am_to_uploadtoken.py
new file mode 100644
index 0000000..e23217d
--- /dev/null
+++ b/app/stiftung/migrations/0064_add_einwilligung_erteilt_am_to_uploadtoken.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.6 on 2026-03-21 22:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('stiftung', '0063_add_anrede_to_destinataer'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='uploadtoken',
+ name='einwilligung_erteilt_am',
+ field=models.DateTimeField(blank=True, help_text='Zeitpunkt der DSGVO-Einwilligung beim Upload (Art. 7 Abs. 1 DSGVO)', null=True, verbose_name='Einwilligung erteilt am'),
+ ),
+ ]
diff --git a/app/stiftung/models/destinataere.py b/app/stiftung/models/destinataere.py
index ba716dc..00926d4 100644
--- a/app/stiftung/models/destinataere.py
+++ b/app/stiftung/models/destinataere.py
@@ -1362,6 +1362,10 @@ class UploadToken(models.Model):
erinnerung_gesendet = models.BooleanField(
default=False, verbose_name="Erinnerung gesendet"
)
+ einwilligung_erteilt_am = models.DateTimeField(
+ null=True, blank=True, verbose_name="Einwilligung erteilt am",
+ help_text="Zeitpunkt der DSGVO-Einwilligung beim Upload (Art. 7 Abs. 1 DSGVO)"
+ )
class Meta:
verbose_name = "Upload-Token"
diff --git a/app/stiftung/portal_urls.py b/app/stiftung/portal_urls.py
index 8635555..17a2221 100644
--- a/app/stiftung/portal_urls.py
+++ b/app/stiftung/portal_urls.py
@@ -6,6 +6,7 @@ Diese URLs sind ohne Login zugänglich (tokenbasierte Authentifizierung).
from django.urls import path
from stiftung.views.portal import (
+ datenschutzerklaerung,
onboarding_danke,
onboarding_schritt,
upload_danke,
@@ -15,6 +16,12 @@ from stiftung.views.portal import (
app_name = "portal"
urlpatterns = [
+ # Datenschutzerklärung (öffentlich, kein Token erforderlich)
+ path(
+ "datenschutz/",
+ datenschutzerklaerung,
+ name="datenschutzerklaerung",
+ ),
# Upload-Portal (bestehende Destinatäre – Token-basiert)
path(
"upload//",
diff --git a/app/stiftung/tasks.py b/app/stiftung/tasks.py
index a7ca3bd..449dd59 100644
--- a/app/stiftung/tasks.py
+++ b/app/stiftung/tasks.py
@@ -547,6 +547,7 @@ def send_nachweis_aufforderung(self, destinataer_id, nachweis_id, base_url=None)
"gueltig_bis": gueltig_bis,
"halbjahr_label": halbjahr_label,
"quartal_label": quartal_label,
+ "datenschutz_url": f"{base_url}/portal/datenschutz/",
}
subject = f"Nachweis-Aufforderung: {quartal_label} ({halbjahr_label}) – vHTV-Stiftung"
@@ -618,6 +619,7 @@ def send_nachweis_erinnerung(self, token_id, base_url=None):
"gueltig_bis": upload_token.gueltig_bis,
"halbjahr_label": halbjahr_label,
"ist_erinnerung": True,
+ "datenschutz_url": f"{base_url}/portal/datenschutz/",
}
subject = f"Erinnerung: Nachweis-Upload noch ausstehend – {halbjahr_label}"
diff --git a/app/stiftung/views/portal.py b/app/stiftung/views/portal.py
index 81924f4..7a68026 100644
--- a/app/stiftung/views/portal.py
+++ b/app/stiftung/views/portal.py
@@ -33,6 +33,11 @@ from django.views.decorators.http import require_http_methods
from stiftung.models import DokumentDatei, OnboardingEinladung, UploadToken, VierteljahresNachweis
+
+def datenschutzerklaerung(request):
+ """Datenschutzerklärung für das öffentliche Portal."""
+ return render(request, "portal/datenschutzerklaerung.html")
+
logger = logging.getLogger(__name__)
# Erlaubte Dateitypen für Uploads
@@ -105,6 +110,19 @@ def upload_formular(request, token):
if request.method == "GET":
return render(request, "portal/upload_formular.html", base_context)
+ # POST: Einwilligung prüfen
+ einwilligung = request.POST.get("einwilligung")
+ if not einwilligung:
+ ctx = {
+ **base_context,
+ "einwilligung_fehler": "Bitte erteilen Sie Ihre Einwilligung zur Datenverarbeitung, um fortzufahren.",
+ }
+ for kat in [
+ "studiennachweis", "einkommenssituation", "vermogenssituation", "weitere_dokumente"
+ ]:
+ ctx[f"{kat}_text"] = request.POST.get(f"{kat}_text", "")
+ return render(request, "portal/upload_formular.html", ctx)
+
# POST: Kategorisierte Dateien und Texte verarbeiten
# Kategorien mit ihren DMS-Kontext-Werten und FK-Feldern auf VierteljahresNachweis
KATEGORIEN = [
@@ -228,6 +246,10 @@ def upload_formular(request, token):
if nachweis_update_fields:
nachweis.save(update_fields=list(set(nachweis_update_fields)))
+ # DSGVO-Einwilligung protokollieren (Art. 7 Abs. 1 DSGVO)
+ upload_token.einwilligung_erteilt_am = timezone.now()
+ upload_token.save(update_fields=["einwilligung_erteilt_am"])
+
# Token einlösen
ip = _get_client_ip(request)
upload_token.einloesen(ip_address=ip)
diff --git a/app/templates/email/nachweis_aufforderung.html b/app/templates/email/nachweis_aufforderung.html
index 561c188..9791617 100644
--- a/app/templates/email/nachweis_aufforderung.html
+++ b/app/templates/email/nachweis_aufforderung.html
@@ -71,7 +71,8 @@
@@ -128,6 +132,17 @@
+
+
+ {% if einwilligung_fehler %}
+
{{ einwilligung_fehler }}
+ {% endif %}
+
+