aether-framework/resources/[aether]/aether-medical/server/module/behandlung.lua
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

674 lines
29 KiB
Lua
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 / Modul: Behandlungsverwaltung
--
-- Das System speichert AUSSCHLIESSLICH die Dokumentation.
--
-- Es heilt nicht, es diagnostiziert nicht, es schlägt keine Diagnose
-- vor und es leitet aus Symptomen nichts ab. Jedes Feld ist ein
-- Freitext, den ein Mediziner schreibt.
--
-- Auch die Medikamentengabe wird hier nur DOKUMENTIERT. Das System
-- verabreicht nichts und verordnet nichts — es bucht den Bestand
-- nach, weil ein Mensch etwas entnommen hat.
-- =====================================================================
Kern.RegistriereModul({
id = 'behandlung', name = 'Behandlungen', icon = '📋',
reihenfolge = 5, recht = 'behandlung.dokumentieren',
beschreibung = 'Untersuchung, Diagnose, Maßnahmen und Medikamentengaben',
})
-- ---------------------------------------------------------------------
-- Grundbestand an Medikamenten
--
-- Wird einmalig angelegt, damit sich eine Gabe überhaupt dokumentieren
-- lässt. Die Pflege übernimmt später die Medikamentenverwaltung.
-- Die Standarddosierung ist ein VORSCHLAG und nichts weiter.
-- ---------------------------------------------------------------------
local GRUNDMEDIKAMENTE = {
-- kennung, name, wirkstoff, kategorie, dosierung, einheit, bestand, mindest, btm
{ 'ANA-01', 'Novalgin', 'Metamizol', 'analgetika', '1 g i.v.', 'Ampulle', 40, 10, 0 },
{ 'ANA-02', 'Morphin 10 mg', 'Morphin', 'analgetika', '2–10 mg i.v.', 'Ampulle', 12, 4, 1 },
{ 'ANA-03', 'Ibuprofen 600', 'Ibuprofen', 'analgetika', '600 mg p.o.', 'Tablette', 90, 20, 0 },
{ 'ANT-01', 'Amoxicillin', 'Amoxicillin', 'antibiotika', '1 g p.o.', 'Tablette', 50, 15, 0 },
{ 'ANT-02', 'Cefuroxim', 'Cefuroxim', 'antibiotika', '1,5 g i.v.', 'Ampulle', 20, 6, 0 },
{ 'KRE-01', 'Adrenalin 1 mg', 'Epinephrin', 'kreislauf', '1 mg i.v.', 'Ampulle', 25, 8, 0 },
{ 'KRE-02', 'Amiodaron', 'Amiodaron', 'kreislauf', '300 mg i.v.', 'Ampulle', 10, 3, 0 },
{ 'KRE-03', 'Acetylsalicylsäure', 'ASS', 'kreislauf', '250 mg i.v.', 'Ampulle', 18, 6, 0 },
{ 'NAR-01', 'Propofol', 'Propofol', 'narkose', 'nach Wirkung', 'Ampulle', 15, 5, 1 },
{ 'NAR-02', 'Midazolam', 'Midazolam', 'narkose', '2–5 mg i.v.', 'Ampulle', 14, 5, 1 },
{ 'NAR-03', 'Ketamin', 'Esketamin', 'narkose', 'nach Wirkung', 'Ampulle', 10, 4, 1 },
{ 'INF-01', 'Ringer-Lactat', '—', 'infusion', '500–1000 ml', 'Beutel', 60, 20, 0 },
{ 'INF-02', 'NaCl 0,9 %', 'Kochsalz', 'infusion', '500 ml', 'Beutel', 80, 25, 0 },
{ 'VER-01', 'Kompressen steril', '—', 'verband', '—', 'Packung', 120, 30, 0 },
{ 'VER-02', 'Verbandpäckchen', '—', 'verband', '—', 'Stück', 100, 25, 0 },
{ 'VER-03', 'Druckverband', '—', 'verband', '—', 'Stück', 40, 12, 0 },
{ 'SON-01', 'Sauerstoff', 'O₂', 'sonstiges', '2–15 l/min', 'Flasche', 16, 5, 0 },
}
CreateThread(function()
Wait(2000)
local vorhanden = tonumber(MySQL.scalar.await('SELECT COUNT(*) FROM medical_medikament')) or 0
if vorhanden > 0 then return end
for reihenfolge, eintrag in ipairs(GRUNDMEDIKAMENTE) do
MySQL.insert.await([[
INSERT INTO medical_medikament
(kennung, name, wirkstoff, kategorie, dosierung_standard, einheit,
bestand, mindestbestand, betaeubungsmittel, lagerort, sortierung)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
]], {
eintrag[1], eintrag[2], eintrag[3], eintrag[4], eintrag[5], eintrag[6],
eintrag[7], eintrag[8], eintrag[9],
eintrag[9] == 1 and 'BTM-Schrank' or 'Apotheke',
reihenfolge,
})
end
Aether.Debug('Grundbestand an Medikamenten angelegt.')
end)
-- ---------------------------------------------------------------------
-- Hilfsfunktionen
-- ---------------------------------------------------------------------
--- Begrenzt einen Text und liefert nil bei leerer Eingabe
local function Text(wert, laenge)
if type(wert) ~= 'string' then return nil end
local sauber = wert:gsub('^%s+', ''):gsub('%s+$', '')
if sauber == '' then return nil end
return sauber:sub(1, laenge)
end
--- Name eines Mitarbeiters für die Anzeige
local function NameVon(charakterId)
if not charakterId then return 'unbekannt' end
local zeilen = MySQL.query.await([[
SELECT c.firstname, c.lastname, p.personalnummer
FROM aether_characters c
LEFT JOIN medical_personal p ON p.`character` = c.id
WHERE c.id = ?
]], { charakterId })
if not zeilen or not zeilen[1] then return 'unbekannt' end
return zeilen[1].firstname .. ' ' .. zeilen[1].lastname
end
-- =====================================================================
-- ÜBERSICHT
-- =====================================================================
Kern.RegistriereAktion('behandlung', 'uebersicht', nil, function(quelle, mitarbeiter, daten)
local ordner = tostring(daten.ordner or 'laufend')
local bedingungen, werte = {}, {}
if ordner == 'meine' then
bedingungen[#bedingungen + 1] = 'b.erstellt_von = ?'
werte[#werte + 1] = mitarbeiter.charakterId
elseif ordner == 'abgeschlossen' then
bedingungen[#bedingungen + 1] = "b.status = 'abgeschlossen'"
else
bedingungen[#bedingungen + 1] = "b.status = 'laufend'"
end
if type(daten.begriff) == 'string' and #daten.begriff >= 2 then
bedingungen[#bedingungen + 1] =
'(b.kennung LIKE ? OR b.diagnose LIKE ? OR c.firstname LIKE ? OR c.lastname LIKE ?)'
local muster = '%' .. daten.begriff .. '%'
for _ = 1, 4 do werte[#werte + 1] = muster end
end
local liste = MySQL.query.await(([[
SELECT b.id, b.kennung, b.patient, b.aufnahme, b.einsatz,
b.diagnose, b.massnahmen, b.status,
b.erstellt_am, b.erstellt_von, b.abgeschlossen_am,
TIMESTAMPDIFF(MINUTE, b.erstellt_am, NOW()) AS offenSeit,
c.firstname, c.lastname, c.dob,
m.allergien, m.hinweis,
e.nummer AS einsatzNummer,
(SELECT COUNT(*) FROM medical_behandlung_medikament g
WHERE g.behandlung = b.id) AS gaben
FROM medical_behandlung b
JOIN aether_characters c ON c.id = b.patient
LEFT JOIN medical_patient m ON m.`character` = b.patient
LEFT JOIN medical_einsatz e ON e.id = b.einsatz
WHERE %s
ORDER BY b.id DESC
LIMIT 120
]]):format(table.concat(bedingungen, ' AND ')), werte) or {}
for _, eintrag in ipairs(liste) do
eintrag.patientName = eintrag.firstname .. ' ' .. eintrag.lastname
eintrag.geburt = tostring(eintrag.dob):sub(1, 10)
eintrag.autor = NameVon(eintrag.erstellt_von)
eintrag.eigene = eintrag.erstellt_von == mitarbeiter.charakterId
eintrag.gaben = tonumber(eintrag.gaben) or 0
eintrag.firstname, eintrag.lastname, eintrag.dob = nil, nil, nil
eintrag.erstellt_von = nil
end
return {
ordner = ordner,
liste = liste,
zaehler = {
laufend = tonumber(MySQL.scalar.await(
"SELECT COUNT(*) FROM medical_behandlung WHERE status = 'laufend'")) or 0,
meine = tonumber(MySQL.scalar.await([[
SELECT COUNT(*) FROM medical_behandlung
WHERE status = 'laufend' AND erstellt_von = ?
]], { mitarbeiter.charakterId })) or 0,
heute = tonumber(MySQL.scalar.await([[
SELECT COUNT(*) FROM medical_behandlung
WHERE status = 'abgeschlossen' AND DATE(abgeschlossen_am) = CURDATE()
]])) or 0,
},
rechte = {
dokumentieren = Rechte.Hat(mitarbeiter, 'behandlung.dokumentieren'),
diagnose = Rechte.Hat(mitarbeiter, 'behandlung.diagnose'),
abschliessen = Rechte.Hat(mitarbeiter, 'behandlung.abschliessen'),
medikament = Rechte.Hat(mitarbeiter, 'medikament.ausgeben'),
btm = Rechte.Hat(mitarbeiter, 'medikament.btm'),
},
}
end)
Kern.RegistriereAktion('behandlung', 'oeffnen', nil, function(quelle, mitarbeiter, daten)
local behandlungId = tonumber(daten.id)
if not behandlungId then return nil, 'Ungültige Kennung.' end
local zeilen = MySQL.query.await([[
SELECT b.*, c.firstname, c.lastname, c.dob,
m.blutgruppe, m.allergien, m.vorerkrankungen, m.dauermedikation,
m.hinweis, m.lichtbild,
e.nummer AS einsatzNummer, e.titel AS einsatzTitel,
a.grund AS aufnahmeGrund, a.status AS aufnahmeStatus
FROM medical_behandlung b
JOIN aether_characters c ON c.id = b.patient
LEFT JOIN medical_patient m ON m.`character` = b.patient
LEFT JOIN medical_einsatz e ON e.id = b.einsatz
LEFT JOIN medical_aufnahme a ON a.id = b.aufnahme
WHERE b.id = ?
]], { behandlungId })
if not zeilen or not zeilen[1] then return nil, 'Behandlung nicht gefunden.' end
local b = zeilen[1]
-- Dokumentierte Medikamentengaben
local gaben = MySQL.query.await([[
SELECT g.id, g.dosierung, g.menge, g.gegeben_am, g.gegeben_von, g.hinweis,
m.id AS medikamentId, m.name, m.wirkstoff, m.einheit, m.betaeubungsmittel
FROM medical_behandlung_medikament g
JOIN medical_medikament m ON m.id = g.medikament
WHERE g.behandlung = ?
ORDER BY g.id DESC
]], { behandlungId }) or {}
for _, gabe in ipairs(gaben) do
gabe.autor = NameVon(gabe.gegeben_von)
gabe.eigene = gabe.gegeben_von == mitarbeiter.charakterId
gabe.btm = gabe.betaeubungsmittel == 1
gabe.betaeubungsmittel, gabe.gegeben_von = nil, nil
end
-- Operationen zu diesem Patienten, damit die Doku vollständig wird
local operationen = MySQL.query.await([[
SELECT id, kennung, art, status, beginn, ende
FROM medical_operation
WHERE patient = ? ORDER BY id DESC LIMIT 10
]], { b.patient }) or {}
-- Laborergebnisse zu diesem Patienten
local labor = MySQL.query.await([[
SELECT id, kennung, art, status, ergebnis, befund, auftrag_am
FROM medical_labor
WHERE patient = ? ORDER BY id DESC LIMIT 10
]], { b.patient }) or {}
return {
behandlung = {
id = b.id, kennung = b.kennung, patient = b.patient,
patientName = b.firstname .. ' ' .. b.lastname,
geburt = tostring(b.dob):sub(1, 10),
aufnahme = b.aufnahme, aufnahmeGrund = b.aufnahmeGrund,
aufnahmeStatus = b.aufnahmeStatus,
einsatz = b.einsatz, einsatzNummer = b.einsatzNummer,
einsatzTitel = b.einsatzTitel,
symptome = b.symptome, untersuchung = b.untersuchung,
diagnose = b.diagnose, massnahmen = b.massnahmen,
empfehlung = b.empfehlung, nachsorge = b.nachsorge,
status = b.status,
erstelltAm = b.erstellt_am,
erstelltVon = NameVon(b.erstellt_von),
istMeine = b.erstellt_von == mitarbeiter.charakterId,
abgeschlossenAm = b.abgeschlossen_am,
abgeschlossenVon = b.abgeschlossen_von
and NameVon(b.abgeschlossen_von) or nil,
},
stamm = {
blutgruppe = b.blutgruppe, allergien = b.allergien,
vorerkrankungen = b.vorerkrankungen, dauermedikation = b.dauermedikation,
hinweis = b.hinweis, lichtbild = b.lichtbild,
},
gaben = gaben,
operationen = operationen,
labor = labor,
rechte = {
dokumentieren = Rechte.Hat(mitarbeiter, 'behandlung.dokumentieren'),
diagnose = Rechte.Hat(mitarbeiter, 'behandlung.diagnose'),
abschliessen = Rechte.Hat(mitarbeiter, 'behandlung.abschliessen'),
medikament = Rechte.Hat(mitarbeiter, 'medikament.ausgeben'),
btm = Rechte.Hat(mitarbeiter, 'medikament.btm'),
bestand = Rechte.Hat(mitarbeiter, 'medikament.bestand'),
},
}
end)
-- =====================================================================
-- ANLEGEN
-- =====================================================================
--- Patientensuche mit laufender Aufnahme, damit die Doku andocken kann
Kern.RegistriereAktion('behandlung', 'patientSuche', 'behandlung.dokumentieren', function(quelle, mitarbeiter, daten)
local begriff = type(daten.begriff) == 'string' and daten.begriff or ''
if #begriff < 2 then return { treffer = {} } end
local muster = '%' .. begriff .. '%'
local treffer = MySQL.query.await([[
SELECT c.id, c.firstname, c.lastname, c.dob,
m.blutgruppe, m.allergien, m.hinweis,
(SELECT a.id FROM medical_aufnahme a
WHERE a.patient = c.id AND a.status IN ('wartend', 'aufgenommen')
ORDER BY a.id DESC LIMIT 1) AS aufnahme,
(SELECT a.grund FROM medical_aufnahme a
WHERE a.patient = c.id AND a.status IN ('wartend', 'aufgenommen')
ORDER BY a.id DESC LIMIT 1) AS aufnahmeGrund
FROM aether_characters c
LEFT JOIN medical_patient m ON m.`character` = c.id
WHERE c.deleted = 0
AND (c.firstname LIKE ? OR c.lastname LIKE ?
OR CONCAT(c.firstname, ' ', c.lastname) LIKE ? OR c.dob LIKE ?)
ORDER BY c.lastname, c.firstname
LIMIT 20
]], { muster, muster, muster, muster }) or {}
for _, eintrag in ipairs(treffer) do
eintrag.dob = tostring(eintrag.dob):sub(1, 10)
end
-- Laufende Einsätze, an denen der Mitarbeiter beteiligt ist
local einsaetze = MySQL.query.await([[
SELECT e.id, e.nummer, e.titel
FROM medical_einsatz e
JOIN medical_einsatz_kraft k ON k.einsatz = e.id AND k.bis IS NULL
WHERE k.`character` = ? AND e.status <> 'abgeschlossen'
ORDER BY e.id DESC LIMIT 10
]], { mitarbeiter.charakterId }) or {}
return { treffer = treffer, einsaetze = einsaetze }
end)
Kern.RegistriereAktion('behandlung', 'anlegen', 'behandlung.dokumentieren', function(quelle, mitarbeiter, daten)
local patientId = tonumber(daten.patient)
if not patientId then return nil, 'Kein Patient gewählt.' end
local person = MySQL.query.await(
'SELECT firstname, lastname FROM aether_characters WHERE id = ? AND deleted = 0',
{ patientId })
if not person or not person[1] then return nil, 'Diese Person ist nicht erfasst.' end
-- Aufnahme und Einsatz sind freiwillig, müssen aber existieren
local aufnahmeId = tonumber(daten.aufnahme)
if aufnahmeId then
local gueltig = MySQL.scalar.await(
'SELECT id FROM medical_aufnahme WHERE id = ? AND patient = ?',
{ aufnahmeId, patientId })
if not gueltig then return nil, 'Diese Aufnahme gehört nicht zu dem Patienten.' end
end
local einsatzId = tonumber(daten.einsatz)
if einsatzId then
local gueltig = MySQL.scalar.await(
'SELECT id FROM medical_einsatz WHERE id = ?', { einsatzId })
if not gueltig then return nil, 'Unbekannter Einsatz.' end
end
local kennung = Kern.ErzeugeKennung('BEH', 'medical_behandlung', 'kennung')
local id = MySQL.insert.await([[
INSERT INTO medical_behandlung
(kennung, patient, aufnahme, einsatz, symptome, untersuchung, status, erstellt_von)
VALUES (?, ?, ?, ?, ?, ?, 'laufend', ?)
]], {
kennung, patientId, aufnahmeId, einsatzId,
Text(daten.symptome, 4000),
Text(daten.untersuchung, 4000),
mitarbeiter.charakterId,
})
Protokoll.Schreibe(mitarbeiter, 'behandlung', 'Behandlung angelegt',
{ typ = 'behandlung', id = id },
{ kennung = kennung, patient = patientId, aufnahme = aufnahmeId, einsatz = einsatzId })
Kern.Loese('behandlung.angelegt', id, mitarbeiter)
return { id = id, kennung = kennung }
end)
-- =====================================================================
-- DOKUMENTIEREN
--
-- Die Diagnose ist ein eigenes Recht. Wer sie nicht hat, kann alles
-- andere schreiben — das Feld bleibt dann unverändert.
-- =====================================================================
Kern.RegistriereAktion('behandlung', 'speichern', 'behandlung.dokumentieren', function(quelle, mitarbeiter, daten)
local behandlungId = tonumber(daten.id)
if not behandlungId then return nil, 'Ungültige Kennung.' end
local zeilen = MySQL.query.await(
'SELECT id, kennung, status, diagnose FROM medical_behandlung WHERE id = ?',
{ behandlungId })
if not zeilen or not zeilen[1] then return nil, 'Behandlung nicht gefunden.' end
local behandlung = zeilen[1]
if behandlung.status == 'abgeschlossen' then
return nil, 'Eine abgeschlossene Behandlung lässt sich nicht mehr ändern.'
end
-- Diagnose nur mit dem passenden Recht
local darfDiagnose = Rechte.Hat(mitarbeiter, 'behandlung.diagnose')
local neueDiagnose = darfDiagnose and Text(daten.diagnose, 4000) or behandlung.diagnose
if not darfDiagnose and Text(daten.diagnose, 4000) ~= nil
and Text(daten.diagnose, 4000) ~= behandlung.diagnose then
return nil, 'Eine Diagnose darfst du nicht eintragen.'
end
MySQL.update.await([[
UPDATE medical_behandlung
SET symptome = ?, untersuchung = ?, diagnose = ?,
massnahmen = ?, empfehlung = ?, nachsorge = ?
WHERE id = ?
]], {
Text(daten.symptome, 4000),
Text(daten.untersuchung, 4000),
neueDiagnose,
Text(daten.massnahmen, 4000),
Text(daten.empfehlung, 4000),
Text(daten.nachsorge, 4000),
behandlungId,
})
Protokoll.Schreibe(mitarbeiter, 'behandlung', 'Dokumentation gespeichert',
{ typ = 'behandlung', id = behandlungId },
{ kennung = behandlung.kennung,
diagnoseGeaendert = neueDiagnose ~= behandlung.diagnose })
return { ok = true }
end)
Kern.RegistriereAktion('behandlung', 'abschliessen', 'behandlung.abschliessen', function(quelle, mitarbeiter, daten)
local behandlungId = tonumber(daten.id)
if not behandlungId then return nil, 'Ungültige Kennung.' end
local zeilen = MySQL.query.await([[
SELECT id, kennung, status, diagnose, massnahmen, untersuchung
FROM medical_behandlung WHERE id = ?
]], { behandlungId })
if not zeilen or not zeilen[1] then return nil, 'Behandlung nicht gefunden.' end
local behandlung = zeilen[1]
if behandlung.status == 'abgeschlossen' then
return nil, 'Diese Behandlung ist bereits abgeschlossen.'
end
-- Eine leere Dokumentation abzuschliessen ergibt keinen Sinn.
-- Geprüft wird nur, DASS etwas dasteht — nicht, was.
local leer = not behandlung.untersuchung and not behandlung.massnahmen
and not behandlung.diagnose
if leer then
return nil, 'Die Dokumentation ist leer. Bitte zuerst etwas eintragen.'
end
MySQL.update.await([[
UPDATE medical_behandlung
SET status = 'abgeschlossen', abgeschlossen_von = ?, abgeschlossen_am = NOW()
WHERE id = ?
]], { mitarbeiter.charakterId, behandlungId })
Protokoll.Schreibe(mitarbeiter, 'behandlung', 'Behandlung abgeschlossen',
{ typ = 'behandlung', id = behandlungId },
{ kennung = behandlung.kennung, ohneDiagnose = behandlung.diagnose == nil },
Text(daten.vermerk, 255))
Kern.Loese('behandlung.abgeschlossen', behandlungId, mitarbeiter)
return { ok = true, ohneDiagnose = behandlung.diagnose == nil }
end)
--- Wieder öffnen, falls etwas nachzutragen ist — immer mit Begründung
Kern.RegistriereAktion('behandlung', 'wiedereroeffnen', 'behandlung.abschliessen', function(quelle, mitarbeiter, daten)
local behandlungId = tonumber(daten.id)
if not behandlungId then return nil, 'Ungültige Kennung.' end
local grund = Text(daten.grund, 255)
if not grund then return nil, 'Bitte begründen, warum die Doku wieder geöffnet wird.' end
local zeilen = MySQL.query.await(
'SELECT id, kennung, status FROM medical_behandlung WHERE id = ?', { behandlungId })
if not zeilen or not zeilen[1] then return nil, 'Behandlung nicht gefunden.' end
if zeilen[1].status ~= 'abgeschlossen' then
return nil, 'Diese Behandlung läuft bereits.'
end
MySQL.update.await([[
UPDATE medical_behandlung
SET status = 'laufend', abgeschlossen_von = NULL, abgeschlossen_am = NULL
WHERE id = ?
]], { behandlungId })
Protokoll.Schreibe(mitarbeiter, 'behandlung', 'Behandlung wieder geöffnet',
{ typ = 'behandlung', id = behandlungId },
{ kennung = zeilen[1].kennung }, grund)
return { ok = true }
end)
-- =====================================================================
-- MEDIKAMENTENGABE
--
-- Dokumentation einer Gabe, die ein Mediziner vorgenommen hat.
-- Das System gibt nichts aus und verordnet nichts.
-- =====================================================================
--- Auswahlliste der Medikamente
Kern.RegistriereAktion('behandlung', 'medikamente', 'medikament.ausgeben', function(quelle, mitarbeiter, daten)
local medikamente = MySQL.query.await([[
SELECT id, kennung, name, wirkstoff, kategorie, dosierung_standard,
einheit, bestand, mindestbestand, betaeubungsmittel, hinweis, lagerort,
verfaellt_am, DATEDIFF(verfaellt_am, CURDATE()) AS tageBisVerfall
FROM medical_medikament
WHERE aktiv = 1
ORDER BY kategorie, sortierung, name
]]) or {}
local darfBtm = Rechte.Hat(mitarbeiter, 'medikament.btm')
for _, eintrag in ipairs(medikamente) do
eintrag.btm = eintrag.betaeubungsmittel == 1
-- Betäubungsmittel bleiben sichtbar, sind aber ohne Recht nicht wählbar
eintrag.waehlbar = not eintrag.btm or darfBtm
-- Abgelaufene Praeparate werden markiert, aber nicht gesperrt:
-- ob sie verwendbar sind, beurteilt ein Mensch.
local tage = tonumber(eintrag.tageBisVerfall)
eintrag.abgelaufen = tage ~= nil and tage < 0
eintrag.betaeubungsmittel = nil
end
return {
medikamente = medikamente,
kategorien = Medizin.Config.Medikamentkategorien,
darfBtm = darfBtm,
}
end)
Kern.RegistriereAktion('behandlung', 'medikamentGeben', 'medikament.ausgeben', function(quelle, mitarbeiter, daten)
local behandlungId = tonumber(daten.id)
local medikamentId = tonumber(daten.medikament)
local menge = math.floor(tonumber(daten.menge) or 0)
if not behandlungId or not medikamentId then return nil, 'Unvollständige Angaben.' end
if menge < 1 or menge > 999 then return nil, 'Ungültige Menge.' end
local behandlung = MySQL.query.await(
'SELECT id, kennung, patient, status FROM medical_behandlung WHERE id = ?',
{ behandlungId })
if not behandlung or not behandlung[1] then return nil, 'Behandlung nicht gefunden.' end
if behandlung[1].status == 'abgeschlossen' then
return nil, 'Die Behandlung ist abgeschlossen. Bitte zuerst wieder öffnen.'
end
local zeilen = MySQL.query.await([[
SELECT id, kennung, name, einheit, bestand, mindestbestand, betaeubungsmittel
FROM medical_medikament WHERE id = ? AND aktiv = 1
]], { medikamentId })
if not zeilen or not zeilen[1] then return nil, 'Unbekanntes Medikament.' end
local medikament = zeilen[1]
-- Betäubungsmittel erfordern ein eigenes Recht
if medikament.betaeubungsmittel == 1 and not Rechte.Hat(mitarbeiter, 'medikament.btm') then
return nil, 'Betäubungsmittel darfst du nicht dokumentieren.'
end
-- Reicht der Bestand? Falls nicht, wird die Gabe trotzdem dokumentiert,
-- aber nur auf ausdrücklichen Wunsch und ohne Bestandsbuchung.
-- Eine Dokumentation darf nie an einer Lagerzahl scheitern.
local ohneBestand = daten.ohneBestand == true
if medikament.bestand < menge and not ohneBestand then
return nil, ('Im Bestand sind nur %d %s. Du kannst die Gabe trotzdem dokumentieren — dann ohne Bestandsbuchung.')
:format(medikament.bestand, medikament.einheit)
end
local gabeId = MySQL.insert.await([[
INSERT INTO medical_behandlung_medikament
(behandlung, medikament, dosierung, menge, gegeben_von, hinweis)
VALUES (?, ?, ?, ?, ?, ?)
]], {
behandlungId, medikamentId,
Text(daten.dosierung, 64), menge, mitarbeiter.charakterId,
ohneBestand
and ((Text(daten.hinweis, 200) or '') .. ' [ohne Bestandsbuchung]')
or Text(daten.hinweis, 255),
})
-- Bestand nachführen, weil jemand etwas entnommen hat
if not ohneBestand then
local neuerBestand = medikament.bestand - menge
MySQL.update.await('UPDATE medical_medikament SET bestand = ? WHERE id = ?',
{ neuerBestand, medikamentId })
MySQL.insert.await([[
INSERT INTO medical_medikament_buchung
(medikament, art, menge, bestand_danach, patient, grund, gebucht_von)
VALUES (?, 'ausgabe', ?, ?, ?, ?, ?)
]], {
medikamentId, menge, neuerBestand, behandlung[1].patient,
'Gabe zu ' .. behandlung[1].kennung, mitarbeiter.charakterId,
})
-- Mindestbestand erreicht? Erinnern, nicht nachbestellen.
if neuerBestand <= medikament.mindestbestand then
Hinweise.Erstelle(nil, {
kategorie = 'medikament',
prioritaet = neuerBestand <= 0 and 'hoch' or 'normal',
titel = neuerBestand <= 0 and 'Medikament nicht mehr vorrätig'
or 'Medikament fast leer',
text = ('%s — noch %d %s.')
:format(medikament.name, neuerBestand, medikament.einheit),
zielTyp = 'medikament', zielId = medikamentId,
})
end
end
Protokoll.Schreibe(mitarbeiter, 'behandlung', 'Medikamentengabe dokumentiert',
{ typ = 'behandlung', id = behandlungId },
{ medikament = medikament.kennung, menge = menge,
btm = medikament.betaeubungsmittel == 1, ohneBestand = ohneBestand })
Kern.Loese('medikament.gegeben', gabeId, medikamentId, mitarbeiter)
return { id = gabeId }
end)
--- Fehleintrag zurücknehmen. Der Bestand wird als Korrektur zurückgebucht.
Kern.RegistriereAktion('behandlung', 'medikamentZuruecknehmen', 'medikament.ausgeben', function(quelle, mitarbeiter, daten)
local gabeId = tonumber(daten.gabe)
if not gabeId then return nil, 'Ungültige Kennung.' end
local grund = Text(daten.grund, 255)
if not grund then return nil, 'Bitte begründen, warum der Eintrag zurückgenommen wird.' end
local zeilen = MySQL.query.await([[
SELECT g.id, g.behandlung, g.medikament, g.menge, g.gegeben_von, g.hinweis,
m.name, m.bestand, m.einheit, b.status
FROM medical_behandlung_medikament g
JOIN medical_medikament m ON m.id = g.medikament
JOIN medical_behandlung b ON b.id = g.behandlung
WHERE g.id = ?
]], { gabeId })
if not zeilen or not zeilen[1] then return nil, 'Eintrag nicht gefunden.' end
local gabe = zeilen[1]
if gabe.status == 'abgeschlossen' then
return nil, 'Die Behandlung ist abgeschlossen. Bitte zuerst wieder öffnen.'
end
-- Nur der eigene Eintrag, ausser man verwaltet den Bestand
if gabe.gegeben_von ~= mitarbeiter.charakterId
and not Rechte.Hat(mitarbeiter, 'medikament.bestand') then
return nil, 'Du kannst nur eigene Einträge zurücknehmen.'
end
MySQL.query.await('DELETE FROM medical_behandlung_medikament WHERE id = ?', { gabeId })
-- Nur zurückbuchen, wenn seinerzeit auch gebucht wurde
local wurdeGebucht = not (gabe.hinweis and gabe.hinweis:find('ohne Bestandsbuchung', 1, true))
if wurdeGebucht then
local neuerBestand = gabe.bestand + gabe.menge
MySQL.update.await('UPDATE medical_medikament SET bestand = ? WHERE id = ?',
{ neuerBestand, gabe.medikament })
MySQL.insert.await([[
INSERT INTO medical_medikament_buchung
(medikament, art, menge, bestand_danach, grund, gebucht_von)
VALUES (?, 'korrektur', ?, ?, ?, ?)
]], { gabe.medikament, gabe.menge, neuerBestand,
'Rücknahme: ' .. grund, mitarbeiter.charakterId })
end
Protokoll.Schreibe(mitarbeiter, 'behandlung', 'Medikamentengabe zurückgenommen',
{ typ = 'behandlung', id = gabe.behandlung },
{ medikament = gabe.name, menge = gabe.menge, zurueckgebucht = wurdeGebucht }, grund)
return { ok = true }
end)
-- ---------------------------------------------------------------------
-- Schnittstelle für andere Ressourcen
-- ---------------------------------------------------------------------
--- Zählt offene Dokumentationen eines Mitarbeiters
--- @param charakterId number Charakter
--- @return number
exports('OffeneDokumentationen', function(charakterId)
if not tonumber(charakterId) then return 0 end
return tonumber(MySQL.scalar.await([[
SELECT COUNT(*) FROM medical_behandlung
WHERE status = 'laufend' AND erstellt_von = ?
]], { tonumber(charakterId) })) or 0
end)