Files
betreuungs-cockpit/CHANGELOG.md
T
2026-09-01 16:39:59 +02:00

1371 lines
63 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Änderungen
Alle nennenswerten Änderungen an dieser Anwendung, in umgekehrter
Reihenfolge. Aufbau nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/),
Versionsnummern nach [Semantic Versioning](https://semver.org/lang/de/).
**Was hier steht:** was sich für dich beim Arbeiten ändert — nicht, welche
Datei angefasst wurde. Wer den technischen Verlauf braucht, findet ihn im
Git-Log; jeder Commit trägt seine Etappe.
**Was die Zahlen bedeuten:**
| Stelle | Wann sie steigt | Was du tun musst |
|---|---|---|
| **Major** (1.x.x → 2.0.0) | Etwas verhält sich anders als vorher, oder eine Migration lässt sich nicht zurücknehmen | Changelog lesen, **vorher sichern**, Zeit einplanen |
| **Minor** (1.0.x → 1.1.0) | Neue Funktionen, alles Bisherige bleibt wie es war | Changelog überfliegen, normal aktualisieren |
| **Patch** (1.0.0 → 1.0.1) | Fehler behoben, sonst nichts | Einfach aktualisieren |
Eine Datenbank-Migration lässt sich **nicht** zurückrollen. Vor jedem
Update ein Backup anstoßen — siehe README, Abschnitt *Sicherung und
Wiederherstellung*.
---
## [Unveröffentlicht]
_Noch nichts._
## [1.12.0] – 2026-09-01
Diese Fassung geht aus einer Fehlersuche im laufenden Betrieb hervor. Ein
Schreiben ließ sich nicht in der Akte ablegen — und hinter derselben
Meldung lagen nacheinander drei verschiedene Ursachen. Beim Nachsehen kam
ein vierter Punkt dazu, der schwerer wiegt als alle anderen zusammen.
### Nach dem Update: einmal die Sicherung prüfen
```bash
docker compose up -d
docker compose exec sicherung /bin/sh /sicherung.sh
```
Erwartet werden drei Zeilen: `Datenbank gesichert`, `Dokumente gesichert`
und `Selbstpruefung bestanden - Datenbank und Dokumente.`
### Behoben
- **Deine Dokumente waren in keiner Sicherung.** Der Sicherungsdienst
machte täglich ein `pg_dump` und sonst nichts. Nach einem Plattenschaden
wäre die Akte vollständig wiederhergestellt worden — mit allen
Einträgen, Verweisen und Prüfsummen, aber ohne eine einzige Datei
dahinter. Die Prüfsumme in der Akte hätte dann ein Dokument belegt, das
es nicht mehr gibt.
Je Lauf entstehen jetzt zwei verschlüsselte Dateien mit demselben
Zeitstempel — der Datenbankstand und ein Archiv der Dokumente. Beide mit
Prüfsumme, beide in der Aufbewahrung, beide in der Selbstprüfung.
Schlägt der Dokumententeil fehl, bricht der Lauf ab und räumt den
angefangenen Datenbankstand weg: Eine Sicherung, die nur die halbe
Wahrheit enthält, aber vollständig aussieht, ist schlimmer als gar
keine.
- **Der mitgelieferte Objektspeicher konnte keine Dokumente aufnehmen.**
Die Anwendung verlangte bei jeder Ablage serverseitige Verschlüsselung,
das mitgelieferte MinIO kann sie ohne Zusatzdienst nicht. Wer der
Installationsanleitung folgte, landete zwangsläufig dort. Sie ist jetzt
abschaltbar und standardmäßig aus — gebraucht wird sie nicht, weil die
Anwendung seit 1.11.0 selbst verschlüsselt.
- **„Das Schreiben konnte nicht abgelegt werden" sagte nichts.** Jetzt
steht dort, was los ist: fehlender Ablagebereich mit Verweis auf den
Installationsschritt, abgelehnte Zugangsdaten, nicht erreichbarer
Speicher. Und in jedem Fall, ob deine Eingaben erhalten bleiben.
- **Zwei Einstellungen erreichten die Anwendung nie.**
`ERSTEINRICHTUNG_GEHEIMNIS` und `VERTRAUE_PROXY` standen zwar in der
Anleitung, wurden aber nicht an den Behälter durchgereicht. Bei
`VERTRAUE_PROXY` fiel das nicht auf: Die Anmeldesperre zählte still eine
Dimension weniger.
- **Die Beispielkonfiguration zeigte auf einen fremden Anbieter.**
`S3_ENDPOINT` war mit einer externen Adresse vorbelegt und hebelte damit
den richtigen Standard aus. Die Zeile ist jetzt auskommentiert.
### Neu
- **Die Anwendung prüft ihre Dateiablage selbst.** Sie schreibt eine
winzige Datei, liest sie zurück und räumt sie weg — und beantwortet
damit auf einmal, ob der Speicher erreichbar ist, der Ablagebereich
existiert und geschrieben werden darf. Das Ergebnis steht unter
`/api/gesundheit` und im Log. Bisher fiel eine kaputte Konfiguration
erst auf, wenn jemand ein Dokument ablegen wollte.
- **Ein neuer Abschnitt in der Anleitung: „Wo deine Daten liegen".**
Die drei Orte, der Pfad auf der Platte, welche Schlüssel unverzichtbar
sind — und wie man prüft, ob zu jedem Akteneintrag auch eine Datei
gehört.
### Geändert
- **Ausschlussfristen warnen nur noch, wenn sie näherrücken.** Auf dem
Dashboard standen dauerhaft drei rote Kästen mit Warndreieck — für
Aufwandspauschalen, die 2027, 2028 und 2029 ablaufen. Eine Warnung, die
monatelang dasteht und nichts von einem will, stumpft ab; man übersieht
die vierte, die etwas will. Ab drei Monaten vor Ablauf meldet sich eine
Frist wieder, Versäumtes immer. In der Fristenliste stehen sie
unverändert weiter.
## [1.11.0] – 2026-08-24
Diese Fassung geht aus einer Sicherheitsprüfung hervor. Sie ändert nichts
an der Bedienung, schließt aber mehrere Lücken — darunter drei, bei denen
im Code eine Zusage stand, die so nicht galt.
### Vor dem Update: zwei neue Zeilen in der `.env`
```bash
ERSTEINRICHTUNG_GEHEIMNIS=<zufälliger Wert, npm run schluessel erzeugt einen>
VERTRAUE_PROXY=true
```
Beide sind nicht zwingend, aber beide haben Folgen, wenn sie fehlen:
- Ohne `ERSTEINRICHTUNG_GEHEIMNIS` läuft alles weiter — nur der **erste**
Zugang ließe sich nicht mehr anlegen. Auf einer bestehenden Installation
ist das genau richtig; bei einer Neuinstallation brauchst du den Wert.
- Ohne `VERTRAUE_PROXY=true` zählt die Anmeldesperre nur noch nach
E-Mail-Adresse, nicht mehr zusätzlich nach Anschluss. Beim ersten Mal
steht eine erklärende Zeile im Log.
### Nach dem Update, nur mit Objektspeicher
Bereits abgelegte Dateien liegen weiter unverschlüsselt, bis sie einmal
umgeschrieben werden. Lesen funktioniert durchgehend, der Lauf lässt sich
jederzeit abbrechen und wiederholen:
```bash
npm run speicher:verschluesseln # erst schauen
npm run speicher:verschluesseln -- --anwenden
```
Wer beim Dateitreiber bleibt, muss nichts tun — dort wurde immer schon
verschlüsselt.
### Behoben
- **Der zweite Faktor war eine Anzeige, keine Grenze.** „TOTP ist Pflicht"
galt nur für Seiten. Server Actions und Downloads liefen daran vorbei:
Wer sich mit Passwort angemeldet, die Einrichtung aber nie abgeschlossen
hatte, kam an die Daten. Die Prüfung sitzt jetzt an der Stelle, durch die
alles muss.
- **Ein Dokumenten-Download fragte nur nach der Anmeldung.** Dieselbe
Lücke, an einer Stelle, die bei der ersten Durchsicht übersehen wurde.
- **Die Anmeldesperre sperrte dauerhaft aus.** Jeder Versuch während einer
laufenden Sperre verlängerte sie um die volle Dauer. Wer alle zehn
Minuten ein falsches Passwort an eine fremde Adresse schickte, sperrte
deren Inhaber aus — ohne das Passwort zu kennen. Nebenbei wuchs die
Tabelle mit jedem Versuch.
- **Ein Code aus der Authenticator-App galt mehrfach.** Bis zu 90 Sekunden
lang ließ sich derselbe Code erneut einlösen — abgelesen, abgefangen
oder aus dem Browserverlauf. Jetzt gilt jeder genau einmal.
- **Passkeys ließen sich ohne Rückfrage anlegen und entfernen.** An einem
unbeaufsichtigten Rechner waren das zwei Klicks zu einem dauerhaften
eigenen Zugang, der auch einen Passwortwechsel überlebt hätte. Jetzt
fragt die Anwendung vorher nach Passwort und Code — wie beim Export.
- **Die Ersteinrichtung stand offen.** Zwischen dem ersten Start und dem
Anlegen des Zugangs konnte jeder, der die Seite fand, Administrator
werden. Zwei gleichzeitige Anfragen legten außerdem beide einen Zugang
an.
- **Dateien im Objektspeicher lagen mit dem Schlüssel des Anbieters da.**
Gegen eine verlorene Festplatte im Rechenzentrum half das, gegen den
Anbieter und gegen gestohlene Zugangsdaten zum Speicher nicht. Jetzt
liegt derselbe Umschlag darum wie bei der Ablage auf dem eigenen Server,
mit einem Schlüssel, der die Anwendung nie verlässt.
- **Ein präpariertes PDF konnte den Server umbringen.** Beim Auslesen des
eingebetteten Texts gab es keine Obergrenze: Wenige Kilobyte Füllung
blähen sich beim Auspacken auf Gigabyte auf. Ebenso beim Umwandeln in
Bilder — die Seitengröße steht im Dokument, und bei fester Auflösung
ergab eine künstlich große Seite ein Bild von mehreren Gigabyte.
- **32 Schwachstellen in mitgelieferten Paketen, davon 5 kritische.**
Jetzt keine.
### Geändert
- **Downloads laufen jetzt immer über die Anwendung**, auch mit
Objektspeicher. Das ist die Folge der Verschlüsselung — entschlüsseln
kann nur die Anwendung. Nebeneffekt: Jeder Abruf prüft die Berechtigung
und wird protokolliert. Ein weitergeleiteter Link allein öffnet nichts
mehr.
- **`docs/SICHERHEIT.md`** ist neu: Bedrohungsmodell, der Durchgang entlang
der OWASP Top 10 — und ein Abschnitt darüber, was ausdrücklich **nicht**
geschützt ist.
## [1.10.2] – 2026-08-24
### Behoben
- **Das Update brach ohne Meldung ab.** Beim Sprung von 1.5.0 auf 1.10.1
endete `scripts/update.sh` mitten in der Ausgabe — kein Fehler, keine
Zeile, einfach zurück zur Eingabeaufforderung. Ursache war eine
abgeschnittene Leitung:
```
git log --oneline "HEAD..$ZIEL" | head -15 | sed ...
```
`head` schließt die Leitung nach 15 Zeilen, `git log` schreibt weiter
und bekommt SIGPIPE. Weil das Skript mit `set -o pipefail` und
`set -e` läuft, gilt die ganze Kette als gescheitert und das Skript
beendet sich — stumm.
Das ist zeitabhängig und deshalb monatelang nicht aufgefallen: Solange
`git log` fertig war, bevor `head` zumachte, ging alles gut. Genau bei
1.10.0 lagen zum ersten Mal **mehr als 15 Commits** dazwischen.
- **Dieselbe Falle in der Selbstprüfung der Sicherung.** `sicherung.sh`
prüfte mit `openssl … | head -c 5 | grep -q "PGDMP"`, ob sich die
frische Sicherung wieder entschlüsseln lässt. Bei kleiner Datenbank
ging das gut, weil alles im Puffer stand. Mit wachsender Datenbank
hätte es gekippt — und dann hätte das Skript eine **völlig intakte
Sicherung als kaputt gemeldet** und das Update verweigert.
### Geändert
- **Ein stummer Abbruch ist nicht mehr möglich.** `update.sh` meldet
jetzt bei jedem unerwarteten Ende die Zeilennummer und den Exitcode,
und erklärt bei 141 ausdrücklich, dass es sich um SIGPIPE handelt und
um einen Fehler im Skript, nicht um einen Bedienfehler.
- Stehen mehr als 15 Commits an, steht jetzt „… und N weitere" darunter,
statt sie wortlos wegzulassen.
### Geprüft
- Ein Test verbietet das Muster künftig in allen Betriebsskripten:
Wer wieder `| head` in eine Leitung schreibt, bekommt die Zeilennummer
genannt. Gegenprobe gemacht — der Test schlägt an.
- Der Ablauf von 1.5.0 auf 1.10.1 einmal nachgestellt: läuft durch.
## [1.10.1] – 2026-08-23
### Geändert
- **Der Beispielfall hat jetzt ein Jahr Kontobewegungen**
([#22](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/22)).
Bisher brachte er Konten und Kontostände mit, aber keine einzige
Buchung — Kassenbuch, Belegpflicht, Dauerzahlungen und die Erkennung
wiederkehrender Zahlungen liefen darin also leer. Wer die Anwendung zum
ersten Mal ansah, musste erst eine CSV importieren.
Neu sind 107 Buchungen über zwölf Monate: Rente, Betriebsrente und
Pflegekasse als Einnahmen, Heimentgelt, Barbetrag, Sterbegeldbeitrag,
Telefon und Kontoführung als monatliche Ausgaben, dazu Rundfunkbeitrag
und Grundsteuer vierteljährlich sowie eine Handvoll Einzelposten.
Das betrifft nur neue Installationen mit `SEED_DEMO=true` — an einem
bestehenden Bestand ändert sich nichts.
### Behoben
- **„gGmbH" wurde nicht als Rechtsform erkannt.** Beim Zusammenfassen
wiederkehrender Zahlungen galten „Seniorenzentrum St. Katharina" und
„Seniorenzentrum St. Katharina gGmbH" als zwei verschiedene Empfänger —
und damit zerfiel die Gruppe genau dort, wo sie am meisten zählt: Heime,
Sozialdienste und Träger sind fast durchweg gGmbH.
### Was der Beispielfall jetzt zeigt
- Das Heimentgelt steht in **drei Schreibweisen** auf dem Auszug und wird
trotzdem als eine monatliche Zahlung von 2.489 € geführt.
- Genau **eine** Buchung hat weder Quittung noch Begründung — damit die
Belegpflicht etwas zu zeigen hat, aber die Liste nicht rot leuchtet.
- Die Sterbegeld-Lastschriften sind bewusst **nicht** zugeordnet: So lässt
sich der Handgriff „zwölf Zahlungen als belegt zuordnen" ausprobieren.
Der Beitrag steigt im Mai von 18,40 € auf 19,80 €, damit auch die
Abweichungsprüfung sichtbar wird.
- Der Kontostand zum 31.07.2026 wird aus dem Anfangsstand und den
Buchungen **gerechnet**, nicht behauptet. Ein Beispielfall, der sich
selbst widerspricht, wäre schlimmer als einer ohne Buchungen.
### Geprüft
- Ein Einheitstest für die gGmbH-Erkennung.
- Vier End-to-End-Tests am Beispielfall: Umfang und Zeitraum der
Buchungen, die Übereinstimmung von Kontostand und Buchungssumme, die
erkannten Zahlungsgruppen und das Kassenbuch.
## [1.10.0] – 2026-08-23
### Hinzugefügt
- **Deine Anschrift steht jetzt in jedem Schreiben**
([#21](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/21)).
Bisher ging jeder Brief ohne Rücksendeadresse hinaus — im
Absenderblock stand nur dein Name, und in der Datumszeile fehlte der
Ort. Bei einer Versicherung oder Behörde wirkt das unseriös und macht
es der Gegenstelle unnötig schwer zu antworten.
Unter **Mein Konto → Meine Anschrift** einmal Straße, PLZ und Ort
hinterlegen. Ab da stehen sie im Absenderblock jedes erzeugten
Schreibens, und die Datumszeile lautet „Bayreuth, den 23.08.2026".
Betroffen ist alles: Rückkaufswert-Anfrage, Serienabfrage, Kündigung,
Sachstandsanfrage und die eigenen Vorlagen.
- **Zwei neue Platzhalter** für eigene Vorlagen: `{{betreuer_anschrift}}`
und `{{betreuer_ort}}`.
### Eine Zusage
**Was fehlt, bleibt weg.** Jede der drei Angaben ist einzeln freiwillig.
Hinterlegst du nur den Ort, bekommst du die Datumszeile mit Ort und einen
Absenderblock, der weiterhin nur aus deinem Namen besteht — keine leeren
Zeilen, kein Komma ohne Ort. Eine halb ausgefüllte Adresse sieht nach
Fehler aus, ein bloßer Name nach Absicht.
### Datenbank
Eine Migration: drei Felder am Benutzer. Die Anschrift hängt an dir und
nicht an der betreuten Person — sie gilt für alle Akten.
### Geprüft
- Sieben Einheitstests für den Absenderblock, darunter jede
Teilbefüllung und die Gegenprobe, dass nie die Heimadresse der
betreuten Person im Absender landet.
- Vier End-to-End-Tests: der Brief ohne hinterlegte Anschrift, das
Speichern unter *Mein Konto*, der Brief danach und die Gegenprobe am
Rückkaufswert-Schreiben.
## [1.9.0] – 2026-08-23
### Hinzugefügt
- **Ansprechpartner am Vorgang**
([#9](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/9)).
Ein namentlich adressiertes Schreiben landet bei der Person, die den
Vorgang kennt, statt in der Poststelle — beim Bezirk ist das der
Unterschied zwischen zwei Wochen und zwei Monaten. Am Vorgang lässt
sich jetzt ein Kontakt aus dem Verzeichnis als Ansprechpartner
benennen.
- **Die Sachstandsanfrage redet ihn an.** Aus „Sehr geehrte Damen und
Herren" wird „Sehr geehrte Frau Sachs" — und der Name steht über der
Behörde, damit die Poststelle den Brief zur Person sortiert. Auch das
Schreiben aus dem Editor übernimmt Anrede, Empfänger und Anschrift,
sodass nichts abgetippt werden muss.
- **Anrede am Kontakt.** Damit die Briefanrede stimmt, trägt ein Kontakt
wahlweise „Frau" oder „Herr". **Geraten wird nie:** Ohne Angabe wird
mit vollem Namen angeredet („Sehr geehrte(r) Kim Berger,"). Eine
falsch geratene Anrede fällt dem Empfänger sofort auf und kostet genau
die Aufmerksamkeit, die man gewinnen wollte.
- **Vor dem Anruf weiß man, wen man fragt.** Auf der Vorgangsseite steht
der Ansprechpartner mit Organisation, Durchwahl und E-Mail, verlinkt
auf den Kontakt.
### Zwei Zusagen
**Ohne Ansprechpartner ändert sich nichts.** Ist keiner benannt, bleibt
es bei „Sehr geehrte Damen und Herren" — und die Vorgangsseite sagt das
auch, statt ein leeres Feld zu zeigen. Ein halb gefüllter Name führt nie
zu einer verstümmelten Anrede wie „Sehr geehrte Frau ,".
**Der Wechsel bleibt nachvollziehbar.** Wer den Ansprechpartner
austauscht, findet den Vorgänger im Protokoll — mit **Namen**, nicht mit
der internen Kennung. Zwei Kennungen nebeneinander wären protokolliert,
aber nicht nachvollziehbar.
### Datenbank
Eine Migration: `ansprechpartnerId` am Vorgang und `anrede` am Kontakt.
Wird ein Kontakt gelöscht, bleibt der Vorgang bestehen und verliert nur
den Verweis — ein Vorgang darf nicht daran hängen, dass es den Kontakt
noch gibt.
### Geprüft
- Neun Einheitstests für die Anrede, darunter der Nachname aus „Dr. Anna
Maria Schmitt", der Doppelname und der Fall, der nie passieren darf:
leerer Name mit gesetzter Anrede.
- Fünf End-to-End-Tests: die allgemeine Anrede ohne Ansprechpartner, das
Benennen samt Anzeige mit Durchwahl, die namentliche Anrede in der
Sachstandsanfrage, die vorbelegte Anschrift im Schreiben und der
Wechsel im Protokoll.
## [1.8.0] – 2026-08-23
### Hinzugefügt
- **Der Rückkaufswert steht jetzt beim Vertrag**
([#8](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/8)).
Am Telefon mit der Gesellschaft braucht man die Zahl dort, wo man sie
gefragt hat — nicht nach einem Umweg über das Vermögensverzeichnis.
Auf der Vertragsseite steht deshalb oben der zuletzt mitgeteilte Wert
mit Stichtag und Quelle, darunter die vollständige Historie.
- **Ein alter Wert sagt, dass er alt ist.** Ist der jüngste Wert älter
als ein Jahr, steht „über ein Jahr alt" daneben — in der
Vertragsübersicht wie auf der Vertragsseite. Ein veralteter Betrag
ohne erkennbares Alter ist schlimmer als kein Betrag: Man rechnet mit
ihm weiter und merkt es erst, wenn der Bezirk nachfragt. Die Grenze von
einem Jahr gilt jetzt überall dieselbe; im Vermögensverzeichnis stand
sie schon, am Vertrag fehlte sie.
- **Verträge ohne Wert bleiben nicht stumm.** Wo ein Rückkaufswert zu
erwarten wäre, aber keiner erfasst ist, steht „noch nicht erfasst"
statt einer leeren Zelle.
### Behoben
- **Ein Vertrag hatte unter Umständen zwei Wertreihen.** Vertrag und
Vermögensposten wurden über die *Art* des Postens zusammengeführt.
Wer einen Posten von Hand anlegte und ihn als etwas anderes als
„Versicherung" führte, bekam beim ersten Eintragen am Vertrag
klammheimlich einen zweiten Posten — und damit zwei Wahrheiten, von
denen der Bericht die eine und der Vertrag die andere zeigte.
Maßgeblich ist jetzt die Verknüpfung mit dem Vertrag, nicht die Art.
- **Fehlende Prüfung bei Verweisen auf Vertrag und Immobilie.** Beim
Speichern eines Vermögenspostens wurde geprüft, ob das verknüpfte
*Konto* zur betreuten Person gehört — bei Vertrag und Immobilie fehlte
dieselbe Prüfung, ebenso beim Vertragsverweis einer Buchung. Bei
mehreren betreuten Personen hätte ein manipuliertes Formular eine
Verknüpfung über Aktengrenzen hinweg anlegen können. Jetzt wird jeder
der drei Verweise gleich behandelt.
### Geprüft
- Zehn Einheitstests: die Ein-Jahres-Grenze (auch der Tag davor und
danach), das Alter taggenau und ohne Uhrzeiteinfluss, ein künftiger
Stichtag, und die Zusammenführung mehrerer Posten zu einer Historie.
- Vier End-to-End-Tests: Wert und Stichtag in der Übersicht, das
Abzeichen am alten Wert, die Historie am Vertrag, und ein neu
eingetragener Wert, der unverändert im Vermögensverzeichnis
wiederauftaucht.
## [1.7.0] – 2026-08-23
### Hinzugefügt
- **Eigene Vorlagen für Anschreiben**
([#7](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/7)).
Wer zum dritten Mal denselben Brief tippt, tippt ihn beim vierten Mal
falsch. Neu unter **Einstellungen → Schreibvorlagen**: eigene Texte
hinterlegen, beim freien Schreiben auswählen — der Rest füllt sich aus
den Daten, die ohnehin im System stehen.
- **Platzhalter, die sich selbst erklären.** Neben dem Editor steht die
vollständige Liste — Name, wofür er steht, woher der Wert kommt. Ein
Klick setzt ihn an die Cursorstelle; niemand muss `{{…}}`-Namen raten
oder abtippen. Verfügbar sind unter anderem Name und Geburtsdatum der
betreuten Person, ihre Anschrift, Betreuungsgericht und Aktenzeichen,
das Bestellungsdatum, der Stichtag und das heutige Datum.
- **Vier mitgelieferte Vorlagen** als Ausgangspunkt: Auskunftsersuchen an
eine noch unbekannte Stelle, Adressänderung nach Heimeinzug, Nachweis
der Vertretungsmacht (wenn eine Stelle die Auskunft verweigert) und die
Kurzfassung der Rückkaufswert-Anfrage. Sie lassen sich nicht kaputt
bearbeiten — „Als Kopie übernehmen" legt eine eigene Fassung an, das
Original bleibt liegen. Für Rückkaufswert und Kündigung führt die
Oberfläche weiter zuerst zu den fallbezogenen Erzeugern: Die rechnen
Kündigungstermine aus, was eine Vorlage nicht kann.
### Zwei Zusagen
**Nichts geht stillschweigend schief.** Fehlt ein Wert (etwa das
Aktenzeichen), steht im Brief eine sichtbare Lücke `[Aktenzeichen des
Gerichts]` — kein Leerraum, den erst der Empfänger bemerkt. Ein
Tippfehler im Platzhalternamen bricht nicht ab, sondern wird im Text
markiert und über dem Editor benannt.
**Der Bestellungsabsatz wird nicht vergessen.** Ohne den Nachweis der
Vertretungsmacht kommt keine Auskunft — und das fällt erst nach vier
Wochen auf. Beim Speichern einer Vorlage ohne Bestellungsabsatz kommt
deshalb eine Rückfrage. Wer ihn bewusst weglässt (etwa für die zweite
Mail an eine Stelle, die den Nachweis schon hat), bestätigt einmal und
wird nicht weiter gefragt. Ein ausformulierter Absatz zählt genauso wie
der Platzhalter.
### Geändert
- Gelöschte Vorlagen liegen wie alles andere **30 Tage im Papierkorb**
und lassen sich zurückholen. Beim Vollexport der Akte sind sie mit
dabei.
### Geprüft
- Dreizehn Einheitstests für die Platzhalter-Ersetzung, darunter jeder
der drei Fälle (Wert einsetzen, sichtbare Lücke, unbekannter
Platzhalter) und die Bestellungsabsatz-Erkennung, die sich nicht von
einem halben Nachweis („Beschluss der Eigentümerversammlung") täuschen
lässt.
- Sechs End-to-End-Tests: die zweistufige Warnung, das Übernehmen einer
mitgelieferten Vorlage, die Auflösung im Editor samt markierter
Tippfehler und der Weg durch den Papierkorb.
## [1.6.0] – 2026-08-23
### Hinzugefügt
- **Rückkaufswerte aller Versicherungen in einem Zug anfragen**
([#6](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/6)).
Vor dem Vermögensverzeichnis und vor jedem Jahresbericht braucht es von
jeder Lebens- und Rentenversicherung den Rückkaufswert zum Stichtag —
jedes Mal dieselben fünf bis zehn Briefe. Das war der Handgriff, der im
Jahr am meisten Zeit gekostet hat.
Neu unter **Verträge → Rückkaufswerte anfragen**: Stichtag wählen, Liste
prüfen, ein Klick. Heraus kommt ein **ZIP mit einer Word-Datei je
Gesellschaft** plus Begleitblatt — und je Vertrag entsteht nebenbei die
PDF-Fassung in der Akte, die Wiedervorlage nach 28 Tagen und der
Vermerk, dass angefragt wurde. Auf „Heute" steht danach unter *Wartet
auf andere*, worauf gewartet wird und seit wann.
- **Die Auswahl begründet sich selbst.** Vorausgewählt ist nur, was in
Frage kommt. Was nicht in Frage kommt, wird nicht stumm ausgeblendet,
sondern steht mit Grund in der Liste: „Ohne Gesellschaft fehlt der
Empfänger", „bereits vor 10 Tagen angefragt". Nur Vertragsarten, die
nie einen Rückkaufswert bilden (Strom, Haftpflicht), bleiben draußen.
Gekündigte Verträge bleiben anfragbar — bis zum Wirksamwerden besteht
der Vertrag, und genau dann will der Sozialhilfeträger den Wert sehen.
- **Die Zahl steht vor dem Klick auf dem Knopf** („4 Schreiben erzeugen").
Einen Fehlgriff soll man bemerken, bevor zehn Briefe entstehen — nicht
danach beim Aufräumen.
### Zwei Zusagen
**Kein Alles-oder-nichts.** Jeder Vertrag läuft für sich. Scheitert einer
(etwa am Objektspeicher), steht er mit Grund im Begleitblatt des ZIP —
die übrigen laufen durch. Eine Transaktion über alle wäre die falsche
Zusage: Wenn der neunte Brief scheitert, sollen die acht fertigen nicht
verworfen werden.
**Der Doppelklick kostet nichts.** Ein zweiter Lauf am selben Tag
überspringt bereits angefragte Verträge und sagt es im Begleitblatt —
keine doppelten Dokumente, keine doppelten Wiedervorlagen. Eine bewusste
zweite Anfrage nach einer Woche (Brief verloren) bleibt möglich: von Hand
anwählen genügt.
### Geprüft
- Zwölf Einheitstests für die Auswahllogik, darunter der Gleichschritt
von Sperrfrist und Wiedervorlage (beide 28 Tage — liefen sie
auseinander, würde gemahnt und zugleich neu angefragt).
- Drei End-to-End-Tests: Vorauswahl samt Begründungen, der volle Lauf mit
allen Nebenwirkungen in der Datenbank, und der Dublettenschutz beim
zweiten Lauf.
## [1.5.0] – 2026-08-23
### Hinzugefügt
- **Schreiben als E-Mail übernehmen**
([#5](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/5)).
Vieles geht heute per E-Mail hinaus, nicht per Brief. Im Schreiben-Editor
gibt es jetzt den Umschalter **Als Brief / Als E-Mail**.
Die E-Mail-Fassung wird aus dem — womöglich schon bearbeiteten —
Brieftext abgeleitet: ohne Absenderblock, Anschrift und Datumszeile, die
gehören in den Brief und nicht in die Mail. **Der Bestellungsabsatz
bleibt drin** — ohne ihn kommt die Auskunft nicht. Betreff und Text sind
einzeln kopierbar.
- **Zwei Wege ins Mailprogramm.** Die offene Entscheidung aus dem Issue
(mailto oder .eml) ist mit *beides* beantwortet:
**„Im Mailprogramm öffnen"** (mailto) für kurze Texte — der Knopf
verschwindet, sobald der kodierte Link 1800 Zeichen übersteigt. mailto
übergibt den Text in der Adresszeile, deren Länge nirgends zugesichert
ist; ein stillschweigend halbierter Brief fällt erst beim Empfänger auf.
**„Als E-Mail-Entwurf (.eml)"** immer: eine Datei, die sich im
Mailprogramm als fertiger Entwurf öffnet — Adresse, Betreff und voller
Text garantiert vollständig, mit korrekt kodierten Umlauten. Die Datei
erfindet bewusst keinen Absender: den trägt das Mailprogramm beim
Senden selbst ein.
Beide Knöpfe erscheinen nur, wenn eine E-Mail-Adresse da ist — aus dem
gewählten Kontakt übernommen oder von Hand eingetragen. Ohne Adresse
bleibt das Kopieren.
- **Die Ablage kennt den Unterschied.** Eine als E-Mail abgelegte
Nachricht heißt in der Akte „E-Mail: …" und die PDF-Fassung trägt die
Empfängeradresse im Kopf — abgelegt wird die E-Mail-Fassung, nicht der
Brieftext. In der Akte soll stehen, was tatsächlich hinausging.
### Geprüft
- 20 Einheitstests: die Ableitung der E-Mail-Fassung (nichts geht
stillschweigend verloren, der Bestellungsabsatz bleibt), die
mailto-Längengrenze **nach** der Kodierung, das .eml-Format samt
Umlauten nach RFC 2047 und quoted-printable.
- Vier End-to-End-Tests: Fassung ohne Briefkopf, .eml-Download, kein
Mailknopf ohne Adresse, Ablage als gekennzeichnete E-Mail.
## [1.4.0] – 2026-08-23
### Hinzugefügt
- **Schreiben erzeugen, ausgeben und ablegen**
([#4](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/4)).
Die Anschreiben an Versicherer und Behörden gab es bisher nur als Text
zum Kopieren. Danach war Handarbeit: in Word nachbauen, ausdrucken,
abschicken — und noch einmal von Hand als Dokument in die Akte legen.
Vier Schritte, und der letzte wurde am häufigsten vergessen. Dann fehlt
der Nachweis genau dort, wo er gebraucht wird.
Neu unter **Schreiben**: Text bearbeiten, als **Word-Datei**
herunterladen, **in die Akte legen** und dabei eine **Wiedervorlage**
setzen — in einem Zug.
Der Text bleibt vollständig bearbeitbar. Jeder Fall hat eine
Besonderheit, die in den Brief gehört; ein Formulartext, den man nicht
mehr ändern kann, wird früher oder später falsch abgeschickt.
- **Schreiben ohne Vertrag und ohne Vorgang.** Bisher ging ein Anschreiben
nur zu einem vorhandenen Datensatz. Am Anfang einer Betreuung schreibt
man aber gerade an Stellen, zu denen es noch keinen gibt — man fragt ja
an, um zu erfahren, ob dort überhaupt etwas besteht. Jetzt geht es an
einen **Kontakt** oder an eine **frei getippte Anschrift**, aus der sich
mit einem Klick ein Kontakt anlegen lässt. So wächst das Adressbuch
nebenbei beim Schreiben statt durch Pflege.
- **Die Wiedervorlage entsteht mit.** Rückkaufswert 28 Tage, Kündigung 21,
Sachstandsanfrage 42 — jeweils änderbar, und daneben steht, warum die
Frist so lang ist. Bei einer Sachstandsanfrage wird zusätzlich der letzte
Kontakt am Vorgang nachgezogen, damit *Wartet auf andere* auf „Heute"
stimmt.
### Zwei Entscheidungen, die erklärt gehören
**In die Akte kommt eine PDF-Fassung, heruntergeladen wird eine
Word-Datei.** Der Objektspeicher nimmt nur PDF und Bilder an, geprüft an
den Magic Bytes — eine Word-Datei ist ein ZIP-Archiv, sie durchzulassen
hieße jedes ZIP durchzulassen. Diese Prüfung ist eine bewusste Zusage der
Anwendung; sie für eine Bequemlichkeit aufzuweichen wäre der falsche
Handel. Und sachlich ist PDF für den Nachweis ohnehin das richtige Format:
Es ändert sich nicht mehr.
Daraus folgt eine Einschränkung, die auch in der Oberfläche steht:
**Änderst du die Word-Datei danach noch**, weicht die Akte davon ab. Dann
lieber dort fertig schreiben, den Text zurückkopieren und erst dann
ablegen.
**Der Berichtssatz wurde nicht umgebogen.** Die vorhandene DOCX-Ausgabe
setzt einen Bericht mit Kopfzeilen und Unterschriftenblock; ein Brief
braucht Absender, Anschriftenfeld, Datumszeile und Grußformel. Beides in
eine Funktion zu zwingen hätte beide Ausgaben schlechter gemacht.
### Geprüft
- Zehn Einheitstests, darunter der Gleichschritt der Wiedervorlagefristen:
Sie stehen an zwei Stellen und dürfen nicht auseinanderlaufen, sonst
mahnt die Anwendung zu zwei verschiedenen Terminen.
- Fünf End-to-End-Tests: Entwurf mit Bestellungsabsatz, Vertragsbezug,
Word-Download, Ablage samt Wiedervorlage und Kontakt aus freier
Anschrift.
Zwei Fehler hat erst der Browsertest gefunden — beide in den Tests
selbst: Die Dokumentenliste rendert Tabelle **und** Karten, je nach
Bildschirmbreite ist eine davon versteckt. Ein Treffer auf die
unsichtbare Fassung sieht aus wie ein Fehler in der Anwendung.
## [1.3.0] – 2026-08-23
### Hinzugefügt
- **Dauerzahlungen zu einem Vertrag gelten als belegt**
([#11](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/11)).
Bei einer monatlichen Versicherungsprämie gibt es keinen Beleg — es gibt
einen Vertrag, und der wird einmal abgeschlossen, nicht monatlich
quittiert. Bisher blieb nur ein Freitextfeld pro Buchung: zwölfmal im
Jahr derselbe Satz, mal Anzahl der Verträge.
Eine Buchung lässt sich jetzt einem **Vertrag** zuordnen, und diese
Zuordnung zählt als Nachweis. Bei einem Dauerschuldverhältnis ist der
Vertrag der Beleg: Ein Rechtspfleger will für zwölf gleiche Lastschriften
keine zwölf Quittungen sehen, sondern die Police.
**Die Belegquote weist drei Fälle getrennt aus** — mit Beleg, durch
Vertrag belegt, begründet ohne Beleg. Zusammengeworfen verlöre die Zahl
ihre Aussage.
- **Automatisch beim Import**, wenn die Gegenstelle zu genau *einem*
Vertrag passt. Bei Mehrdeutigkeit geschieht nichts: Hausrat und
Haftpflicht bei derselben Gesellschaft lassen sich nicht auseinanderhalten,
und eine falsche Zuordnung wäre schlimmer als gar keine — sie behauptet
einen Beleg, und niemand sieht noch einmal hin. Automatisch gesetzte
Zuordnungen sind in der Liste als solche gekennzeichnet.
- **Sammelzuordnung** unter *Wiederkehrende Zahlungen*: Liegt zu einer
Gruppe ein Vertrag vor, werden alle ihre Zahlungen mit einem Klick
zugeordnet. Bei drei Jahren Laufzeit sind das 36 Buchungen.
- **Die Rechnungslegung nennt den Vertrag.** Statt „ohne Beleg – …" steht
dort „Dauerzahlung zu Vertrag «Bezeichnung», Nr. …". Die Lücke wird nicht
versteckt, sondern erklärt: Wer nachprüfen will, weiß sofort, welches
Papier er aus dem Ordner holt.
### Die Sicherung dagegen
**Weicht der Betrag um mehr als ein Fünftel vom hinterlegten Beitrag ab,
gilt die Zahlung nicht als belegt**, sondern wird als *Betrag prüfen*
markiert. Sonst wäre die Zuordnung eine Hintertür, durch die auch eine
verzehnfachte Prämie unbemerkt durchginge.
Ein Fünftel ist bewusst großzügig: Beitragsanpassungen und
Versicherungsteuer bewegen sich in dieser Größenordnung, und dabei soll
die Anwendung nicht meckern. Wer die Grenze enger zieht, bekommt täglich
Warnungen und sieht die eine wichtige nicht mehr.
Steht am Vertrag **kein** Beitrag, findet keine Prüfung statt. Die
Zuordnung gilt trotzdem — es wird nur nicht so getan, als wäre geprüft
worden.
Und: **Verschwindet der Vertrag, fällt die Buchung zurück in „Beleg
fehlt".** Ein Nachweis, der auf etwas zeigt, das es nicht mehr gibt, wäre
schlimmer als gar keiner.
### Zu beachten
- **Eine Datenbank-Migration ist nötig**
(`20260823062000_buchung_vertragszahlung`). Sie fügt zwei Spalten hinzu
und ändert nichts an vorhandenen Daten.
- Bestehende Buchungen sind nicht betroffen: Ohne Zuordnung bleibt alles,
wie es war.
### Geprüft
- 23 neue Einheitstests. Die Hälfte prüft, dass die Anerkennung auch
wirklich **wegfällt** — der gefährliche Fehler wäre hier ein zu mildes
Urteil, nicht ein zu strenges.
- Fünf End-to-End-Tests: Sammelzuordnung, Anzeige in der Buchungsliste,
die abweichende Zahlung, die Kachel auf „Heute", und der Rückfall nach
dem Löschen des Vertrags.
Der Browsertest hat zwei Fehler gefunden, die alle anderen Prüfungen
durchgelassen haben — siehe unten.
### Behoben
- **Die Buchungsliste hatte eine eigene, fast gleiche Datenabfrage** neben
der gemeinsamen aus `berichte/gemeinsam.ts`. Als die Dauerzahlungen
dazukamen, wurde nur eine von beiden nachgezogen: In der Liste stand
weiter „Beleg fehlt", obwohl der Vertrag hinterlegt war. Beide benutzen
jetzt dieselbe Abbildung — die kann nicht halb vergessen werden.
## [1.2.0] – 2026-08-23
### Hinzugefügt
- **Wiederkehrende Zahlungen aufspüren**
([#3](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/3)).
Am Anfang einer Betreuung weiß niemand, welche Verträge es gibt — es liegt
kein Ordner bereit, und die betreute Person kann es oft selbst nicht mehr
sagen. Die Antwort steht aber schon im Kontoauszug: **Wer regelmäßig
abbucht, hat einen Vertrag.**
Neu unter *Vermögen & Kasse → Wiederkehrende Zahlungen*: Die importierten
Kontobewegungen werden nach Empfänger gruppiert und nach Jahresbetrag
sortiert. Je Zeile stehen Rhythmus, Betrag, Zeitraum und ob dazu schon ein
Vertrag in der Akte liegt. Aus der Zeile heraus geht es weiter — *Vertrag
anlegen* mit vorbelegter Gesellschaft und vorbelegtem Beitrag, oder *kein
Vertrag* mit Begründung.
Die Seite wertet nur aus. Sie legt von sich aus nichts an; jede Zeile
endet in einer Entscheidung, die ein Mensch trifft.
**Was sie nennt, begründet sie.** Statt „monatlich" zu behaupten, steht
daneben „6 Zahlungen, etwa monatlich" — man soll widersprechen können.
Schwankt der Betrag, wird die Spanne genannt statt eines Mittelwerts, der
Genauigkeit vortäuscht. Und ohne erkennbaren Abstand wird nicht aufs Jahr
hochgerechnet, sondern gezeigt, was tatsächlich geflossen ist.
**Zusammengefasst wird nur, was sicher dasselbe ist:** Groß- und
Kleinschreibung, Satzzeichen, Umlaute und die Rechtsform am Ende.
„Stadtwerke Bamberg" und „Stadtwerke Bayreuth" unterscheiden sich in drei
Zeichen und bleiben zwei Zeilen. Zwei Zeilen zu viel sind harmlos — eine
falsch zusammengefasste Gruppe verbirgt dagegen einen Vertrag, und der
fehlt dann im Vermögensverzeichnis.
- **Einzelne größere Zahlungen** stehen in einem eigenen Abschnitt darunter.
Grund: Die Gruppierung braucht mindestens zwei Zahlungen, eine jährlich
abgebuchte Prämie erscheint bei einem importierten Jahr aber nur einmal —
und ausgerechnet Versicherungen werden oft jährlich gezahlt. Ohne diese
zweite Liste wäre die Suche genau dort blind, wo sie am meisten bringen
soll.
- **Hinweis auf „Heute"**, sobald es unbearbeitete Kandidaten gibt. Er steht
in der Kachel *Unverbuchte Belege*, nicht in einer eigenen: Es ist
dieselbe Frage — wozu gehört diese Zahlung? —, nur eine Ebene höher.
### Zu beachten
- **Eine Datenbank-Migration ist nötig**
(`20260823000500_zahlungsgruppe_geklaert`). Sie läuft beim Start des
Behälters automatisch mit und legt nur eine neue Tabelle an; bestehende
Daten bleiben unberührt.
- Ausgewertet werden **nur Ausgaben der betreuten Person**. Umbuchungen
zwischen eigenen Konten und Auslagen des Betreuers bleiben außen vor —
wer monatlich aufs Sparbuch legt, hat deswegen keinen Vertrag.
### Geprüft
- 37 Einheitstests (`tests/wiederkehrend.test.ts`), die Hälfte davon in der
Gegenrichtung: dass ähnliche, aber verschiedene Namen **nicht**
zusammenfallen.
- Vier End-to-End-Tests gegen einen echten Browser: Erkennung, getrennte
Liste der Einzelzahlungen, Übernahme ins Vertragsformular, Abhaken und
Zurückholen samt Neuladen.
Zwei Fehler hat erst der Browsertest gefunden. Einer davon hätte die
Vorbelegung des Beitrags stillschweigend verschluckt — Typprüfung und
Einheitstests waren grün.
## [1.1.2] – 2026-08-23
### Behoben
- **„Mein Konto" war am Schreibtisch praktisch nicht zu finden**
([#1](https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/issues/1)).
Damit kam man dort auch nicht an den Passwortwechsel, den zweiten Faktor
und die frisch eingebaute Passkey-Verwaltung.
Ursache: Die Seitenleiste hatte keine feste Höhe und wuchs mit der Seite
mit. Ihr Fuß — und damit das Benutzermenü — landete deshalb am **Ende der
Seite** statt am unteren Bildschirmrand. Auf „Heute" waren das 1632 Pixel
bei 900 Pixel Fensterhöhe: Man musste bis ganz nach unten scrollen, um
an sein eigenes Konto zu kommen.
Der Fehler sah nach nichts aus. Kein Element fehlte, nichts war kaputt,
die Seite sah richtig aus — und genau deshalb sucht man an der falschen
Stelle.
Die Leiste bleibt jetzt stehen (`sticky`, Fensterhöhe). Kopf,
Betreutenauswahl und Fuß sind immer sichtbar; die Navigation dazwischen
scrollt bei Bedarf in sich selbst. Am Handy war es nie ein Problem —
dort sitzt das Menü in der ohnehin festen Kopfzeile.
### Geprüft
- Fünf End-to-End-Tests (`e2e/benutzermenue.spec.ts`) fragen nicht, ob das
Menü **im DOM** ist, sondern ob es **im sichtbaren Bereich** steht: auf
fünf verschiedenen Seiten, an drei Scrollpositionen, auf einem flachen
Laptopfenster (1280 × 620) und am Handy.
Die Scrollposition in der Mitte ist Absicht. Nur oben und unten zu messen
hätte nicht gereicht: Am Seitenende wird eine mitwachsende Leiste
zufällig doch sichtbar — der Test wäre grün gewesen und der Fehler
trotzdem da. Beide Sichtbarkeitstests sind gegen den kaputten Zustand
gegengeprüft und schlagen dort an.
## [1.1.1] – 2026-08-23
### Behoben
- **`./scripts/update.sh` endete in „Permission denied".** Die
Shell-Skripte lagen ohne Ausführungsrecht im Repository (Modus 644 statt
755). Die Anleitung sagt `./scripts/update.sh` — auf Linux ging genau
das nicht.
Ursache ist der Entwicklungsrechner: Unter Windows steht
`core.filemode=false`, dort merkt git von einem fehlenden
Ausführungsrecht nichts. Aufgefallen ist es erst auf dem Server.
**Wer von 1.1.0 oder älter kommt**, braucht einmalig den Umweg — das
alte Skript ist ja noch das nicht ausführbare:
```bash
bash scripts/update.sh
```
Danach stimmt das Recht, und der Punktschrägstrich funktioniert.
- **`update.sh` setzt das Recht nach dem Umschalten selbst.** Wer Dateien
von Hand kopiert hat — etwa um an das Skript zu kommen, bevor es in der
laufenden Fassung existierte —, verliert es dabei. Ein `chmod +x` nach
dem Umschalten fängt das ab.
- **Das Versionsskript datierte in UTC.** Eine Veröffentlichung nach
22 Uhr deutscher Sommerzeit bekam im Changelog das Datum des Vortages —
diese Fassung hier war der erste Fall. Es rechnet jetzt in
`Europe/Berlin`, wie alles andere in dieser Anwendung auch.
### Geprüft
- Vier neue Prüfungen (`tests/skripte.test.ts`) sehen sich nicht das
Programm an, sondern die **Verpackung**: Liegt jedes `.sh` ausführbar im
Index? Hat es eine Shebang-Zeile? Kommt `scripts/` ins Docker-Abbild?
Sind die Sicherungsskripte in der Compose-Datei eingehängt?
Das ist der zweite Fehler dieser Art — vorher fehlte `scripts/` ganz im
Abbild (1.0.1). Beide Male war der Code richtig und kein Test schlug an,
weil keiner hinsah, wie die Dateien beim Server ankommen. Jetzt tut es
einer. Die Gegenprobe ist gemacht: Mit zurückgesetztem Recht schlägt er
fehl und nennt die Datei.
## [1.1.0] – 2026-08-22
### Hinzugefügt
- **Anmelden mit Passkey.** Auf der Anmeldeseite steht jetzt unter dem
Formular ein zweiter Weg: *Mit Passkey anmelden*. Ein Fingerabdruck, ein
Blick in die Kamera oder die Geräte-PIN — keine E-Mail, kein Passwort,
kein abgetippter Code.
**Der Passkey ersetzt den zweiten Faktor, er kommt nicht zusätzlich.**
Das ist kein Nachlassen: Ein Passkey verlangt den Besitz des Geräts
*und* dessen Entsperrung, das sind dieselben zwei Faktoren wie vorher.
Die Anwendung fordert die Nutzerverifikation ausdrücklich an und weist
eine Antwort ohne sie zurück. Ein Gerät, das auf blosses Antippen hin
signiert, kommt nicht herein.
In einem Punkt ist er dem bisherigen Weg sogar überlegen: Ein Passkey
ist an die Adresse gebunden, unter der er eingerichtet wurde. Auf einer
nachgebauten Anmeldeseite funktioniert er nicht — und Phishing ist der
übliche Weg, an einen TOTP-Code zu kommen.
**Die Authenticator-App muss trotzdem eingerichtet sein**, bevor sich
ein Passkey anlegen lässt. Ein Passkey hängt an einem Gerät. Ginge das
verloren und wäre er der einzige Weg hinein, käme niemand mehr an die
Akte — und darin stehen Unterlagen, die eine betreute Person braucht.
Der Passkey ist die bequeme Tür, nicht die einzige.
Verwaltet werden sie unter **Mein Konto → Passkeys**: einrichten,
benennen, entfernen. Die Liste zeigt, wann ein Gerät zuletzt benutzt
wurde und ob der Schlüssel gesichert ist (iCloud, Google, Bitwarden)
oder nur auf diesem einen Gerät liegt. Einrichten und Entfernen stehen
im Protokoll, jede Anmeldung nennt den verwendeten Passkey namentlich.
- **Bestätigen mit Passkey.** Export, endgültiges Löschen und die
Benutzerverwaltung verlangen eine frische Anmeldung. Auch die geht jetzt
mit dem Passkey. Geprüft wird zusätzlich, dass er zum angemeldeten
Zugang gehört — sonst liesse sich mit dem eigenen Finger eine fremde,
offen stehende Sitzung freischalten. Genau der Fall, gegen den diese
Zwischenfrage gebaut ist.
### Zu beachten
- **Passkeys brauchen `https`.** Der Browser gibt das Verfahren nur in
einem sicheren Kontext frei. Läuft die Anwendung ohne TLS, erscheint der
Knopf gar nicht erst, statt beim Klick mit einer kryptischen Meldung
abzubrechen.
- **`AUTH_URL` muss stimmen — jetzt noch mehr als vorher.** Aus ihr wird
die Kennung abgeleitet, an die der Authenticator den Schlüssel bindet.
Steht dort eine andere Adresse als die im Browser, lässt sich zwar ein
Passkey einrichten, aber nie wieder verwenden.
**Ein Wechsel der Domain macht alle Passkeys unbrauchbar.** Das ist
keine Panne, sondern der Phishingschutz. Nach einem Umzug richtet man
sie neu ein; die Anmeldung mit Passwort und Code bleibt währenddessen
offen.
- **Eine Datenbank-Migration ist nötig** (`20260822160000_passkey_anmeldung`).
Sie läuft beim Start des Behälters automatisch mit. Bestehende Zugänge
sind nicht betroffen: Ohne eingerichteten Passkey ändert sich nichts.
### Geprüft
- 21 neue Prüfungen (`tests/passkey.test.ts`), vor allem für die Herleitung
von Kennung und Herkunft aus `AUTH_URL`: Port, Schrägstrich am Ende,
Unterdomain, `http` gegen `https`, `localhost`. Das ist die Stelle, an
der eine Installation still kaputtginge — dieselbe Fehlerklasse wie
seinerzeit bei `APP_BIND` und `ENC_KEY_V2`.
- Drei End-to-End-Tests gegen einen echten Browser (`e2e/passkey.spec.ts`),
mit Chromiums virtuellem Authenticator: einrichten, abmelden, mit Passkey
anmelden, Benutzung vermerkt, entfernen. Dazu die Bestätigung vor dem
Vollexport und der Fall, dass ein Gerät ohne Nutzerverifikation nicht
hereinkommt.
## [1.0.5] – 2026-08-22
### Hinzugefügt
- **`scripts/update.sh`** — aktualisiert die Anwendung mit einem Befehl.
```bash
./scripts/update.sh # auf die neueste Fassung
./scripts/update.sh --pruefen # nur nachsehen, nichts ändern
./scripts/update.sh v1.2.0 # auf eine bestimmte Fassung
```
Es nimmt die Schritte ab, die man von Hand vergisst: Sicherung
anstoßen und bei Misserfolg **abbrechen**, den Changelog zwischen altem
und neuem Stand anzeigen, umschalten, neu bauen, warten bis die
Anwendung antwortet, **die beiden SQL-Skripte ausführen** und zum
Schluss prüfen, ob wirklich die neue Fassung läuft.
Zwei Eigenschaften, die dabei zählen:
**Bis zur Rückfrage wird nichts verändert.** Bricht etwas vorher ab —
Server nicht erreichbar, falsche Versionsangabe — findest du alles so
vor wie zuvor. Kein Halbzustand.
**Eigene Änderungen an versionierten Dateien blockieren nicht.** Im
Betrieb passt man die `docker-compose.yml` an, bis etwas läuft; genau
diese Anpassungen sind später oft schon eingearbeitet. Das Skript legt
sie vor dem Umschalten mit `git stash` beiseite und sagt, wie man sie
zurückholt. Mit `--verwerfen` werden sie stattdessen weggeworfen. Die
`.env` bleibt immer unangetastet — sie ist nicht versioniert.
Schlägt der Bau fehl, nennt das Skript den Weg zurück auf den
vorherigen Stand.
### Behoben
- **Der Port der MinIO-Konsole ließ sich nicht ändern.** Er stand fest auf
`9001`; lief dort schon etwas, startete der ganze Stapel nicht
(`port is already allocated`). Jetzt über `MINIO_KONSOLE_PORT` in der
`.env` einstellbar — dieselbe Möglichkeit, die `APP_PORT` schon hatte.
## [1.0.4] – 2026-08-22
### Behoben
- **Die Ersteinrichtung ließ sich nicht abschließen.** Schritt 3 meldete
„Bitte prüfe die markierten Felder" — markierte aber keines. Man kam an
dieser Stelle nicht weiter.
Ursache: Das kurze Formular der Ersteinrichtung schickt neun Felder, das
vollständige unter *Stammdaten* alle. Beide gehen durch dasselbe Schema,
und die optionalen Felder waren zwar `nullable`, aber nicht `optional` —
ein Feld, das gar nicht abgeschickt wurde, ist `undefined`, nicht
`null`. Beanstandet wurden also zwei Dutzend Felder, die auf dem
Bildschirm nie standen.
- **Eine Meldung ohne zugehöriges Feld kann nicht mehr unsichtbar
bleiben.** Beanstandet die Prüfung ein Feld, das das Formular gar nicht
gesendet hat, sagt die Anwendung das jetzt ausdrücklich und schreibt es
ins Serverlog — statt es als Eingabefehler auszugeben, den niemand
finden kann. Das ist ein Fehler im Programm, und er soll auch so
aussehen.
- **Eine betreute Person ließ sich nicht endgültig löschen.** Beim Löschen
setzt Postgres den Verweis im Audit-Log auf NULL (`ON DELETE SET NULL`),
und genau dieses UPDATE hat der Unveränderlichkeits-Trigger abgewiesen.
Betroffen war damit auch das Leeren des Papierkorbs nach 30 Tagen und
das Entfernen des Demofalls.
Der Trigger erlaubt jetzt genau eine Ausnahme: das Nullen der beiden
Fremdschlüssel `betreuteId` und `userId`. Jede andere Änderung bleibt
verboten, auch das Setzen eines Fremdschlüssels auf einen **anderen**
Wert. Der Nachweis bleibt vollständig — Zeitpunkt, Aktion, Bezeichnung,
die verschlüsselte Änderung und die Momentaufnahmen von Name und
E-Mail sind unberührt. Es verschwindet nur der Verweis auf einen
Datensatz, den es nicht mehr gibt.
**Beim Update nötig:** das SQL-Skript erneut ausführen, sonst bleibt der
alte Trigger stehen.
```bash
docker compose exec app node node_modules/prisma/build/index.js db execute --schema prisma/schema.prisma --file prisma/sql/audit-schutz.sql
```
### Geprüft
- Sieben neue Tests (`tests/stammdaten.test.ts`) prüfen genau die
Formularinhalte der Ersteinrichtung, dazu das vollständige Formular mit
leeren Feldern und die weiterhin verlangten Pflichtangaben.
- Der Ablauf ist zusätzlich im Browser durchgespielt worden: Anmeldung,
zweiter Faktor, Schritt 3, Landung auf dem Dashboard — und die daraus
abgeleiteten Pflichtfristen samt Ausschlussfrist für die
Aufwandspauschale.
- Der geänderte Trigger ist gegen eine laufende Datenbank geprüft:
Löschen gelingt, Fälschen und Löschen von Einträgen scheitert weiterhin.
## [1.0.3] – 2026-08-22
Keine Änderung am Programm — nur an der Konfigurationsvorlage und der
Anleitung. Ein Neubau des Abbilds ist nicht nötig.
### Behoben
- **`.env.example` band die Anwendung stillschweigend an `127.0.0.1`.**
Der Wert stand dort als fertig ausgefüllte Zeile zwischen lauter anderen
fertigen Zeilen. Wer die Datei kopiert hat und seinen Reverse Proxy auf
einem **anderen** Server betreibt, kam nicht an die Anwendung heran —
und der Fehler sah aus, als läge es am Proxy.
Jetzt steht dort eine Entscheidung mit drei benannten Fällen statt eines
Vorgabewerts. Der sichere Standard bleibt `127.0.0.1`; er wird nur nicht
mehr als „schon erledigt" präsentiert.
Für bestehende Installationen genügt eine Zeile in der `.env`:
```bash
APP_BIND=10.0.0.5 # Adresse, über die der Proxy diesen Server erreicht
docker compose up -d
```
### Geändert
- **`docs/INSTALLATION.md`** nennt `APP_BIND` jetzt schon in Schritt 3, wo
die `.env` ausgefüllt wird — nicht erst in Schritt 7, wo der Proxy
drankommt. Dazu ein eigener Abschnitt in der Fehlersuche: „Der Proxy
erreicht die Anwendung nicht", mit den zwei `curl`-Proben, die den
Unterschied zwischen falscher Bindeadresse und Firewall zeigen.
## [1.0.2] – 2026-08-22
### Behoben
- **Die Einrichtung des zweiten Faktors brach mit einem Serverfehler ab**
(„Application error: a server-side exception has occurred"). Im
Serverlog stand `Umgebungsvariable ENC_KEY_V2 fehlt` — obwohl niemand
einen zweiten Schlüssel wollte.
Ursache: `docker-compose.yml` reicht `ENC_KEY_V2` immer an den Behälter
durch, mit **leerem Wert**, wenn in der `.env` keiner steht. Die
Anwendung erkannte Schlüssel aber am Variablennamen statt am Inhalt,
fand also ein leeres `ENC_KEY_V2` und brach ab. Leere
Schlüsselvariablen werden jetzt übersprungen.
Betroffen war jede Stelle, die verschlüsselte Felder schreibt oder
liest — also praktisch alles außer der reinen Anmeldung. Die Anmeldung
ging durch, weil sie ohne Verschlüsselung auskommt; der Fehler trat
deshalb erst auf der nächsten Seite auf.
- **Die Meldung beim Schlüsselwechsel** nennt jetzt, welche Versionen
tatsächlich vorhanden sind, und die richtige Reihenfolge: erst den
neuen Schlüssel eintragen, dann `ENC_KEY_AKTIV` umstellen.
### Hinzugefügt
- **`scripts/anmeldung-pruefen.mjs`** — beantwortet die Frage, warum eine
Anmeldung nicht klappt. Das Formular sagt bewusst nicht, woran es lag,
sonst ließe sich daran ablesen, welche Adressen es gibt. Auf dem Server
darf man es wissen: Jeder Versuch wird mit Grund protokolliert, und das
Skript liest ihn vor — dazu den Zustand des Zugangs, den Stand der
Login-Sperre und die eingestellte `AUTH_URL`.
```bash
docker compose exec app node scripts/anmeldung-pruefen.mjs deine@adresse.de
```
Es ändert nichts, es liest nur.
### Geprüft
- Sieben neue Tests (`tests/umgebung.test.ts`) decken die
Schlüsselauswertung ab, darunter genau die Konstellation aus dem
Fehlerbericht: gesetztes `ENC_KEY_V1`, leeres `ENC_KEY_V2`.
## [1.0.1] – 2026-08-22
### Behoben
- **Betriebsskripte fehlten im Docker-Abbild.** Das Verzeichnis `scripts/`
wurde beim Bauen nie in die Laufzeitstufe übernommen. `docker compose
exec app node scripts/totp-geheimnis.mjs …` endete deshalb mit
`Cannot find module`. Betroffen waren alle Skripte, die im laufenden
Behälter gedacht sind — auch `wartungsschluessel.mjs` und
`schluessel-erzeugen.mjs`.
Nach dem Update funktioniert der Befehl wie dokumentiert. Wer nicht
gleich aktualisieren will, kommt einmalig auch so an das Skript:
```bash
docker compose cp scripts/totp-geheimnis.mjs app:/app/totp-geheimnis.mjs
docker compose exec app node totp-geheimnis.mjs deine@adresse.de
```
### Hinzugefügt
- **`scripts/totp-geheimnis.mjs`** — liest das TOTP-Geheimnis eines Zugangs
aus, um denselben Zugang auf einem zweiten Gerät einzurichten (Handy und
Tablet, oder ein Ersatzgerät). Die Anwendung zeigt das Geheimnis sonst
nur einmal an, beim Einrichten; bisher blieb nur, den zweiten Faktor
zurückzusetzen und alle Wiederherstellungscodes neu zu erzeugen.
Das Skript läuft nur auf dem Server und nur mit Zugriff auf die Datenbank
**und** auf `ENC_KEY_V1`. Wer beides hat, kann ohnehin alles lesen — es
ist also keine Hintertür, sondern erspart eine unnötige Neueinrichtung.
## [1.0.0] – 2026-08-22
Erste vollständige Fassung. Alle acht Etappen umgesetzt, alle
Abnahmekriterien als Test hinterlegt.
### Hinzugefügt
**Der Alltag**
- **Dashboard „Heute"** — beantwortet morgens, was ansteht: Fristen-Ampel
(überfällig / diese Woche / diesen Monat), wer gerade am Zug ist,
offene Nachforderungen des Bezirks, ungeprüfter Posteingang,
unverbuchte Belege, Kennzahlen zu Vermögen und Heimkosten.
- **Fristen** — die gesetzlichen Pflichtfristen entstehen von selbst aus
dem Bestellungsdatum: Vermögensverzeichnis und Anfangsbericht nach drei
Monaten, Jahresbericht und Vermögensübersicht zum Ende jedes
Betreuungsjahres, Aufwandspauschale, Landespflegegeld,
Zuzahlungsbefreiung, Rückkaufswerte. Liste, Monatskalender und ein
ICS-Feed zum Abonnieren im eigenen Kalender.
- **Vorgänge** als Fallakte mit Zeitstrahl über Dokumente, Protokolle,
Fristen und Buchungen. Neun Vorlagen mit vorbefüllter Aufgabenliste,
darunter Heimeinzug, Antrag Hilfe zur Pflege, Wohnungsauflösung,
Hausverkauf, Darlehen nach § 91 SGB XII und Widerspruch gegen einen
Bescheid.
- **Kontakte und Kontaktprotokoll** — Verzeichnis aller Stellen mit
Aktenzeichen, dazu eine Schnellerfassung, die am Handy in unter zwanzig
Sekunden zu bedienen ist.
**Papier**
- **Dokumentenablage** mit Drag & Drop, Sammel-Upload und Handy-Foto.
Texterkennung auf Deutsch, Volltextsuche über alle Schreiben,
Posteingangsbuch mit den Stufen neu → gesichtet → zugeordnet →
beantwortet, Feld für den Ablageort des Papieroriginals.
- Beim Import werden **Absender, Datum, Aktenzeichen, Dokumentart und
eine im Text genannte Frist vorgeschlagen** — mit der Textstelle
daneben, damit man prüfen kann. Übernommen wird nur auf Klick.
- **Belegmappe**: alle Dokumente eines Vorgangs oder Zeitraums als eine
chronologisch sortierte, paginierte PDF mit Deckblatt und
Inhaltsverzeichnis.
**Geld**
- **Kassenbuch** mit Belegzwang, Kategorien, CSV- und CAMT.053-Import.
Harte Trennung zwischen dem Vermögen der betreuten Person und eigenen
Auslagen, mit Warnung bei Buchungen auf ein als privat markiertes Konto.
- **Konten** mit verschlüsselter IBAN und Saldoständen je Stichtag,
**Vermögensposten** mit vollständiger Wert-Historie und Pflichtangabe
zur Wertquelle. Werte werden nie überschrieben.
- **Vermögensverzeichnis (§ 1835 BGB)** und **Vermögensübersicht bzw.
Rechnungslegung (§§ 1859, 1865 BGB)** als druckfertige PDF, in zwei
Ausgabevarianten für befreite und nicht befreite Betreuer.
**Behörde**
- **Sozialhilfe-Modul** für das Verfahren beim Bezirk: Antragsakte,
Nachforderungs-Tracker mit Fortschrittsbalken, vorbefüllte Checkliste
der Standardunterlagen mit Begründung, warum der Träger sie will.
- **Bescheid-Prüfung** mit Berechnung der Rechtsbehelfsfrist ab dem
Zugangsdatum, einschließlich Wochenende, bayerischer Feiertage und dem
Sonderfall fehlender Rechtsbehelfsbelehrung. Der Rechenweg wird
ausgegeben.
- **Heimkosten-Gegenüberstellung** Monat für Monat: Kosten gegen
Pflegekasse, Leistungszuschlag nach § 43c SGB XI, Rente und
Sozialhilfeanteil — die Deckungslücke wird sichtbar.
- **Schonvermögens-Bewertung** je Posten (verwertbar / geschützt /
strittig) mit Begründungsfeld und Textbausteinen zu § 90 SGB XII.
- **Katalog weiterer Leistungen**: Landespflegegeld, Zuzahlungsbefreiung,
Rundfunkbeitragsbefreiung, Schwerbehindertenausweis, Grundsicherung.
**Substanz**
- **Immobilie** mit Grundbuchdaten, Belastungen nach Abteilung II und
III, Wert-Historie, Status im Sozialhilfeverfahren und einer
Pflichten-Checkliste bei Leerstand — zehn Punkte, jeder mit Begründung.
- **Versicherungen und Verträge** mit Kündigungsfristen-Rechner, der den
spätesten Absendetag nennt, Anschreiben zur Rückkaufswert-Anfrage mit
automatischer Wiedervorlage und einem Kündigungs-Assistenten.
- **Heim und Pflege** mit Heimvertrag, Leistungszuschlag-Staffel,
Pflegegrad-Historie, Abwesenheitstagen und einem Gesundheitsverlauf mit
COPD-Bezug: Exazerbationen, Sauerstoff, Stürze, Krankenhaus,
Hilfsmittel, Impfungen nach STIKO-Plan.
**Ausgabe**
- **Jahresbericht (§ 1863 BGB)** als Entwurf zum Überarbeiten, gespeist
aus Kontaktprotokollen, Gesundheitsereignissen und Vorgangsstatus.
Ausgabe als PDF und als DOCX.
- **Unterlagenpaket für den Bezirk**: die im Tracker markierten
Dokumente als eine PDF mit Anschreiben und Inhaltsverzeichnis.
- **Vollexport** als ZIP mit allen Originaldateien, allen Daten als
entschlüsseltes JSON, den erzeugten Berichten, einer LIESMICH und
SHA-256-Prüfsummen.
- **Wissensbasis** mit 37 Hinweiskarten, pflegbar als JSON-Datei ohne
Neustart. Jede mit Rechtsgrundlage, Link und dem stehenden Vermerk,
dass sie keine Rechtsberatung ersetzt.
**Verwaltung**
- **Mitleser-Rolle** mit modulweiser Freigabe und ausdrücklicher Warnung
bei Gesundheit, Finanzen und Sozialhilfe.
- **Papierkorb** mit 30 Tagen Aufbewahrung.
- **Wartungslauf** über `/api/wartung`: Fristerinnerungen per E-Mail,
Papierkorb leeren, alte Anmeldeversuche entfernen.
### Sicherheit
- **Pflicht-Zweitfaktor** (TOTP nach RFC 6238). Lässt sich nicht
abschalten. Acht Wiederherstellungscodes, einzeln Argon2id-gehasht.
- **Anwendungsseitige Feldverschlüsselung** mit AES-256-GCM für
Diagnosen, den ermittelten Willen, IBANs, Versicherungs-, Renten- und
Steuernummern sowie alle Freitext-Notizen. Der Feldname wird
mitauthentifiziert — ein Geheimtext lässt sich nicht von einem Feld in
ein anderes umhängen. Schlüsselrotation ohne Datenverlust vorgesehen.
- **Argon2id** nach OWASP-Empfehlung, mit automatischem Nachziehen bei
späterer Parametererhöhung.
- **Rate-Limiting** am Login nach E-Mail und Anschluss. Die Sperre wird
geprüft, bevor überhaupt gerechnet wird, und die Antwortzeit ist
gestreckt — sie verrät nicht, welche Adressen existieren.
- **Unveränderliches Audit-Log**: ein Datenbank-Trigger weist UPDATE und
DELETE ab, auch bei direktem Zugriff mit `psql`.
- **Strenge Content-Security-Policy** mit Nonce pro Anfrage. Keine
externen Schriften, kein CDN, keine Tracker — nachgewiesen durch einen
Test, der jede Anfrage an einen fremden Host als Fehler wertet.
- **Uploads** werden an den Magic Bytes geprüft, nicht an Endung oder
gemeldetem MIME-Typ. Ausgeliefert wird nur über kurzlebige signierte
URLs, immer als Anhang mit `nosniff`.
- **Erneute Anmeldung** vor Export, endgültigem Löschen und
Rollenänderungen. Ein Passwortwechsel beendet alle offenen Sitzungen.
### Betrieb
- Docker Compose mit Anwendung, PostgreSQL, MinIO und Sicherungsdienst.
**Ohne Reverse Proxy** — der ist auf dem Zielserver vorhanden. Die
Datenbank hat keine Portfreigabe.
- **Tägliche verschlüsselte Sicherung** um 03:15 Uhr mit Selbstprüfung:
lässt sich die Datei nicht wieder entschlüsseln, endet der Lauf mit
Fehler statt mit einem stillen Scheinerfolg.
- Verarbeitungsverzeichnis und Datenschutzhinweise als ausfüllbare
Textbausteine.
- Bauprotokoll aus den Commit-Trailern.
### Bekannte Einschränkungen
- Die Texterkennung ist gegen ein echtes Tesseract **nicht** end-to-end
geprüft. Der Weg „nicht verfügbar, Upload funktioniert trotzdem" ist
getestet.
- Der CAMT.053-Import ist nur gegen selbst gebaute Beispiele geprüft, nicht
gegen einen echten Bankauszug.
- Das Kostenkonto der Immobilie wertet über Buchungskategorien aus und
wäre bei einem **zweiten** Grundstück falsch. Die Oberfläche sagt das
an der Auswertung dazu.
- Eine Aufgabe aus einer Vorgangs-Vorlage lässt sich nicht entfernen —
passt sie im Einzelfall nicht, bleibt sie offen stehen.
- `@react-pdf/renderer` braucht in diesem Abhängigkeitsbaum eine Krücke
(`src/lib/pdf/standardschrift.ts`), weil seine Unterabhängigkeiten für
Standardschriften kein Schriftobjekt mehr liefern.
Was davon wann angegangen wird, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md).
---
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.12.0...HEAD
[1.12.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.11.0...v1.12.0
[1.11.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.10.2...v1.11.0
[1.10.2]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.10.1...v1.10.2
[1.10.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.10.0...v1.10.1
[1.10.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.9.0...v1.10.0
[1.9.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.8.0...v1.9.0
[1.8.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.7.0...v1.8.0
[1.7.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.6.0...v1.7.0
[1.6.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.5.0...v1.6.0
[1.5.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.4.0...v1.5.0
[1.4.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.3.0...v1.4.0
[1.3.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.2.0...v1.3.0
[1.2.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.2...v1.2.0
[1.1.2]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.1...v1.1.2
[1.1.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.1.0...v1.1.1
[1.1.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.5...v1.1.0
[1.0.5]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.4...v1.0.5
[1.0.4]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.3...v1.0.4
[1.0.3]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.2...v1.0.3
[1.0.2]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.1...v1.0.2
[1.0.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.0...v1.0.1
[1.0.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/releases/tag/v1.0.0