aether-framework/resources/[aether]/aether-medical/html/js/module/medikament.js
Jerrit Fritzsche e4b82dd685 Aether-Framework: Fachmodule, Admin-Panel und Docker-Datenbank
- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark,
  Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI)
- aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource,
  an aether-core gekoppelt (Rechte über aether_users.admin_group), mit
  WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU)
- Prototypen admin/ vervollständigt
- SQL-Schema aether_admin.sql
- Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:55:32 +02:00

680 lines
28 KiB
JavaScript
Raw Permalink 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.

/* =====================================================================
aether-medical / Ansicht: Medikamentenverwaltung
Bestand, Buchungen und BTM-Buch. Das System zählt und erinnert —
nachbestellt, entnommen und entsorgt wird von Hand. Abgelaufene
Präparate werden auffällig markiert, aber nicht gesperrt.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, aufruf, meldung, fenster, bestaetigen } = App;
const z = {
reiter: "bestand", // bestand | btm
filter: "",
kategorie: "",
suche: "",
stillgelegt: false,
daten: null,
offen: null,
btmMedikament: null,
// Aus der jeweils letzten Antwort uebernommen, damit Dialoge sie sicher haben
rechte: { bestand: false, btm: false, katalog: false },
kategorien: [],
};
/* ---- Übersicht ----------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
// Nach Kategorie gruppieren
const gruppen = {};
d.liste.forEach((m) => { (gruppen[m.kategorie] = gruppen[m.kategorie] || []).push(m); });
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>Medikamente</h2>
<p>Bestandsführung. Das System gibt nichts aus, verordnet nichts und
bestellt nichts nach — es zählt, was gebucht wurde.</p>
</div>
${d.rechte.katalog
? `<button class="knopf" data-neu="1">+ Medikament anlegen</button>` : ""}
</div>
<div class="raster k5">
${kachel("gesamt", "Im Katalog", d.zaehler.gesamt, "aktive Präparate", "")}
${kachel("knapp", "Unter Mindestbestand", d.zaehler.knapp,
"nachbestellen", d.zaehler.knapp > 0 ? "warn" : "")}
${kachel("leer", "Nicht vorrätig", d.zaehler.leer,
"Bestand null", d.zaehler.leer > 0 ? "hoch" : "")}
${kachel("verfall", "Läuft ab", d.zaehler.verfall,
"Verfallsdatum nahe", d.zaehler.verfall > 0 ? "warn" : "")}
${kachel("btm", "Betäubungsmittel", d.zaehler.btm, "gesondert geführt", "")}
</div>
<div class="reiter">
<button class="${z.reiter === "bestand" ? "aktiv" : ""}" data-reiter="bestand">
Bestand</button>
${d.rechte.btm
? `<button class="${z.reiter === "btm" ? "aktiv" : ""}" data-reiter="btm">
BTM-Buch</button>` : ""}
</div>
<div class="filterleiste">
<input data-suche="1" placeholder="Kennung, Name oder Wirkstoff…"
value="${sicher(z.suche)}">
<select data-kategorie="1">
<option value="">Alle Kategorien</option>
${d.kategorien.map((k) =>
`<option value="${sicher(k.id)}" ${k.id === z.kategorie ? "selected" : ""}>
${sicher(k.name)}</option>`).join("")}
</select>
${z.filter
? `<button class="knopf leise" data-filterweg="1">Filter „${sicher(z.filter)}" aufheben</button>`
: ""}
${d.rechte.katalog
? `<button class="knopf ${z.stillgelegt ? "" : "leise"}" data-stillgelegt="1">
${z.stillgelegt ? "Stillgelegte" : "Aktive"}</button>` : ""}
</div>
${Object.keys(gruppen).length
? Object.keys(gruppen).map((kategorie) => `
<div class="karte" style="margin-bottom:14px">
<div class="karte-kopf">
<span class="karte-titel">${sicher(gruppen[kategorie][0].kategorieName)}</span>
<span class="feld-hinweis">${gruppen[kategorie].length} Präparate</span>
</div>
<div class="liste">${gruppen[kategorie].map(zeile).join("")}</div>
</div>`).join("")
: `<div class="liste-leer">Kein Medikament gefunden.</div>`}
`;
verdrahteUebersicht(behaelter);
}
function kachel(filter, titel, wert, zusatz, art) {
const aktiv = z.filter === filter;
return `<div class="kennzahl klickbar ${art} ${aktiv ? "gewaehlt" : ""}"
data-filter="${sicher(filter)}">
<div class="kz-titel">${sicher(titel)}</div>
<div class="kz-wert">${wert}</div>
<div class="kz-zusatz">${sicher(zusatz)}</div>
</div>`;
}
function zeile(m) {
// Der Bestandsbalken zeigt das Verhältnis zum Mindestbestand
const anteil = m.mindestbestand > 0
? Math.min(100, Math.round((m.bestand / (m.mindestbestand * 3)) * 100))
: (m.bestand > 0 ? 100 : 0);
const farbe = m.leer ? "var(--kritisch)" : m.knapp ? "var(--warn)" : "var(--gut)";
return `<div class="liste-zeile ${m.aktiv ? "" : "med-aus"}" data-oeffne="${m.id}">
<span class="db-nummer">${sicher(m.kennung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(m.name)}
${m.btm ? `<span class="marker kritisch">BTM</span>` : ""}</div>
<div class="lz-unter">
${sicher(m.wirkstoff || "—")}
${m.dosierung_standard ? " · " + sicher(m.dosierung_standard) : ""}
${m.lagerort ? " · " + sicher(m.lagerort) : ""}
</div>
</div>
${m.abgelaufen
? `<span class="marker kritisch" title="Verfallsdatum überschritten">abgelaufen</span>`
: m.laeuftAb
? `<span class="marker warn" title="Verfällt am ${sicher(m.verfaellt_am)}">
in ${m.tageBisVerfall} Tagen</span>` : ""}
${!m.aktiv ? `<span class="marker neutral">stillgelegt</span>` : ""}
<div class="med-bestand">
<div class="med-balken"><i style="width:${anteil}%;background:${farbe}"></i></div>
<span class="med-zahl ${m.leer ? "leer" : m.knapp ? "knapp" : ""}">
${m.bestand} ${sicher(m.einheit)}</span>
</div>
</div>`;
}
function verdrahteUebersicht(behaelter) {
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
el.addEventListener("click", () => {
z.reiter = el.dataset.reiter;
rendern(behaelter);
});
});
behaelter.querySelectorAll("[data-filter]").forEach((el) => {
el.addEventListener("click", () => {
const gewaehlt = el.dataset.filter;
// „gesamt" ist kein Filter, sondern hebt ihn auf
z.filter = (gewaehlt === "gesamt" || z.filter === gewaehlt) ? "" : gewaehlt;
rendern(behaelter);
});
});
const filterweg = behaelter.querySelector("[data-filterweg]");
if (filterweg) filterweg.addEventListener("click", () => {
z.filter = "";
rendern(behaelter);
});
behaelter.querySelector("[data-kategorie]").addEventListener("change", (e) => {
z.kategorie = e.target.value;
rendern(behaelter);
});
const stillgelegt = behaelter.querySelector("[data-stillgelegt]");
if (stillgelegt) stillgelegt.addEventListener("click", () => {
z.stillgelegt = !z.stillgelegt;
rendern(behaelter);
});
const suchfeld = behaelter.querySelector("[data-suche]");
let warten = null;
suchfeld.addEventListener("input", () => {
clearTimeout(warten);
warten = setTimeout(() => {
z.suche = suchfeld.value.trim();
rendern(behaelter);
}, 280);
});
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", () => katalogFenster(behaelter, null));
}
/* ---- Einzelansicht --------------------------------------------------------- */
function einzelAnsicht(behaelter, d) {
const m = d.medikament;
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>${sicher(m.kennung)} · ${sicher(m.name)}</h2>
<p>${sicher(m.wirkstoff || "ohne Wirkstoffangabe")}
${m.btm ? " · <b>Betäubungsmittel</b>" : ""}
${m.aktiv ? "" : " · stillgelegt"}</p>
</div>
<button class="knopf leise" data-zurueck="1">← Bestand</button>
</div>
${m.abgelaufen ? `
<div class="warnung kritisch">
<span class="w-symbol">⏳</span>
<div><b>Verfallsdatum überschritten</b> — am ${zeit(m.verfaelltAm)}.
<br><span class="feld-hinweis">Das Präparat bleibt buchbar und
dokumentierbar. Ob es noch verwendbar ist, beurteilt die Apotheke —
das System sperrt nichts anhand eines Datums.</span></div>
</div>` : ""}
${m.bestand <= m.mindestbestand ? `
<div class="warnung ${m.bestand <= 0 ? "kritisch" : "warn"}">
<span class="w-symbol">📦</span>
<div><b>${m.bestand <= 0 ? "Nicht vorrätig" : "Unter Mindestbestand"}</b>
— ${m.bestand} von mindestens ${m.mindestbestand} ${sicher(m.einheit)}.
Nachbestellen muss jemand von Hand.</div>
</div>` : ""}
<div class="beh-kopf">
<div class="werte-liste">
<div><span>Bestand</span><b>${m.bestand} ${sicher(m.einheit)}</b></div>
<div><span>Mindestbestand</span><b>${m.mindestbestand} ${sicher(m.einheit)}</b></div>
<div class="${m.dosierung ? "" : "leer"}"><span>Standarddosierung</span>
<b>${sicher(m.dosierung || "keine hinterlegt")}</b></div>
<div class="${m.lagerort ? "" : "leer"}"><span>Lagerort</span>
<b>${sicher(m.lagerort || "nicht angegeben")}</b></div>
<div class="${m.verfaelltAm ? "" : "leer"}"><span>Verfällt am</span>
<b>${m.verfaelltAm ? String(m.verfaelltAm).slice(0, 10) : "kein Datum"}</b></div>
<div class="${m.hinweis ? "" : "leer"}"><span>Hinweis</span>
<b>${sicher(m.hinweis || "keiner")}</b></div>
</div>
<div class="beh-kopf-aktionen">
${d.rechte.bestand && m.aktiv
? `<button class="knopf klein" data-buchen="1">Bestand buchen</button>` : ""}
${d.rechte.katalog
? `<button class="knopf leise klein" data-bearbeiten="1">Stammdaten</button>` : ""}
${d.rechte.katalog
? `<button class="knopf ${m.aktiv ? "leise" : ""} klein" data-schalten="1">
${m.aktiv ? "Stilllegen" : "Freigeben"}</button>` : ""}
</div>
</div>
<div class="db-spalten">
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Buchungen</span>
<span class="feld-hinweis">Bestand nach jeder Buchung</span>
</div>
<div class="liste">
${d.buchungen.length ? d.buchungen.map((b) => `
<div class="liste-zeile">
<span class="marker ${b.art === "ausgabe" || b.art === "verfall"
? "warn" : b.art === "korrektur" ? "info" : "gut"}">
${sicher(b.artName)}</span>
<div class="lz-haupt">
<div class="lz-titel">${b.art === "korrektur" ? "auf" : b.art === "ausgabe"
|| b.art === "verfall" ? "−" : "+"} ${b.menge} ${sicher(m.einheit)}
<span class="pat-geburt">Stand danach: ${b.bestand_danach}</span></div>
<div class="lz-unter">${sicher(b.grund || "")}
${b.patientName ? " · " + sicher(b.patientName) : ""}
· ${sicher(b.autor)} · ${zeit(b.gebucht_am)}</div>
</div>
</div>`).join("")
: `<div class="liste-leer">Noch keine Buchung.</div>`}
</div>
</div>
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Dokumentierte Gaben</span>
<span class="feld-hinweis">aus den Behandlungsbögen</span>
</div>
<div class="liste">
${d.gaben.length ? d.gaben.map((g) => `
<div class="liste-zeile">
<span class="db-nummer">${sicher(g.behandlung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(g.patientName)}
<span class="pat-geburt">${g.menge} ${sicher(m.einheit)}</span></div>
<div class="lz-unter">${g.dosierung ? sicher(g.dosierung) + " · " : ""}
${sicher(g.autor)} · ${zeit(g.gegeben_am)}</div>
</div>
${g.ohneBuchung
? `<span class="marker warn" title="Ohne Bestandsbuchung dokumentiert">
ohne Buchung</span>` : ""}
</div>`).join("")
: `<div class="liste-leer">Noch keine Gabe dokumentiert.</div>`}
</div>
</div>
</div>
`;
verdrahteEinzel(behaelter, d);
}
function verdrahteEinzel(behaelter, d) {
const m = d.medikament;
behaelter.querySelector("[data-zurueck]").addEventListener("click", () => {
z.offen = null;
rendern(behaelter);
});
const buchen = behaelter.querySelector("[data-buchen]");
if (buchen) buchen.addEventListener("click", () => buchungsFenster(behaelter, d));
const bearbeiten = behaelter.querySelector("[data-bearbeiten]");
if (bearbeiten) bearbeiten.addEventListener("click", () => katalogFenster(behaelter, m));
const schalten = behaelter.querySelector("[data-schalten]");
if (schalten) schalten.addEventListener("click", () => {
const freigeben = !m.aktiv;
bestaetigen({
titel: freigeben ? "Medikament freigeben" : "Medikament stilllegen",
gefahr: !freigeben,
inhalt: `<p><b>${sicher(m.kennung)}</b> — ${sicher(m.name)}</p>
<p>${freigeben
? "Das Präparat steht wieder zur Auswahl."
: "Das Präparat lässt sich für neue Gaben nicht mehr auswählen."}</p>
${!freigeben && m.bestand > 0 ? `
<div class="warnung warn">
<span class="w-symbol">📦</span>
<div>Es liegen noch <b>${m.bestand} ${sicher(m.einheit)}</b> im Schrank.
Die Entnahme muss gebucht werden — das System bucht sie nicht weg.</div>
</div>` : ""}
<p class="feld-hinweis">Gelöscht wird nichts. Bestehende Gaben und Buchungen
bleiben vollständig nachvollziehbar.</p>
<div class="feld"><label>Grund (freiwillig)</label>
<input name="grund" maxlength="255"></div>`,
bestaetigenText: freigeben ? "Freigeben" : "Stilllegen",
bestaetigt: async (el, zu) => {
try {
await aufruf("medikament", "schalten", {
id: m.id, aktiv: freigeben,
grund: el.querySelector("[name=grund]").value.trim(),
});
zu();
meldung(freigeben ? "Freigegeben." : "Stillgelegt.", "gut");
oeffne(behaelter, m.id);
} catch (f) { meldung(f.message, "fehler"); }
},
});
});
}
/* ---- Buchen ----------------------------------------------------------------- */
function buchungsFenster(behaelter, d) {
const m = d.medikament;
// Die Ausgabe entsteht nur aus einer Gabe und fehlt hier bewusst
const arten = ["eingang", "ruecknahme", "verfall", "korrektur"];
bestaetigen({
titel: "Bestand buchen · " + m.name,
inhalt: `
<p>Aktueller Bestand: <b>${m.bestand} ${sicher(m.einheit)}</b></p>
<div class="feld"><label>Art der Buchung</label>
<select name="art">
${arten.map((a) =>
`<option value="${sicher(a)}">${sicher(d.buchungsarten[a].name)}</option>`).join("")}
</select>
<div class="feld-hinweis">
<b>Wareneingang</b> und <b>Rücknahme</b> erhöhen den Bestand,
<b>Verfall entnommen</b> verringert ihn.
<b>Korrektur</b> setzt ihn auf den gezählten Wert.
Eine <b>Ausgabe</b> entsteht ausschließlich aus einer dokumentierten Gabe.
</div></div>
<div class="feld"><label data-mengenlabel="1">Menge</label>
<input name="menge" type="number" min="1" max="100000" value="1"></div>
<div class="feld"><label>Grund (Pflicht)</label>
<input name="grund" maxlength="255"
placeholder="z. B. Lieferung Apotheke, Inventur vom 12.05."></div>`,
bestaetigenText: "Buchen",
bestaetigt: async (el, zu) => {
const grund = el.querySelector("[name=grund]").value.trim();
if (grund.length < 3) return meldung("Bitte einen Grund angeben.", "fehler");
try {
const antwort = await aufruf("medikament", "buchen", {
id: m.id,
art: el.querySelector("[name=art]").value,
menge: +el.querySelector("[name=menge]").value,
grund,
});
zu();
meldung("Gebucht. Neuer Bestand: " + antwort.bestand + " " + m.einheit + ".", "gut");
oeffne(behaelter, m.id);
} catch (f) { meldung(f.message, "fehler"); }
},
nachOeffnen: (hg) => {
// Bei einer Korrektur ist die Menge der neue Stand, nicht die Differenz
const art = hg.querySelector("[name=art]");
const label = hg.querySelector("[data-mengenlabel]");
art.addEventListener("change", () => {
label.textContent = art.value === "korrektur"
? "Gezählter Bestand" : "Menge";
});
},
});
}
/* ---- Katalog ---------------------------------------------------------------- */
function katalogFenster(behaelter, m) {
const neu = !m;
m = m || {
kennung: "", name: "", wirkstoff: "", kategorie: "sonstiges",
dosierung: "", einheit: "Stück", mindestbestand: 0, lagerort: "",
verfaelltAm: null, hinweis: "", btm: false, sortierung: 0,
};
const darfBtm = z.rechte.btm;
fenster({
titel: neu ? "Medikament anlegen" : "Stammdaten · " + m.name,
inhalt: `
<div class="feld-reihe">
<div class="feld"><label>Kennung</label>
<input name="kennung" maxlength="24" value="${sicher(m.kennung)}"
placeholder="z. B. ANA-04"></div>
<div class="feld"><label>Kategorie</label>
<select name="kategorie">
${z.kategorien.map((k) =>
`<option value="${sicher(k.id)}" ${k.id === m.kategorie ? "selected" : ""}>
${sicher(k.name)}</option>`).join("")}
</select></div>
</div>
<div class="feld"><label>Name</label>
<input name="name" maxlength="120" value="${sicher(m.name)}"></div>
<div class="feld"><label>Wirkstoff</label>
<input name="wirkstoff" maxlength="120" value="${sicher(m.wirkstoff || "")}"></div>
<div class="feld-reihe">
<div class="feld"><label>Standarddosierung</label>
<input name="dosierung" maxlength="64" value="${sicher(m.dosierung || "")}"
placeholder="z. B. 1 g i.v.">
<div class="feld-hinweis">Ein Vorschlag im Gabe-Dialog, keine Verordnung.</div></div>
<div class="feld"><label>Einheit</label>
<input name="einheit" maxlength="24" value="${sicher(m.einheit || "Stück")}"></div>
</div>
<div class="feld-reihe">
<div class="feld"><label>Mindestbestand</label>
<input name="mindestbestand" type="number" min="0" value="${m.mindestbestand || 0}">
<div class="feld-hinweis">Ab hier erinnert das System.</div></div>
<div class="feld"><label>Verfällt am</label>
<input name="verfaellt" maxlength="10" placeholder="JJJJ-MM-TT"
value="${m.verfaelltAm ? String(m.verfaelltAm).slice(0, 10) : ""}"></div>
</div>
<div class="feld-reihe">
<div class="feld"><label>Lagerort</label>
<input name="lagerort" maxlength="64" value="${sicher(m.lagerort || "")}"></div>
<div class="feld"><label>Sortierung</label>
<input name="sortierung" type="number" value="${m.sortierung || 0}"></div>
</div>
<div class="feld"><label>Hinweis</label>
<input name="hinweis" maxlength="255" value="${sicher(m.hinweis || "")}"
placeholder="z. B. Wechselwirkung mit Antikoagulantien"></div>
<div class="feld">
<label><input type="checkbox" name="btm" class="haken"
${m.btm ? "checked" : ""} ${darfBtm ? "" : "disabled"}>
Betäubungsmittel</label>
<div class="feld-hinweis">${darfBtm
? "Betäubungsmittel erscheinen zusätzlich im BTM-Buch und erfordern für jede Gabe und Buchung das BTM-Recht."
: "Zum Kennzeichnen als Betäubungsmittel fehlt dir das BTM-Recht."}</div>
</div>
${neu ? `<div class="grundsatz">
<span>ℹ</span>
<div>Ein neues Präparat startet mit <b>Bestand 0</b>. Den Zugang buchst du
anschließend als Wareneingang — damit steht jede Menge im Schrank
nachvollziehbar in der Historie.</div>
</div>` : ""}`,
knoepfe: [
{ text: "Abbrechen", art: "leise", klick: (_, zu) => zu() },
{ text: neu ? "Anlegen" : "Speichern", klick: async (el, zu) => {
const wert = (name) => el.querySelector(`[name=${name}]`).value.trim();
try {
const antwort = await aufruf("medikament", "speichern", {
id: neu ? null : m.id,
kennung: wert("kennung"),
name: wert("name"),
wirkstoff: wert("wirkstoff"),
kategorie: el.querySelector("[name=kategorie]").value,
dosierung: wert("dosierung"),
einheit: wert("einheit"),
mindestbestand: +wert("mindestbestand"),
verfaelltAm: wert("verfaellt"),
lagerort: wert("lagerort"),
sortierung: +wert("sortierung"),
hinweis: wert("hinweis"),
btm: el.querySelector("[name=btm]").checked,
});
zu();
meldung(neu ? "Angelegt: " + antwort.kennung : "Gespeichert.", "gut");
if (neu) rendern(behaelter);
else oeffne(behaelter, m.id);
} catch (f) { meldung(f.message, "fehler"); }
} },
],
});
}
/* ---- BTM-Buch ---------------------------------------------------------------- */
function btmAnsicht(behaelter, d) {
behaelter.innerHTML = `
<div class="seiten-kopf">
<div>
<h2>BTM-Buch</h2>
<p>Lückenlose Bewegungsliste aller Betäubungsmittel. Reine Auskunft —
hier lässt sich nichts ändern.</p>
</div>
<button class="knopf leise" data-zurueck="1">← Bestand</button>
</div>
<div class="karte" style="margin-bottom:14px">
<div class="karte-kopf"><span class="karte-titel">Aktueller Stand</span></div>
<div class="liste">
${d.bestaende.length ? d.bestaende.map((b) => `
<div class="liste-zeile klickbar" data-med="${b.id}">
<span class="db-nummer">${sicher(b.kennung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(b.name)}</div>
<div class="lz-unter">${sicher(b.lagerort || "kein Lagerort")}
${b.verfaellt_am ? " · verfällt "
+ String(b.verfaellt_am).slice(0, 10) : ""}</div>
</div>
${b.abgelaufen ? `<span class="marker kritisch">abgelaufen</span>` : ""}
<span class="med-zahl ${b.bestand <= b.mindestbestand ? "knapp" : ""}">
${b.bestand} ${sicher(b.einheit)}</span>
</div>`).join("")
: `<div class="liste-leer">Keine Betäubungsmittel im Katalog.</div>`}
</div>
</div>
${d.ohneBuchung.length ? `
<div class="karte" style="margin-bottom:14px">
<div class="karte-kopf">
<span class="karte-titel">Gaben ohne Bestandsbuchung</span>
<span class="feld-hinweis">${d.ohneBuchung.length} Einträge</span>
</div>
<p class="feld-hinweis" style="margin-top:0">
Diese Gaben wurden bewusst ohne Bestandsbuchung dokumentiert — etwa weil
das Präparat aus einem Fahrzeug stammte. Sie stehen hier, damit die
Zahlen im Buch erklärbar bleiben, nicht als Beanstandung.
</p>
<div class="liste">
${d.ohneBuchung.map((g) => `
<div class="liste-zeile">
<span class="db-nummer">${sicher(g.behandlung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(g.name)}
<span class="pat-geburt">${g.menge} ${sicher(g.einheit)}</span></div>
<div class="lz-unter">${sicher(g.patientName)}
· ${sicher(g.autor)} · ${zeit(g.gegeben_am)}</div>
</div>
<span class="marker warn">ohne Buchung</span>
</div>`).join("")}
</div>
</div>` : ""}
<div class="karte">
<div class="karte-kopf">
<span class="karte-titel">Bewegungen</span>
<span class="feld-hinweis">${d.bewegungen.length} Einträge
${d.gewaehlt ? "· gefiltert" : ""}</span>
</div>
${d.gewaehlt
? `<button class="knopf leise klein" data-alle="1"
style="margin-bottom:10px">Alle Präparate zeigen</button>` : ""}
<div class="liste">
${d.bewegungen.length ? d.bewegungen.map((b) => `
<div class="liste-zeile">
<span class="marker ${b.art === "ausgabe" || b.art === "verfall"
? "warn" : b.art === "korrektur" ? "info" : "gut"}">
${sicher(b.artName)}</span>
<span class="db-nummer">${sicher(b.kennung)}</span>
<div class="lz-haupt">
<div class="lz-titel">${sicher(b.name)}
<span class="pat-geburt">${b.art === "korrektur" ? "auf"
: b.art === "ausgabe" || b.art === "verfall" ? "−" : "+"}
${b.menge} ${sicher(b.einheit)} · Stand danach: ${b.bestand_danach}</span></div>
<div class="lz-unter">${sicher(b.grund || "")}
${b.patientName ? " · " + sicher(b.patientName) : ""}
· ${sicher(b.autor)}</div>
</div>
<span class="db-zeit">${zeit(b.gebucht_am)}</span>
</div>`).join("")
: `<div class="liste-leer">Keine Bewegungen erfasst.</div>`}
</div>
</div>
`;
behaelter.querySelector("[data-zurueck]").addEventListener("click", () => {
z.reiter = "bestand";
z.btmMedikament = null;
rendern(behaelter);
});
behaelter.querySelectorAll("[data-med]").forEach((el) => {
el.addEventListener("click", () => {
z.btmMedikament = +el.dataset.med;
rendern(behaelter);
});
});
const alle = behaelter.querySelector("[data-alle]");
if (alle) alle.addEventListener("click", () => {
z.btmMedikament = null;
rendern(behaelter);
});
}
/* ---- Laden -------------------------------------------------------------------- */
async function oeffne(behaelter, medikamentId) {
try {
const d = await aufruf("medikament", "oeffnen", { id: medikamentId });
z.offen = medikamentId;
z.rechte = d.rechte;
z.kategorien = d.kategorien;
einzelAnsicht(behaelter, d);
} catch (f) { meldung(f.message, "fehler"); }
}
async function rendern(behaelter) {
try {
if (z.reiter === "btm") {
const d = await aufruf("medikament", "btmBuch", { medikament: z.btmMedikament });
return btmAnsicht(behaelter, d);
}
z.daten = await aufruf("medikament", "uebersicht", {
filter: z.filter, kategorie: z.kategorie,
begriff: z.suche, stillgelegt: z.stillgelegt,
});
z.rechte = z.daten.rechte;
z.kategorien = z.daten.kategorien;
// Sprungziel aus einem Hinweis, z. B. „Medikament fast leer"
const ziel = App.zustand.modulZiel;
if (ziel && ziel.medikament) {
App.zustand.modulZiel = null;
return oeffne(behaelter, ziel.medikament);
}
if (z.offen) await oeffne(behaelter, z.offen);
else uebersicht(behaelter);
} catch (f) {
behaelter.innerHTML = `<div class="seiten-kopf"><div><h2>Medikamente</h2>
<p>Konnte nicht geladen werden: ${sicher(f.message)}</p></div></div>`;
}
}
App.registriereAnsicht("medikament", { rendern });
App.registriereSuche(async (begriff) => {
try {
const d = await aufruf("medikament", "uebersicht", { begriff });
return (d.liste || []).slice(0, 5).map((m) => ({
bereich: "Medikament",
titel: m.kennung + " · " + m.name,
unter: m.bestand + " " + m.einheit + (m.knapp ? " · unter Mindestbestand" : ""),
}));
} catch (e) { return []; }
});
})();