Notfall-Card: "Zuletzt aktualisiert am"-Zeitstempel #1
@@ -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/<kurzbeschreibung>` für neue Funktionen, `fix/<kurzbeschreibung>` 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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
+35
@@ -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')
|
||||
@@ -958,6 +958,11 @@ function PatientDetailPageInner({ params }: { params: Promise<{ id: string }> })
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{patient.notfallkarte_daten_aktualisiert_am && (
|
||||
<p className="text-xs text-slate-500">
|
||||
Zuletzt aktualisiert am: {formatZeitpunkt(patient.notfallkarte_daten_aktualisiert_am)}
|
||||
</p>
|
||||
)}
|
||||
{notfallkarteLink && notfallkarteQr ? (
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
{/* 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 (
|
||||
<div>
|
||||
|
||||
@@ -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")}
|
||||
</p>
|
||||
)}
|
||||
{daten.notfallkarte_daten_aktualisiert_am && (
|
||||
<p className="mt-2 text-xs text-slate-400">
|
||||
Zuletzt aktualisiert am: {formatZeitpunkt(daten.notfallkarte_daten_aktualisiert_am)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{daten.blutgruppe && <Feld label="Blutgruppe" wert={daten.blutgruppe} />}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user