- 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>
674 lines
29 KiB
Lua
674 lines
29 KiB
Lua
-- =====================================================================
|
||
-- 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)
|