-- ===================================================================== -- aether-medical / Modul: Operationsverwaltung -- -- Operationen werden vollständig manuell dokumentiert. -- -- Das System führt keine Operation durch, startet keine, beendet keine -- und entscheidet nie über einen Ausgang. Der geplante Zeitpunkt ist -- eine Erinnerung — er beginnt nichts. Beginn und Ende trägt das -- OP-Team ein, wenn es soweit ist. -- -- Überschneidungen im OP-Saal werden GEMELDET, nicht verhindert. -- Ob zwei Eingriffe im selben Saal ein Problem sind, weiss nur ein -- Mensch — vielleicht dauert der erste kürzer als geplant. -- ===================================================================== Kern.RegistriereModul({ id = 'operation', name = 'Operationen', icon = '🔪', reihenfolge = 7, recht = { 'operation.planen', 'operation.dokumentieren' }, beschreibung = 'OP-Planung, OP-Team und Operationsbericht', }) -- --------------------------------------------------------------------- -- 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 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 Funktion im OP-Team --- @param kennung any Eingabe --- @return string local function TeamFunktion(kennung) for _, eintrag in ipairs(Medizin.Config.OpFunktionen) do if eintrag.id == kennung then return eintrag.id end end return 'assistenz' end --- Lädt das OP-Team einer Operation --- @param operationId number Operation --- @return table local function LadeTeam(operationId) local team = MySQL.query.await([[ SELECT t.`character`, t.funktion, c.firstname, c.lastname, p.personalnummer, p.rolle FROM medical_operation_team t LEFT JOIN aether_characters c ON c.id = t.`character` LEFT JOIN medical_personal p ON p.`character` = t.`character` WHERE t.operation = ? ]], { operationId }) or {} local funktionsNamen = {} for _, eintrag in ipairs(Medizin.Config.OpFunktionen) do funktionsNamen[eintrag.id] = eintrag.name end for _, mitglied in ipairs(team) do mitglied.name = (mitglied.firstname or 'Unbekannt') .. ' ' .. (mitglied.lastname or '') local rolle = Medizin.HoleRolle(mitglied.rolle) mitglied.rolleName = rolle and rolle.name or mitglied.rolle mitglied.funktionName = funktionsNamen[mitglied.funktion] or mitglied.funktion mitglied.firstname, mitglied.lastname = nil, nil end -- Operateur zuerst, dann der Rest table.sort(team, function(a, b) if a.funktion == b.funktion then return (a.name or '') < (b.name or '') end return a.funktion == 'operateur' end) return team end --- Sucht Operationen, die sich im selben Saal zeitlich überschneiden. --- Reine Auskunft — das Ergebnis verhindert nichts. --- @param saalId number Zimmer --- @param geplantFuer string|nil Zeitpunkt --- @param ohneId number|nil Eigene Operation ausklammern --- @return table local function Ueberschneidungen(saalId, geplantFuer, ohneId) if not saalId then return {} end local treffer = MySQL.query.await([[ SELECT o.id, o.kennung, o.art, o.status, o.geplant_fuer, o.beginn, c.firstname, c.lastname FROM medical_operation o JOIN aether_characters c ON c.id = o.patient WHERE o.saal = ? AND o.status IN ('geplant', 'laufend') AND (? IS NULL OR o.id <> ?) AND ( o.status = 'laufend' OR (? IS NOT NULL AND o.geplant_fuer IS NOT NULL AND ABS(TIMESTAMPDIFF(MINUTE, o.geplant_fuer, ?)) < 120) ) ORDER BY o.geplant_fuer ]], { saalId, ohneId, ohneId, geplantFuer, geplantFuer }) or {} for _, eintrag in ipairs(treffer) do eintrag.patientName = (eintrag.firstname or '') .. ' ' .. (eintrag.lastname or '') eintrag.firstname, eintrag.lastname = nil, nil end return treffer end -- ===================================================================== -- ÜBERSICHT UND OP-PLAN -- ===================================================================== Kern.RegistriereAktion('operation', 'uebersicht', nil, function(quelle, mitarbeiter, daten) local ordner = tostring(daten.ordner or 'plan') local bedingung if ordner == 'archiv' then bedingung = "o.status IN ('abgeschlossen', 'abgesagt')" elseif ordner == 'laufend' then bedingung = "o.status = 'laufend'" else bedingung = "o.status IN ('geplant', 'laufend')" end local liste = MySQL.query.await(([[ SELECT o.id, o.kennung, o.patient, o.art, o.saal, o.status, o.geplant_fuer, o.beginn, o.ende, o.erstellt_von, o.komplikationen, TIMESTAMPDIFF(MINUTE, NOW(), o.geplant_fuer) AS restMinuten, TIMESTAMPDIFF(MINUTE, o.beginn, COALESCE(o.ende, NOW())) AS dauerMinuten, c.firstname, c.lastname, c.dob, m.blutgruppe, m.allergien, m.hinweis, z.kennung AS saalName, (SELECT COUNT(*) FROM medical_operation_team t WHERE t.operation = o.id) AS teamGroesse, (SELECT COUNT(*) FROM medical_operation_team t WHERE t.operation = o.id AND t.`character` = ?) AS ichDabei FROM medical_operation o JOIN aether_characters c ON c.id = o.patient LEFT JOIN medical_patient m ON m.`character` = o.patient LEFT JOIN medical_zimmer z ON z.id = o.saal WHERE %s ORDER BY CASE o.status WHEN 'laufend' THEN 0 ELSE 1 END, o.geplant_fuer IS NULL, o.geplant_fuer, o.id DESC LIMIT 120 ]]):format(bedingung), { mitarbeiter.charakterId }) or {} for _, eintrag in ipairs(liste) do eintrag.patientName = eintrag.firstname .. ' ' .. eintrag.lastname eintrag.geburt = tostring(eintrag.dob):sub(1, 10) eintrag.erstelltVon = NameVon(eintrag.erstellt_von) eintrag.ichDabei = tonumber(eintrag.ichDabei) > 0 eintrag.teamGroesse = tonumber(eintrag.teamGroesse) or 0 eintrag.hatKomplikationen = eintrag.komplikationen ~= nil eintrag.firstname, eintrag.lastname, eintrag.dob = nil, nil, nil eintrag.erstellt_von, eintrag.komplikationen = nil, nil end -- OP-Säle mit ihrer aktuellen Lage local saele = MySQL.query.await([[ SELECT z.id, z.kennung, z.status, z.hinweis, s.name AS stationName FROM medical_zimmer z JOIN medical_station s ON s.id = z.station WHERE z.art = 'op' ORDER BY z.kennung ]]) or {} for _, saal in ipairs(saele) do local laufend = MySQL.query.await([[ SELECT o.id, o.kennung, o.art, o.beginn, TIMESTAMPDIFF(MINUTE, o.beginn, NOW()) AS dauer, c.firstname, c.lastname FROM medical_operation o JOIN aether_characters c ON c.id = o.patient WHERE o.saal = ? AND o.status = 'laufend' ORDER BY o.beginn LIMIT 1 ]], { saal.id }) if laufend and laufend[1] then saal.laufend = { id = laufend[1].id, kennung = laufend[1].kennung, art = laufend[1].art, dauer = tonumber(laufend[1].dauer) or 0, patientName = laufend[1].firstname .. ' ' .. laufend[1].lastname, } end saal.geplant = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_operation WHERE saal = ? AND status = 'geplant' ]], { saal.id })) or 0 end return { ordner = ordner, liste = liste, saele = saele, funktionen = Medizin.Config.OpFunktionen, zaehler = { geplant = tonumber(MySQL.scalar.await( "SELECT COUNT(*) FROM medical_operation WHERE status = 'geplant'")) or 0, laufend = tonumber(MySQL.scalar.await( "SELECT COUNT(*) FROM medical_operation WHERE status = 'laufend'")) or 0, faellig = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_operation WHERE status = 'geplant' AND geplant_fuer IS NOT NULL AND geplant_fuer <= NOW() ]])) or 0, heute = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_operation WHERE status = 'abgeschlossen' AND DATE(ende) = CURDATE() ]])) or 0, }, rechte = { planen = Rechte.Hat(mitarbeiter, 'operation.planen'), dokumentieren = Rechte.Hat(mitarbeiter, 'operation.dokumentieren'), }, } end) Kern.RegistriereAktion('operation', 'oeffnen', nil, function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT o.*, c.firstname, c.lastname, c.dob, m.blutgruppe, m.allergien, m.vorerkrankungen, m.dauermedikation, m.implantate, m.hinweis, m.lichtbild, z.kennung AS saalName, a.grund AS aufnahmeGrund, a.status AS aufnahmeStatus FROM medical_operation o JOIN aether_characters c ON c.id = o.patient LEFT JOIN medical_patient m ON m.`character` = o.patient LEFT JOIN medical_zimmer z ON z.id = o.saal LEFT JOIN medical_aufnahme a ON a.id = o.aufnahme WHERE o.id = ? ]], { operationId }) if not zeilen or not zeilen[1] then return nil, 'Operation nicht gefunden.' end local o = zeilen[1] local team = LadeTeam(operationId) local ichDabei = false for _, mitglied in ipairs(team) do if mitglied.character == mitarbeiter.charakterId then ichDabei = true end end -- Verfügbare OP-Säle für eine Umplanung local saele = MySQL.query.await([[ SELECT z.id, z.kennung, z.status FROM medical_zimmer z WHERE z.art = 'op' ORDER BY z.kennung ]]) or {} return { operation = { id = o.id, kennung = o.kennung, patient = o.patient, patientName = o.firstname .. ' ' .. o.lastname, geburt = tostring(o.dob):sub(1, 10), art = o.art, saal = o.saal, saalName = o.saalName, aufnahme = o.aufnahme, aufnahmeGrund = o.aufnahmeGrund, geplantFuer = o.geplant_fuer, beginn = o.beginn, ende = o.ende, verlauf = o.verlauf, komplikationen = o.komplikationen, nachsorge = o.nachsorge, status = o.status, erstelltAm = o.erstellt_am, erstelltVon = NameVon(o.erstellt_von), }, stamm = { blutgruppe = o.blutgruppe, allergien = o.allergien, vorerkrankungen = o.vorerkrankungen, dauermedikation = o.dauermedikation, implantate = o.implantate, hinweis = o.hinweis, lichtbild = o.lichtbild, }, team = team, ichDabei = ichDabei, saele = saele, funktionen = Medizin.Config.OpFunktionen, -- Zeitgleiche Eingriffe im selben Saal — als Hinweis, nicht als Sperre ueberschneidungen = Ueberschneidungen(o.saal, o.geplant_fuer, o.id), rechte = { planen = Rechte.Hat(mitarbeiter, 'operation.planen'), dokumentieren = Rechte.Hat(mitarbeiter, 'operation.dokumentieren'), }, } end) -- ===================================================================== -- PLANEN -- ===================================================================== Kern.RegistriereAktion('operation', 'patientSuche', 'operation.planen', function(quelle, mitarbeiter, daten) local begriff = type(daten.begriff) == 'string' and daten.begriff or '' if #begriff < 2 then return { treffer = {}, saele = {} } 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 = 'aufgenommen' ORDER BY a.id DESC LIMIT 1) AS aufnahme 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 local saele = MySQL.query.await([[ SELECT z.id, z.kennung, z.status, (SELECT COUNT(*) FROM medical_operation o WHERE o.saal = z.id AND o.status = 'laufend') AS belegt FROM medical_zimmer z WHERE z.art = 'op' ORDER BY z.kennung ]]) or {} -- Kollegen im Dienst als mögliche Teammitglieder local kollegen = {} for _, eintrag in pairs(Dienst.AlleImDienst()) do kollegen[#kollegen + 1] = { charakterId = eintrag.charakterId, name = eintrag.name, personalnummer = eintrag.personalnummer, rolleName = eintrag.rolleName, } end table.sort(kollegen, function(a, b) return a.personalnummer < b.personalnummer end) return { treffer = treffer, saele = saele, kollegen = kollegen } end) --- Kollegen im Dienst, die sich ins OP-Team einteilen lassen Kern.RegistriereAktion('operation', 'kollegen', 'operation.planen', function(quelle, mitarbeiter) local kollegen = {} for _, eintrag in pairs(Dienst.AlleImDienst()) do kollegen[#kollegen + 1] = { charakterId = eintrag.charakterId, name = eintrag.name, personalnummer = eintrag.personalnummer, rolleName = eintrag.rolleName, funktion = eintrag.funktion, } end table.sort(kollegen, function(a, b) return a.personalnummer < b.personalnummer end) return { kollegen = kollegen, funktionen = Medizin.Config.OpFunktionen } end) Kern.RegistriereAktion('operation', 'planen', 'operation.planen', 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 local art = Text(daten.art, 160) if not art or #art < 3 then return nil, 'Bitte die Art des Eingriffs angeben.' end -- OP-Saal ist freiwillig, muss aber existieren local saalId = nil if daten.saal and tonumber(daten.saal) then local saal = MySQL.query.await( "SELECT id, kennung, status FROM medical_zimmer WHERE id = ? AND art = 'op'", { tonumber(daten.saal) }) if not saal or not saal[1] then return nil, 'Unbekannter OP-Saal.' end if saal[1].status == 'gesperrt' then return nil, ('OP-Saal %s ist gesperrt.'):format(saal[1].kennung) end saalId = saal[1].id end -- Zeitpunkt in Minuten ab jetzt. 0 heisst: keine Zeitplanung. local vorlauf = math.floor(tonumber(daten.vorlaufMinuten) or 0) if vorlauf < 0 or vorlauf > 20160 then return nil, 'Der Vorlauf muss zwischen 0 Minuten und 14 Tagen liegen.' end 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 aufnahmeId = nil end end local kennung = Kern.ErzeugeKennung('OP', 'medical_operation', 'kennung') local id = MySQL.insert.await([[ INSERT INTO medical_operation (kennung, patient, aufnahme, art, saal, geplant_fuer, status, erstellt_von) VALUES (?, ?, ?, ?, ?, CASE WHEN ? > 0 THEN DATE_ADD(NOW(), INTERVAL ? MINUTE) ELSE NULL END, 'geplant', ?) ]], { kennung, patientId, aufnahmeId, art, saalId, vorlauf, vorlauf, mitarbeiter.charakterId }) -- Der Planende gehört als Operateur ins Team, sofern nicht anders gewünscht if daten.selbstOperateur ~= false then MySQL.query.await([[ INSERT INTO medical_operation_team (operation, `character`, funktion) VALUES (?, ?, 'operateur') ON DUPLICATE KEY UPDATE funktion = VALUES(funktion) ]], { id, mitarbeiter.charakterId }) end -- Weitere Teammitglieder aus der Planung if type(daten.team) == 'table' then for _, mitglied in ipairs(daten.team) do local charakterId = tonumber(mitglied.charakterId) if charakterId and charakterId ~= mitarbeiter.charakterId then MySQL.query.await([[ INSERT INTO medical_operation_team (operation, `character`, funktion) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE funktion = VALUES(funktion) ]], { id, charakterId, TeamFunktion(mitglied.funktion) }) end end end Protokoll.Schreibe(mitarbeiter, 'operation', 'Operation geplant', { typ = 'operation', id = id }, { kennung = kennung, patient = patientId, art = art, saal = saalId, vorlaufMinuten = vorlauf }) Hinweise.Erstelle(nil, { kategorie = 'op', prioritaet = 'normal', titel = ('Operation geplant · %s'):format(kennung), text = ('%s %s — %s%s'):format(person[1].firstname, person[1].lastname, art, vorlauf > 0 and (' (in %d Minuten)'):format(vorlauf) or ' (ohne Zeitangabe)'), zielTyp = 'operation', zielId = id, }) Kern.Loese('operation.geplant', id, mitarbeiter) return { id = id, kennung = kennung, ueberschneidungen = saalId and #Ueberschneidungen(saalId, nil, id) or 0, } end) --- Zeitpunkt oder Saal einer geplanten Operation ändern Kern.RegistriereAktion('operation', 'umplanen', 'operation.planen', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, status, saal, geplant_fuer FROM medical_operation WHERE id = ?', { operationId }) if not zeilen or not zeilen[1] then return nil, 'Operation nicht gefunden.' end local operation = zeilen[1] if operation.status ~= 'geplant' then return nil, 'Nur geplante Operationen lassen sich umplanen.' end local grund = Text(daten.grund, 255) if not grund then return nil, 'Bitte begründen, warum umgeplant wird.' end local saalId = nil if daten.saal and tonumber(daten.saal) then local saal = MySQL.query.await( "SELECT id, kennung, status FROM medical_zimmer WHERE id = ? AND art = 'op'", { tonumber(daten.saal) }) if not saal or not saal[1] then return nil, 'Unbekannter OP-Saal.' end if saal[1].status == 'gesperrt' then return nil, ('OP-Saal %s ist gesperrt.'):format(saal[1].kennung) end saalId = saal[1].id end local vorlauf = math.floor(tonumber(daten.vorlaufMinuten) or 0) if vorlauf < 0 or vorlauf > 20160 then return nil, 'Der Vorlauf muss zwischen 0 Minuten und 14 Tagen liegen.' end MySQL.update.await([[ UPDATE medical_operation SET saal = ?, geplant_fuer = CASE WHEN ? > 0 THEN DATE_ADD(NOW(), INTERVAL ? MINUTE) ELSE NULL END WHERE id = ? ]], { saalId, vorlauf, vorlauf, operationId }) Protokoll.Schreibe(mitarbeiter, 'operation', 'Operation umgeplant', { typ = 'operation', id = operationId }, { kennung = operation.kennung, saal = saalId, vorlaufMinuten = vorlauf }, grund) Kern.Loese('operation.umgeplant', operationId, mitarbeiter) return { ok = true } end) Kern.RegistriereAktion('operation', 'absagen', 'operation.planen', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local grund = Text(daten.grund, 512) if not grund or #grund < 5 then return nil, 'Eine Absage erfordert eine Begründung.' end local zeilen = MySQL.query.await([[ SELECT o.id, o.kennung, o.status, o.art, c.firstname, c.lastname FROM medical_operation o JOIN aether_characters c ON c.id = o.patient WHERE o.id = ? ]], { operationId }) if not zeilen or not zeilen[1] then return nil, 'Operation nicht gefunden.' end local operation = zeilen[1] if operation.status == 'abgeschlossen' then return nil, 'Eine abgeschlossene Operation lässt sich nicht absagen.' end if operation.status == 'abgesagt' then return nil, 'Diese Operation ist bereits abgesagt.' end MySQL.update.await([[ UPDATE medical_operation SET status = 'abgesagt', verlauf = CONCAT(COALESCE(verlauf, ''), ?) WHERE id = ? ]], { '\n[Abgesagt] ' .. grund, operationId }) Protokoll.Schreibe(mitarbeiter, 'operation', 'Operation abgesagt', { typ = 'operation', id = operationId }, { kennung = operation.kennung, warLaufend = operation.status == 'laufend' }, grund) Hinweise.Erstelle(nil, { kategorie = 'op', prioritaet = 'normal', titel = ('Operation abgesagt · %s'):format(operation.kennung), text = ('%s %s — %s'):format(operation.firstname, operation.lastname, grund), zielTyp = 'operation', zielId = operationId, }) Kern.Loese('operation.abgeschlossen', operationId, mitarbeiter) return { ok = true } end) -- ===================================================================== -- OP-TEAM -- ===================================================================== Kern.RegistriereAktion('operation', 'teamSetzen', 'operation.planen', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) local charakterId = tonumber(daten.charakterId) if not operationId or not charakterId then return nil, 'Unvollständige Angaben.' end local operation = MySQL.query.await( 'SELECT id, kennung, status FROM medical_operation WHERE id = ?', { operationId }) if not operation or not operation[1] then return nil, 'Operation nicht gefunden.' end if operation[1].status == 'abgeschlossen' or operation[1].status == 'abgesagt' then return nil, 'Das Team einer abgeschlossenen Operation lässt sich nicht ändern.' end local personal = MySQL.scalar.await( 'SELECT `character` FROM medical_personal WHERE `character` = ? AND aktiv = 1', { charakterId }) if not personal then return nil, 'Diese Person gehört nicht zum Personal.' end local funktion = TeamFunktion(daten.funktion) MySQL.query.await([[ INSERT INTO medical_operation_team (operation, `character`, funktion) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE funktion = VALUES(funktion) ]], { operationId, charakterId, funktion }) Protokoll.Schreibe(mitarbeiter, 'operation', 'OP-Team geändert', { typ = 'operation', id = operationId }, { kennung = operation[1].kennung, mitglied = charakterId, funktion = funktion }) -- Das Teammitglied erfährt davon; teilnehmen muss es trotzdem selbst Hinweise.Erstelle({ charakterId = charakterId }, { kategorie = 'op', prioritaet = 'normal', titel = ('Du bist im OP-Team · %s'):format(operation[1].kennung), text = ('Eingeteilt als %s durch %s.') :format(funktion, mitarbeiter.name), zielTyp = 'operation', zielId = operationId, }) return { ok = true } end) Kern.RegistriereAktion('operation', 'teamEntfernen', 'operation.planen', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) local charakterId = tonumber(daten.charakterId) if not operationId or not charakterId then return nil, 'Unvollständige Angaben.' end MySQL.query.await( 'DELETE FROM medical_operation_team WHERE operation = ? AND `character` = ?', { operationId, charakterId }) Protokoll.Schreibe(mitarbeiter, 'operation', 'Aus OP-Team entfernt', { typ = 'operation', id = operationId }, { mitglied = charakterId }, Text(daten.grund, 255)) return { ok = true } end) --- Selbst dazustossen oder das Team verlassen Kern.RegistriereAktion('operation', 'teamWechsel', 'operation.dokumentieren', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local operation = MySQL.query.await( 'SELECT id, kennung, status FROM medical_operation WHERE id = ?', { operationId }) if not operation or not operation[1] then return nil, 'Operation nicht gefunden.' end if operation[1].status == 'abgeschlossen' or operation[1].status == 'abgesagt' then return nil, 'Diese Operation ist abgeschlossen.' end if daten.beitreten == false then MySQL.query.await( 'DELETE FROM medical_operation_team WHERE operation = ? AND `character` = ?', { operationId, mitarbeiter.charakterId }) Protokoll.Schreibe(mitarbeiter, 'operation', 'OP-Team verlassen', { typ = 'operation', id = operationId }, { kennung = operation[1].kennung }) return { ok = true, dabei = false } end MySQL.query.await([[ INSERT INTO medical_operation_team (operation, `character`, funktion) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE funktion = VALUES(funktion) ]], { operationId, mitarbeiter.charakterId, TeamFunktion(daten.funktion) }) Protokoll.Schreibe(mitarbeiter, 'operation', 'OP-Team beigetreten', { typ = 'operation', id = operationId }, { kennung = operation[1].kennung, funktion = TeamFunktion(daten.funktion) }) return { ok = true, dabei = true } end) -- ===================================================================== -- DURCHFÜHRUNG -- -- Beginn und Ende trägt das OP-Team ein. Der geplante Zeitpunkt -- startet nichts — er erinnert nur. -- ===================================================================== Kern.RegistriereAktion('operation', 'beginnen', 'operation.dokumentieren', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT o.id, o.kennung, o.status, o.art, o.saal, c.firstname, c.lastname FROM medical_operation o JOIN aether_characters c ON c.id = o.patient WHERE o.id = ? ]], { operationId }) if not zeilen or not zeilen[1] then return nil, 'Operation nicht gefunden.' end local operation = zeilen[1] if operation.status ~= 'geplant' then return nil, 'Nur eine geplante Operation lässt sich beginnen.' end MySQL.update.await([[ UPDATE medical_operation SET status = 'laufend', beginn = NOW() WHERE id = ? ]], { operationId }) -- Wer den Beginn einträgt, gehört ins Team MySQL.query.await([[ INSERT INTO medical_operation_team (operation, `character`, funktion) VALUES (?, ?, 'assistenz') ON DUPLICATE KEY UPDATE funktion = funktion ]], { operationId, mitarbeiter.charakterId }) Protokoll.Schreibe(mitarbeiter, 'operation', 'Operation begonnen', { typ = 'operation', id = operationId }, { kennung = operation.kennung }) Hinweise.Erstelle(nil, { kategorie = 'op', prioritaet = 'normal', titel = ('Operation begonnen · %s'):format(operation.kennung), text = ('%s %s — %s'):format(operation.firstname, operation.lastname, operation.art), zielTyp = 'operation', zielId = operationId, }) Kern.Loese('operation.begonnen', operationId, mitarbeiter) return { ok = true } end) --- Verlauf, Komplikationen und Nachsorge dokumentieren Kern.RegistriereAktion('operation', 'dokumentieren', 'operation.dokumentieren', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, status FROM medical_operation WHERE id = ?', { operationId }) if not zeilen or not zeilen[1] then return nil, 'Operation nicht gefunden.' end if zeilen[1].status == 'abgesagt' then return nil, 'Diese Operation wurde abgesagt.' end MySQL.update.await([[ UPDATE medical_operation SET art = COALESCE(?, art), verlauf = ?, komplikationen = ?, nachsorge = ? WHERE id = ? ]], { Text(daten.art, 160), Text(daten.verlauf, 8000), Text(daten.komplikationen, 4000), Text(daten.nachsorge, 4000), operationId, }) Protokoll.Schreibe(mitarbeiter, 'operation', 'OP-Bericht gespeichert', { typ = 'operation', id = operationId }, { kennung = zeilen[1].kennung }) return { ok = true } end) Kern.RegistriereAktion('operation', 'beenden', 'operation.dokumentieren', function(quelle, mitarbeiter, daten) local operationId = tonumber(daten.id) if not operationId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT o.id, o.kennung, o.status, o.verlauf, o.beginn, c.firstname, c.lastname FROM medical_operation o JOIN aether_characters c ON c.id = o.patient WHERE o.id = ? ]], { operationId }) if not zeilen or not zeilen[1] then return nil, 'Operation nicht gefunden.' end local operation = zeilen[1] if operation.status ~= 'laufend' then return nil, 'Nur eine laufende Operation lässt sich beenden.' end -- Ein OP-Bericht ohne Verlauf ist kein Bericht. -- Geprüft wird nur, DASS etwas dasteht — nicht, was. local verlauf = Text(daten.verlauf, 8000) or operation.verlauf if not verlauf then return nil, 'Bitte zuerst den Verlauf dokumentieren.' end MySQL.update.await([[ UPDATE medical_operation SET status = 'abgeschlossen', ende = NOW(), verlauf = ?, komplikationen = ?, nachsorge = ? WHERE id = ? ]], { verlauf, Text(daten.komplikationen, 4000), Text(daten.nachsorge, 4000), operationId, }) local team = LadeTeam(operationId) Protokoll.Schreibe(mitarbeiter, 'operation', 'Operation abgeschlossen', { typ = 'operation', id = operationId }, { kennung = operation.kennung, teamGroesse = #team, mitKomplikationen = Text(daten.komplikationen, 4000) ~= nil }) Hinweise.Erstelle(nil, { kategorie = 'op', prioritaet = 'normal', titel = ('Operation abgeschlossen · %s'):format(operation.kennung), text = ('%s %s — OP-Saal wird frei.') :format(operation.firstname, operation.lastname), zielTyp = 'operation', zielId = operationId, }) Kern.Loese('operation.abgeschlossen', operationId, mitarbeiter) return { ok = true } end) -- --------------------------------------------------------------------- -- Schnittstelle für andere Ressourcen -- --------------------------------------------------------------------- --- Liefert die Operationen eines Patienten --- @param patientId number Charakter --- @return table exports('HoleOperationen', function(patientId) if not tonumber(patientId) then return {} end return MySQL.query.await([[ SELECT kennung, art, status, geplant_fuer, beginn, ende, verlauf, komplikationen, nachsorge FROM medical_operation WHERE patient = ? ORDER BY id DESC LIMIT 50 ]], { tonumber(patientId) }) or {} end)