-- ===================================================================== -- aether-medical / Modul: Patientenverwaltung -- -- Anmeldung, Warteliste, Aufnahme, Verlegung und Entlassung. -- -- Nichts davon geschieht von selbst. Das System führt die Warteliste -- und zeigt, welche Zimmer frei sind — welcher Patient wann und wohin -- aufgenommen wird, entscheidet ausschliesslich ein Mediziner. -- -- Besonders wichtig: die Dringlichkeit wird NICHT aus der Wartezeit -- berechnet. Ein Patient, der lange wartet, rutscht nicht automatisch -- nach oben — das System erinnert nur daran, dass er wartet. -- ===================================================================== Kern.RegistriereModul({ id = 'patienten', name = 'Patienten', icon = '🧍', reihenfolge = 3, recht = 'patient.suchen', beschreibung = 'Anmeldung, Warteliste, Belegung und Entlassung', }) -- Dringlichkeitsstufen. Reine Beschriftung — keine Stufe löst etwas aus. local DRINGLICHKEIT = { [1] = { name = 'Sofort', hinweis = 'Lebensbedrohlich' }, [2] = { name = 'Dringend', hinweis = 'Zeitkritisch' }, [3] = { name = 'Regulär', hinweis = 'Kann warten' }, [4] = { name = 'Nachrangig', hinweis = 'Nicht akut' }, } -- --------------------------------------------------------------------- -- Grundbestand an Stationen und Zimmern -- -- Wird einmalig angelegt, damit sich überhaupt jemand aufnehmen -- lässt. Die Verwaltung übernimmt später die Krankenhausverwaltung. -- --------------------------------------------------------------------- local GRUNDSTATIONEN = { -- kennung, name, art, etage, zimmer = { kennung, betten, art } { 'NA', 'Notaufnahme', 'notaufnahme', 'EG', { { 'NA-1', 1, 'behandlung' }, { 'NA-2', 1, 'behandlung' }, { 'NA-3', 1, 'behandlung' }, { 'SCHOCK', 1, 'behandlung' }, }}, { 'CH', 'Chirurgie', 'normal', '1. OG', { { 'CH-101', 2, 'bett' }, { 'CH-102', 2, 'bett' }, { 'CH-103', 1, 'bett' }, }}, { 'IN', 'Innere Medizin', 'normal', '1. OG', { { 'IN-111', 2, 'bett' }, { 'IN-112', 2, 'bett' }, }}, { 'ITS', 'Intensivstation', 'intensiv', '2. OG', { { 'ITS-1', 1, 'intensiv' }, { 'ITS-2', 1, 'intensiv' }, { 'ITS-3', 1, 'intensiv' }, { 'ISO-1', 1, 'isolierung' }, }}, { 'OP', 'OP-Bereich', 'op', '2. OG', { { 'OP-1', 1, 'op' }, { 'OP-2', 1, 'op' }, }}, } CreateThread(function() Wait(1500) local vorhanden = tonumber(MySQL.scalar.await('SELECT COUNT(*) FROM medical_station')) or 0 if vorhanden > 0 then return end for reihenfolge, eintrag in ipairs(GRUNDSTATIONEN) do local stationId = MySQL.insert.await([[ INSERT INTO medical_station (kennung, name, art, etage, sortierung) VALUES (?, ?, ?, ?, ?) ]], { eintrag[1], eintrag[2], eintrag[3], eintrag[4], reihenfolge }) for _, zimmer in ipairs(eintrag[5]) do MySQL.insert.await([[ INSERT INTO medical_zimmer (station, kennung, betten, art) VALUES (?, ?, ?, ?) ]], { stationId, zimmer[1], zimmer[2], zimmer[3] }) end end Aether.Debug('Grundbestand an Stationen und Zimmern 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 --- Prüft eine Dringlichkeitsstufe --- @param wert any Eingabe --- @return number 1 bis 4 local function Dringlichkeit(wert) local stufe = tonumber(wert) or 3 if stufe < 1 or stufe > 4 then return 3 end return math.floor(stufe) 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 --- Wie viele Betten eines Zimmers sind gerade belegt? --- @param zimmerId number Zimmer --- @return number local function BelegteBetten(zimmerId) return tonumber(MySQL.scalar.await( 'SELECT COUNT(*) FROM medical_belegung WHERE zimmer = ? AND frei_am IS NULL', { zimmerId })) or 0 end --- Sucht ein freies Bett in einem Zimmer --- @param zimmerId number Zimmer --- @param betten number Bettenzahl des Zimmers --- @return number|nil Bettnummer local function FreiesBett(zimmerId, betten) local belegt = MySQL.query.await( 'SELECT bett FROM medical_belegung WHERE zimmer = ? AND frei_am IS NULL', { zimmerId }) or {} local genommen = {} for _, eintrag in ipairs(belegt) do genommen[tonumber(eintrag.bett)] = true end for nummer = 1, betten do if not genommen[nummer] then return nummer end end return nil end --- Gibt alle Betten einer Aufnahme frei --- @param aufnahmeId number Aufnahme --- @param mitarbeiter table Handelnder --- @return table Liste der frei gewordenen Zimmer local function BettenFreigeben(aufnahmeId, mitarbeiter) local belegungen = MySQL.query.await([[ SELECT b.id, b.zimmer, z.kennung, z.betten FROM medical_belegung b JOIN medical_zimmer z ON z.id = b.zimmer WHERE b.aufnahme = ? AND b.frei_am IS NULL ]], { aufnahmeId }) or {} for _, belegung in ipairs(belegungen) do MySQL.update.await( 'UPDATE medical_belegung SET frei_am = NOW(), frei_von = ? WHERE id = ?', { mitarbeiter.charakterId, belegung.id }) end return belegungen end -- ===================================================================== -- ÜBERSICHT -- ===================================================================== Kern.RegistriereAktion('patienten', 'uebersicht', nil, function(quelle, mitarbeiter, daten) local ordner = tostring(daten.ordner or 'warteliste') local bedingung, werte if ordner == 'entlassen' then bedingung = "a.status = 'entlassen'" werte = {} elseif ordner == 'station' then bedingung = "a.status = 'aufgenommen'" werte = {} else bedingung = "a.status = 'wartend'" werte = {} end if daten.station and daten.station ~= '' then bedingung = bedingung .. ' AND a.station = ?' werte[#werte + 1] = daten.station end local liste = MySQL.query.await(([[ SELECT a.id, a.patient, a.grund, a.prioritaet, a.station, a.status, a.aufgenommen_am, a.aufgenommen_von, a.entlassen_am, a.entlassen_von, a.entlass_grund, TIMESTAMPDIFF(MINUTE, a.aufgenommen_am, NOW()) AS wartet, c.firstname, c.lastname, c.dob, m.blutgruppe, m.allergien, m.hinweis, z.kennung AS zimmer, s.name AS stationName, (SELECT COUNT(*) FROM medical_intensiv i WHERE i.aufnahme = a.id AND i.beendet_am IS NULL) AS aufIntensiv FROM medical_aufnahme a JOIN aether_characters c ON c.id = a.patient LEFT JOIN medical_patient m ON m.`character` = a.patient LEFT JOIN medical_zimmer z ON z.id = a.zimmer LEFT JOIN medical_station s ON s.kennung = a.station WHERE %s ORDER BY a.prioritaet, a.aufgenommen_am LIMIT 150 ]]):format(bedingung), werte) or {} for _, eintrag in ipairs(liste) do eintrag.name = eintrag.firstname .. ' ' .. eintrag.lastname eintrag.geburt = tostring(eintrag.dob):sub(1, 10) eintrag.firstname, eintrag.lastname, eintrag.dob = nil, nil, nil eintrag.dringlichkeit = DRINGLICHKEIT[eintrag.prioritaet] and DRINGLICHKEIT[eintrag.prioritaet].name or '—' eintrag.aufgenommenVon = NameVon(eintrag.aufgenommen_von) eintrag.entlassenVon = eintrag.entlassen_von and NameVon(eintrag.entlassen_von) or nil eintrag.aufIntensiv = tonumber(eintrag.aufIntensiv) > 0 eintrag.aufgenommen_von, eintrag.entlassen_von = nil, nil end -- Zählwerte für die Reiter local zaehler = { wartend = tonumber(MySQL.scalar.await( "SELECT COUNT(*) FROM medical_aufnahme WHERE status = 'wartend'")) or 0, aufgenommen = tonumber(MySQL.scalar.await( "SELECT COUNT(*) FROM medical_aufnahme WHERE status = 'aufgenommen'")) or 0, entlassenHeute = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_aufnahme WHERE status = 'entlassen' AND DATE(entlassen_am) = CURDATE() ]])) or 0, } -- Belegung je Station: was ist frei, was ist belegt local stationen = MySQL.query.await([[ SELECT s.id, s.kennung, s.name, s.art, s.etage, COALESCE(SUM(z.betten), 0) AS betten, COUNT(z.id) AS zimmer FROM medical_station s LEFT JOIN medical_zimmer z ON z.station = s.id AND z.status <> 'gesperrt' WHERE s.aktiv = 1 GROUP BY s.id ORDER BY s.sortierung, s.kennung ]]) or {} for _, station in ipairs(stationen) do station.belegt = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_belegung b JOIN medical_zimmer z ON z.id = b.zimmer WHERE z.station = ? AND b.frei_am IS NULL ]], { station.id })) or 0 station.betten = tonumber(station.betten) or 0 station.frei = math.max(0, station.betten - station.belegt) end return { ordner = ordner, liste = liste, zaehler = zaehler, stationen = stationen, dringlichkeit = DRINGLICHKEIT, rechte = { aufnehmen = Rechte.Hat(mitarbeiter, 'patient.aufnehmen'), verlegen = Rechte.Hat(mitarbeiter, 'patient.verlegen'), entlassen = Rechte.Hat(mitarbeiter, 'patient.entlassen'), belegen = Rechte.Hat(mitarbeiter, 'station.belegen'), }, } end) Kern.RegistriereAktion('patienten', 'oeffnen', nil, function(quelle, mitarbeiter, daten) local aufnahmeId = tonumber(daten.id) if not aufnahmeId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT a.*, c.firstname, c.lastname, c.dob, m.blutgruppe, m.allergien, m.vorerkrankungen, m.dauermedikation, m.hinweis, m.notfallkontakt, m.lichtbild, z.kennung AS zimmer, z.art AS zimmerArt, s.name AS stationName FROM medical_aufnahme a JOIN aether_characters c ON c.id = a.patient LEFT JOIN medical_patient m ON m.`character` = a.patient LEFT JOIN medical_zimmer z ON z.id = a.zimmer LEFT JOIN medical_station s ON s.kennung = a.station WHERE a.id = ? ]], { aufnahmeId }) if not zeilen or not zeilen[1] then return nil, 'Aufnahme nicht gefunden.' end local a = zeilen[1] -- Welches Bett belegt der Patient gerade? local bett = MySQL.query.await([[ SELECT b.bett, b.belegt_am, b.belegt_von, z.kennung FROM medical_belegung b JOIN medical_zimmer z ON z.id = b.zimmer WHERE b.aufnahme = ? AND b.frei_am IS NULL LIMIT 1 ]], { aufnahmeId }) bett = bett and bett[1] or nil -- Laufende Behandlungen zu dieser Aufnahme local behandlungen = MySQL.query.await([[ SELECT id, kennung, diagnose, status, erstellt_am, erstellt_von FROM medical_behandlung WHERE aufnahme = ? ORDER BY id DESC LIMIT 20 ]], { aufnahmeId }) or {} for _, eintrag in ipairs(behandlungen) do eintrag.autor = NameVon(eintrag.erstellt_von) eintrag.erstellt_von = nil end return { aufnahme = { id = a.id, patient = a.patient, name = a.firstname .. ' ' .. a.lastname, geburt = tostring(a.dob):sub(1, 10), grund = a.grund, prioritaet = a.prioritaet, dringlichkeit = DRINGLICHKEIT[a.prioritaet] and DRINGLICHKEIT[a.prioritaet].name or '—', station = a.station, stationName = a.stationName, zimmer = a.zimmer, zimmerArt = a.zimmerArt, bett = bett and bett.bett or nil, status = a.status, aufgenommenAm = a.aufgenommen_am, aufgenommenVon = NameVon(a.aufgenommen_von), entlassenAm = a.entlassen_am, entlassenVon = a.entlassen_von and NameVon(a.entlassen_von) or nil, entlassGrund = a.entlass_grund, }, stamm = { blutgruppe = a.blutgruppe, allergien = a.allergien, vorerkrankungen = a.vorerkrankungen, dauermedikation = a.dauermedikation, hinweis = a.hinweis, notfallkontakt = a.notfallkontakt, lichtbild = a.lichtbild, }, behandlungen = behandlungen, dringlichkeit = DRINGLICHKEIT, rechte = { aufnehmen = Rechte.Hat(mitarbeiter, 'patient.aufnehmen'), verlegen = Rechte.Hat(mitarbeiter, 'patient.verlegen'), entlassen = Rechte.Hat(mitarbeiter, 'patient.entlassen'), }, } end) -- ===================================================================== -- ANMELDUNG -- -- Der erste Schritt: ein Patient meldet sich oder wird gebracht. -- Er landet auf der Warteliste — mehr passiert nicht. -- ===================================================================== Kern.RegistriereAktion('patienten', 'anmelden', 'patient.aufnehmen', function(quelle, mitarbeiter, daten) local patientId = tonumber(daten.patient) local grund = Text(daten.grund, 512) if not patientId then return nil, 'Kein Patient gewählt.' end if not grund or #grund < 3 then return nil, 'Bitte den Grund der Vorstellung angeben.' 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 -- Doppelte Anmeldung verhindern local offen = MySQL.scalar.await([[ SELECT id FROM medical_aufnahme WHERE patient = ? AND status IN ('wartend', 'aufgenommen') ]], { patientId }) if offen then return nil, 'Für diesen Patienten läuft bereits eine Aufnahme.' end local prioritaet = Dringlichkeit(daten.prioritaet) local id = MySQL.insert.await([[ INSERT INTO medical_aufnahme (patient, grund, prioritaet, status, aufgenommen_von) VALUES (?, ?, ?, 'wartend', ?) ]], { patientId, grund, prioritaet, mitarbeiter.charakterId }) Protokoll.Schreibe(mitarbeiter, 'patienten', 'Patient angemeldet', { typ = 'aufnahme', id = id }, { patient = patientId, prioritaet = prioritaet }) Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = prioritaet <= 2 and 'hoch' or 'normal', titel = ('Patient wartet · %s'):format(DRINGLICHKEIT[prioritaet].name), text = ('%s %s — %s'):format(person[1].firstname, person[1].lastname, grund:sub(1, 80)), zielTyp = 'aufnahme', zielId = id, }) Kern.Loese('aufnahme.angemeldet', id, mitarbeiter) return { id = id } end) --- Personensuche für die Anmeldung Kern.RegistriereAktion('patienten', 'patientSuche', 'patient.aufnehmen', 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.status FROM medical_aufnahme a WHERE a.patient = c.id AND a.status IN ('wartend', 'aufgenommen') LIMIT 1) AS laufend 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 return { treffer = treffer } end) -- ===================================================================== -- AUFNAHME AUF STATION -- ===================================================================== --- Freie Zimmer einer Station — reine Auskunft, das System wählt keines aus Kern.RegistriereAktion('patienten', 'freieZimmer', nil, function(quelle, mitarbeiter, daten) local bedingung, werte = 's.aktiv = 1', {} if daten.station and daten.station ~= '' then bedingung = bedingung .. ' AND s.kennung = ?' werte[#werte + 1] = daten.station end local zimmer = MySQL.query.await(([[ SELECT z.id, z.kennung, z.betten, z.art, z.status, z.hinweis, s.kennung AS station, s.name AS stationName, s.art AS stationArt FROM medical_zimmer z JOIN medical_station s ON s.id = z.station WHERE %s ORDER BY s.sortierung, z.kennung ]]):format(bedingung), werte) or {} for _, eintrag in ipairs(zimmer) do eintrag.belegt = BelegteBetten(eintrag.id) eintrag.frei = math.max(0, eintrag.betten - eintrag.belegt) -- Verfügbar heisst: nicht gesperrt, nicht in Reinigung, noch Platz eintrag.verfuegbar = eintrag.status == 'frei' and eintrag.frei > 0 end return { zimmer = zimmer } end) Kern.RegistriereAktion('patienten', 'aufnehmen', 'patient.aufnehmen', function(quelle, mitarbeiter, daten) local aufnahmeId = tonumber(daten.id) if not aufnahmeId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT a.id, a.patient, a.status, c.firstname, c.lastname FROM medical_aufnahme a JOIN aether_characters c ON c.id = a.patient WHERE a.id = ? ]], { aufnahmeId }) if not zeilen or not zeilen[1] then return nil, 'Aufnahme nicht gefunden.' end local aufnahme = zeilen[1] if aufnahme.status ~= 'wartend' then return nil, 'Dieser Patient ist bereits aufgenommen oder entlassen.' end -- Station prüfen local station = MySQL.query.await( 'SELECT kennung, name FROM medical_station WHERE kennung = ? AND aktiv = 1', { tostring(daten.station or '') }) if not station or not station[1] then return nil, 'Unbekannte Station.' end -- Zimmer ist freiwillig — ein Patient kann auch ohne Bett aufgenommen werden local zimmerId, bettNummer = nil, nil if daten.zimmer and tonumber(daten.zimmer) then local zimmer = MySQL.query.await([[ SELECT z.id, z.kennung, z.betten, z.status, s.kennung AS station FROM medical_zimmer z JOIN medical_station s ON s.id = z.station WHERE z.id = ? ]], { tonumber(daten.zimmer) }) if not zimmer or not zimmer[1] then return nil, 'Unbekanntes Zimmer.' end if zimmer[1].status ~= 'frei' then return nil, ('Zimmer %s steht nicht zur Verfügung (%s).') :format(zimmer[1].kennung, zimmer[1].status) end bettNummer = FreiesBett(zimmer[1].id, zimmer[1].betten) if not bettNummer then return nil, ('In Zimmer %s ist kein Bett mehr frei.'):format(zimmer[1].kennung) end zimmerId = zimmer[1].id end MySQL.update.await([[ UPDATE medical_aufnahme SET status = 'aufgenommen', station = ?, zimmer = ? WHERE id = ? ]], { station[1].kennung, zimmerId, aufnahmeId }) if zimmerId then MySQL.insert.await([[ INSERT INTO medical_belegung (zimmer, bett, patient, aufnahme, belegt_von) VALUES (?, ?, ?, ?, ?) ]], { zimmerId, bettNummer, aufnahme.patient, aufnahmeId, mitarbeiter.charakterId }) end Protokoll.Schreibe(mitarbeiter, 'patienten', 'Patient aufgenommen', { typ = 'aufnahme', id = aufnahmeId }, { station = station[1].kennung, zimmer = zimmerId, bett = bettNummer }, Text(daten.vermerk, 255)) Kern.Loese('aufnahme.aufgenommen', aufnahmeId, mitarbeiter) return { ok = true, bett = bettNummer } end) -- ===================================================================== -- VERLEGUNG -- ===================================================================== Kern.RegistriereAktion('patienten', 'verlegen', 'patient.verlegen', function(quelle, mitarbeiter, daten) local aufnahmeId = tonumber(daten.id) if not aufnahmeId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT a.id, a.patient, a.status, a.station, 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 zeilen or not zeilen[1] then return nil, 'Aufnahme nicht gefunden.' end local aufnahme = zeilen[1] if aufnahme.status ~= 'aufgenommen' then return nil, 'Nur aufgenommene Patienten lassen sich verlegen.' end local grund = Text(daten.grund, 255) if not grund then return nil, 'Bitte den Grund der Verlegung angeben.' end local station = MySQL.query.await( 'SELECT kennung, name FROM medical_station WHERE kennung = ? AND aktiv = 1', { tostring(daten.station or '') }) if not station or not station[1] then return nil, 'Unbekannte Station.' end -- Neues Zimmer prüfen, bevor das alte freigegeben wird local zimmerId, bettNummer = nil, nil if daten.zimmer and tonumber(daten.zimmer) then local zimmer = MySQL.query.await( 'SELECT id, kennung, betten, status FROM medical_zimmer WHERE id = ?', { tonumber(daten.zimmer) }) if not zimmer or not zimmer[1] then return nil, 'Unbekanntes Zimmer.' end if zimmer[1].status ~= 'frei' then return nil, ('Zimmer %s steht nicht zur Verfügung.'):format(zimmer[1].kennung) end -- Verlegung ins selbe Zimmer ist keine Verlegung if zimmer[1].id == aufnahme.zimmer then return nil, 'Der Patient liegt bereits in diesem Zimmer.' end bettNummer = FreiesBett(zimmer[1].id, zimmer[1].betten) if not bettNummer then return nil, ('In Zimmer %s ist kein Bett mehr frei.'):format(zimmer[1].kennung) end zimmerId = zimmer[1].id end local vorher = BettenFreigeben(aufnahmeId, mitarbeiter) MySQL.update.await('UPDATE medical_aufnahme SET station = ?, zimmer = ? WHERE id = ?', { station[1].kennung, zimmerId, aufnahmeId }) if zimmerId then MySQL.insert.await([[ INSERT INTO medical_belegung (zimmer, bett, patient, aufnahme, belegt_von) VALUES (?, ?, ?, ?, ?) ]], { zimmerId, bettNummer, aufnahme.patient, aufnahmeId, mitarbeiter.charakterId }) end Protokoll.Schreibe(mitarbeiter, 'patienten', 'Patient verlegt', { typ = 'aufnahme', id = aufnahmeId }, { vonStation = aufnahme.station, nachStation = station[1].kennung, zimmer = zimmerId, bett = bettNummer }, grund) Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'normal', titel = 'Neue Verlegung', text = ('%s %s wurde auf %s verlegt.') :format(aufnahme.firstname, aufnahme.lastname, station[1].name), zielTyp = 'aufnahme', zielId = aufnahmeId, }) -- Frei gewordene Zimmer melden — das Freigeben zur Reinigung bleibt manuell for _, belegung in ipairs(vorher) do if BelegteBetten(belegung.zimmer) == 0 then Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'niedrig', titel = 'Zimmer wurde frei', text = ('Zimmer %s ist jetzt leer.'):format(belegung.kennung), zielTyp = 'zimmer', zielId = belegung.zimmer, }) end end Kern.Loese('aufnahme.verlegt', aufnahmeId, mitarbeiter) return { ok = true, bett = bettNummer } end) -- ===================================================================== -- DRINGLICHKEIT -- -- Wird ausschliesslich von Hand gesetzt. Wartezeit ändert sie nie. -- ===================================================================== Kern.RegistriereAktion('patienten', 'prioritaet', 'patient.aufnehmen', function(quelle, mitarbeiter, daten) local aufnahmeId = tonumber(daten.id) if not aufnahmeId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await( 'SELECT id, prioritaet, status FROM medical_aufnahme WHERE id = ?', { aufnahmeId }) if not zeilen or not zeilen[1] then return nil, 'Aufnahme nicht gefunden.' end if zeilen[1].status == 'entlassen' then return nil, 'Dieser Vorgang ist bereits abgeschlossen.' end local neu = Dringlichkeit(daten.prioritaet) local grund = Text(daten.grund, 255) if not grund then return nil, 'Bitte begründen, warum sich die Dringlichkeit ändert.' end MySQL.update.await('UPDATE medical_aufnahme SET prioritaet = ? WHERE id = ?', { neu, aufnahmeId }) Protokoll.Schreibe(mitarbeiter, 'patienten', 'Dringlichkeit geändert', { typ = 'aufnahme', id = aufnahmeId }, { von = zeilen[1].prioritaet, nach = neu }, grund) return { ok = true } end) -- ===================================================================== -- ENTLASSUNG -- -- Geschieht nie automatisch. Auch nicht, wenn alle Behandlungen -- abgeschlossen sind — das entscheidet ein Arzt. -- ===================================================================== Kern.RegistriereAktion('patienten', 'entlassen', 'patient.entlassen', function(quelle, mitarbeiter, daten) local aufnahmeId = tonumber(daten.id) if not aufnahmeId then return nil, 'Ungültige Kennung.' end local grund = Text(daten.grund, 512) if not grund or #grund < 5 then return nil, 'Eine Entlassung erfordert eine Begründung.' end local zeilen = MySQL.query.await([[ SELECT a.id, a.patient, a.status, c.firstname, c.lastname FROM medical_aufnahme a JOIN aether_characters c ON c.id = a.patient WHERE a.id = ? ]], { aufnahmeId }) if not zeilen or not zeilen[1] then return nil, 'Aufnahme nicht gefunden.' end local aufnahme = zeilen[1] if aufnahme.status == 'entlassen' then return nil, 'Dieser Patient ist bereits entlassen.' end -- Offene Punkte NUR melden, nicht verhindern und nicht abschliessen local offeneBehandlungen = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_behandlung WHERE aufnahme = ? AND status = 'laufend' ]], { aufnahmeId })) or 0 local aufIntensiv = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_intensiv WHERE aufnahme = ? AND beendet_am IS NULL ]], { aufnahmeId })) or 0 local freigeworden = BettenFreigeben(aufnahmeId, mitarbeiter) MySQL.update.await([[ UPDATE medical_aufnahme SET status = 'entlassen', zimmer = NULL, entlassen_von = ?, entlassen_am = NOW(), entlass_grund = ? WHERE id = ? ]], { mitarbeiter.charakterId, grund, aufnahmeId }) Protokoll.Schreibe(mitarbeiter, 'patienten', 'Patient entlassen', { typ = 'aufnahme', id = aufnahmeId }, { patient = aufnahme.patient, offeneBehandlungen = offeneBehandlungen, aufIntensiv = aufIntensiv > 0 }, grund) -- Frei gewordene Zimmer melden for _, belegung in ipairs(freigeworden) do if BelegteBetten(belegung.zimmer) == 0 then Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'niedrig', titel = 'Zimmer wurde frei', text = ('Zimmer %s ist jetzt leer.'):format(belegung.kennung), zielTyp = 'zimmer', zielId = belegung.zimmer, }) end end -- Laufende Dokumentation bleibt offen und wird nur in Erinnerung gerufen if offeneBehandlungen > 0 then Hinweise.Erstelle({ charakterId = mitarbeiter.charakterId }, { kategorie = 'doku', prioritaet = 'normal', titel = 'Dokumentation noch offen', text = ('%s %s wurde entlassen — %d Behandlung(en) sind noch nicht abgeschlossen.') :format(aufnahme.firstname, aufnahme.lastname, offeneBehandlungen), zielTyp = 'aufnahme', zielId = aufnahmeId, }) end -- Ein laufender Intensivaufenthalt wird NICHT beendet, nur gemeldet if aufIntensiv > 0 then Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'hoch', titel = 'Entlassung trotz laufender Intensivbetreuung', text = ('%s %s wurde entlassen, der Intensivaufenthalt ist noch offen. Bitte prüfen.') :format(aufnahme.firstname, aufnahme.lastname), zielTyp = 'aufnahme', zielId = aufnahmeId, }) end Kern.Loese('aufnahme.entlassen', aufnahmeId, mitarbeiter) return { ok = true, offeneBehandlungen = offeneBehandlungen } end) -- ===================================================================== -- ZIMMERSTATUS -- -- Reinigung und Sperrung setzt immer ein Mensch. Ein leer gewordenes -- Zimmer wird nicht von selbst wieder als frei gemeldet. -- ===================================================================== Kern.RegistriereAktion('patienten', 'zimmerStatus', 'station.belegen', function(quelle, mitarbeiter, daten) local zimmerId = tonumber(daten.id) local status = tostring(daten.status or '') if not zimmerId then return nil, 'Ungültige Kennung.' end local erlaubt = { frei = true, reinigung = true, gesperrt = true } if not erlaubt[status] then return nil, 'Unbekannter Zimmerstatus.' end local zimmer = MySQL.query.await( 'SELECT id, kennung, betten FROM medical_zimmer WHERE id = ?', { zimmerId }) if not zimmer or not zimmer[1] then return nil, 'Zimmer nicht gefunden.' end -- Ein belegtes Zimmer lässt sich nicht sperren, ohne die Belegung zu klären if status == 'gesperrt' and BelegteBetten(zimmerId) > 0 then return nil, 'In diesem Zimmer liegen noch Patienten.' end MySQL.update.await('UPDATE medical_zimmer SET status = ?, hinweis = ? WHERE id = ?', { status, Text(daten.hinweis, 255), zimmerId }) Protokoll.Schreibe(mitarbeiter, 'patienten', 'Zimmerstatus gesetzt', { typ = 'zimmer', id = zimmerId }, { kennung = zimmer[1].kennung, status = status }) return { ok = true } end) -- --------------------------------------------------------------------- -- Schnittstelle für andere Ressourcen -- --------------------------------------------------------------------- --- Liegt dieser Patient gerade im Krankenhaus? --- Reine Auskunft — keine Ressource kann darüber jemanden aufnehmen. --- @param patientId number Charakter --- @return table|nil { status, station, zimmer, seit } exports('HoleAufenthalt', function(patientId) if not tonumber(patientId) then return nil end local zeilen = MySQL.query.await([[ SELECT a.status, a.station, a.aufgenommen_am, z.kennung AS zimmer FROM medical_aufnahme a LEFT JOIN medical_zimmer z ON z.id = a.zimmer WHERE a.patient = ? AND a.status IN ('wartend', 'aufgenommen') ORDER BY a.id DESC LIMIT 1 ]], { tonumber(patientId) }) if not zeilen or not zeilen[1] then return nil end return { status = zeilen[1].status, station = zeilen[1].station, zimmer = zeilen[1].zimmer, seit = zeilen[1].aufgenommen_am, } end)