Uni-Verein 1.10.0 – Einnahmen, EÜR-Export, Zwei-Faktor-Authentifizierung und Demo-Modus 💶🔐
Veröffentlicht am 30. Oktober 2026
Uni-Verein 1.10.0 macht aus der Beitragsverwaltung eine vollständige Einnahmenverwaltung mit Quittungen, stabilen Belegnummern und Zahlungsdatum. Die neue Finanzauswertung vergleicht Einnahmen und Ausgaben und erzeugt auf Knopfdruck den EÜR-Export für die Steuer. Dazu kommen die Zwei-Faktor-Authentifizierung, ein Demo-Modus und eine ganze Reihe von Sicherheitsverbesserungen. Beim Update gibt es einiges zu beachten, siehe die Hinweise unten.
Highlights
- 💶 Einnahmen (UV-65): Die Seite "Beiträge" ist jetzt die Seite "Einnahmen". Neben den Mitgliedsbeiträgen gibt es frei konfigurierbare Einnahmekategorien wie Spenden oder Sponsoring, auch ohne Bankverbindung
- 🧾 Quittungen (UV-65): Für jeden bezahlten Beitrag und jede bezahlte Einnahme lässt sich eine Quittung als PDF mit fortlaufender Nummer erzeugen
- 📑 EÜR-Export (UV-65): Die Einnahmen-Überschuss-Rechnung als Excel-Datei mit steuerlichen Prüfungen, Journal und Beitragsnachweis. Dafür braucht es ein Mitglied mit der Aufgabe "Finanzvorstand"
- 🔢 Stabile Belegnummern (UV-68):
E-JJJJ-NNNNfür Einnahmen,A-JJJJ-NNNNfür Belege, dazu ein explizites Zahlungsdatum - 📊 Finanzauswertung (UV-60): Einnahmen und Ausgaben im Vergleich, ersetzt die Belegauswertung
- 🔐 Zwei-Faktor-Authentifizierung (UV-67): Anmeldung mit Code aus einer Authenticator-App und Wiederherstellungscodes
- 🎬 Demo-Modus (UV-66): Eine Demo-Instanz mit Beispieldaten, die sich jede Nacht zurücksetzt
- 🛡️ Härtung (UV-29, UV-64): Anmelde-Token im httpOnly-Cookie, Mail-Verschlüsselung erzwungen, abgesicherte Backup-Wiederherstellung und mehr
Neue Funktionen
- 💶 Einnahmen (UV-65): Der Reiter "Mitgliedsbeiträge" zeigt die SEPA-Beiträge, jeder weitere Reiter ist eine konfigurierbare Einnahmekategorie (unter "Einnahmekategorien verwalten", z. B. "Spenden"). Admins und Finanzverwalter können Einnahmen erfassen, bearbeiten, als bezahlt markieren und löschen, Admins stellen gelöschte wieder her. Kennzahlen und ein Zeitraumfilter (Standard: aktuelles Jahr) sind neu. Bezahlte Beiträge werden nicht mehr nach 14 Tagen automatisch gelöscht, bereits so entfernte Beiträge stellt die Migration wieder her
- 🧾 Quittungen (UV-65): Ein PDF mit fortlaufender Quittungsnummer (
JJJJ-NNNN) für jeden bezahlten Beitrag und jede bezahlte Einnahme. Sie wird so aufbewahrt, wie sie ausgestellt wurde, danach lässt sich die Zahlung nicht mehr ändern. Die Quittung ist keine Zuwendungsbestätigung nach § 10b EStG - 📑 EÜR-Export (UV-65): Der EÜR-Export steht für Admins und Finanzverwalter auf der Seite "Finanzauswertung" bereit, für alle Jahre ab 2020. Die Excel-Datei enthält die Blätter "EÜR", "Stammdaten", "Journal", "Beitragsnachweis" und "Hinweise". Die steuerlichen Prüfungen nutzen die Grenzen des exportierten Jahres (45.000 € bis 2025, 50.000 € / 50.000 € / 100.000 € ab 2026). Anfangsbestände und der Abgleich Kasse/Bank sind leere Zellen zum Ausfüllen von Hand. Damit der Kassenwart in der EÜR erscheint, muss es ein Mitglied mit der Aufgabe "Finanzvorstand" geben, siehe die Voraussetzungen
- 🏷️ Steuerlicher Bereich (UV-65): Einnahme- und Belegkategorien haben einen Steuerlichen Bereich (Ideeller Bereich, Vermögensverwaltung, Zweckbetrieb, Wirtschaftlicher Geschäftsbetrieb), die Bankverbindung eine optionale "Steuernummer"
- 🔢 Belegnummern und Zahlungsdatum (UV-68): Beim Buchen als bezahlt erhält jeder Eintrag einmalig eine Belegnummer,
E-JJJJ-NNNNfür Beiträge und Einnahmen,A-JJJJ-NNNNfür Belege, die sich nie ändert und nie neu vergeben wird. Das Zahlungsdatum (Standard: heute) bestimmt EÜR, Finanzauswertung und Einnahmen-Summen. Bereits bezahlte Einträge behalten ihr bisheriges Datum, frühere Zahlen ändern sich nicht - 📊 Finanzauswertung (UV-60): Die Finanzauswertung ersetzt die Belegauswertung und vergleicht Einnahmen und Ausgaben (Übersicht, nach Monat und Kategorie, Kategorientabelle). Es zählen nur bezahlte Einträge, jeweils im Monat ihres Zahlungsdatums, sodass der Saldo zur EÜR passt. Offene Belege erscheinen nicht mehr in den Ausgaben, bis sie bezahlt sind. Nur Admins und Finanzverwalter können sie öffnen
- 🔐 Zwei-Faktor-Authentifizierung (UV-67): Unter "Profileinstellungen" → "Sicherheit" richtet jeder Benutzer eine 2FA (TOTP) mit QR-Code und 10 Wiederherstellungscodes ein. Bei aktiver 2FA folgt auf das Passwort die Code-Abfrage, falsche Codes werden pro Adresse und pro Konto gesperrt (Wiederherstellungscodes funktionieren währenddessen weiter). Admins können die 2FA eines Benutzers zurücksetzen. Das Geheimnis wird verschlüsselt gespeichert, optional lässt sich der Name in der App mit
TwoFactor__Issuerfestlegen. Im Demo-Modus nicht verfügbar. Wer als einziger Admin Gerät und Wiederherstellungscodes verliert, hat keinen Weg zurück - 🔒 Sitzungen (UV-67): Abmelden, Passwort ändern und das Ändern der 2FA beenden die Sitzungen auf allen anderen Geräten
- 🎬 Demo-Modus (UV-66):
docker-compose-demo.ymlbaut nichts selbst und braucht keinen SQL-Dump.VERSIONin der.envwählt die veröffentlichten Images,DEMO_DATA_PATHden Datenordner. MitDemo__Enabled=truelöscht das Backend bei jedem Start und nachts um 03:00 Uhr UTC Datenbank und Belegdateien und erzeugt Beispieldaten relativ zum aktuellen Datum. E-Mails fängt Mailpit unter/mail/ab.GET /web-page-configliefertdemo, sodass das veröffentlichte Frontend-Image den Demo-Hinweis ohne eigenen Build zeigt - 👥 Finanzverwalter mit mehr Rechten (UV-70): Finanzverwalter öffnen die Beitragstarifverwaltung sowie die Einnahme- und Belegkategorien (das Menü "Einstellungen" zeigt ihnen nur diese drei Einträge) und legen Beitragstarife und Kategorien wie Admins an, bearbeiten und löschen sie
Änderungen
- 🍪 Anmelde-Token im Cookie (UV-29): Das Token liegt in einem httpOnly-Cookie (
uv_auth) statt imlocalStorage, sodass eingeschleuste Skripte es nicht mehr auslesen können. Datenändernde Cookie-Anfragen tragen den HeaderX-UV-CSRF,Authorization: Bearerfunktioniert weiter. Neu sindGET /auth/meundPOST /auth/logout.?access_token=wird bei/backup,/receipts/exportund/emailProgressnicht mehr akzeptiert - 📧 Mail-Verschlüsselung (UV-64): Bei aktiviertem SSL wird eine verschlüsselte Verbindung erzwungen (TLS auf Port 465/993, sonst STARTTLS). Vorher konnte das Mailbox-Passwort im Klartext gesendet werden, wenn STARTTLS fehlte. Die Test-Mail meldet, warum etwas fehlgeschlagen ist
- 🔌 API-Änderungen:
GET /receipts/analyticswird durchGET /finance/analyticsersetzt.POST /contributions/{id}undPOST /income/{id}erwarten{ "paid", "paymentDate" }im Body statt des Query-Parameterspaid,POST /receipts/{id}/payundPOST /incomeakzeptieren ein optionalespaymentDate
Fehlerbehebungen und Sicherheit
- 🛡️ Backup-Wiederherstellung (UV-64): Eine hochgeladene SQL-Datei kann keine Shell-Befehle mehr im Backend-Container ausführen (
psqlläuft im eingeschränkten Modus, nötig ist PostgreSQL 17.6 oder neuer, sonst wird die Wiederherstellung abgelehnt) - 🎬 Demo-Modus auf dem Server (UV-64): Backup-Download und -Wiederherstellung antworten im Demo-Modus mit 403
DEMO_MODE_DISABLED. Vorher existierte der Demo-Modus nur im Frontend - ⚠️ Demo-Löschung (UV-65): Die Löschung der Datenbank braucht ein ausdrückliches
Demo__Enabledmittrue,1,yesoderon. Vorher hätte jeder andere Wert (z. B.disabled) eine Produktivdatenbank gelöscht - 📝 Selbstregistrierung (UV-64): Alle Textfelder haben eine Maximallänge,
POST /self-enrollmentlehnt Bodys über 32 KB ab. Anfragen über einem Größenlimit (auch Beleg-Uploads) werden mit 413 statt 500 beantwortet - 💶 Beträge (UV-64): Beleg- und Einnahmebeträge sind auf 1.000.000 begrenzt. Vorher ließ sich die Finanzauswertung mit Belegen nahe dem Maximalwert nicht mehr laden (HTTP 500). Bereits gespeicherte höhere Beträge bleiben unverändert
- 📄 CSV-Exporte (UV-64): Belege-ZIP und Mitgliederexport entschärfen Tabellenformeln (Felder, die mit
=,+,-oder@beginnen) - 🖼️ PDF-Vorschau (UV-64): Seiten werden in begrenzter Größe gerendert, ein PDF mit riesiger Seite kann den Browser nicht mehr überlasten. Eine nicht darstellbare Kachel zeigt einen Platzhalter mit dem Link zum Original
- 🔑 Mail-Einstellungen (UV-64): Das SMTP-/IMAP-Passwort muss bei jedem Speichern neu eingegeben werden, damit das gespeicherte Passwort nicht auf einen anderen Mail-Server umgeleitet werden kann
- 📊 Einnahmen-Summen (UV-65): Bezahlte Beträge werden nach Zahlungsdatum dem Zeitraum zugeordnet, bezahlte Einnahmen ohne Kategorie fehlen nicht mehr
- 🗑️ Gelöschte Beiträge (UV-65): Ein gelöschter Beitrag lässt sich nicht mehr als bezahlt markieren
⚠️ Hinweise zum Update
- Immer mit
docker-compose-update.ymlaktualisieren, wie in der README beschrieben. Dienginx.confmuss zusammen mit den Images aktualisiert werden (X-Forwarded-Proto), das erledigt das Update-Skript - Alle Benutzer müssen sich nach dem Update einmal neu anmelden (neue Spalte
Users.session_versionund Wechsel auf das Cookie) - Steuerliche Bereiche zuordnen: Die Migration setzt alle vorhandenen Einnahme- und Belegkategorien auf "Ideeller Bereich". Weise die Bereiche zu, bevor du die erste EÜR exportierst, siehe Steuerliche Bereiche zuordnen
- Für die EÜR: Lege ein Mitglied mit der Aufgabe "Finanzvorstand" an, damit der Kassenwart in der Datei erscheint
- API-Clients: Der Token muss als
Authorization: Bearergesendet werden (?access_token=entfällt),POST /contributions/{id}undPOST /income/{id}brauchen den Body{ "paid", "paymentDate" } - Mail-Server ohne Verschlüsselung funktionieren mit aktiviertem SSL nicht mehr
- Offene Belege erscheinen in der Finanzauswertung erst, wenn sie bezahlt sind
- Bezahlte Beiträge, die bisher nach 14 Tagen automatisch gelöscht wurden, stellt die Migration wieder her. Künftig bleiben bezahlte Beiträge dauerhaft erhalten
Warum diese Updates wichtig sind
Mit den Einnahmen, den stabilen Belegnummern und dem Zahlungsdatum führt Uni-Verein die Vereinskasse nachvollziehbar: Jede Zahlung hat eine feste Nummer und ein Datum, Spenden lassen sich per Quittung belegen, und die EÜR für das Finanzamt entsteht aus denselben Daten wie die Auswertung. Die Zwei-Faktor-Authentifizierung schützt Konten mit Zugriff auf Mitglieder- und Bankdaten, und die Härtung von Token, Mail-Verschlüsselung und Backup-Wiederherstellung schließt mehrere Angriffswege.
Wie geht es weiter?
Weiteres Feedback aus der Praxis ist wie immer herzlich willkommen, gerne über GitHub Issues. Schaut auch weiterhin gerne hier vorbei!