- 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>
695 lines
28 KiB
Lua
695 lines
28 KiB
Lua
-- =====================================================================
|
|
-- aether-medical / Modul: Intensivstation
|
|
--
|
|
-- Ein Intensivaufenthalt ist hier eine Akte, kein Monitor.
|
|
--
|
|
-- Das System misst nichts. Es kennt keinen Blutdruck, keine Sättigung
|
|
-- und keinen Alarm. Der Zustand eines Patienten steht genau deshalb
|
|
-- im Datensatz, weil ein Mediziner ihn dort eingetragen hat — er
|
|
-- ändert sich nie von selbst, weder durch Zeitablauf noch durch
|
|
-- irgendeine Rechnung.
|
|
--
|
|
-- Auch Geräte werden nur benannt, nicht gesteuert. Und ein Aufenthalt
|
|
-- endet erst, wenn jemand ihn beendet: nicht mit der Entlassung,
|
|
-- nicht mit dem Bett und nicht mit dem Dienstende.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereModul({
|
|
id = 'intensiv', name = 'Intensiv', icon = '📈',
|
|
reihenfolge = 10, recht = 'intensiv.sehen',
|
|
beschreibung = 'Intensivaufenthalte, Zustand, Geräte und Verlauf',
|
|
})
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Hilfsfunktionen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Begrenzt einen Text und liefert nil bei leerer Eingabe
|
|
--- @param wert any Eingabe
|
|
--- @param laenge number Maximale Zeichenzahl
|
|
--- @return string|nil
|
|
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
|
|
--- @param charakterId number|nil Charakter
|
|
--- @return string
|
|
local function NameVon(charakterId)
|
|
if not charakterId then return 'unbekannt' end
|
|
local zeilen = MySQL.query.await(
|
|
'SELECT firstname, lastname FROM aether_characters WHERE id = ?', { charakterId })
|
|
if not zeilen or not zeilen[1] then return 'unbekannt' end
|
|
return zeilen[1].firstname .. ' ' .. zeilen[1].lastname
|
|
end
|
|
|
|
--- Prüft einen Zustand gegen die Konfiguration
|
|
--- @param wert any Eingabe
|
|
--- @return string|nil Gültige Kennung
|
|
local function Zustand(wert)
|
|
local gesucht = tostring(wert or '')
|
|
for _, eintrag in ipairs(Medizin.Config.Intensivzustaende) do
|
|
if eintrag.id == gesucht then return eintrag.id end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- Anzeigename eines Zustands
|
|
--- @param kennung string Zustand
|
|
--- @return string
|
|
local function ZustandName(kennung)
|
|
for _, eintrag in ipairs(Medizin.Config.Intensivzustaende) do
|
|
if eintrag.id == kennung then return eintrag.name end
|
|
end
|
|
return tostring(kennung)
|
|
end
|
|
|
|
--- Baut die Sortierreihenfolge der Zustände für MySQL.
|
|
--- Sortiert wird nach dem, was Menschen eingetragen haben — das System
|
|
--- bewertet dabei nichts, es stellt nur das Eingetragene nach oben.
|
|
--- @return string FIELD(...)-Ausdruck
|
|
local function ZustandSortierung()
|
|
local teile = {}
|
|
for _, eintrag in ipairs(Medizin.Config.Intensivzustaende) do
|
|
-- Die Kennungen stammen aus der eigenen Konfiguration
|
|
teile[#teile + 1] = ("'%s'"):format(eintrag.id:gsub("'", ''))
|
|
end
|
|
return ('FIELD(i.zustand, %s)'):format(table.concat(teile, ', '))
|
|
end
|
|
|
|
--- Lädt einen Intensivaufenthalt mit allen Begleitdaten
|
|
--- @param intensivId number Aufenthalt
|
|
--- @return table|nil Zeile
|
|
local function LadeAufenthalt(intensivId)
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT i.*,
|
|
TIMESTAMPDIFF(MINUTE, i.aufgenommen_am,
|
|
COALESCE(i.beendet_am, NOW())) AS liegtSeit,
|
|
c.firstname, c.lastname, c.dob,
|
|
z.kennung AS zimmerKennung, z.art AS zimmerArt,
|
|
s.name AS stationName,
|
|
a.status AS aufnahmeStatus, a.grund AS aufnahmeGrund
|
|
FROM medical_intensiv i
|
|
JOIN aether_characters c ON c.id = i.patient
|
|
LEFT JOIN medical_zimmer z ON z.id = i.zimmer
|
|
LEFT JOIN medical_station s ON s.id = z.station
|
|
LEFT JOIN medical_aufnahme a ON a.id = i.aufnahme
|
|
WHERE i.id = ?
|
|
]], { intensivId })
|
|
|
|
return zeilen and zeilen[1] or nil
|
|
end
|
|
|
|
--- Schreibt einen Verlaufseintrag.
|
|
--- Der Text stammt immer von einem Menschen — das System formuliert
|
|
--- hier nichts und fasst nichts zusammen.
|
|
--- @param intensivId number Aufenthalt
|
|
--- @param text string Wortlaut
|
|
--- @param charakterId number Verfasser
|
|
--- @return number Eintrag
|
|
local function SchreibeVerlauf(intensivId, text, charakterId)
|
|
return MySQL.insert.await(
|
|
'INSERT INTO medical_intensiv_verlauf (intensiv, text, von) VALUES (?, ?, ?)',
|
|
{ intensivId, text, charakterId })
|
|
end
|
|
|
|
-- =====================================================================
|
|
-- ÜBERSICHT
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('intensiv', 'uebersicht', nil, function(quelle, mitarbeiter, daten)
|
|
local ordner = tostring(daten.ordner or 'laufend')
|
|
local beendet = ordner == 'beendet'
|
|
|
|
local liste = MySQL.query.await(([[
|
|
SELECT i.id, i.patient, i.aufnahme, i.zimmer, i.zustand, i.geraete, i.notiz,
|
|
i.verantwortlich, i.aufgenommen_am, i.aufgenommen_von,
|
|
i.beendet_am, i.beendet_von,
|
|
TIMESTAMPDIFF(MINUTE, i.aufgenommen_am,
|
|
COALESCE(i.beendet_am, NOW())) AS liegtSeit,
|
|
TIMESTAMPDIFF(MINUTE, COALESCE(
|
|
(SELECT MAX(v.am) FROM medical_intensiv_verlauf v WHERE v.intensiv = i.id),
|
|
i.aufgenommen_am), NOW()) AS stillSeit,
|
|
(SELECT COUNT(*) FROM medical_intensiv_verlauf v
|
|
WHERE v.intensiv = i.id) AS eintraege,
|
|
c.firstname, c.lastname, c.dob,
|
|
p.blutgruppe, p.allergien, p.hinweis AS warnhinweis,
|
|
z.kennung AS zimmerKennung, s.name AS stationName
|
|
FROM medical_intensiv i
|
|
JOIN aether_characters c ON c.id = i.patient
|
|
LEFT JOIN medical_patient p ON p.`character` = i.patient
|
|
LEFT JOIN medical_zimmer z ON z.id = i.zimmer
|
|
LEFT JOIN medical_station s ON s.id = z.station
|
|
WHERE i.beendet_am IS %s NULL
|
|
ORDER BY %s
|
|
LIMIT 100
|
|
]]):format(beendet and 'NOT' or '',
|
|
beendet and 'i.beendet_am DESC' or (ZustandSortierung() .. ', i.aufgenommen_am'))) or {}
|
|
|
|
local stille = Medizin.Config.Erinnerungen.intensivStilleNach
|
|
|
|
for _, eintrag in ipairs(liste) do
|
|
eintrag.name = eintrag.firstname .. ' ' .. eintrag.lastname
|
|
eintrag.geburt = tostring(eintrag.dob):sub(1, 10)
|
|
eintrag.zustandName = ZustandName(eintrag.zustand)
|
|
eintrag.liegtSeit = tonumber(eintrag.liegtSeit) or 0
|
|
eintrag.stillSeit = tonumber(eintrag.stillSeit) or 0
|
|
eintrag.eintraege = tonumber(eintrag.eintraege) or 0
|
|
eintrag.verantwortlichName = eintrag.verantwortlich
|
|
and NameVon(eintrag.verantwortlich) or nil
|
|
eintrag.aufgenommenVon = NameVon(eintrag.aufgenommen_von)
|
|
eintrag.beendetVon = eintrag.beendet_von and NameVon(eintrag.beendet_von) or nil
|
|
-- Nur eine Erinnerung, keine Bewertung der Betreuung
|
|
eintrag.langeStill = not beendet and eintrag.stillSeit >= stille
|
|
eintrag.firstname, eintrag.lastname, eintrag.dob = nil, nil, nil
|
|
eintrag.aufgenommen_von, eintrag.beendet_von = nil, nil
|
|
end
|
|
|
|
-- Zählwerte über die laufenden Aufenthalte
|
|
local zaehler = {
|
|
laufend = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_intensiv WHERE beendet_am IS NULL')) or 0,
|
|
kritisch = tonumber(MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM medical_intensiv
|
|
WHERE beendet_am IS NULL AND zustand = 'kritisch'
|
|
]])) or 0,
|
|
ohneArzt = tonumber(MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM medical_intensiv
|
|
WHERE beendet_am IS NULL AND verantwortlich IS NULL
|
|
]])) or 0,
|
|
beendetHeute = tonumber(MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM medical_intensiv
|
|
WHERE beendet_am IS NOT NULL AND DATE(beendet_am) = CURDATE()
|
|
]])) or 0,
|
|
}
|
|
|
|
-- Wie viele laufende Aufenthalte haben lange keinen Eintrag bekommen?
|
|
zaehler.langeStill = tonumber(MySQL.scalar.await(([[
|
|
SELECT COUNT(*) FROM medical_intensiv i
|
|
WHERE i.beendet_am IS NULL
|
|
AND COALESCE(
|
|
(SELECT MAX(v.am) FROM medical_intensiv_verlauf v WHERE v.intensiv = i.id),
|
|
i.aufgenommen_am) < DATE_SUB(NOW(), INTERVAL ? MINUTE)
|
|
]]), { stille })) or 0
|
|
|
|
return {
|
|
ordner = ordner,
|
|
liste = liste,
|
|
zaehler = zaehler,
|
|
zustaende = Medizin.Config.Intensivzustaende,
|
|
stilleNach = stille,
|
|
rechte = {
|
|
betreuen = Rechte.Hat(mitarbeiter, 'intensiv.betreuen'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- EINZELANSICHT
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('intensiv', 'oeffnen', nil, function(quelle, mitarbeiter, daten)
|
|
local intensivId = tonumber(daten.id)
|
|
if not intensivId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeAufenthalt(intensivId)
|
|
if not eintrag then return nil, 'Intensivaufenthalt nicht gefunden.' end
|
|
|
|
-- Stammdaten, die am Bett gebraucht werden
|
|
local stamm = MySQL.query.await([[
|
|
SELECT blutgruppe, allergien, vorerkrankungen, dauermedikation,
|
|
hinweis, notfallkontakt, lichtbild
|
|
FROM medical_patient WHERE `character` = ?
|
|
]], { eintrag.patient })
|
|
stamm = stamm and stamm[1] or {}
|
|
|
|
-- Der Verlauf — jüngster Eintrag zuerst
|
|
local verlauf = MySQL.query.await([[
|
|
SELECT v.id, v.text, v.am, v.von, c.firstname, c.lastname
|
|
FROM medical_intensiv_verlauf v
|
|
LEFT JOIN aether_characters c ON c.id = v.von
|
|
WHERE v.intensiv = ?
|
|
ORDER BY v.id DESC
|
|
LIMIT 100
|
|
]], { intensivId }) or {}
|
|
|
|
for _, zeile in ipairs(verlauf) do
|
|
zeile.autor = zeile.firstname and (zeile.firstname .. ' ' .. zeile.lastname) or 'unbekannt'
|
|
zeile.firstname, zeile.lastname, zeile.von = nil, nil, nil
|
|
end
|
|
|
|
return {
|
|
aufenthalt = {
|
|
id = eintrag.id, patient = eintrag.patient, aufnahme = eintrag.aufnahme,
|
|
name = eintrag.firstname .. ' ' .. eintrag.lastname,
|
|
geburt = tostring(eintrag.dob):sub(1, 10),
|
|
zustand = eintrag.zustand, zustandName = ZustandName(eintrag.zustand),
|
|
geraete = eintrag.geraete, notiz = eintrag.notiz,
|
|
zimmer = eintrag.zimmer, zimmerKennung = eintrag.zimmerKennung,
|
|
zimmerArt = eintrag.zimmerArt, stationName = eintrag.stationName,
|
|
aufnahmeStatus = eintrag.aufnahmeStatus,
|
|
aufnahmeGrund = eintrag.aufnahmeGrund,
|
|
liegtSeit = tonumber(eintrag.liegtSeit) or 0,
|
|
verantwortlich = eintrag.verantwortlich,
|
|
verantwortlichName = eintrag.verantwortlich
|
|
and NameVon(eintrag.verantwortlich) or nil,
|
|
aufgenommenAm = eintrag.aufgenommen_am,
|
|
aufgenommenVon = NameVon(eintrag.aufgenommen_von),
|
|
beendetAm = eintrag.beendet_am,
|
|
beendetVon = eintrag.beendet_von and NameVon(eintrag.beendet_von) or nil,
|
|
laufend = eintrag.beendet_am == nil,
|
|
},
|
|
stamm = stamm,
|
|
verlauf = verlauf,
|
|
zustaende = Medizin.Config.Intensivzustaende,
|
|
geraeteliste = Medizin.Config.Intensivgeraete,
|
|
rechte = {
|
|
betreuen = Rechte.Hat(mitarbeiter, 'intensiv.betreuen'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- AUFNAHME AUF DIE INTENSIVSTATION
|
|
--
|
|
-- Wer auf Intensiv kommt, entscheidet ein Arzt. Kein Zustand, keine
|
|
-- Diagnose und kein Wert bringt jemanden von selbst hierher.
|
|
-- =====================================================================
|
|
|
|
--- Aufgenommene Patienten ohne laufenden Intensivaufenthalt
|
|
--- sowie die Zimmer, in denen eine Betreuung stattfinden könnte.
|
|
Kern.RegistriereAktion('intensiv', 'kandidaten', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local patienten = MySQL.query.await([[
|
|
SELECT a.id AS aufnahme, a.patient, a.grund, a.prioritaet, a.station,
|
|
TIMESTAMPDIFF(MINUTE, a.aufgenommen_am, NOW()) AS imHausSeit,
|
|
c.firstname, c.lastname, c.dob,
|
|
p.blutgruppe, p.allergien, p.hinweis AS warnhinweis,
|
|
z.id AS zimmer, z.kennung AS zimmerKennung,
|
|
s.name AS stationName
|
|
FROM medical_aufnahme a
|
|
JOIN aether_characters c ON c.id = a.patient
|
|
LEFT JOIN medical_patient p ON p.`character` = a.patient
|
|
LEFT JOIN medical_zimmer z ON z.id = a.zimmer
|
|
LEFT JOIN medical_station s ON s.id = z.station
|
|
WHERE a.status = 'aufgenommen'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM medical_intensiv i
|
|
WHERE i.patient = a.patient AND i.beendet_am IS NULL)
|
|
ORDER BY a.prioritaet, a.aufgenommen_am
|
|
LIMIT 100
|
|
]]) or {}
|
|
|
|
for _, eintrag in ipairs(patienten) do
|
|
eintrag.name = eintrag.firstname .. ' ' .. eintrag.lastname
|
|
eintrag.geburt = tostring(eintrag.dob):sub(1, 10)
|
|
eintrag.imHausSeit = tonumber(eintrag.imHausSeit) or 0
|
|
eintrag.firstname, eintrag.lastname, eintrag.dob = nil, nil, nil
|
|
end
|
|
|
|
-- Intensivplätze zur Auswahl. Das Bett selbst verwaltet die
|
|
-- Stationsverwaltung — hier wird nur festgehalten, wo betreut wird.
|
|
local zimmer = MySQL.query.await([[
|
|
SELECT z.id, z.kennung, z.art, z.status, z.betten, z.hinweis,
|
|
s.name AS stationName,
|
|
(SELECT COUNT(*) FROM medical_belegung b
|
|
WHERE b.zimmer = z.id AND b.frei_am IS NULL) AS belegt
|
|
FROM medical_zimmer z
|
|
JOIN medical_station s ON s.id = z.station
|
|
WHERE s.aktiv = 1 AND z.art IN ('intensiv', 'isolierung')
|
|
ORDER BY s.sortierung, z.kennung
|
|
]]) or {}
|
|
|
|
for _, eintrag in ipairs(zimmer) do
|
|
eintrag.belegt = tonumber(eintrag.belegt) or 0
|
|
eintrag.betten = tonumber(eintrag.betten) or 1
|
|
end
|
|
|
|
return { patienten = patienten, zimmer = zimmer }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('intensiv', 'aufnehmen', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local aufnahmeId = tonumber(daten.aufnahme)
|
|
if not aufnahmeId then return nil, 'Kein Patient gewählt.' end
|
|
|
|
local zustand = Zustand(daten.zustand)
|
|
if not zustand then return nil, 'Bitte einen Zustand eintragen.' end
|
|
|
|
local notiz = Text(daten.notiz, 1000)
|
|
if not notiz or #notiz < 5 then
|
|
return nil, 'Bitte festhalten, warum der Patient auf die Intensivstation kommt.'
|
|
end
|
|
|
|
local aufnahmen = MySQL.query.await([[
|
|
SELECT a.id, a.patient, a.status, a.zimmer, c.firstname, c.lastname
|
|
FROM medical_aufnahme a
|
|
JOIN aether_characters c ON c.id = a.patient
|
|
WHERE a.id = ?
|
|
]], { aufnahmeId })
|
|
if not aufnahmen or not aufnahmen[1] then return nil, 'Aufnahme nicht gefunden.' end
|
|
|
|
local aufnahme = aufnahmen[1]
|
|
if aufnahme.status ~= 'aufgenommen' then
|
|
return nil, 'Nur aufgenommene Patienten lassen sich auf die Intensivstation legen.'
|
|
end
|
|
|
|
-- Zwei laufende Aufenthalte für denselben Patienten ergeben keinen Sinn
|
|
local laufend = MySQL.scalar.await(
|
|
'SELECT id FROM medical_intensiv WHERE patient = ? AND beendet_am IS NULL',
|
|
{ aufnahme.patient })
|
|
if laufend then
|
|
return nil, 'Für diesen Patienten läuft bereits ein Intensivaufenthalt.'
|
|
end
|
|
|
|
-- Zimmer ist freiwillig: die Betreuung kann auch ohne festen Platz beginnen
|
|
local zimmerId = tonumber(daten.zimmer)
|
|
if zimmerId then
|
|
local zimmer = MySQL.query.await(
|
|
'SELECT id FROM medical_zimmer WHERE id = ?', { zimmerId })
|
|
if not zimmer or not zimmer[1] then return nil, 'Unbekanntes Zimmer.' end
|
|
end
|
|
|
|
-- Die Verantwortung übernimmt nur, wer das ausdrücklich angibt
|
|
local verantwortlich = daten.selbst == true and mitarbeiter.charakterId or nil
|
|
|
|
local geraete = Text(daten.geraete, 512)
|
|
|
|
local intensivId = MySQL.insert.await([[
|
|
INSERT INTO medical_intensiv
|
|
(patient, aufnahme, zimmer, geraete, verantwortlich, zustand, notiz, aufgenommen_von)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
]], { aufnahme.patient, aufnahmeId, zimmerId, geraete, verantwortlich,
|
|
zustand, notiz, mitarbeiter.charakterId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'intensiv', 'Intensivaufenthalt eröffnet',
|
|
{ typ = 'intensiv', id = intensivId },
|
|
{ patient = aufnahme.patient, aufnahme = aufnahmeId,
|
|
zimmer = zimmerId, zustand = zustand,
|
|
verantwortlich = verantwortlich }, notiz)
|
|
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'patient',
|
|
prioritaet = zustand == 'kritisch' and 'hoch' or 'normal',
|
|
titel = ('Intensivaufnahme · %s'):format(ZustandName(zustand)),
|
|
text = ('%s %s wird ab jetzt auf der Intensivstation betreut.')
|
|
:format(aufnahme.firstname, aufnahme.lastname),
|
|
zielTyp = 'intensiv', zielId = intensivId,
|
|
})
|
|
|
|
-- Ohne zuständigen Arzt bleibt der Aufenthalt sichtbar offen
|
|
if not verantwortlich then
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'patient', prioritaet = 'hoch',
|
|
titel = 'Intensivpatient ohne zuständigen Arzt',
|
|
text = ('Für %s %s ist noch niemand eingetragen.')
|
|
:format(aufnahme.firstname, aufnahme.lastname),
|
|
zielTyp = 'intensiv', zielId = intensivId,
|
|
})
|
|
end
|
|
|
|
Kern.Loese('intensiv.aufgenommen', intensivId, mitarbeiter)
|
|
|
|
return { id = intensivId }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- ZUSTAND
|
|
--
|
|
-- Der Zustand ist eine ärztliche Einschätzung und sonst nichts. Er
|
|
-- wird von Hand gesetzt, mit Begründung, und der Wortlaut dieser
|
|
-- Begründung wandert unverändert in den Verlauf.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('intensiv', 'zustand', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local intensivId = tonumber(daten.id)
|
|
if not intensivId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local neu = Zustand(daten.zustand)
|
|
if not neu then return nil, 'Unbekannter Zustand.' end
|
|
|
|
local begruendung = Text(daten.grund, 1000)
|
|
if not begruendung or #begruendung < 3 then
|
|
return nil, 'Bitte begründen, worauf sich die Einschätzung stützt.'
|
|
end
|
|
|
|
local eintrag = LadeAufenthalt(intensivId)
|
|
if not eintrag then return nil, 'Intensivaufenthalt nicht gefunden.' end
|
|
if eintrag.beendet_am then return nil, 'Dieser Aufenthalt ist bereits beendet.' end
|
|
|
|
MySQL.update.await('UPDATE medical_intensiv SET zustand = ? WHERE id = ?',
|
|
{ neu, intensivId })
|
|
|
|
-- Der Wortlaut des Arztes bleibt der Wortlaut des Arztes
|
|
SchreibeVerlauf(intensivId, begruendung, mitarbeiter.charakterId)
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'intensiv', 'Zustand eingetragen',
|
|
{ typ = 'intensiv', id = intensivId },
|
|
{ patient = eintrag.patient, von = eintrag.zustand, nach = neu }, begruendung)
|
|
|
|
-- Eine Verschlechterung auf kritisch erfährt das ganze Haus
|
|
if neu == 'kritisch' and eintrag.zustand ~= 'kritisch' then
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'patient', prioritaet = 'kritisch',
|
|
titel = 'Intensivpatient als kritisch eingetragen',
|
|
text = ('%s %s — eingetragen von %s.')
|
|
:format(eintrag.firstname, eintrag.lastname, mitarbeiter.name),
|
|
zielTyp = 'intensiv', zielId = intensivId,
|
|
})
|
|
end
|
|
|
|
Kern.Loese('intensiv.zustand', intensivId, neu, mitarbeiter)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- VERLAUF
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('intensiv', 'verlauf', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local intensivId = tonumber(daten.id)
|
|
if not intensivId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local text = Text(daten.text, 1000)
|
|
if not text or #text < 3 then return nil, 'Bitte einen Text eingeben.' end
|
|
|
|
local eintrag = LadeAufenthalt(intensivId)
|
|
if not eintrag then return nil, 'Intensivaufenthalt nicht gefunden.' end
|
|
if eintrag.beendet_am then
|
|
return nil, 'Zu einem beendeten Aufenthalt lässt sich nichts mehr nachtragen.'
|
|
end
|
|
|
|
local verlaufId = SchreibeVerlauf(intensivId, text, mitarbeiter.charakterId)
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'intensiv', 'Verlaufseintrag geschrieben',
|
|
{ typ = 'intensiv', id = intensivId },
|
|
{ patient = eintrag.patient, eintrag = verlaufId })
|
|
|
|
Kern.Loese('intensiv.verlauf', intensivId, verlaufId, mitarbeiter)
|
|
|
|
return { id = verlaufId }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- GERÄTE
|
|
--
|
|
-- Eine Liste von Namen. Das System schliesst nichts an, trennt nichts
|
|
-- und überwacht nichts — es weiss nur, was jemand notiert hat.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('intensiv', 'geraete', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local intensivId = tonumber(daten.id)
|
|
if not intensivId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeAufenthalt(intensivId)
|
|
if not eintrag then return nil, 'Intensivaufenthalt nicht gefunden.' end
|
|
if eintrag.beendet_am then return nil, 'Dieser Aufenthalt ist bereits beendet.' end
|
|
|
|
-- Leere Eingabe heisst: es ist nichts mehr angeschlossen
|
|
local geraete = Text(daten.geraete, 512)
|
|
|
|
MySQL.update.await('UPDATE medical_intensiv SET geraete = ? WHERE id = ?',
|
|
{ geraete, intensivId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'intensiv', 'Geräte eingetragen',
|
|
{ typ = 'intensiv', id = intensivId },
|
|
{ patient = eintrag.patient, vorher = eintrag.geraete, nachher = geraete })
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- ZUSTÄNDIGKEIT
|
|
--
|
|
-- Verantwortung wird übernommen oder übertragen, nie zugeteilt.
|
|
-- =====================================================================
|
|
|
|
--- Wer im Dienst darf einen Intensivaufenthalt verantworten?
|
|
Kern.RegistriereAktion('intensiv', 'betreuer', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local liste = {}
|
|
|
|
for _, eintrag in pairs(Dienst.AlleImDienst()) do
|
|
if Rechte.Hat(eintrag, 'intensiv.betreuen') then
|
|
liste[#liste + 1] = {
|
|
charakterId = eintrag.charakterId,
|
|
name = eintrag.name,
|
|
rolleName = eintrag.rolleName,
|
|
personalnummer = eintrag.personalnummer,
|
|
funktion = eintrag.funktion,
|
|
status = eintrag.status,
|
|
selbst = eintrag.charakterId == mitarbeiter.charakterId,
|
|
}
|
|
end
|
|
end
|
|
|
|
table.sort(liste, function(a, b) return tostring(a.name) < tostring(b.name) end)
|
|
|
|
return { liste = liste }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('intensiv', 'verantwortlich', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local intensivId = tonumber(daten.id)
|
|
if not intensivId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeAufenthalt(intensivId)
|
|
if not eintrag then return nil, 'Intensivaufenthalt nicht gefunden.' end
|
|
if eintrag.beendet_am then return nil, 'Dieser Aufenthalt ist bereits beendet.' end
|
|
|
|
-- Ohne Ziel wird die Zuständigkeit niedergelegt
|
|
local zielId = tonumber(daten.charakter)
|
|
|
|
if zielId then
|
|
local zielQuelle = Dienst.QuelleVonCharakter(zielId)
|
|
local ziel = zielQuelle and Dienst.Hole(zielQuelle)
|
|
if not ziel then return nil, 'Diese Person ist nicht im Dienst.' end
|
|
if not Rechte.Hat(ziel, 'intensiv.betreuen') then
|
|
return nil, 'Diese Person darf keinen Intensivaufenthalt verantworten.'
|
|
end
|
|
else
|
|
local grund = Text(daten.grund, 255)
|
|
if not grund then
|
|
return nil, 'Bitte angeben, warum die Zuständigkeit niedergelegt wird.'
|
|
end
|
|
end
|
|
|
|
MySQL.update.await('UPDATE medical_intensiv SET verantwortlich = ? WHERE id = ?',
|
|
{ zielId, intensivId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'intensiv',
|
|
zielId and 'Zuständigkeit übernommen oder übertragen' or 'Zuständigkeit niedergelegt',
|
|
{ typ = 'intensiv', id = intensivId },
|
|
{ patient = eintrag.patient, vorher = eintrag.verantwortlich, nachher = zielId },
|
|
Text(daten.grund, 255))
|
|
|
|
if zielId and zielId ~= mitarbeiter.charakterId then
|
|
-- Die betroffene Person erfährt es unmittelbar
|
|
Hinweise.Erstelle({ charakterId = zielId }, {
|
|
kategorie = 'patient', prioritaet = 'hoch',
|
|
titel = 'Du bist jetzt für einen Intensivpatienten zuständig',
|
|
text = ('%s %s — eingetragen von %s.')
|
|
:format(eintrag.firstname, eintrag.lastname, mitarbeiter.name),
|
|
zielTyp = 'intensiv', zielId = intensivId,
|
|
})
|
|
end
|
|
|
|
if not zielId then
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'patient', prioritaet = 'hoch',
|
|
titel = 'Intensivpatient ohne zuständigen Arzt',
|
|
text = ('Für %s %s ist niemand mehr eingetragen.')
|
|
:format(eintrag.firstname, eintrag.lastname),
|
|
zielTyp = 'intensiv', zielId = intensivId,
|
|
})
|
|
end
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- AUFENTHALT BEENDEN
|
|
--
|
|
-- Beendet ausschliesslich die Intensivbetreuung. Der Patient bleibt
|
|
-- aufgenommen, sein Bett bleibt belegt, und keine Behandlung wird
|
|
-- dadurch abgeschlossen.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('intensiv', 'beenden', 'intensiv.betreuen', function(quelle, mitarbeiter, daten)
|
|
local intensivId = tonumber(daten.id)
|
|
if not intensivId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local grund = Text(daten.grund, 1000)
|
|
if not grund or #grund < 5 then
|
|
return nil, 'Das Ende der Intensivbetreuung erfordert eine Begründung.'
|
|
end
|
|
|
|
local eintrag = LadeAufenthalt(intensivId)
|
|
if not eintrag then return nil, 'Intensivaufenthalt nicht gefunden.' end
|
|
if eintrag.beendet_am then return nil, 'Dieser Aufenthalt ist bereits beendet.' end
|
|
|
|
-- Ein kritischer Zustand hält nicht auf, wird aber festgehalten
|
|
local warKritisch = eintrag.zustand == 'kritisch'
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_intensiv
|
|
SET beendet_am = NOW(), beendet_von = ?
|
|
WHERE id = ?
|
|
]], { mitarbeiter.charakterId, intensivId })
|
|
|
|
-- Die Begründung schliesst den Verlauf mit den Worten des Arztes ab
|
|
SchreibeVerlauf(intensivId, grund, mitarbeiter.charakterId)
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'intensiv', 'Intensivaufenthalt beendet',
|
|
{ typ = 'intensiv', id = intensivId },
|
|
{ patient = eintrag.patient, zustand = eintrag.zustand,
|
|
warKritisch = warKritisch }, grund)
|
|
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'patient',
|
|
prioritaet = warKritisch and 'hoch' or 'normal',
|
|
titel = 'Intensivbetreuung beendet',
|
|
text = ('%s %s — der Patient bleibt aufgenommen, Bett und Behandlungen sind unberührt.')
|
|
:format(eintrag.firstname, eintrag.lastname),
|
|
zielTyp = eintrag.aufnahme and 'aufnahme' or 'intensiv',
|
|
zielId = eintrag.aufnahme or intensivId,
|
|
})
|
|
|
|
Kern.Loese('intensiv.beendet', intensivId, mitarbeiter)
|
|
|
|
return { ok = true, warKritisch = warKritisch }
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Schnittstelle für andere Ressourcen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Liegt dieser Patient gerade auf der Intensivstation?
|
|
--- Reine Auskunft — keine Ressource kann darüber jemanden aufnehmen,
|
|
--- entlassen oder seinen Zustand ändern.
|
|
--- @param patientId number Charakter
|
|
--- @return table|nil { id, zustand, zimmer, seit, verantwortlich }
|
|
exports('HoleIntensivaufenthalt', function(patientId)
|
|
if not tonumber(patientId) then return nil end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT i.id, i.zustand, i.aufgenommen_am, i.verantwortlich,
|
|
z.kennung AS zimmer
|
|
FROM medical_intensiv i
|
|
LEFT JOIN medical_zimmer z ON z.id = i.zimmer
|
|
WHERE i.patient = ? AND i.beendet_am IS NULL
|
|
ORDER BY i.id DESC LIMIT 1
|
|
]], { tonumber(patientId) })
|
|
|
|
if not zeilen or not zeilen[1] then return nil end
|
|
|
|
return {
|
|
id = zeilen[1].id,
|
|
zustand = zeilen[1].zustand,
|
|
zimmer = zeilen[1].zimmer,
|
|
seit = zeilen[1].aufgenommen_am,
|
|
verantwortlich = zeilen[1].verantwortlich
|
|
and NameVon(zeilen[1].verantwortlich) or nil,
|
|
}
|
|
end)
|