/* ===================================================================== aether-medical / Ansicht: Berichte Ein Bericht ist ein Dokument mit einer Unterschrift darunter. Kein Satz darin entsteht von selbst: Text aus einer Behandlung wird nur übernommen, wenn du es auslöst, und die Freigabe ist eine Unterschrift von Hand. Ein freigegebener Bericht bleibt, wie er ist. ===================================================================== */ "use strict"; (() => { const { sicher, zeit, aufruf, meldung, fenster, bestaetigen } = App; const z = { ordner: "meine", // meine | freigabe | freigegeben | alle suche: "", offen: null, // geöffneter Bericht daten: null, }; // Marker-Art je Status const statusArt = { entwurf: "neutral", eingereicht: "warn", freigegeben: "gut" }; // Beschriftung der Unterschriftsarten const unterschriftArt = { verfasser: "Verfasser", freigabe: "Freigabe", gegenzeichnung: "Gegengezeichnet", }; /* ---- Kleine Helfer ---------------------------------------------------- */ /** Anzeigename eines Status */ function statusName(kennung) { const treffer = (z.daten.status || []).find((s) => s.id === kennung); return treffer ? treffer.name : kennung; } /* ---- Gerüst ------------------------------------------------------------ */ function reiterleiste(d) { const eintraege = [ { id: "meine", text: "Meine Entwürfe", zahl: d.zaehler.meine }, { id: "freigabe", text: "Zur Freigabe", zahl: d.zaehler.freigabe }, { id: "freigegeben", text: "Freigegeben", zahl: d.zaehler.freigegeben }, { id: "alle", text: "Alle", zahl: 0 }, ]; return `
${eintraege.map((e) => ` `).join("")}
`; } function kennzahlen(d) { const zk = d.zaehler; return `
Eigene Entwürfe
${zk.meine}
nur du siehst sie
Warten auf Freigabe
${zk.freigabe}
liegen zur Unterschrift
Davon von dir
${zk.meineOffen}
eingereicht, noch nicht gezeichnet
Heute freigegeben
${zk.freigegeben}
endgültig, unveränderlich
`; } /* ---- Übersicht ---------------------------------------------------------- */ function uebersicht(behaelter) { const d = z.daten; behaelter.innerHTML = `

Berichte

Das System schreibt keinen Satz. Der Weg vom Entwurf zur Freigabe wird Schritt für Schritt gegangen — freigegeben wird durch Unterschrift, nie durch Zeitablauf.

${d.rechte.erstellen ? `` : ""}
${kennzahlen(d)} ${reiterleiste(d)}
Fremde Entwürfe sind nicht einsehbar — erst mit dem Einreichen wird ein Bericht sichtbar.
${ z.ordner === "meine" ? "Deine offenen Entwürfe" : z.ordner === "freigabe" ? "Eingereicht, wartet auf Unterschrift" : z.ordner === "freigegeben" ? "Freigegebene Berichte" : "Alle Berichte"} ${d.liste.length} Einträge
${d.liste.length ? d.liste.map(zeile).join("") : `
Keine Berichte in diesem Ordner.
`}
`; verdrahteUebersicht(behaelter); } function zeile(b) { return `
${sicher(b.nummer)}
${sicher(b.titel)}
${b.patientName ? sicher(b.patientName) : "ohne Patientenbezug"} · ${sicher(b.autor)} ${b.behandlungKennung ? " · " + sicher(b.behandlungKennung) : ""} ${b.einsatzNummer ? " · " + sicher(b.einsatzNummer) : ""}
${b.vonMir ? `von dir` : ""} ${b.hatDiagnose ? "" : `ohne Diagnose`} ${b.unterschriften > 0 ? `${b.unterschriften} Unterschrift(en)` : ""} ${sicher(statusName(b.status))} ${zeit(b.erstellt_am)}
`; } function verdrahteUebersicht(behaelter) { behaelter.querySelectorAll("[data-reiter]").forEach((el) => { el.addEventListener("click", () => { z.ordner = el.dataset.reiter; rendern(behaelter); }); }); const suche = behaelter.querySelector("[data-suche]"); let warten = null; suche.addEventListener("input", () => { clearTimeout(warten); warten = setTimeout(() => { z.suche = suche.value.trim(); rendern(behaelter); }, 300); }); behaelter.querySelectorAll("[data-oeffne]").forEach((el) => { el.addEventListener("click", () => oeffne(behaelter, +el.dataset.oeffne)); }); const neu = behaelter.querySelector("[data-neu]"); if (neu) neu.addEventListener("click", () => anlegenFenster(behaelter)); } /* ---- Einzelansicht -------------------------------------------------------- */ /** Ein Feld des Berichtsbogens */ function bogenFeld(feld, wert, schreibbar) { if (!schreibbar) { return `
${wert ? sicher(wert).replace(/\n/g, "
") : "Nicht ausgefüllt."}
`; } return `
`; } function einzelAnsicht(behaelter, d) { const b = d.bericht; const schreibbar = d.rechte.aendern; behaelter.innerHTML = `

${sicher(b.titel)}

${sicher(b.nummer)} · ${sicher(statusName(b.status))} ${b.patientName ? " · " + sicher(b.patientName) + (b.patientGeburt ? ", geb. " + sicher(b.patientGeburt) : "") : ""}

${b.status === "freigegeben" ? `
🔒
Freigegeben. Der Wortlaut steht fest und wird nicht mehr geändert. Eine weitere Unterschrift ist möglich, eine Änderung nicht.
` : ""} ${b.status === "eingereicht" ? `
✍
Liegt zur Freigabe. Bis zur Unterschrift ist der Text gesperrt. ${b.vonMir ? "Zum Weiterschreiben musst du ihn zurückziehen." : ""}
` : ""} ${b.selbstFreigabe ? `
ℹ
Verfasser und Freigabe sind dieselbe Person. Das ist zulässig und steht so im Dokument.
` : ""}
Nummer${sicher(b.nummer)}
Verfasser${sicher(b.autor)}
Angelegt${zeit(b.erstelltAm)}
Patient ${sicher(b.patientName || "kein Bezug")}
Behandlung ${sicher(b.behandlungKennung || "keine")}
Einsatz ${sicher(b.einsatzNummer || "keiner")}
${b.patient ? `` : ""} ${schreibbar ? ` ${b.behandlung ? `` : ""} ` : ""} ${b.status === "eingereicht" && b.vonMir && d.rechte.erstellen ? `` : ""} ${b.status === "eingereicht" && d.rechte.freigeben ? ` ` : ""} ${b.status !== "entwurf" && d.rechte.freigeben ? `` : ""}
${schreibbar ? `
` : ""}
${d.felder.map((feld) => bogenFeld(feld, b[feld.id], schreibbar)).join("")}
Unterschriften ${d.unterschriften.length} · nicht zurücknehmbar
${d.unterschriften.length ? d.unterschriften.map((u) => `
${sicher(unterschriftArt[u.art] || u.art)}
${sicher(u.name)}
${sicher(u.rolle || "")} ${u.personalnummer ? " · " + sicher(u.personalnummer) : ""}
${zeit(u.am)}
`).join("") : `
Noch nicht unterschrieben.
`}
Anlagen ${d.anlagen.length}
${d.anlagen.length ? d.anlagen.map((a, i) => `
${sicher(a.titel)}
${sicher(a.von)} · ${zeit(a.am)}
${schreibbar ? `` : ""}
`).join("") : `
Keine Anlagen.
`}
Weg des Berichts aus dem Protokoll, unveränderlich
${d.spur.length ? d.spur.map((s) => `
${sicher(s.aktion)}
${sicher(s.autor)} ${s.grund ? " · " + sicher(s.grund) : ""}
${zeit(s.am)}
`).join("") : `
Noch kein Eintrag.
`}
`; verdrahteEinzel(behaelter, d); } function verdrahteEinzel(behaelter, d) { const b = d.bericht; /** Liest die Felder des Bogens aus */ const bogen = () => { const werte = { id: b.id }; const titel = behaelter.querySelector("[name=titel]"); if (titel) werte.titel = titel.value.trim(); d.felder.forEach((feld) => { const el = behaelter.querySelector(`[name=${feld.id}]`); if (el) werte[feld.id] = el.value.trim(); }); return werte; }; behaelter.querySelector("[data-zurueck]").addEventListener("click", () => { z.offen = null; rendern(behaelter); }); const akte = behaelter.querySelector("[data-akte]"); if (akte) { akte.addEventListener("click", () => { if (!App.zustand.module.some((m) => m.id === "mdt")) { return meldung("Das MDT steht dir nicht zur Verfügung.", "fehler"); } App.oeffneModul("mdt", { patient: b.patient }); }); } // Baustein einsetzen: füllt nur den Bogen, gespeichert wird danach von Hand const baustein = behaelter.querySelector("[data-baustein]"); if (baustein) { baustein.addEventListener("click", () => { Bausteine.einsetzen({ modul: "bericht", behaelter }); }); } const speichern = behaelter.querySelector("[data-speichern]"); if (speichern) { speichern.addEventListener("click", async () => { try { await aufruf("bericht", "speichern", bogen()); meldung("Entwurf gespeichert.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }); } const uebernehmen = behaelter.querySelector("[data-uebernehmen]"); if (uebernehmen) { uebernehmen.addEventListener("click", () => uebernehmenFenster(behaelter, b, bogen)); } const anlage = behaelter.querySelector("[data-anlage]"); if (anlage) anlage.addEventListener("click", () => anlageFenster(behaelter, b)); behaelter.querySelectorAll("[data-anlageweg]").forEach((el) => { el.addEventListener("click", async () => { try { await aufruf("bericht", "anlage", { id: b.id, entfernen: +el.dataset.anlageweg }); meldung("Anlage entfernt.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }); }); const einreichen = behaelter.querySelector("[data-einreichen]"); if (einreichen) { einreichen.addEventListener("click", () => einreichenFenster(behaelter, b, d, bogen)); } const zurueckziehen = behaelter.querySelector("[data-zurueckziehen]"); if (zurueckziehen) { zurueckziehen.addEventListener("click", () => zurueckziehenFenster(behaelter, b)); } const freigeben = behaelter.querySelector("[data-freigeben]"); if (freigeben) freigeben.addEventListener("click", () => freigebenFenster(behaelter, b, d)); const zurueckweisen = behaelter.querySelector("[data-zurueckweisen]"); if (zurueckweisen) { zurueckweisen.addEventListener("click", () => zurueckweisenFenster(behaelter, b)); } const gegenzeichnen = behaelter.querySelector("[data-gegenzeichnen]"); if (gegenzeichnen) { gegenzeichnen.addEventListener("click", () => gegenzeichnenFenster(behaelter, b)); } } /* ---- Bericht anlegen ------------------------------------------------------- */ function anlegenFenster(behaelter) { let patient = null; // gewählter Patient let behandlung = null; // gewählte Behandlung let einsatz = null; // gewählter Einsatz bestaetigen({ titel: "Bericht anlegen", inhalt: `
Nicht jeder Bericht gehört zu einem Patienten.
Eine Verknüpfung erlaubt es später, den Wortlaut der Behandlung ausdrücklich zu übernehmen. Von selbst wird nichts kopiert.
`, bestaetigenText: "Anlegen", bestaetigt: async (el, zu) => { const titel = el.querySelector("[name=titel]").value.trim(); if (!titel) return meldung("Bitte einen Titel angeben.", "fehler"); try { const antwort = await aufruf("bericht", "anlegen", { titel, patient, behandlung, einsatz, }); zu(); meldung(`Bericht ${antwort.nummer} angelegt.`, "gut"); z.ordner = "meine"; oeffne(behaelter, antwort.id); } catch (f) { meldung(f.message, "fehler"); } }, nachOeffnen: (hg) => { const suchfeld = hg.querySelector("[name=suche]"); const liste = hg.querySelector("[data-treffer]"); const vorgaenge = hg.querySelector("[data-vorgaenge]"); let warten = null; /** Lädt Behandlungen und Einsätze zum gewählten Patienten */ async function ladeVorgaenge() { let antwort; try { antwort = await aufruf("bericht", "quellen", { patient }); } catch (f) { return meldung(f.message, "fehler"); } vorgaenge.style.display = ""; const behListe = hg.querySelector("[data-behandlungen]"); behListe.innerHTML = antwort.behandlungen.length ? antwort.behandlungen.map((b) => `
${sicher(b.kennung)}
${sicher(b.diagnose || "ohne Diagnose")}
${zeit(b.erstellt_am)}
${sicher(b.status)}
`).join("") : `
Keine Behandlung erfasst.
`; const einListe = hg.querySelector("[data-einsaetze]"); einListe.innerHTML = antwort.einsaetze.length ? antwort.einsaetze.map((e) => `
${sicher(e.nummer)}
${sicher(e.titel)}
${zeit(e.erstellt_am)}
`).join("") : `
Kein Einsatz verknüpft.
`; // Ein zweiter Klick nimmt die Verknüpfung wieder zurück behListe.querySelectorAll("[data-behandlung]").forEach((el) => { el.addEventListener("click", () => { const schon = behandlung === +el.dataset.behandlung; behListe.querySelectorAll("[data-behandlung]") .forEach((k) => k.classList.remove("gewaehlt")); behandlung = schon ? null : +el.dataset.behandlung; if (!schon) el.classList.add("gewaehlt"); }); }); einListe.querySelectorAll("[data-einsatz]").forEach((el) => { el.addEventListener("click", () => { const schon = einsatz === +el.dataset.einsatz; einListe.querySelectorAll("[data-einsatz]") .forEach((k) => k.classList.remove("gewaehlt")); einsatz = schon ? null : +el.dataset.einsatz; if (!schon) el.classList.add("gewaehlt"); }); }); } suchfeld.addEventListener("input", () => { clearTimeout(warten); warten = setTimeout(async () => { const begriff = suchfeld.value.trim(); if (begriff.length < 2) return; try { const antwort = await aufruf("bericht", "quellen", { begriff }); const treffer = antwort.treffer || []; liste.innerHTML = treffer.length ? treffer.map((t) => `
${sicher(t.name)}
geb. ${sicher(t.dob)}
${t.blutgruppe ? `${sicher(t.blutgruppe)}` : ""}
`).join("") : `
Keine Person gefunden.
`; liste.querySelectorAll("[data-person]").forEach((el) => { el.addEventListener("click", () => { patient = +el.dataset.person; behandlung = null; einsatz = null; liste.querySelectorAll("[data-person]") .forEach((k) => k.classList.remove("gewaehlt")); el.classList.add("gewaehlt"); ladeVorgaenge(); }); }); } catch (f) { meldung(f.message, "fehler"); } }, 280); }); }, }); } /* ---- Wortlaut übernehmen ----------------------------------------------------- */ function uebernehmenFenster(behaelter, b, bogen) { bestaetigen({ titel: "Wortlaut aus der Behandlung übernehmen", inhalt: `

Aus ${sicher(b.behandlungKennung)} wird der dort dokumentierte Text in diesen Bericht kopiert.

ℹ
Das System formuliert nichts um und fasst nichts zusammen — es kopiert den Wortlaut. Bereits ausgefüllte Felder bleiben unberührt; nur leere werden gefüllt. Was danach im Bericht steht, verantwortest du.
Ungespeicherte Änderungen im Bogen gehen dabei verloren — der Entwurf wird zuerst gespeichert.
`, bestaetigenText: "Übernehmen", bestaetigt: async (_, zu) => { try { // Erst sichern, was der Verfasser bereits geschrieben hat await aufruf("bericht", "speichern", bogen()); const antwort = await aufruf("bericht", "uebernehmen", { id: b.id, behandlung: b.behandlung, }); zu(); meldung(`Übernommen: ${antwort.uebernommen.join(", ")}.`, "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Anlage ------------------------------------------------------------------- */ function anlageFenster(behaelter, b) { bestaetigen({ titel: "Anlage hinzufügen", inhalt: `
Die Anlage wird verlinkt, nicht hochgeladen. Entfernen lässt sie sich nur, solange der Bericht ein Entwurf ist.
`, bestaetigenText: "Hinzufügen", bestaetigt: async (el, zu) => { try { await aufruf("bericht", "anlage", { id: b.id, titel: el.querySelector("[name=titel]").value.trim(), adresse: el.querySelector("[name=adresse]").value.trim(), }); zu(); meldung("Anlage hinzugefügt.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Einreichen und zurückziehen ------------------------------------------------ */ function einreichenFenster(behaelter, b, d, bogen) { // Welche Felder sind im Bogen gerade leer? const werte = bogen(); const leer = d.felder.filter((f) => !werte[f.id]).map((f) => f.name); bestaetigen({ titel: "Bericht einreichen", inhalt: `

${sicher(b.nummer)} geht zur Freigabe. Du unterschreibst ihn damit als Verfasser.

${leer.length ? `
ℹ
Noch nicht ausgefüllt: ${sicher(leer.join(", "))}. Das hält die Einreichung nicht auf — es steht nur so im Protokoll.
` : ""}
✍
Nach dem Einreichen ist der Text gesperrt. Zum Weiterschreiben musst du den Bericht zurückziehen — das wird festgehalten.
`, bestaetigenText: "Unterschreiben und einreichen", bestaetigt: async (_, zu) => { try { // Der zuletzt getippte Stand ist der, der eingereicht wird await aufruf("bericht", "speichern", werte); await aufruf("bericht", "einreichen", { id: b.id }); zu(); meldung("Bericht eingereicht.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }, }); } function zurueckziehenFenster(behaelter, b) { bestaetigen({ titel: "Bericht zurückziehen", inhalt: `

${sicher(b.nummer)} geht zurück in den Entwurf und lässt sich wieder bearbeiten.

ℹ
Deine Unterschrift als Verfasser bleibt stehen — sie wurde geleistet. Dass du danach weitergeschrieben hast, steht im Protokoll.
`, bestaetigenText: "Zurückziehen", bestaetigt: async (el, zu) => { const grund = el.querySelector("[name=grund]").value.trim(); if (!grund) return meldung("Bitte einen Grund angeben.", "fehler"); try { await aufruf("bericht", "zurueckziehen", { id: b.id, grund }); zu(); meldung("Bericht zurückgezogen.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Freigabe ------------------------------------------------------------------- */ function freigebenFenster(behaelter, b, d) { const leer = d.felder.filter((f) => !b[f.id]).map((f) => f.name); bestaetigen({ titel: "Bericht freigeben", inhalt: `

Du unterschreibst ${sicher(b.nummer)} — „${sicher(b.titel)}", verfasst von ${sicher(b.autor)}.

🔒
Die Freigabe ist endgültig. Danach wird am Wortlaut nichts mehr geändert — von niemandem.
${b.vonMir ? `
ℹ
Du hast diesen Bericht selbst geschrieben. Verfasser und Freigabe in einer Person ist zulässig und wird so im Dokument vermerkt.
` : ""} ${leer.length ? `
📄
Nicht ausgefüllt: ${sicher(leer.join(", "))}.
` : ""}
`, bestaetigenText: "Freigeben", bestaetigt: async (el, zu) => { try { await aufruf("bericht", "freigeben", { id: b.id, vermerk: el.querySelector("[name=vermerk]").value.trim(), }); zu(); meldung("Bericht freigegeben.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }, }); } function zurueckweisenFenster(behaelter, b) { bestaetigen({ titel: "Bericht zurückweisen", gefahr: true, inhalt: `

${sicher(b.nummer)} geht als Entwurf an ${sicher(b.autor)} zurück.

Der Verfasser bekommt diese Begründung angezeigt.
`, bestaetigenText: "Zurückweisen", bestaetigt: async (el, zu) => { const grund = el.querySelector("[name=grund]").value.trim(); if (grund.length < 5) return meldung("Bitte eine Begründung angeben.", "fehler"); try { await aufruf("bericht", "zurueckweisen", { id: b.id, grund }); zu(); meldung("Bericht zurückgewiesen.", "gut"); z.offen = null; rendern(behaelter); } catch (f) { meldung(f.message, "fehler"); } }, }); } function gegenzeichnenFenster(behaelter, b) { bestaetigen({ titel: "Bericht gegenzeichnen", inhalt: `

Du setzt deine Unterschrift unter ${sicher(b.nummer)}.

✍
Eine Gegenzeichnung ändert nichts am Wortlaut und lässt sich nicht zurücknehmen. Sie sagt nur: du hast diesen Bericht gelesen und trägst ihn mit.
`, bestaetigenText: "Gegenzeichnen", bestaetigt: async (el, zu) => { try { await aufruf("bericht", "gegenzeichnen", { id: b.id, vermerk: el.querySelector("[name=vermerk]").value.trim(), }); zu(); meldung("Gegengezeichnet.", "gut"); oeffne(behaelter, b.id); } catch (f) { meldung(f.message, "fehler"); } }, }); } /* ---- Laden -------------------------------------------------------------------- */ async function oeffne(behaelter, berichtId) { try { const d = await aufruf("bericht", "oeffnen", { id: berichtId }); z.offen = berichtId; einzelAnsicht(behaelter, d); } catch (f) { meldung(f.message, "fehler"); z.offen = null; } } async function rendern(behaelter) { try { z.daten = await aufruf("bericht", "uebersicht", { ordner: z.ordner, suche: z.suche, }); // Sprungziel aus einem Hinweis, z. B. „Bericht zurückgewiesen" const ziel = App.zustand.modulZiel; if (ziel && ziel.bericht) { App.zustand.modulZiel = null; return oeffne(behaelter, ziel.bericht); } if (z.offen) await oeffne(behaelter, z.offen); else uebersicht(behaelter); } catch (f) { behaelter.innerHTML = `

Berichte

Konnte nicht geladen werden: ${sicher(f.message)}

`; } } App.registriereAnsicht("bericht", { rendern, aktualisieren() { // Einen offenen Bogen nie überschreiben — dort steht ungespeicherter Text if (App.zustand.aktivesModul === "bericht" && !z.offen && !document.querySelector(".fenster-hg")) { rendern(App.$("inhalt")); } }, }); })();