From f9c2883b6bebe8a1d0cad734ab52fd370ff07107 Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Mon, 10 Aug 2026 07:22:09 +0200 Subject: [PATCH] pflegepilot_v3: Notfall-Card zeigt "Zuletzt aktualisiert am"-Zeitstempel Sowohl in der Patientenakte als auch auf der oeffentlichen Notfall-Card- Seite ist jetzt ersichtlich, wann die Notfalldaten (Blutgruppe, Allergien, Medikamente, Notfallkontakt) oder eine neue Patientenverfuegung zuletzt geaendert wurden. Neues Feld Patient.notfallkarte_daten_aktualisiert_am, wird nur bei echter inhaltlicher Aenderung gesetzt, nicht bei jedem Speichern des Bearbeiten-Formulars. Changelog-Eintrag 1.18.5. Ausserdem: neuer CLAUDE.md-Abschnitt zum kuenftigen PR-Workflow ueber Gitea (dieser Commit ist der erste PR unter dem neuen Schema). Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 24 +++++++++++++ pflegepilot_v3/backend/app/changelog_seed.py | 8 +++++ pflegepilot_v3/backend/app/models.py | 8 +++++ pflegepilot_v3/backend/app/routers/briefe.py | 6 ++++ .../backend/app/routers/notfallkarte.py | 1 + .../backend/app/routers/patienten.py | 19 ++++++++++ pflegepilot_v3/backend/app/schemas.py | 5 +++ ...b4d7_notfallkarte_daten_aktualisiert_am.py | 35 +++++++++++++++++++ .../src/app/(app)/patienten/[id]/page.tsx | 13 +++++++ .../src/app/notfallkarte/[token]/page.tsx | 14 ++++++++ pflegepilot_v3/frontend/src/lib/patienten.ts | 4 +++ 11 files changed, 137 insertions(+) create mode 100644 pflegepilot_v3/backend/migrations/versions/e2f5c8a1b4d7_notfallkarte_daten_aktualisiert_am.py diff --git a/CLAUDE.md b/CLAUDE.md index 186301c..c293562 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,18 @@ The `pflegepilot_v3` product was renamed from "Pflegepilot" to **"MeinPflegeFuch - 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. +## Git-Workflow: Pull Requests über Gitea statt Direkt-Push auf `master` (ab 2026-08-10) + +Ab sofort läuft alle Arbeit an diesem Repo über den Remote `origin` (`ssh://git@192.168.1.222:2222/admin/pflegefuchs.git`, self-hosted Gitea auf dem NAS, Web-UI unter `http://192.168.1.222:3666`) — Betreiber-Wunsch, um bei mehreren gleichzeitigen Änderungen die Übersicht zu behalten, statt jede Änderung nur in diesem CLAUDE.md-Dokument und einzelnen `git log`-Commits nachzuvollziehen. + +- **Kein Direkt-Push auf `master` mehr durch Claude.** Jede abgeschlossene, in sich sinnvolle Änderung (ein Feature, ein Bugfix, ein Tester-Feedback-Punkt aus `Feedbackfromtester.md`) bekommt einen eigenen Branch, wird dorthin committet/gepusht und als Pull Request gegen `master` geöffnet — **ein PR pro Feature/Feedback-Punkt**, nicht ein Sammel-PR pro Konversation/Session. +- **Branch-Namensschema**: `feature/` für neue Funktionen, `fix/` für Bugfixes, jeweils kurze englische oder deutsche Kebab-Case-Beschreibung (z. B. `feature/notfallkarte-zeitstempel`, `fix/pdf-upload-passwortgeschuetzt`). +- **PR wird nicht automatisch gemerged** — der Betreiber merged jeden PR manuell nach eigener Durchsicht über die Gitea-Weboberfläche. Das ist die eigentliche Absicht dieses Workflows (Review-Gate + Übersicht), nicht nur eine Formalität — Claude darf einen von sich selbst geöffneten PR nicht selbst mergen, auch nicht auf Nachfrage "mach doch gleich fertig", ohne das explizit noch einmal zu bestätigen. +- **Werkzeug**: `tea` (offizielle Gitea-CLI, bereits unter `admin`-Login auf diesem Rechner eingerichtet, `tea login list` zeigt den Eintrag) — PRs werden per `tea pr create` geöffnet, nicht per Klick im Web-UI. `gh` (GitHub CLI) ist ebenfalls installiert, aber **nicht** für dieses Repo relevant, da hier kein GitHub im Spiel ist. +- **PR-Titel/-Beschreibung**: Titel entspricht dem, was sonst die Commit-Message-Subject-Zeile gewesen wäre (deutsch, gleicher Stil wie die bisherigen Commit-Messages/Changelog-Einträge); Beschreibung fasst die Änderung stichpunktartig zusammen, mit Verweis auf den zugehörigen Tester-Feedback-Punkt (T-Nummer) falls zutreffend und auf den neuen `changelog_seed.py`-Versionseintrag, falls einer dazugehört. +- **Gilt für alle Änderungen an diesem Repo**, nicht nur `pflegepilot_v3` — auch Änderungen an `pflegepilot_alpha_v2` oder an diesem `CLAUDE.md` selbst laufen künftig über einen PR, keine Ausnahme für "nur Dokumentation" oder "nur ein Einzeiler". +- Dieser Workflow-Abschnitt selbst wurde als erster PR unter dem neuen Schema eingeführt (zusammen mit dem bereits fertigen Notfallkarte-Zeitstempel-Feature, siehe unten) — als lebendes Beispiel, nicht nur als Ankündigung. + ## Notfall-Card-Bereich im Bearbeiten-Formular hervorgehoben (pflegepilot_v3 only, 2026-08-08) Betreiber-Feedback: die Notfall-Card-Felder (Blutgruppe, Allergien, Medikamente, Notfallkontakt) im "Patient bearbeiten"-Formular waren nur durch eine kleine graue Zwischenüberschrift von den übrigen Abschnitten (Persönliche Daten, Adresse, Krankenversicherung, Pflegesituation) abgesetzt — optisch identisch zu jedem anderen Abschnittstitel im selben durchlaufenden weißen Formular, dadurch leicht zu übersehen. @@ -61,6 +73,18 @@ Betreiber-Meldung noch am selben Tag: unter "Dokumente → Datei hochladen" lie - **Keine verwaisten Dateien gefunden**: ein Abgleich aller Dateien auf der Platte gegen die `Dokument`-Tabelle (NAS) ergab keine Datei ohne zugehörige DB-Zeile — der genaue vom Betreiber getestete Fall bleibt daher nicht zweifelsfrei rekonstruiert (das reproduzierte Passwort-Szenario ist der plausibelste Kandidat für dieselbe Fehlerklasse, aber nicht mit Sicherheit exakt derselbe Auslöser). - Verifiziert per echtem HTTP-Upload des Passwort-PDFs gegen die lokale Docker-Instanz (201 statt 500, `ocr_text=null`) sowie eines normalen Text-PDFs als Regressionstest (weiterhin korrekt erkannter Text). Changelog-Eintrag `1.18.2`. +## Notfall-Card: "Zuletzt aktualisiert am"-Zeitstempel (pflegepilot_v3 only, 2026-08-08) + +Betreiber-Feedback: weder intern (Patientenakte) noch extern (öffentliche Notfall-Card-Seite) war erkennbar, wann die angezeigten Notfalldaten zuletzt eingegeben/geändert wurden. + +- **Neue Spalte `Patient.notfallkarte_daten_aktualisiert_am`** (Migration `e2f5c8a1b4d7`) — bewusst ein eigenes Feld statt `Patient.geaendert_am` wiederzuverwenden, das bei **jeder** Patientenänderung (Adresse, Telefonnummer, …) mitläuft und damit fälschlich häufiger "aktualisiert" wirken würde, als es für die Notfalldaten selbst zutrifft. +- **Zwei Stellen setzen den Zeitstempel**, beide nur bei einer echten inhaltlichen Änderung: + 1. `PUT /api/patienten/{pid}` (`routers/patienten.py`): vergleicht bei jedem Speichern die 5 Notfall-Felder (`blutgruppe`, `allergien`, `notfallkarte_medikamente`, `notfallkontakt_name`, `notfallkontakt_telefon`) explizit gegen die bisherigen Werte (`NOTFALLKARTE_FELDER`-Set) — bewusst nicht "war das Feld im Request enthalten", da das Bearbeiten-Formular bei jedem Speichern alle Felder mitschickt, auch unveränderte. Nur bei mindestens einer echten Wertänderung wird der Zeitstempel neu gesetzt. + 2. `POST /api/patienten/{pid}/patientenverfuegung` (`routers/briefe.py`): eine neu eingereichte Patientenverfügung zählt ebenfalls als "Notfalldaten aktualisiert" (sie wird auf der Notfall-Card mit angezeigt/verlinkt) — nur beim echten Neuanlage-Pfad, nicht wenn `_juengster_duplikat()` einen bereits bestehenden Antrag zurückgibt. +- **Anzeige an beiden Stellen**: intern im "Notfall-Card"-Collapsible der Patientenakte (nur wenn die Karte aktiv ist, direkt unter dem Titel) sowie extern auf der öffentlichen `/notfallkarte/[token]`-Seite (unter Vorname/Nachname/Geburtsdatum) — beide mit identischem `formatZeitpunkt()`-Format ("TT.MM.JJJJ, HH:MM Uhr"), bewusst dieselbe kleine Hilfsfunktion an beiden Stellen dupliziert statt in ein gemeinsames Modul ausgelagert (unterschiedliche Nachbarschaft: Server- vs. Client-Component). +- **Verifiziert** end-to-end gegen die lokale Docker-Instanz (Feldänderung setzt den Zeitstempel, ein reines Ändern eines NICHT-Notfall-Felds bei unveränderten Notfall-Werten setzt ihn nachweislich NICHT neu, öffentlicher Endpunkt liefert denselben Wert, neue Patientenverfügung bumpt ihn erneut) sowie live auf der NAS gegen den echten Test-Account (Allergien-Feld testweise geändert und zurückgesetzt, Zeitstempel beide Male korrekt aktualisiert, intern sichtbar bestätigt per Screenshot). Der öffentliche Seiten-Teil wurde bewusst **nicht** am echten, bereits aktiven Token des Testpatienten verifiziert, um dessen laufenden QR-Code/Link nicht durch eine nötige Neu-Generierung ungültig zu machen — Logik ist identisch zur bereits lokal bestätigten. +- Changelog-Eintrag `1.18.5`. + ## Apple-/Google-Wallet-Passes für die Notfall-Card: bewusst weiterhin zurückgestellt (Stand 2026-08-08) Anschlussfrage des Betreibers nach T37 ("ist die Notfallkarte schon wallet-ready?"), zusammen mit den technischen Rahmenbedingungen besprochen, aber (noch) nicht umgesetzt: diff --git a/pflegepilot_v3/backend/app/changelog_seed.py b/pflegepilot_v3/backend/app/changelog_seed.py index 6fae214..80e727c 100644 --- a/pflegepilot_v3/backend/app/changelog_seed.py +++ b/pflegepilot_v3/backend/app/changelog_seed.py @@ -13,6 +13,14 @@ Message. """ CHANGELOG = [ + { + "version": "1.18.5", + "datum": "2026-08-08", + "titel": "Notfall-Card zeigt jetzt an, wann die Daten zuletzt aktualisiert wurden", + "punkte": [ + "Sowohl in der Patientenakte als auch auf der öffentlichen Notfall-Card-Seite ist jetzt ersichtlich, wann die Notfalldaten (oder eine neue Patientenverfügung) zuletzt geändert wurden.", + ], + }, { "version": "1.18.4", "datum": "2026-08-08", diff --git a/pflegepilot_v3/backend/app/models.py b/pflegepilot_v3/backend/app/models.py index 00490fe..b6750bb 100644 --- a/pflegepilot_v3/backend/app/models.py +++ b/pflegepilot_v3/backend/app/models.py @@ -126,6 +126,14 @@ class Patient(Base): notfallkarte_aktiviert_am: Mapped[datetime.datetime | None] = mapped_column( DateTime(timezone=True) ) + # Wann sich die auf der Notfall-Card angezeigten Daten zuletzt inhaltlich + # geaendert haben (die 5 Notfall-Felder oben, oder eine neu erstellte + # Patientenverfuegung) - bewusst getrennt von geaendert_am, das bei jeder + # beliebigen Patientenaenderung mitlaeuft. Siehe Migration + # e2f5c8a1b4d7 fuer die volle Begruendung. + notfallkarte_daten_aktualisiert_am: Mapped[datetime.datetime | None] = mapped_column( + DateTime(timezone=True) + ) pflegeperson: Mapped[str | None] = mapped_column(String(200)) zielgruppe: Mapped[str] = mapped_column(String(20), nullable=False, default="alterspflege") notizen: Mapped[str | None] = mapped_column(Text) diff --git a/pflegepilot_v3/backend/app/routers/briefe.py b/pflegepilot_v3/backend/app/routers/briefe.py index 7d89054..bdc00d0 100644 --- a/pflegepilot_v3/backend/app/routers/briefe.py +++ b/pflegepilot_v3/backend/app/routers/briefe.py @@ -496,6 +496,12 @@ def patientenverfuegung_erstellen( antrag = Antrag(patient_id=pid, typ="patientenverfuegung", status="offen", brief_text=text, pdf_pfad=pdf_pfad) db.add(antrag) + # T37-Nachtrag: eine neu eingereichte Patientenverfuegung zaehlt fuer die + # Notfall-Card ebenfalls als "Notfalldaten aktualisiert" - sie wird dort + # zusammen mit den Notfall-Basisdaten angezeigt/verlinkt (siehe + # routers/notfallkarte.py). Nur der echte Neuanlage-Pfad hier, nicht der + # Duplikat-Rueckgabepfad oben. + patient.notfallkarte_daten_aktualisiert_am = datetime.datetime.now(datetime.timezone.utc) db.commit() db.refresh(antrag) return antrag diff --git a/pflegepilot_v3/backend/app/routers/notfallkarte.py b/pflegepilot_v3/backend/app/routers/notfallkarte.py index 249debb..4fabc0f 100644 --- a/pflegepilot_v3/backend/app/routers/notfallkarte.py +++ b/pflegepilot_v3/backend/app/routers/notfallkarte.py @@ -108,6 +108,7 @@ def oeffentliche_ansicht(token: str, request: Request, db: Session = Depends(get notfallkontakt_name=patient.notfallkontakt_name, notfallkontakt_telefon=patient.notfallkontakt_telefon, patientenverfuegung_vorhanden=bool(verfuegung and verfuegung.pdf_pfad), + notfallkarte_daten_aktualisiert_am=patient.notfallkarte_daten_aktualisiert_am, ) diff --git a/pflegepilot_v3/backend/app/routers/patienten.py b/pflegepilot_v3/backend/app/routers/patienten.py index cd7caef..b8dce02 100644 --- a/pflegepilot_v3/backend/app/routers/patienten.py +++ b/pflegepilot_v3/backend/app/routers/patienten.py @@ -21,6 +21,19 @@ router = APIRouter( prefix="/api/patienten", tags=["patienten"], dependencies=[Depends(require_active_access)] ) +# T37-Nachtrag: genau diese 5 Felder speisen die Notfall-Card - nur wenn sich +# mindestens eines davon tatsaechlich aendert, wird notfallkarte_daten_aktualisiert_am +# neu gesetzt (siehe aktualisieren() unten). Bewusst nicht einfach "irgendein +# Feld war im Request enthalten", da das Bearbeiten-Formular bei jedem +# Speichern alle Felder mitschickt, auch unveraendert gebliebene. +NOTFALLKARTE_FELDER = { + "blutgruppe", + "allergien", + "notfallkarte_medikamente", + "notfallkontakt_name", + "notfallkontakt_telefon", +} + @router.get("", response_model=list[PatientOut]) def liste( @@ -96,8 +109,14 @@ def aktualisieren( ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) + notfall_geaendert = any( + feld in aenderungen and getattr(patient, feld) != aenderungen[feld] + for feld in NOTFALLKARTE_FELDER + ) for feld, wert in aenderungen.items(): setattr(patient, feld, wert) + if notfall_geaendert: + patient.notfallkarte_daten_aktualisiert_am = datetime.now(timezone.utc) db.commit() db.refresh(patient) return patient diff --git a/pflegepilot_v3/backend/app/schemas.py b/pflegepilot_v3/backend/app/schemas.py index eb3a0ec..66b39b9 100644 --- a/pflegepilot_v3/backend/app/schemas.py +++ b/pflegepilot_v3/backend/app/schemas.py @@ -144,6 +144,7 @@ class PatientOut(PatientBase): # Feld aus dem ORM-Objekt (siehe PatientOut-Validator unten). notfallkarte_aktiv: bool notfallkarte_aktiviert_am: datetime.datetime | None + notfallkarte_daten_aktualisiert_am: datetime.datetime | None class PflegegradPatch(BaseModel): @@ -849,6 +850,10 @@ class NotfallkarteOeffentlichOut(BaseModel): notfallkontakt_name: str | None notfallkontakt_telefon: str | None patientenverfuegung_vorhanden: bool + # T37-Nachtrag: wann sich diese Daten (die Felder oben oder eine neu + # eingereichte Patientenverfuegung) zuletzt geaendert haben - fuer die + # "Zuletzt aktualisiert am:"-Anzeige auf der oeffentlichen Seite. + notfallkarte_daten_aktualisiert_am: datetime.datetime | None class NotfallkartenZugriffOut(BaseModel): diff --git a/pflegepilot_v3/backend/migrations/versions/e2f5c8a1b4d7_notfallkarte_daten_aktualisiert_am.py b/pflegepilot_v3/backend/migrations/versions/e2f5c8a1b4d7_notfallkarte_daten_aktualisiert_am.py new file mode 100644 index 0000000..ffc2021 --- /dev/null +++ b/pflegepilot_v3/backend/migrations/versions/e2f5c8a1b4d7_notfallkarte_daten_aktualisiert_am.py @@ -0,0 +1,35 @@ +"""notfallkarte: zeitstempel fuer letzte aenderung der notfall-relevanten daten + +Revision ID: e2f5c8a1b4d7 +Revises: d9f2b6e4a8c1 +Create Date: 2026-08-08 00:00:00.000000 + +Betreiber-Feedback: auf der Notfall-Card (intern wie extern) soll erkennbar +sein, wann die angezeigten Daten zuletzt aktualisiert wurden - bewusst ein +eigenes Feld statt Patient.geaendert_am wiederzuverwenden, da dieses bei +JEDER Patientenaenderung (Adresse, Telefonnummer, ...) mitlaeuft und damit +faelschlich haeufiger "aktualisiert" wirken wuerde, als es fuer die +Notfalldaten selbst zutrifft. Wird gezielt nur bei tatsaechlicher Aenderung +der 5 Notfall-Felder sowie beim Anlegen einer neuen Patientenverfuegung +gesetzt (siehe routers/patienten.py und routers/briefe.py). +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e2f5c8a1b4d7' +down_revision = 'd9f2b6e4a8c1' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + 'patienten', + sa.Column('notfallkarte_daten_aktualisiert_am', sa.DateTime(timezone=True), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column('patienten', 'notfallkarte_daten_aktualisiert_am') diff --git a/pflegepilot_v3/frontend/src/app/(app)/patienten/[id]/page.tsx b/pflegepilot_v3/frontend/src/app/(app)/patienten/[id]/page.tsx index 6c0b136..2c466be 100644 --- a/pflegepilot_v3/frontend/src/app/(app)/patienten/[id]/page.tsx +++ b/pflegepilot_v3/frontend/src/app/(app)/patienten/[id]/page.tsx @@ -958,6 +958,11 @@ function PatientDetailPageInner({ params }: { params: Promise<{ id: string }> }) ) : (
+ {patient.notfallkarte_daten_aktualisiert_am && ( +

+ Zuletzt aktualisiert am: {formatZeitpunkt(patient.notfallkarte_daten_aktualisiert_am)} +

+ )} {notfallkarteLink && notfallkarteQr ? (
{/* eslint-disable-next-line @next/next/no-img-element -- lokal generierte Data-URI, next/image ist dafuer nicht vorgesehen */} @@ -1179,6 +1184,14 @@ function PatientDetailPageInner({ params }: { params: Promise<{ id: string }> }) ); } +function formatZeitpunkt(iso: string) { + const datum = new Date(iso); + return `${datum.toLocaleDateString("de-DE")}, ${datum.toLocaleTimeString("de-DE", { + hour: "2-digit", + minute: "2-digit", + })} Uhr`; +} + function Field({ label, value }: { label: string; value?: string | null }) { return (
diff --git a/pflegepilot_v3/frontend/src/app/notfallkarte/[token]/page.tsx b/pflegepilot_v3/frontend/src/app/notfallkarte/[token]/page.tsx index 04a2aa2..1583076 100644 --- a/pflegepilot_v3/frontend/src/app/notfallkarte/[token]/page.tsx +++ b/pflegepilot_v3/frontend/src/app/notfallkarte/[token]/page.tsx @@ -18,8 +18,17 @@ type NotfallkarteDaten = { notfallkontakt_name: string | null; notfallkontakt_telefon: string | null; patientenverfuegung_vorhanden: boolean; + notfallkarte_daten_aktualisiert_am: string | null; }; +function formatZeitpunkt(iso: string) { + const datum = new Date(iso); + return `${datum.toLocaleDateString("de-DE")}, ${datum.toLocaleTimeString("de-DE", { + hour: "2-digit", + minute: "2-digit", + })} Uhr`; +} + // Gleiches Server-Fetch-Muster wie die Landingpage (siehe (marketing)/page.tsx: // ladeOeffentlicheEinstellungen()) - API_INTERNAL_URL ist der interne Docker- // Compose-Servicename, nicht der browserseitig erreichbare NEXT_PUBLIC_API_URL. @@ -90,6 +99,11 @@ export default async function NotfallkartePage({ geboren am {new Date(daten.geburtsdatum).toLocaleDateString("de-DE")}

)} + {daten.notfallkarte_daten_aktualisiert_am && ( +

+ Zuletzt aktualisiert am: {formatZeitpunkt(daten.notfallkarte_daten_aktualisiert_am)} +

+ )}
{daten.blutgruppe && } diff --git a/pflegepilot_v3/frontend/src/lib/patienten.ts b/pflegepilot_v3/frontend/src/lib/patienten.ts index f42a2dc..4473796 100644 --- a/pflegepilot_v3/frontend/src/lib/patienten.ts +++ b/pflegepilot_v3/frontend/src/lib/patienten.ts @@ -34,6 +34,10 @@ export type Patient = { // Der Token-Hash selbst wird nie ausgeliefert, nur dieses abgeleitete bool. notfallkarte_aktiv: boolean; notfallkarte_aktiviert_am: string | null; + // Wann sich die Notfall-Basisdaten oder eine neu eingereichte + // Patientenverfuegung zuletzt geaendert haben - fuer "Zuletzt + // aktualisiert am:" in der Notfall-Card-Sektion. + notfallkarte_daten_aktualisiert_am: string | null; pflegeperson: string | null; zielgruppe: string; notizen: string | null; -- 2.54.0