-- ===================================================================== -- aether-medical / Modul: Stationen und Zimmerbelegung -- -- Der Bettenspiegel zeigt, wer wo liegt und was frei ist. Mehr nicht. -- -- Das System belegt kein Bett, gibt keines frei, schiebt niemanden von -- einem Zimmer ins andere und setzt ein Zimmer nach der Entlassung -- nicht von selbst wieder auf "frei". Jede Zeile hier entsteht, weil -- ein Mensch sie ausgelöst hat — und trägt dessen Namen. -- -- Auch die Reinigung ist eine Entscheidung: ein leer gewordenes -- Zimmer meldet sich, bleibt aber in dem Status, in dem es war. -- ===================================================================== Kern.RegistriereModul({ id = 'station', name = 'Stationen', icon = '🛏', reihenfolge = 9, recht = 'station.sehen', beschreibung = 'Bettenspiegel, Zimmerstatus und Stationsverwaltung', }) -- Zimmerstatus, die von Hand gesetzt werden dürfen local ZIMMERSTATUS = { frei = true, reinigung = true, gesperrt = true, } -- --------------------------------------------------------------------- -- 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 eine Kennung aus einer Konfigurationsliste --- @param liste table Liste aus Medizin.Config --- @param wert any Eingabe --- @return string|nil Gültige Kennung local function AusListe(liste, wert) local gesucht = tostring(wert or '') for _, eintrag in ipairs(liste) do if eintrag.id == gesucht then return eintrag.id end end return nil 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 --- Liefert die Nummern der belegten Betten eines Zimmers --- @param zimmerId number Zimmer --- @return table { [bettNummer] = true } local function BelegteBettNummern(zimmerId) local zeilen = MySQL.query.await( 'SELECT bett FROM medical_belegung WHERE zimmer = ? AND frei_am IS NULL', { zimmerId }) or {} local genommen = {} for _, eintrag in ipairs(zeilen) do genommen[tonumber(eintrag.bett)] = true end return genommen end --- Sucht das erste freie Bett eines Zimmers --- @param zimmerId number Zimmer --- @param betten number Bettenzahl des Zimmers --- @return number|nil Bettnummer local function FreiesBett(zimmerId, betten) local genommen = BelegteBettNummern(zimmerId) for nummer = 1, betten do if not genommen[nummer] then return nummer end end return nil end --- Meldet ein leer gewordenes Zimmer — ohne seinen Status anzufassen --- @param zimmerId number Zimmer --- @param kennung string Zimmerkennung für den Text local function MeldeWennLeer(zimmerId, kennung) if BelegteBetten(zimmerId) > 0 then return end Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'niedrig', titel = 'Zimmer wurde frei', text = ('Zimmer %s ist jetzt leer. Reinigung und Freigabe bleiben eine Entscheidung.') :format(kennung), zielTyp = 'zimmer', zielId = zimmerId, }) end -- ===================================================================== -- BETTENSPIEGEL -- -- Eine reine Auskunft: welche Station, welches Zimmer, welches Bett, -- wer liegt darin. Das System bewertet die Belegung nicht und schlägt -- keine Verteilung vor. -- ===================================================================== Kern.RegistriereAktion('station', 'uebersicht', nil, function(quelle, mitarbeiter, daten) local darfVerwalten = Rechte.Hat(mitarbeiter, 'station.verwalten') -- Abgeschaltete Stationen sieht nur, wer sie auch pflegen darf local zeigeInaktive = darfVerwalten and daten.alle == true local stationen = MySQL.query.await(([[ SELECT id, kennung, name, art, etage, aktiv, sortierung FROM medical_station %s ORDER BY sortierung, kennung ]]):format(zeigeInaktive and '' or 'WHERE aktiv = 1')) or {} local zimmerListe = MySQL.query.await([[ SELECT id, station, kennung, betten, art, status, hinweis FROM medical_zimmer ORDER BY kennung ]]) or {} -- Alle laufenden Belegungen in einem Zug — je Zimmer zugeordnet local belegungen = MySQL.query.await([[ SELECT b.id, b.zimmer, b.bett, b.patient, b.aufnahme, b.belegt_am, b.belegt_von, TIMESTAMPDIFF(MINUTE, b.belegt_am, NOW()) AS liegtSeit, c.firstname, c.lastname, a.status AS aufnahmeStatus, a.prioritaet, a.grund, p.blutgruppe, p.allergien, p.hinweis AS warnhinweis, (SELECT COUNT(*) FROM medical_intensiv i WHERE i.aufnahme = b.aufnahme AND i.beendet_am IS NULL) AS aufIntensiv FROM medical_belegung b JOIN aether_characters c ON c.id = b.patient LEFT JOIN medical_aufnahme a ON a.id = b.aufnahme LEFT JOIN medical_patient p ON p.`character` = b.patient WHERE b.frei_am IS NULL ]]) or {} -- Belegungen nach Zimmer sortieren, damit die Betten schnell zu füllen sind local belegungNachZimmer = {} for _, belegung in ipairs(belegungen) do belegung.name = belegung.firstname .. ' ' .. belegung.lastname belegung.belegtVon = NameVon(belegung.belegt_von) belegung.liegtSeit = tonumber(belegung.liegtSeit) or 0 belegung.aufIntensiv = (tonumber(belegung.aufIntensiv) or 0) > 0 belegung.firstname, belegung.lastname, belegung.belegt_von = nil, nil, nil local zimmerId = tonumber(belegung.zimmer) belegungNachZimmer[zimmerId] = belegungNachZimmer[zimmerId] or {} belegungNachZimmer[zimmerId][tonumber(belegung.bett)] = belegung end -- Zimmer den Stationen zuordnen und jedes Bett einzeln aufführen local zimmerNachStation = {} for _, zimmer in ipairs(zimmerListe) do zimmer.betten = tonumber(zimmer.betten) or 1 local imZimmer = belegungNachZimmer[tonumber(zimmer.id)] or {} zimmer.bettenListe = {} for nummer = 1, zimmer.betten do zimmer.bettenListe[nummer] = { nummer = nummer, belegung = imZimmer[nummer], } end zimmer.belegt = 0 for _ in pairs(imZimmer) do zimmer.belegt = zimmer.belegt + 1 end zimmer.frei = math.max(0, zimmer.betten - zimmer.belegt) -- Verfügbar heisst: nicht gesperrt, nicht in Reinigung, noch Platz zimmer.verfuegbar = zimmer.status == 'frei' and zimmer.frei > 0 local stationId = tonumber(zimmer.station) zimmerNachStation[stationId] = zimmerNachStation[stationId] or {} table.insert(zimmerNachStation[stationId], zimmer) end -- Zählwerte über das ganze Haus local zaehler = { betten = 0, belegt = 0, frei = 0, zimmer = 0, gesperrt = 0, reinigung = 0, } for _, station in ipairs(stationen) do station.zimmer = zimmerNachStation[tonumber(station.id)] or {} station.aktiv = tonumber(station.aktiv) == 1 station.betten, station.belegt, station.frei = 0, 0, 0 for _, zimmer in ipairs(station.zimmer) do station.betten = station.betten + zimmer.betten station.belegt = station.belegt + zimmer.belegt -- Als frei zählt nur, was auch belegt werden dürfte if zimmer.status == 'frei' then station.frei = station.frei + zimmer.frei end zaehler.zimmer = zaehler.zimmer + 1 if zimmer.status == 'gesperrt' then zaehler.gesperrt = zaehler.gesperrt + 1 end if zimmer.status == 'reinigung' then zaehler.reinigung = zaehler.reinigung + 1 end end zaehler.betten = zaehler.betten + station.betten zaehler.belegt = zaehler.belegt + station.belegt zaehler.frei = zaehler.frei + station.frei end -- Aufgenommene Patienten, die in keinem Bett liegen. -- Das ist kein Fehler — es wird nur sichtbar gemacht. local ohneBett = MySQL.query.await([[ SELECT a.id, a.patient, a.station, a.grund, a.prioritaet, a.aufgenommen_am, TIMESTAMPDIFF(MINUTE, a.aufgenommen_am, NOW()) AS wartet, c.firstname, c.lastname, s.name AS stationName, p.blutgruppe, p.allergien, p.hinweis AS warnhinweis FROM medical_aufnahme a JOIN aether_characters c ON c.id = a.patient LEFT JOIN medical_station s ON s.kennung = a.station LEFT JOIN medical_patient p ON p.`character` = a.patient WHERE a.status = 'aufgenommen' AND NOT EXISTS ( SELECT 1 FROM medical_belegung b WHERE b.aufnahme = a.id AND b.frei_am IS NULL) ORDER BY a.prioritaet, a.aufgenommen_am LIMIT 100 ]]) or {} for _, eintrag in ipairs(ohneBett) do eintrag.name = eintrag.firstname .. ' ' .. eintrag.lastname eintrag.wartet = tonumber(eintrag.wartet) or 0 eintrag.firstname, eintrag.lastname = nil, nil end return { stationen = stationen, ohneBett = ohneBett, zaehler = zaehler, stationsarten = Medizin.Config.Stationsarten, zimmerarten = Medizin.Config.Zimmerarten, zimmerstatus = Medizin.Config.Zimmerstatus, rechte = { belegen = Rechte.Hat(mitarbeiter, 'station.belegen'), verwalten = darfVerwalten, verlegen = Rechte.Hat(mitarbeiter, 'patient.verlegen'), }, } end) -- ===================================================================== -- EINZELNES ZIMMER -- ===================================================================== Kern.RegistriereAktion('station', 'zimmer', nil, function(quelle, mitarbeiter, daten) local zimmerId = tonumber(daten.id) if not zimmerId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT z.id, z.kennung, z.betten, z.art, z.status, z.hinweis, s.id AS stationId, s.kennung AS station, s.name AS stationName, s.art AS stationArt, s.etage FROM medical_zimmer z JOIN medical_station s ON s.id = z.station WHERE z.id = ? ]], { zimmerId }) if not zeilen or not zeilen[1] then return nil, 'Zimmer nicht gefunden.' end local zimmer = zeilen[1] zimmer.betten = tonumber(zimmer.betten) or 1 -- Wer liegt gerade darin? local aktuell = MySQL.query.await([[ SELECT b.id, b.bett, b.patient, b.aufnahme, b.belegt_am, b.belegt_von, TIMESTAMPDIFF(MINUTE, b.belegt_am, NOW()) AS liegtSeit, c.firstname, c.lastname, c.dob, a.status AS aufnahmeStatus, a.prioritaet, a.grund, p.blutgruppe, p.allergien, p.hinweis AS warnhinweis FROM medical_belegung b JOIN aether_characters c ON c.id = b.patient LEFT JOIN medical_aufnahme a ON a.id = b.aufnahme LEFT JOIN medical_patient p ON p.`character` = b.patient WHERE b.zimmer = ? AND b.frei_am IS NULL ORDER BY b.bett ]], { zimmerId }) or {} -- Betten einzeln aufführen, damit auch die leeren sichtbar bleiben local nachBett = {} for _, belegung in ipairs(aktuell) do belegung.name = belegung.firstname .. ' ' .. belegung.lastname belegung.geburt = tostring(belegung.dob):sub(1, 10) belegung.belegtVon = NameVon(belegung.belegt_von) belegung.liegtSeit = tonumber(belegung.liegtSeit) or 0 belegung.firstname, belegung.lastname, belegung.dob = nil, nil, nil belegung.belegt_von = nil nachBett[tonumber(belegung.bett)] = belegung end local bettenListe = {} for nummer = 1, zimmer.betten do bettenListe[nummer] = { nummer = nummer, belegung = nachBett[nummer] } end -- Frühere Belegungen — die Spur bleibt, auch wenn das Bett längst frei ist local verlauf = MySQL.query.await([[ SELECT b.bett, b.belegt_am, b.belegt_von, b.frei_am, b.frei_von, c.firstname, c.lastname FROM medical_belegung b JOIN aether_characters c ON c.id = b.patient WHERE b.zimmer = ? AND b.frei_am IS NOT NULL ORDER BY b.frei_am DESC LIMIT 25 ]], { zimmerId }) or {} for _, eintrag in ipairs(verlauf) do eintrag.name = eintrag.firstname .. ' ' .. eintrag.lastname eintrag.belegtVon = NameVon(eintrag.belegt_von) eintrag.freiVon = NameVon(eintrag.frei_von) eintrag.firstname, eintrag.lastname = nil, nil eintrag.belegt_von, eintrag.frei_von = nil, nil end return { zimmer = zimmer, betten = bettenListe, verlauf = verlauf, zimmerarten = Medizin.Config.Zimmerarten, zimmerstatus = Medizin.Config.Zimmerstatus, rechte = { belegen = Rechte.Hat(mitarbeiter, 'station.belegen'), verwalten = Rechte.Hat(mitarbeiter, 'station.verwalten'), }, } end) -- ===================================================================== -- BETT ZUWEISEN -- -- Nur für aufgenommene Patienten, die in keinem Bett liegen. Wer -- bereits eines hat, wird über die Patientenverwaltung verlegt — -- damit eine Verlegung auch als Verlegung dokumentiert ist. -- ===================================================================== Kern.RegistriereAktion('station', 'bettZuweisen', 'station.belegen', function(quelle, mitarbeiter, daten) local aufnahmeId = tonumber(daten.aufnahme) local zimmerId = tonumber(daten.zimmer) if not aufnahmeId then return nil, 'Kein Patient gewählt.' end if not zimmerId then return nil, 'Kein Zimmer gewählt.' end local aufnahmen = 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 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 bekommen ein Bett.' end -- Doppelbelegung ausschliessen local schonBelegt = MySQL.scalar.await( 'SELECT id FROM medical_belegung WHERE aufnahme = ? AND frei_am IS NULL', { aufnahmeId }) if schonBelegt then return nil, 'Der Patient liegt bereits in einem Bett. Eine Verlegung nimmt die Patientenverwaltung vor.' end local zimmerZeilen = MySQL.query.await([[ SELECT z.id, z.kennung, z.betten, z.art, z.status, s.kennung AS station, s.name AS stationName, s.aktiv AS stationAktiv FROM medical_zimmer z JOIN medical_station s ON s.id = z.station WHERE z.id = ? ]], { zimmerId }) if not zimmerZeilen or not zimmerZeilen[1] then return nil, 'Unbekanntes Zimmer.' end local zimmer = zimmerZeilen[1] if tonumber(zimmer.stationAktiv) ~= 1 then return nil, 'Diese Station ist abgeschaltet.' end if zimmer.status ~= 'frei' then return nil, ('Zimmer %s steht nicht zur Verfügung (%s).'):format(zimmer.kennung, zimmer.status) end -- Ein Bett auf einer anderen Station ist eine Verlegung und braucht das Recht dazu local wechseltStation = aufnahme.station ~= nil and aufnahme.station ~= zimmer.station if wechseltStation and not Rechte.Hat(mitarbeiter, 'patient.verlegen') then return nil, 'Dieses Bett liegt auf einer anderen Station — dafür fehlt dir das Recht zur Verlegung.' end -- Bettwunsch prüfen, sonst das erste freie Bett nehmen local bettNummer = tonumber(daten.bett) local betten = tonumber(zimmer.betten) or 1 if bettNummer then if bettNummer < 1 or bettNummer > betten then return nil, ('Zimmer %s hat kein Bett %d.'):format(zimmer.kennung, bettNummer) end if BelegteBettNummern(zimmerId)[bettNummer] then return nil, ('Bett %d ist belegt.'):format(bettNummer) end else bettNummer = FreiesBett(zimmerId, betten) if not bettNummer then return nil, ('In Zimmer %s ist kein Bett mehr frei.'):format(zimmer.kennung) end end MySQL.insert.await([[ INSERT INTO medical_belegung (zimmer, bett, patient, aufnahme, belegt_von) VALUES (?, ?, ?, ?, ?) ]], { zimmerId, bettNummer, aufnahme.patient, aufnahmeId, mitarbeiter.charakterId }) -- Die Aufnahme führt mit, wo der Patient liegt MySQL.update.await('UPDATE medical_aufnahme SET station = ?, zimmer = ? WHERE id = ?', { zimmer.station, zimmerId, aufnahmeId }) Protokoll.Schreibe(mitarbeiter, 'station', 'Bett zugewiesen', { typ = 'zimmer', id = zimmerId }, { aufnahme = aufnahmeId, patient = aufnahme.patient, zimmer = zimmer.kennung, bett = bettNummer, vonStation = aufnahme.station, nachStation = zimmer.station }, Text(daten.vermerk, 255)) -- Ein Stationswechsel wird zusätzlich gemeldet, damit ihn niemand übersieht if wechseltStation then Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'normal', titel = 'Patient auf andere Station gelegt', text = ('%s %s liegt jetzt auf %s, Zimmer %s.') :format(aufnahme.firstname, aufnahme.lastname, zimmer.stationName, zimmer.kennung), zielTyp = 'aufnahme', zielId = aufnahmeId, }) end Kern.Loese('belegung.zugewiesen', aufnahmeId, zimmerId, bettNummer, mitarbeiter) return { ok = true, zimmer = zimmer.kennung, bett = bettNummer } end) -- ===================================================================== -- BETT FREIGEBEN -- -- Gibt nur das Bett frei — der Patient bleibt aufgenommen. Entlassen -- wird ausschliesslich über die Patientenverwaltung. -- ===================================================================== Kern.RegistriereAktion('station', 'bettFreigeben', 'station.belegen', function(quelle, mitarbeiter, daten) local belegungId = tonumber(daten.id) if not belegungId then return nil, 'Ungültige Kennung.' end local grund = Text(daten.grund, 255) if not grund or #grund < 3 then return nil, 'Bitte angeben, warum das Bett frei wird.' end local zeilen = MySQL.query.await([[ SELECT b.id, b.zimmer, b.bett, b.patient, b.aufnahme, b.frei_am, z.kennung AS zimmerKennung, c.firstname, c.lastname FROM medical_belegung b JOIN medical_zimmer z ON z.id = b.zimmer JOIN aether_characters c ON c.id = b.patient WHERE b.id = ? ]], { belegungId }) if not zeilen or not zeilen[1] then return nil, 'Belegung nicht gefunden.' end local belegung = zeilen[1] if belegung.frei_am then return nil, 'Dieses Bett ist bereits frei.' end MySQL.update.await( 'UPDATE medical_belegung SET frei_am = NOW(), frei_von = ? WHERE id = ?', { mitarbeiter.charakterId, belegungId }) -- Die Aufnahme verliert ihr Zimmer, bleibt aber auf ihrer Station if belegung.aufnahme then MySQL.update.await( 'UPDATE medical_aufnahme SET zimmer = NULL WHERE id = ? AND zimmer = ?', { belegung.aufnahme, belegung.zimmer }) end Protokoll.Schreibe(mitarbeiter, 'station', 'Bett freigegeben', { typ = 'zimmer', id = belegung.zimmer }, { belegung = belegungId, patient = belegung.patient, zimmer = belegung.zimmerKennung, bett = belegung.bett, aufnahme = belegung.aufnahme }, grund) -- Der Patient ist weiterhin im Haus — das darf nicht untergehen if belegung.aufnahme then Hinweise.Erstelle(nil, { kategorie = 'patient', prioritaet = 'normal', titel = 'Patient ohne Bett', text = ('%s %s ist weiterhin aufgenommen, liegt aber in keinem Zimmer mehr.') :format(belegung.firstname, belegung.lastname), zielTyp = 'aufnahme', zielId = belegung.aufnahme, }) end MeldeWennLeer(belegung.zimmer, belegung.zimmerKennung) Kern.Loese('belegung.freigegeben', belegungId, belegung.zimmer, mitarbeiter) return { ok = true } end) -- ===================================================================== -- ZIMMERSTATUS -- -- Reinigung und Sperrung setzt immer ein Mensch. Ein leer gewordenes -- Zimmer wird nicht von selbst wieder als frei gemeldet. -- ===================================================================== Kern.RegistriereAktion('station', '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 if not ZIMMERSTATUS[status] then return nil, 'Unbekannter Zimmerstatus.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, status, hinweis FROM medical_zimmer WHERE id = ?', { zimmerId }) if not zeilen or not zeilen[1] then return nil, 'Zimmer nicht gefunden.' end local zimmer = zeilen[1] -- 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 -- Ein leerer Hinweis löscht den alten; ein fehlendes Feld lässt ihn stehen local hinweis = zimmer.hinweis if daten.hinweis ~= nil then hinweis = Text(daten.hinweis, 255) end MySQL.update.await('UPDATE medical_zimmer SET status = ?, hinweis = ? WHERE id = ?', { status, hinweis, zimmerId }) Protokoll.Schreibe(mitarbeiter, 'station', 'Zimmerstatus gesetzt', { typ = 'zimmer', id = zimmerId }, { kennung = zimmer.kennung, von = zimmer.status, nach = status }, hinweis) Kern.Loese('zimmer.status', zimmerId, status, mitarbeiter) return { ok = true } end) -- ===================================================================== -- STATIONSVERWALTUNG -- -- Stammdatenpflege. Eine Station wird abgeschaltet, nie gelöscht — -- sonst verlieren alte Aufnahmen ihren Ort. -- ===================================================================== Kern.RegistriereAktion('station', 'stationSpeichern', 'station.verwalten', function(quelle, mitarbeiter, daten) local stationId = tonumber(daten.id) local kennung = Text(daten.kennung, 24) local name = Text(daten.name, 120) local art = AusListe(Medizin.Config.Stationsarten, daten.art) if not kennung then return nil, 'Bitte eine Kennung angeben.' end if not name then return nil, 'Bitte einen Namen angeben.' end if not art then return nil, 'Unbekannte Stationsart.' end kennung = kennung:upper() -- Kennungen müssen eindeutig bleiben, weil Aufnahmen darauf verweisen local kollision = MySQL.scalar.await( 'SELECT id FROM medical_station WHERE kennung = ? AND id <> ?', { kennung, stationId or 0 }) if kollision then return nil, ('Die Kennung %s ist bereits vergeben.'):format(kennung) end local etage = Text(daten.etage, 24) local sortierung = math.floor(tonumber(daten.sortierung) or 0) if stationId then local vorher = MySQL.query.await( 'SELECT kennung FROM medical_station WHERE id = ?', { stationId }) if not vorher or not vorher[1] then return nil, 'Station nicht gefunden.' end MySQL.update.await([[ UPDATE medical_station SET kennung = ?, name = ?, art = ?, etage = ?, sortierung = ? WHERE id = ? ]], { kennung, name, art, etage, sortierung, stationId }) -- Aufnahmen verweisen über die Kennung — die muss mitwandern if vorher[1].kennung ~= kennung then MySQL.update.await('UPDATE medical_aufnahme SET station = ? WHERE station = ?', { kennung, vorher[1].kennung }) end Protokoll.Schreibe(mitarbeiter, 'station', 'Station bearbeitet', { typ = 'station', id = stationId }, { kennung = kennung, name = name, art = art, etage = etage }) else stationId = MySQL.insert.await([[ INSERT INTO medical_station (kennung, name, art, etage, sortierung) VALUES (?, ?, ?, ?, ?) ]], { kennung, name, art, etage, sortierung }) Protokoll.Schreibe(mitarbeiter, 'station', 'Station angelegt', { typ = 'station', id = stationId }, { kennung = kennung, name = name, art = art, etage = etage }) end Kern.Loese('station.geaendert', stationId, mitarbeiter) return { id = stationId } end) Kern.RegistriereAktion('station', 'stationAktiv', 'station.verwalten', function(quelle, mitarbeiter, daten) local stationId = tonumber(daten.id) if not stationId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, name, aktiv FROM medical_station WHERE id = ?', { stationId }) if not zeilen or not zeilen[1] then return nil, 'Station nicht gefunden.' end local aktiv = daten.aktiv == true -- Eine Station mit Patienten darin wird nicht abgeschaltet if not aktiv then local 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 ]], { stationId })) or 0 if belegt > 0 then return nil, ('Auf dieser Station liegen noch %d Patient(en).'):format(belegt) end end MySQL.update.await('UPDATE medical_station SET aktiv = ? WHERE id = ?', { aktiv and 1 or 0, stationId }) Protokoll.Schreibe(mitarbeiter, 'station', aktiv and 'Station eingeschaltet' or 'Station abgeschaltet', { typ = 'station', id = stationId }, { kennung = zeilen[1].kennung }, Text(daten.grund, 255)) Kern.Loese('station.geaendert', stationId, mitarbeiter) return { ok = true } end) -- ===================================================================== -- ZIMMERVERWALTUNG -- ===================================================================== Kern.RegistriereAktion('station', 'zimmerSpeichern', 'station.verwalten', function(quelle, mitarbeiter, daten) local zimmerId = tonumber(daten.id) local stationId = tonumber(daten.station) local kennung = Text(daten.kennung, 24) local art = AusListe(Medizin.Config.Zimmerarten, daten.art) local betten = math.floor(tonumber(daten.betten) or 0) if not kennung then return nil, 'Bitte eine Kennung angeben.' end if not art then return nil, 'Unbekannte Zimmerart.' end if betten < 1 or betten > 12 then return nil, 'Ein Zimmer hat 1 bis 12 Betten.' end kennung = kennung:upper() if zimmerId then local vorher = MySQL.query.await( 'SELECT id, station, betten FROM medical_zimmer WHERE id = ?', { zimmerId }) if not vorher or not vorher[1] then return nil, 'Zimmer nicht gefunden.' end -- Die Station eines bestehenden Zimmers bleibt, wie sie ist stationId = tonumber(vorher[1].station) -- Betten lassen sich nicht wegkürzen, solange jemand darin liegt local genommen = BelegteBettNummern(zimmerId) for nummer in pairs(genommen) do if nummer > betten then return nil, ('Bett %d ist belegt — die Bettenzahl lässt sich nicht verringern.') :format(nummer) end end end if not stationId then return nil, 'Keine Station gewählt.' end local station = MySQL.query.await( 'SELECT id, kennung, name FROM medical_station WHERE id = ?', { stationId }) if not station or not station[1] then return nil, 'Unbekannte Station.' end -- Innerhalb einer Station soll jede Zimmerkennung nur einmal vorkommen local kollision = MySQL.scalar.await( 'SELECT id FROM medical_zimmer WHERE station = ? AND kennung = ? AND id <> ?', { stationId, kennung, zimmerId or 0 }) if kollision then return nil, ('Zimmer %s gibt es auf dieser Station bereits.'):format(kennung) end local hinweis = Text(daten.hinweis, 255) if zimmerId then MySQL.update.await([[ UPDATE medical_zimmer SET kennung = ?, betten = ?, art = ?, hinweis = ? WHERE id = ? ]], { kennung, betten, art, hinweis, zimmerId }) Protokoll.Schreibe(mitarbeiter, 'station', 'Zimmer bearbeitet', { typ = 'zimmer', id = zimmerId }, { kennung = kennung, betten = betten, art = art, station = station[1].kennung }) else -- Neue Zimmer starten gesperrt: erst freigeben, dann belegen zimmerId = MySQL.insert.await([[ INSERT INTO medical_zimmer (station, kennung, betten, art, status, hinweis) VALUES (?, ?, ?, ?, 'gesperrt', ?) ]], { stationId, kennung, betten, art, hinweis }) Protokoll.Schreibe(mitarbeiter, 'station', 'Zimmer angelegt', { typ = 'zimmer', id = zimmerId }, { kennung = kennung, betten = betten, art = art, station = station[1].kennung }) end Kern.Loese('zimmer.geaendert', zimmerId, mitarbeiter) return { id = zimmerId } end) --- Entfernt ein Zimmer, das nie benutzt wurde. --- Sobald jemand darin gelegen hat, bleibt es bestehen — sonst verlöre --- der Belegungsverlauf seinen Ort. Dann hilft nur noch "gesperrt". Kern.RegistriereAktion('station', 'zimmerEntfernen', 'station.verwalten', function(quelle, mitarbeiter, daten) local zimmerId = tonumber(daten.id) if not zimmerId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, station FROM medical_zimmer WHERE id = ?', { zimmerId }) if not zeilen or not zeilen[1] then return nil, 'Zimmer nicht gefunden.' end local benutzt = tonumber(MySQL.scalar.await( 'SELECT COUNT(*) FROM medical_belegung WHERE zimmer = ?', { zimmerId })) or 0 if benutzt > 0 then return nil, 'In diesem Zimmer lagen bereits Patienten — es lässt sich nur noch sperren.' end -- Auch ein nie belegtes Zimmer kann in einer laufenden OP-Planung stehen local imOpPlan = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_operation WHERE saal = ? AND status IN ('geplant', 'laufend') ]], { zimmerId })) or 0 if imOpPlan > 0 then return nil, 'Für diesen Saal sind noch Operationen geplant.' end MySQL.query.await('DELETE FROM medical_zimmer WHERE id = ?', { zimmerId }) Protokoll.Schreibe(mitarbeiter, 'station', 'Zimmer entfernt', { typ = 'zimmer', id = zimmerId }, { kennung = zeilen[1].kennung }, Text(daten.grund, 255)) Kern.Loese('zimmer.geaendert', zimmerId, mitarbeiter) return { ok = true } end) -- --------------------------------------------------------------------- -- Schnittstelle für andere Ressourcen -- --------------------------------------------------------------------- --- Liefert den Bettenspiegel als flache Auskunft. --- Reine Anzeige — keine Ressource kann darüber ein Bett belegen. --- --- `betten` ist die gesamte Bettenzahl, `frei` zählt nur, was auch --- belegt werden dürfte: gesperrte Zimmer und Zimmer in Reinigung --- bleiben aussen vor. --- @return table Liste { station, name, betten, belegt, frei } exports('HoleBettenspiegel', function() local stationen = MySQL.query.await([[ SELECT id, kennung AS station, name FROM medical_station WHERE aktiv = 1 ORDER BY sortierung, kennung ]]) or {} for _, eintrag in ipairs(stationen) do local zimmerListe = MySQL.query.await( 'SELECT id, betten, status FROM medical_zimmer WHERE station = ?', { eintrag.id }) or {} eintrag.betten, eintrag.belegt, eintrag.frei = 0, 0, 0 for _, zimmer in ipairs(zimmerListe) do local betten = tonumber(zimmer.betten) or 1 local belegt = BelegteBetten(zimmer.id) eintrag.betten = eintrag.betten + betten eintrag.belegt = eintrag.belegt + belegt if zimmer.status == 'frei' then eintrag.frei = eintrag.frei + math.max(0, betten - belegt) end end eintrag.id = nil end return stationen end)