-- ===================================================================== -- aether-medical / Modul: Bausteine (Textvorlagen) -- -- Vorformulierte Textblöcke für den Behandlungs- und Berichtsbogen. -- -- Ein Baustein ist ein Zettel mit vorbereitetem Text — nicht mehr. -- Entscheidend ist, was er NICHT tut: -- -- * Er schreibt nichts in die Datenbank. Ein Baustein füllt nur das -- Formular; gespeichert wird danach von Hand, wie immer. -- * Er überschreibt nichts. Was jemand geschrieben hat, bleibt -- stehen — der Text wird angehängt, nicht ersetzt. -- * Er wird nie von selbst eingesetzt. Es gibt keine Vorauswahl -- nach Diagnose, Einsatzart oder sonst etwas. -- -- Damit bleibt der Wortlaut die Verantwortung dessen, der speichert. -- ===================================================================== Kern.RegistriereModul({ id = 'vorlage', name = 'Bausteine', icon = '🧩', reihenfolge = 15, recht = 'katalog.bearbeiten', beschreibung = 'Textvorlagen für Behandlungs- und Berichtsbogen', }) -- --------------------------------------------------------------------- -- Bereiche und ihre Felder -- -- Ein Bereich entspricht einem Bogen. Die Feldkennungen sind genau -- die des jeweiligen Formulars — ein Baustein kann nur füllen, was -- es dort auch gibt. -- --------------------------------------------------------------------- local BEREICHE = { { id = 'behandlung', name = 'Behandlungsbogen', felder = { { id = 'symptome', name = 'Symptome / Anamnese' }, { id = 'untersuchung', name = 'Untersuchung' }, { id = 'diagnose', name = 'Diagnose' }, { id = 'massnahmen', name = 'Maßnahmen / Behandlung' }, { id = 'empfehlung', name = 'Empfehlung' }, { id = 'nachsorge', name = 'Nachsorge' }, }}, { id = 'bericht', name = 'Berichtsbogen', felder = { { id = 'symptome', name = 'Symptome und Schilderung' }, { id = 'untersuchung', name = 'Untersuchungsbefund' }, { id = 'diagnose', name = 'Diagnose' }, { id = 'massnahmen', name = 'Maßnahmen' }, { id = 'empfehlung', name = 'Empfehlung' }, }}, } -- --------------------------------------------------------------------- -- Grundbestand -- -- Damit die Bausteine von Anfang an brauchbar sind. Jeder Text ist -- ein Gerüst mit Lücken — auszufüllen hat ihn ein Mensch. -- --------------------------------------------------------------------- local GRUNDBAUSTEINE = { { bereich = 'behandlung', name = 'Wundversorgung', sortierung = 1, inhalt = { untersuchung = 'Lokalisation:\nWundart:\nGröße:\nBlutung:\nDurchblutung, Motorik, Sensibilität distal:', massnahmen = 'Wundreinigung:\nWundverschluss:\nVerband:\nTetanusschutz:', nachsorge = 'Verbandwechsel:\nWiedervorstellung bei:\nFadenzug:', }}, { bereich = 'behandlung', name = 'Reanimation', sortierung = 2, inhalt = { symptome = 'Auffindesituation:\nZeitpunkt des Kollapses:\nErsthelfermaßnahmen:', untersuchung = 'Bewusstsein:\nAtmung:\nInitialer Rhythmus:\nPupillen:', massnahmen = 'Beginn Thoraxkompression:\nBeatmung:\nDefibrillationen:\nMedikamente:\nROSC um:', }}, { bereich = 'behandlung', name = 'Verkehrsunfall — Erstversorgung', sortierung = 3, inhalt = { symptome = 'Unfallhergang:\nAnschnallgurt:\nAirbag ausgelöst:\nEingeklemmt:', untersuchung = 'Bodycheck:\nHWS:\nThorax:\nAbdomen:\nBecken:\nExtremitäten:\nVitalwerte:', massnahmen = 'Immobilisation:\nZugang:\nSchmerztherapie:\nTransportziel:', }}, { bereich = 'behandlung', name = 'Routinekontrolle', sortierung = 4, inhalt = { symptome = 'Anlass der Vorstellung:\nBeschwerden seit letzter Kontrolle:', untersuchung = 'Allgemeinzustand:\nVitalwerte:\nBefund:', empfehlung = 'Weiteres Vorgehen:\nNächste Kontrolle:', }}, { bereich = 'bericht', name = 'Notarzteinsatzprotokoll', sortierung = 1, inhalt = { symptome = 'Einsatzmeldung:\nAuffindesituation:\nSchilderung des Patienten:', untersuchung = 'Erstbefund:\nVitalwerte bei Eintreffen:\nVerlauf während des Transports:', massnahmen = 'Maßnahmen vor Ort:\nMedikamente:\nÜbergabe an:', }}, { bereich = 'bericht', name = 'Verlaufsbericht', sortierung = 2, inhalt = { symptome = 'Beschwerdebild im Verlauf:', untersuchung = 'Befunde im Verlauf:\nLaborwerte:', massnahmen = 'Bisherige Behandlung:\nÄnderungen:', empfehlung = 'Weiteres Vorgehen:', }}, { bereich = 'bericht', name = 'Entlassbericht', sortierung = 3, inhalt = { diagnose = 'Hauptdiagnose:\nNebendiagnosen:', massnahmen = 'Durchgeführte Behandlung:\nOperationen:\nMedikation während des Aufenthalts:', empfehlung = 'Medikation bei Entlassung:\nSchonung:\nWiedervorstellung bei:\nKontrolltermin:', }}, } CreateThread(function() Wait(3000) local vorhanden = tonumber(MySQL.scalar.await('SELECT COUNT(*) FROM medical_vorlage')) or 0 if vorhanden > 0 then return end for _, baustein in ipairs(GRUNDBAUSTEINE) do local kennung = Kern.ErzeugeKennung('VOR', 'medical_vorlage', 'kennung') MySQL.insert.await([[ INSERT INTO medical_vorlage (kennung, bereich, name, inhalt, sortierung) VALUES (?, ?, ?, ?, ?) ]], { kennung, baustein.bereich, baustein.name, json.encode(baustein.inhalt), baustein.sortierung }) end Aether.Debug('Grundbestand an Textbausteinen angelegt.') end) -- --------------------------------------------------------------------- -- 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 --- Sucht einen Bereich --- @param kennung any Bereichskennung --- @return table|nil local function HoleBereich(kennung) local gesucht = tostring(kennung or '') for _, bereich in ipairs(BEREICHE) do if bereich.id == gesucht then return bereich end end return nil end --- Liest den Inhalt eines Bausteins aus der Datenbank --- @param roh string|nil JSON --- @return table [feldId] = text local function AlsInhalt(roh) if not roh then return {} end local erfolg, tabelle = pcall(json.decode, roh) if erfolg and type(tabelle) == 'table' then return tabelle end return {} end --- Prüft einen eingereichten Inhalt gegen die Felder eines Bereichs. --- Felder, die es im Bogen nicht gibt, werden verworfen — ein Baustein --- soll nur füllen können, was auch existiert. --- @param bereich table Bereich --- @param roh any Eingabe --- @return table Geprüfter Inhalt, number Anzahl gefüllter Felder local function PruefeInhalt(bereich, roh) local inhalt, anzahl = {}, 0 if type(roh) ~= 'table' then return inhalt, 0 end for _, feld in ipairs(bereich.felder) do local wert = Text(roh[feld.id], 4000) if wert then inhalt[feld.id] = wert anzahl = anzahl + 1 end end return inhalt, anzahl end --- Lädt die einsetzbaren Bausteine eines Bereichs --- @param bereichId string Bereich --- @return table Liste local function LadeBausteine(bereichId) local zeilen = MySQL.query.await([[ SELECT id, kennung, name, inhalt, sortierung FROM medical_vorlage WHERE bereich = ? AND aktiv = 1 ORDER BY sortierung, name ]], { bereichId }) or {} for _, zeile in ipairs(zeilen) do zeile.inhalt = AlsInhalt(zeile.inhalt) end return zeilen end -- ===================================================================== -- ABRUF AUS DEN BÖGEN -- -- Dieselbe Auskunft, in jedem Bogen unter seinem eigenen Modul -- erreichbar. Sie liefert Text zum Einsetzen und sonst nichts. -- ===================================================================== for _, bereich in ipairs(BEREICHE) do Kern.RegistriereAktion(bereich.id, 'bausteine', nil, function(quelle, mitarbeiter, daten) return { bereich = bereich.id, bereichName = bereich.name, felder = bereich.felder, bausteine = LadeBausteine(bereich.id), } end) end -- ===================================================================== -- PFLEGE -- ===================================================================== Kern.RegistriereAktion('vorlage', 'uebersicht', nil, function(quelle, mitarbeiter, daten) local gewaehlt = HoleBereich(daten.bereich) or BEREICHE[1] local liste = MySQL.query.await([[ SELECT id, kennung, bereich, name, inhalt, aktiv, sortierung FROM medical_vorlage WHERE bereich = ? ORDER BY sortierung, name ]], { gewaehlt.id }) or {} for _, zeile in ipairs(liste) do local inhalt = AlsInhalt(zeile.inhalt) zeile.inhalt = inhalt zeile.aktiv = tonumber(zeile.aktiv) == 1 -- Welche Felder füllt dieser Baustein? zeile.felder = {} for _, feld in ipairs(gewaehlt.felder) do if inhalt[feld.id] then zeile.felder[#zeile.felder + 1] = feld.name end end end -- Zählwerte je Bereich für die Reiterleiste local bereiche = {} for _, bereich in ipairs(BEREICHE) do bereiche[#bereiche + 1] = { id = bereich.id, name = bereich.name, felder = bereich.felder, anzahl = tonumber(MySQL.scalar.await( 'SELECT COUNT(*) FROM medical_vorlage WHERE bereich = ? AND aktiv = 1', { bereich.id })) or 0, } end return { bereich = gewaehlt.id, felder = gewaehlt.felder, liste = liste, bereiche = bereiche, } end) Kern.RegistriereAktion('vorlage', 'speichern', 'katalog.bearbeiten', function(quelle, mitarbeiter, daten) local vorlageId = tonumber(daten.id) local bereich = HoleBereich(daten.bereich) if not bereich then return nil, 'Unbekannter Bereich.' end local name = Text(daten.name, 120) if not name then return nil, 'Bitte einen Namen angeben.' end local inhalt, gefuellt = PruefeInhalt(bereich, daten.inhalt) if gefuellt == 0 then return nil, 'Ein Baustein ohne Text füllt nichts aus.' end local sortierung = math.floor(tonumber(daten.sortierung) or 0) if vorlageId then local vorher = MySQL.query.await( 'SELECT id, kennung, bereich, name FROM medical_vorlage WHERE id = ?', { vorlageId }) if not vorher or not vorher[1] then return nil, 'Baustein nicht gefunden.' end -- Der Bereich bleibt: die Felder eines Bogens passen nicht auf einen anderen MySQL.update.await([[ UPDATE medical_vorlage SET name = ?, inhalt = ?, sortierung = ? WHERE id = ? ]], { name, json.encode(inhalt), sortierung, vorlageId }) Protokoll.Schreibe(mitarbeiter, 'vorlage', 'Baustein bearbeitet', { typ = 'vorlage', id = vorlageId }, { kennung = vorher[1].kennung, name = name, bereich = vorher[1].bereich, felder = gefuellt }) else local kennung = Kern.ErzeugeKennung('VOR', 'medical_vorlage', 'kennung') vorlageId = MySQL.insert.await([[ INSERT INTO medical_vorlage (kennung, bereich, name, inhalt, sortierung) VALUES (?, ?, ?, ?, ?) ]], { kennung, bereich.id, name, json.encode(inhalt), sortierung }) Protokoll.Schreibe(mitarbeiter, 'vorlage', 'Baustein angelegt', { typ = 'vorlage', id = vorlageId }, { kennung = kennung, name = name, bereich = bereich.id, felder = gefuellt }) end Kern.Loese('vorlage.geaendert', vorlageId, mitarbeiter) return { id = vorlageId } end) Kern.RegistriereAktion('vorlage', 'aktiv', 'katalog.bearbeiten', function(quelle, mitarbeiter, daten) local vorlageId = tonumber(daten.id) if not vorlageId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, name FROM medical_vorlage WHERE id = ?', { vorlageId }) if not zeilen or not zeilen[1] then return nil, 'Baustein nicht gefunden.' end local aktiv = daten.aktiv == true MySQL.update.await('UPDATE medical_vorlage SET aktiv = ? WHERE id = ?', { aktiv and 1 or 0, vorlageId }) Protokoll.Schreibe(mitarbeiter, 'vorlage', aktiv and 'Baustein eingeschaltet' or 'Baustein abgeschaltet', { typ = 'vorlage', id = vorlageId }, { kennung = zeilen[1].kennung, name = zeilen[1].name }) Kern.Loese('vorlage.geaendert', vorlageId, mitarbeiter) return { ok = true } end) --- Entfernt einen Baustein endgültig. --- Ein Baustein ist Text zum Einsetzen und keine Aufzeichnung über --- einen Patienten — was damit geschrieben wurde, steht ohnehin in --- den Bögen und bleibt dort unberührt. Kern.RegistriereAktion('vorlage', 'entfernen', 'katalog.bearbeiten', function(quelle, mitarbeiter, daten) local vorlageId = tonumber(daten.id) if not vorlageId then return nil, 'Ungültige Kennung.' end local grund = Text(daten.grund, 255) if not grund then return nil, 'Bitte einen Grund angeben.' end local zeilen = MySQL.query.await( 'SELECT id, kennung, name, bereich FROM medical_vorlage WHERE id = ?', { vorlageId }) if not zeilen or not zeilen[1] then return nil, 'Baustein nicht gefunden.' end MySQL.query.await('DELETE FROM medical_vorlage WHERE id = ?', { vorlageId }) Protokoll.Schreibe(mitarbeiter, 'vorlage', 'Baustein entfernt', { typ = 'vorlage', id = vorlageId }, { kennung = zeilen[1].kennung, name = zeilen[1].name, bereich = zeilen[1].bereich }, grund) Kern.Loese('vorlage.geaendert', vorlageId, mitarbeiter) return { ok = true } end) -- --------------------------------------------------------------------- -- Schnittstelle für andere Ressourcen -- --------------------------------------------------------------------- --- Liefert die aktiven Bausteine eines Bereichs. --- Reine Auskunft — keine Ressource kann darüber einen Bogen ausfüllen --- oder eine Dokumentation speichern. --- @param bereichId string behandlung/bericht --- @return table Liste exports('HoleBausteine', function(bereichId) if not HoleBereich(bereichId) then return {} end return LadeBausteine(bereichId) end)