From 69613a8a75a1c15af77f581cc54aa7bacf100b2b Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Wed, 29 Jul 2026 20:36:33 +0200 Subject: [PATCH] pflegepilot_v3: Feature-Ausbau (T1-T18 Tester-Backlog), System-Backup/Restore, Landingpage-SEO Grosser Funktionsschub ueber mehrere Sessions: Vollmacht- und Patientenverfuegung-Generator, Umkreissuche fuer Pflegedienste/Sanitaets- haeuser/Wohnformen/Therapie inkl. Favoriten und Aenderungsbenachrichtigung, Dokumenten-Scan sowie eGK-Kartenerfassung per Kamera, Budget-Tracker, Krisen-Checklisten, PLZ-Autofill, Kalender-Export, Passwort-Reset per SMTP, Checkliste vor Antragstellung, Leistungsauswahl/-rolle im Interview mit Steuervorteil-Hinweis. Admin-seitig: System-Backup inkl. Wiederherstellung aus einem hochgeladenen Archiv, ueberarbeitetes Dashboard mit Patienten- Kacheln. Landingpage inhaltlich und fuer SEO ueberarbeitet (Server- Metadaten, JSON-LD, robots.txt/sitemap.xml), Hero-Text und SEO-Meta ueber die Backend-Einstellungen editierbar gemacht. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 1 + CLAUDE.md | 214 +++++++ Feedbackfromtester.md | 187 +++++++ Feedbackfromtester.txt | 33 ++ pflegepilot_v3/backend/Dockerfile | 12 +- pflegepilot_v3/backend/app/backup.py | 238 ++++++++ .../backend/app/backup_scheduler.py | 67 +++ pflegepilot_v3/backend/app/briefe_text.py | 362 +++++++++++- pflegepilot_v3/backend/app/changelog_seed.py | 108 +++- pflegepilot_v3/backend/app/config.py | 22 + pflegepilot_v3/backend/app/dokument_scan.py | 146 +++++ pflegepilot_v3/backend/app/egk_ocr.py | 169 ++++++ pflegepilot_v3/backend/app/einstellungen.py | 22 +- pflegepilot_v3/backend/app/email_util.py | 58 ++ pflegepilot_v3/backend/app/katalog_watch.py | 131 +++++ pflegepilot_v3/backend/app/main.py | 20 +- pflegepilot_v3/backend/app/models.py | 109 +++- .../backend/app/pdf_templates/brief.html | 106 +++- .../pdf_templates/patientenverfuegung.html | 74 +++ .../backend/app/reminder_scheduler.py | 102 ++++ pflegepilot_v3/backend/app/routers/admin.py | 128 ++++- .../backend/app/routers/aufgaben.py | 55 +- pflegepilot_v3/backend/app/routers/auth.py | 127 ++++- pflegepilot_v3/backend/app/routers/briefe.py | 250 ++++++++- .../backend/app/routers/dashboard.py | 73 ++- .../backend/app/routers/dokumente.py | 73 ++- pflegepilot_v3/backend/app/routers/egk.py | 38 ++ .../backend/app/routers/einstellungen.py | 7 + pflegepilot_v3/backend/app/routers/guides.py | 12 + pflegepilot_v3/backend/app/routers/katalog.py | 2 + .../backend/app/routers/leistungen.py | 178 ++++++ .../backend/app/routers/patienten.py | 38 +- pflegepilot_v3/backend/app/routers/plz.py | 20 + .../backend/app/routers/umkreissuche.py | 96 ++++ pflegepilot_v3/backend/app/schemas.py | 249 ++++++++- .../app/static_data/antrag_checkliste.py | 56 ++ .../backend/app/static_data/dokument_typen.py | 22 + .../backend/app/static_data/katalog.py | 224 ++++++++ .../backend/app/static_data/krankenkassen.py | 2 +- .../backend/app/static_data/krisenguides.py | 70 +++ .../patientenverfuegung_textbausteine.py | 292 ++++++++++ .../app/static_data/pflegepauschbetrag.py | 14 + .../backend/app/static_data/plz_orte.json | 1 + .../backend/app/static_data/plz_orte.py | 35 ++ .../app/static_data/rentenversicherung.py | 2 +- .../backend/app/umkreis_favoriten_watch.py | 227 ++++++++ pflegepilot_v3/backend/app/umkreissuche.py | 224 ++++++++ pflegepilot_v3/backend/app/validierung.py | 48 ++ .../212cec88ae07_widerrufsverzicht.py | 27 + .../versions/333399a0f933_passwort_reset.py | 33 ++ .../versions/a1c9e7d2b4f6_leistung_rolle.py | 30 + .../a7c4e9f1b2d3_umkreissuche_favoriten.py | 40 ++ ...e5f7a1c9_patientenverfuegung_antrag_typ.py | 33 ++ .../c3f8a1d5e9b2_vollmacht_antrag_typ.py | 33 ++ .../d4e7f2a8c1b3_leistung_budget_ausgaben.py | 33 ++ .../e6f9a3c2b7d1_katalog_snapshots.py | 33 ++ .../f1a2b3c4d5e6_reminder_einstellungen.py | 31 ++ .../f552607a045e_berechtigung_bestaetigt.py | 27 + pflegepilot_v3/backend/requirements.txt | 8 + pflegepilot_v3/docker-compose.yml | 16 + .../app/(app)/admin/einstellungen/page.tsx | 28 +- .../frontend/src/app/(app)/admin/page.tsx | 157 +++++- .../frontend/src/app/(app)/aufgaben/page.tsx | 33 +- .../frontend/src/app/(app)/dashboard/page.tsx | 190 ++++++- .../src/app/(app)/einstellungen/page.tsx | 86 +++ .../frontend/src/app/(app)/guides/page.tsx | 52 ++ .../frontend/src/app/(app)/katalog/page.tsx | 58 +- .../frontend/src/app/(app)/layout.tsx | 27 +- .../src/app/(app)/musterbrief/page.tsx | 16 + .../(app)/patienten/[id]/bearbeiten/page.tsx | 76 ++- .../src/app/(app)/patienten/[id]/page.tsx | 338 +++++++++++- .../src/app/(app)/patienten/neu/page.tsx | 382 +++++++++++-- .../app/(app)/patientenverfuegung/page.tsx | 520 ++++++++++++++++++ .../frontend/src/app/(app)/rechner/page.tsx | 17 +- .../src/app/(app)/umkreissuche/page.tsx | 198 +++++++ .../frontend/src/app/(app)/ummeldung/page.tsx | 15 +- .../frontend/src/app/(app)/vollmacht/page.tsx | 185 +++++++ .../src/app/(app)/widerspruch/page.tsx | 3 + .../frontend/src/app/(marketing)/layout.tsx | 10 +- .../frontend/src/app/(marketing)/page.tsx | 217 ++++++-- .../passwort-zuruecksetzen/page.tsx | 105 ++++ pflegepilot_v3/frontend/src/app/layout.tsx | 2 +- pflegepilot_v3/frontend/src/app/robots.ts | 38 ++ pflegepilot_v3/frontend/src/app/sitemap.ts | 19 + .../src/components/AntragCheckliste.tsx | 92 ++++ .../frontend/src/components/AuthOverlay.tsx | 171 +++++- .../frontend/src/components/BentoGrid.tsx | 3 + .../frontend/src/components/BudgetTracker.tsx | 143 +++++ .../frontend/src/components/DokumentScan.tsx | 118 ++++ .../frontend/src/components/EgkScan.tsx | 135 +++++ .../frontend/src/components/InfoTooltip.tsx | 33 ++ pflegepilot_v3/frontend/src/lib/admin.ts | 64 ++- pflegepilot_v3/frontend/src/lib/api.ts | 2 +- pflegepilot_v3/frontend/src/lib/aufgaben.ts | 21 +- pflegepilot_v3/frontend/src/lib/auth.ts | 36 +- pflegepilot_v3/frontend/src/lib/briefe.ts | 37 +- pflegepilot_v3/frontend/src/lib/dokumente.ts | 41 +- pflegepilot_v3/frontend/src/lib/egk.ts | 31 ++ .../frontend/src/lib/einstellungen.ts | 4 + pflegepilot_v3/frontend/src/lib/guides.ts | 19 + pflegepilot_v3/frontend/src/lib/katalog.ts | 1 + pflegepilot_v3/frontend/src/lib/leistungen.ts | 127 +++++ .../frontend/src/lib/leistungserklaerungen.ts | 19 + pflegepilot_v3/frontend/src/lib/patienten.ts | 30 +- .../frontend/src/lib/patientenverfuegung.ts | 43 ++ pflegepilot_v3/frontend/src/lib/pflegegrad.ts | 10 + .../frontend/src/lib/pflegepauschbetrag.ts | 9 + pflegepilot_v3/frontend/src/lib/plz.ts | 41 ++ .../frontend/src/lib/umkreissuche.ts | 93 ++++ .../frontend/src/lib/versichertennummer.ts | 32 ++ .../rechtstexte/Datenschutzerklaerung.txt | 197 +++++++ pflegepilot_v3/rechtstexte/Impressum.txt | 64 +++ .../rechtstexte/Nutzungsbedingungen.txt | 199 +++++++ 113 files changed, 9421 insertions(+), 254 deletions(-) create mode 100644 Feedbackfromtester.md create mode 100644 Feedbackfromtester.txt create mode 100644 pflegepilot_v3/backend/app/backup.py create mode 100644 pflegepilot_v3/backend/app/backup_scheduler.py create mode 100644 pflegepilot_v3/backend/app/dokument_scan.py create mode 100644 pflegepilot_v3/backend/app/egk_ocr.py create mode 100644 pflegepilot_v3/backend/app/email_util.py create mode 100644 pflegepilot_v3/backend/app/katalog_watch.py create mode 100644 pflegepilot_v3/backend/app/pdf_templates/patientenverfuegung.html create mode 100644 pflegepilot_v3/backend/app/reminder_scheduler.py create mode 100644 pflegepilot_v3/backend/app/routers/egk.py create mode 100644 pflegepilot_v3/backend/app/routers/guides.py create mode 100644 pflegepilot_v3/backend/app/routers/leistungen.py create mode 100644 pflegepilot_v3/backend/app/routers/plz.py create mode 100644 pflegepilot_v3/backend/app/routers/umkreissuche.py create mode 100644 pflegepilot_v3/backend/app/static_data/antrag_checkliste.py create mode 100644 pflegepilot_v3/backend/app/static_data/dokument_typen.py create mode 100644 pflegepilot_v3/backend/app/static_data/krisenguides.py create mode 100644 pflegepilot_v3/backend/app/static_data/patientenverfuegung_textbausteine.py create mode 100644 pflegepilot_v3/backend/app/static_data/pflegepauschbetrag.py create mode 100644 pflegepilot_v3/backend/app/static_data/plz_orte.json create mode 100644 pflegepilot_v3/backend/app/static_data/plz_orte.py create mode 100644 pflegepilot_v3/backend/app/umkreis_favoriten_watch.py create mode 100644 pflegepilot_v3/backend/app/umkreissuche.py create mode 100644 pflegepilot_v3/backend/app/validierung.py create mode 100644 pflegepilot_v3/backend/migrations/versions/212cec88ae07_widerrufsverzicht.py create mode 100644 pflegepilot_v3/backend/migrations/versions/333399a0f933_passwort_reset.py create mode 100644 pflegepilot_v3/backend/migrations/versions/a1c9e7d2b4f6_leistung_rolle.py create mode 100644 pflegepilot_v3/backend/migrations/versions/a7c4e9f1b2d3_umkreissuche_favoriten.py create mode 100644 pflegepilot_v3/backend/migrations/versions/b8d3e5f7a1c9_patientenverfuegung_antrag_typ.py create mode 100644 pflegepilot_v3/backend/migrations/versions/c3f8a1d5e9b2_vollmacht_antrag_typ.py create mode 100644 pflegepilot_v3/backend/migrations/versions/d4e7f2a8c1b3_leistung_budget_ausgaben.py create mode 100644 pflegepilot_v3/backend/migrations/versions/e6f9a3c2b7d1_katalog_snapshots.py create mode 100644 pflegepilot_v3/backend/migrations/versions/f1a2b3c4d5e6_reminder_einstellungen.py create mode 100644 pflegepilot_v3/backend/migrations/versions/f552607a045e_berechtigung_bestaetigt.py create mode 100644 pflegepilot_v3/frontend/src/app/(app)/einstellungen/page.tsx create mode 100644 pflegepilot_v3/frontend/src/app/(app)/guides/page.tsx create mode 100644 pflegepilot_v3/frontend/src/app/(app)/patientenverfuegung/page.tsx create mode 100644 pflegepilot_v3/frontend/src/app/(app)/umkreissuche/page.tsx create mode 100644 pflegepilot_v3/frontend/src/app/(app)/vollmacht/page.tsx create mode 100644 pflegepilot_v3/frontend/src/app/(marketing)/passwort-zuruecksetzen/page.tsx create mode 100644 pflegepilot_v3/frontend/src/app/robots.ts create mode 100644 pflegepilot_v3/frontend/src/app/sitemap.ts create mode 100644 pflegepilot_v3/frontend/src/components/AntragCheckliste.tsx create mode 100644 pflegepilot_v3/frontend/src/components/BudgetTracker.tsx create mode 100644 pflegepilot_v3/frontend/src/components/DokumentScan.tsx create mode 100644 pflegepilot_v3/frontend/src/components/EgkScan.tsx create mode 100644 pflegepilot_v3/frontend/src/components/InfoTooltip.tsx create mode 100644 pflegepilot_v3/frontend/src/lib/egk.ts create mode 100644 pflegepilot_v3/frontend/src/lib/guides.ts create mode 100644 pflegepilot_v3/frontend/src/lib/leistungen.ts create mode 100644 pflegepilot_v3/frontend/src/lib/leistungserklaerungen.ts create mode 100644 pflegepilot_v3/frontend/src/lib/patientenverfuegung.ts create mode 100644 pflegepilot_v3/frontend/src/lib/pflegepauschbetrag.ts create mode 100644 pflegepilot_v3/frontend/src/lib/plz.ts create mode 100644 pflegepilot_v3/frontend/src/lib/umkreissuche.ts create mode 100644 pflegepilot_v3/frontend/src/lib/versichertennummer.ts create mode 100644 pflegepilot_v3/rechtstexte/Datenschutzerklaerung.txt create mode 100644 pflegepilot_v3/rechtstexte/Impressum.txt create mode 100644 pflegepilot_v3/rechtstexte/Nutzungsbedingungen.txt diff --git a/.gitignore b/.gitignore index 3498314..805f1ca 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ pflegepilot_v3/backend/.venv*/ pflegepilot_v3/frontend/node_modules/ pflegepilot_v3/frontend/.next/ pflegepilot_v3/.env +pflegepilot_v3/secrets/ diff --git a/CLAUDE.md b/CLAUDE.md index 02d0bda..ced86ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Naming: pflegepilot_v3's product is now branded "MeinPflegeFuchs" (renamed 2026-07-24) + +The `pflegepilot_v3` product was renamed from "Pflegepilot" to **"MeinPflegeFuchs"** (domain: meinpflegefuchs.de, ordered via Strato) — there turned out to be several unrelated existing apps/services already using "Pflegepilot" or very close variants, discovered when checking candidate replacement names for trademark/confusion risk (a lesson worth repeating: the German Pflege-app naming space is extremely crowded — a `"Pflege" + descriptive word` pattern is very likely to already be taken by a real competing service; "MeinPflegeFuchs" was chosen specifically because it's a mascot-style name rather than a literal descriptor). + +- **Every user-visible string, generated-document footer, email subject, page title/metadata, and the two legal texts under `rechtstexte/` were updated** to "MeinPflegeFuchs" — see the `changelog_seed.py` entry (version 1.1.0) for the user-facing announcement of the rename. +- **The `pflegepilot_v3` directory name, package/module names, DB names, and NAS deployment path (`/Dockers/pflege`) were deliberately left unchanged** — renaming those would touch git history, every absolute path referenced throughout this file and past deployment scripts, and the NAS's manually-maintained (non-git) copy, for zero user-facing benefit. Don't be confused that the folder still says "pflegepilot" while the product itself is called MeinPflegeFuchs — that's intentional, not a leftover. +- Only scope was `pflegepilot_v3` — `pflegepilot_alpha_v2` (the old, separately-running Flask app) still legitimately calls itself Pflegepilot and was not touched. +- Historical references to "Pflegepilot" — e.g. the changelog's 1.0.0 entry, or anything below in this file describing what was true *at the time it was written* — are accurate records of the past and were deliberately left as-is rather than rewritten; only forward-looking/current-state documentation and all actual product code got renamed. When adding new documentation to this file, use "MeinPflegeFuchs", not "Pflegepilot". +- `einstellungen.firmenname` (sidebar branding / PDF-footer company name) is a separate, operator-configured field for the *legal entity* name (currently "Daniel Barac Onlinedienste" in production) — unrelated to the product brand name and intentionally not touched by this rename. + ## Repository layout This is not a single project but a working directory with one active app plus reference/planning material: @@ -129,6 +139,48 @@ Added 2026-07-24 per customer feedback: when a patient moves, both the Krankenka Added 2026-07-24: until then there was no way to fix a typo made at patient creation — only Pflegegrad (`PATCH .../pflegegrad`) and Notizen (`POST .../notizen`, audit-trailed via `notizen_verlauf`) had dedicated update routes/UI; every other field (name, address, contact, insurance) was write-once via the "Neuer Patient" wizard. `PUT /api/patienten/{pid}` (`PatientUpdate` schema, generic `setattr` loop in `routers/patienten.py`) already supported updating all of these — it just had no frontend form. `app/(app)/patienten/[id]/bearbeiten/page.tsx` is that form: a single-page (not multi-step, unlike the creation wizard) edit form prefilled from the current patient, "Bearbeiten" link added next to "Löschen" on the patient detail page. Deliberately excludes `notizen` (keeps going through its own audited flow) and `patient_status`/`pflegegrad`/`foto_pfad` (each has its own dedicated update path already). +## Passwort-vergessen per SMTP (pflegepilot_v3 only, 2026-07-24) — deliberate exception to "no SMTP" + +The operator noticed self-registered accounts had no email address at all, so a "Passwort vergessen" flow had no way to verify identity. Fixed by adding `nutzer.email` (unique, mandatory on new self-registrations, optional/backfillable on admin-created accounts) plus a standard token-based reset flow. + +**This was, until 2026-07-28, the only place in `pflegepilot_v3` where the server sends email of its own accord.** (The "Email: mailto hand-off, not SMTP" section further below describes `pflegepilot_alpha_v2`'s design, not v3's — v3's own Musterbrief/Widerspruch/Ummeldung/Vollmacht generators don't send email at all, mailto or otherwise, they only offer a PDF download.) Two more proactive-email use cases were added on 2026-07-28 with operator sign-off; see "Proaktiver Server-Mailversand erweitert" below for the current, complete list and the reasoning for each. Don't add a *fourth* proactive-email use case without the same kind of explicit check-in — this isn't meant to become the default pattern for every notification idea. + +- `app/email_util.py: send_email()` is the one place SMTP is touched. Configured via `SMTP_HOST`/`SMTP_PORT`/`SMTP_USER`/`SMTP_PASSWORD`/`SMTP_FROM`/`SMTP_USE_SSL`/`SMTP_USE_TLS` env vars (`app/config.py`), all optional. **As long as `SMTP_HOST` is unset, it only logs a warning and returns — never raises.** This was deliberate, not a stopgap to remove later: `POST /api/auth/passwort-vergessen` must always return the same generic response regardless of whether the email exists or whether sending succeeds (anti-enumeration), so `send_email()` swallowing its own failures is exactly the right shape for that caller, not a bug. +- `docker-compose.yml`'s `api.environment` block only passes through explicitly listed keys — adding a new `Settings` field in `config.py` is not enough by itself, the corresponding `${VAR}` line must also be added to `environment:` (this bit locally the first time: the `.env` value was set correctly but never reached the container until `docker-compose.yml` itself was updated). `SMTP_USE_SSL`/`smtp_use_ssl` was added alongside `smtp_use_tls` (STARTTLS) because Strato (the operator's SMTP provider) offers both port 465 (implicit SSL, `smtplib.SMTP_SSL`) and 587 (STARTTLS on a plaintext-then-upgraded connection) — these are different connection modes, not interchangeable, and the operator's provider notes specified SSL/465. **Live and verified 2026-07-24** against the operator's real Strato mailbox (`support@db-onlinedienste.de`, port 465/SSL) — both a direct `send_email()` call and the actual `/api/auth/passwort-vergessen` endpoint were tested end-to-end with no errors logged. +- **The real SMTP password lives in `pflegepilot_v3/secrets/SMTP-Secrets.txt`** (gitignored — this folder was untracked-but-NOT-ignored when the operator first created it, meaning a stray `git add -A` would have committed a plaintext password; added to `.gitignore` in the same pass this feature shipped). Never read this file's contents into a chat response, a commit, or any other persisted location — when working with it, treat the value as write-only: read it, pipe it directly into `.env` via file transfer (not inline shell arguments — special characters in the actual password broke naive shell-quoting attempts elsewhere in this project's history), never echo or log it. +- Reset tokens: `secrets.token_urlsafe(32)`, only the SHA-256 hash is stored (`nutzer.reset_token_hash`/`reset_token_laeuft_ab`), 1-hour validity, single-use (cleared on successful reset). Same rate-limiting pattern as `login`/`registrieren` (`is_rate_limited`, keyed by IP). +- The reset-link landing page (`/passwort-zuruecksetzen?token=...`) had to go in `(marketing)/`, not `(app)/` — the `(app)` layout shows a blocking `AuthOverlay` for anyone not logged in, which would swallow the page before the reset form ever rendered. `(marketing)/` has no such gate, which is exactly why Impressum/Datenschutz/AGB already live there. +- **Existing accounts created before this change have no email** and can't use password-reset until one is set — either the account holder re-registers, or an admin sets `email` via `PUT /api/nutzer/{uid}` (schema-ready; no dedicated admin edit-account modal exists yet in pflegepilot_v3's admin dashboard to do this through the UI — only the quick-create form and sperren/entsperren/löschen/Freischalten/Rechnung actions exist there today). Building that modal is a natural next step but wasn't part of this task. + +## Berechtigungsbestätigung je Patient (pflegepilot_v3 only, 2026-07-24) + +Operationalizes what the ToS (Ziffer 5) and Datenschutzerklärung (Ziffer 7) already say in prose — that the Nutzer, not Pflegepilot, is responsible for having a legal basis (Einwilligung, Pflege-/Fürsorgebeziehung, or rechtliche Vertretung) to enter another person's data — as an actual captured declaration, same pattern as the Widerrufsrecht consent at registration (§ 356 BGB) added earlier the same day. + +- **Per patient, not once per account** (deliberate choice, confirmed with the operator): each care recipient is a distinct person who may have a distinct authorization basis (e.g. Vorsorgevollmacht for one parent but not the other), so the checkbox appears on Step 6 ("Übersicht") of the "Neuer Patient" wizard every time, not just for the first patient created. +- Enforced server-side (`POST /api/patienten` rejects with 400 if `berechtigung_bestaetigt` isn't `true`), not just a disabled button — same reasoning as the registration consent: a client-only gate is trivially bypassed by calling the API directly. +- `patient.berechtigung_bestaetigt_am` (migration `f552607a045e`) stores the timestamp as the evidentiary record, mirroring `nutzer.widerrufsverzicht_bestaetigt_am`. Not exposed via `PatientOut` (kept DB-only, same restraint as the Nutzer field) — query directly if ever needed. +- `PatientCreate.berechtigung_bestaetigt` is excluded from the `model_dump()` spread into the `Patient(...)` constructor in `routers/patienten.py` (it isn't a column — only `_am` is) — don't add fields to `PatientCreate` that aren't 1:1 `Patient` columns without checking this exclusion still holds. + +## Incident 2026-07-24: German-comma price value crashed the entire admin dashboard + +`app/einstellungen.py: abo_preise()` used to do a bare `decimal.Decimal(get_setting(...))`. The admin saved the "Aufpreis je zusätzlichem Patienten" field in Backend-Einstellungen with a German-formatted value (`"4,99"` — comma, not dot) — completely natural input for a German user, but `decimal.Decimal("4,99")` raises `InvalidOperation`. Since `abo_preise()` is called from `GET /api/nutzer` (admin's Nutzerliste, to compute `abo_preis_aktuell` per account), this took down the **entire** admin account list with a 500 — reported by the operator as "the accounts are gone," when in fact no data was lost at all, only the listing endpoint crashed. + +Fixed in two ways, not just one: `_preis_decimal()` normalizes `,` → `.` before parsing, *and* falls back to the compiled-in default on any other `InvalidOperation` rather than propagating the exception — a plain textarea/text-input settings field will eventually get a bad value again (typo, copy-paste artifact, whatever), and a single bad price string must never be able to crash every endpoint that happens to call `abo_preise()` (currently: admin Nutzerliste, `/api/abo/status`, `/api/einstellungen/public`, invoice generation). The admin Einstellungen form (`app/(app)/admin/einstellungen/page.tsx`) also got a small inline hint under both price fields ("bitte mit Punkt, nicht Komma") — cheap prevention, but the defensive parsing is the real fix; don't remove it even if the hint seems to make it redundant. + +**General lesson for this settings store**: any numeric `einstellungen` value that gets `decimal.Decimal()`/`int()`/etc.-parsed at read time is one bad save away from taking down every caller, since `get_setting()` returns whatever string is stored with no validation at write time. If you add another numeric settings key, parse it as defensively as `_preis_decimal()` does — don't assume the stored string is well-formed just because the admin UI is "just" a plain text input. + +## Legal-page links inside the logged-in app shell (pflegepilot_v3 only, 2026-07-24) + +Gap found by the operator: `pflegepilot_alpha_v2` already has a `.sb-legal` sidebar link row for Impressum/Datenschutz/AGB (see that section above), but `pflegepilot_v3`'s `(app)/layout.tsx` never got the equivalent — once logged in, there was no way to reach any of the three legal pages at all (they only existed via the `(marketing)/` footer, which a logged-in user viewing `/dashboard` etc. never sees). Fixed with a small link row at the bottom of the sidebar `