- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark, Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI) - aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource, an aether-core gekoppelt (Rechte über aether_users.admin_group), mit WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU) - Prototypen admin/ vervollständigt - SQL-Schema aether_admin.sql - Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
736 lines
29 KiB
Lua
736 lines
29 KiB
Lua
-- =====================================================================
|
|
-- aether-medical / Modul: Medizinisches MDT
|
|
--
|
|
-- Das Herzstück: Patientensuche, Stammdaten und Patientenakte.
|
|
--
|
|
-- Das MDT zeigt an und verknüpft. Es stellt keine Diagnose, es leitet
|
|
-- aus Allergien oder Vorerkrankungen keine Warnung her, die eine
|
|
-- Behandlung verhindert, und es bewertet keinen Befund. Ein roter
|
|
-- Hinweis bedeutet hier immer: ein Mediziner hat ihn eingetragen.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereModul({
|
|
id = 'mdt', name = 'MDT', icon = '🩺',
|
|
reihenfolge = 2, recht = 'patient.suchen',
|
|
beschreibung = 'Patientensuche, Stammdaten und Akten',
|
|
})
|
|
|
|
-- Zulässige Angaben zum Organspendeausweis
|
|
local ORGANSTATUS = { ja = 'Spender', nein = 'Kein Spender', unbekannt = 'Unbekannt' }
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Hilfsfunktionen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Prüft einen Suchbegriff
|
|
--- @param begriff any Eingabe der Oberfläche
|
|
--- @return boolean
|
|
local function SuchbegriffGueltig(begriff)
|
|
return type(begriff) == 'string' and #begriff >= 2
|
|
end
|
|
|
|
--- Begrenzt einen Text und liefert nil bei leerer Eingabe
|
|
--- @param wert any Eingabe
|
|
--- @param laenge number Höchstlänge
|
|
--- @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
|
|
|
|
--- Lädt den Namen 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 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
|
|
.. (zeilen[1].personalnummer and (' (' .. zeilen[1].personalnummer .. ')') or '')
|
|
end
|
|
|
|
-- =====================================================================
|
|
-- PATIENTENSUCHE
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('mdt', 'suche', 'patient.suchen', function(quelle, mitarbeiter, daten)
|
|
if not SuchbegriffGueltig(daten.begriff) then
|
|
return nil, 'Bitte mindestens zwei Zeichen eingeben.'
|
|
end
|
|
|
|
local muster = '%' .. daten.begriff .. '%'
|
|
|
|
-- Sucht über Name, Geburtsdatum und Telefonnummer
|
|
local treffer = MySQL.query.await([[
|
|
SELECT c.id, c.firstname, c.lastname, c.dob, c.gender,
|
|
m.blutgruppe, m.allergien, m.hinweis, m.lichtbild,
|
|
t.number AS telefon,
|
|
(SELECT COUNT(*) FROM medical_aufnahme a
|
|
WHERE a.patient = c.id AND a.status = 'aufgenommen') AS imHaus,
|
|
(SELECT COUNT(*) FROM medical_aufnahme a
|
|
WHERE a.patient = c.id AND a.status = 'wartend') AS wartet
|
|
FROM aether_characters c
|
|
LEFT JOIN medical_patient m ON m.`character` = c.id
|
|
LEFT JOIN aether_phone t ON t.`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 ? OR t.number LIKE ?)
|
|
ORDER BY c.lastname, c.firstname
|
|
LIMIT 40
|
|
]], { muster, muster, muster, muster, muster }) or {}
|
|
|
|
for _, eintrag in ipairs(treffer) do
|
|
eintrag.dob = tostring(eintrag.dob):sub(1, 10)
|
|
eintrag.imHaus = tonumber(eintrag.imHaus) > 0
|
|
eintrag.wartet = tonumber(eintrag.wartet) > 0
|
|
end
|
|
|
|
return { treffer = treffer }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- PATIENTENAKTE
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('mdt', 'oeffnen', 'patient.suchen', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT c.id, c.firstname, c.lastname, c.dob, c.gender, c.nationality,
|
|
c.job, c.is_dead
|
|
FROM aether_characters c
|
|
WHERE c.id = ? AND c.deleted = 0
|
|
]], { patientId })
|
|
|
|
if not zeilen or not zeilen[1] then return nil, 'Diese Person ist nicht erfasst.' end
|
|
local person = zeilen[1]
|
|
|
|
-- --- Medizinische Stammdaten ---
|
|
local stamm = MySQL.query.await(
|
|
'SELECT * FROM medical_patient WHERE `character` = ?', { patientId })
|
|
stamm = stamm and stamm[1] or {}
|
|
|
|
-- --- Telefonnummer ---
|
|
local telefon = MySQL.scalar.await(
|
|
'SELECT number FROM aether_phone WHERE `character` = ?', { patientId })
|
|
|
|
-- --- Aktuelle Aufnahme, falls vorhanden ---
|
|
local aufnahme = MySQL.query.await([[
|
|
SELECT a.id, a.grund, a.prioritaet, a.station, a.status, a.aufgenommen_am,
|
|
a.aufgenommen_von, 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
|
|
]], { patientId })
|
|
aufnahme = aufnahme and aufnahme[1] or nil
|
|
|
|
if aufnahme then
|
|
aufnahme.aufgenommenVon = NameVon(aufnahme.aufgenommen_von)
|
|
aufnahme.aufgenommen_von = nil
|
|
end
|
|
|
|
-- --- Liegt der Patient auf Intensiv? ---
|
|
local intensiv = MySQL.query.await([[
|
|
SELECT i.id, i.zustand, i.geraete, i.verantwortlich, i.aufgenommen_am
|
|
FROM medical_intensiv i
|
|
WHERE i.patient = ? AND i.beendet_am IS NULL
|
|
ORDER BY i.id DESC LIMIT 1
|
|
]], { patientId })
|
|
intensiv = intensiv and intensiv[1] or nil
|
|
|
|
if intensiv then
|
|
intensiv.verantwortlichName = NameVon(intensiv.verantwortlich)
|
|
intensiv.verantwortlich = nil
|
|
end
|
|
|
|
-- --- Zählwerte für die Reiter ---
|
|
local zaehler = {
|
|
behandlungen = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_behandlung WHERE patient = ?', { patientId })) or 0,
|
|
operationen = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_operation WHERE patient = ?', { patientId })) or 0,
|
|
labor = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_labor WHERE patient = ?', { patientId })) or 0,
|
|
akten = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_akte WHERE patient = ?', { patientId })) or 0,
|
|
aufnahmen = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_aufnahme WHERE patient = ?', { patientId })) or 0,
|
|
notizen = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_patient_notiz WHERE patient = ?', { patientId })) or 0,
|
|
}
|
|
|
|
return {
|
|
patient = {
|
|
id = person.id,
|
|
name = person.firstname .. ' ' .. person.lastname,
|
|
vorname = person.firstname,
|
|
nachname = person.lastname,
|
|
geburt = tostring(person.dob):sub(1, 10),
|
|
geschlecht = person.gender,
|
|
herkunft = person.nationality,
|
|
beruf = person.job,
|
|
telefon = telefon,
|
|
-- Nur die Tatsache, dass der Charakter verletzt am Boden liegt.
|
|
-- Das System leitet daraus nichts ab und stellt nichts fest.
|
|
verletzt = person.is_dead == 1,
|
|
},
|
|
stamm = {
|
|
blutgruppe = stamm.blutgruppe,
|
|
allergien = stamm.allergien,
|
|
vorerkrankungen = stamm.vorerkrankungen,
|
|
dauermedikation = stamm.dauermedikation,
|
|
implantate = stamm.implantate,
|
|
organspender = stamm.organspender or 'unbekannt',
|
|
organspenderName = ORGANSTATUS[stamm.organspender or 'unbekannt'],
|
|
notfallkontakt = stamm.notfallkontakt,
|
|
hinweis = stamm.hinweis,
|
|
lichtbild = stamm.lichtbild,
|
|
gepflegtVon = stamm.aktualisiert_von and NameVon(stamm.aktualisiert_von) or nil,
|
|
gepflegtAm = stamm.aktualisiert_am,
|
|
},
|
|
aufnahme = aufnahme,
|
|
intensiv = intensiv,
|
|
zaehler = zaehler,
|
|
stationen = Medizin.Config.Stationen,
|
|
blutgruppen = Medizin.Config.Blutgruppen,
|
|
organstatus = ORGANSTATUS,
|
|
rechte = {
|
|
stammdaten = Rechte.Hat(mitarbeiter, 'patient.stammdaten'),
|
|
hinweis = Rechte.Hat(mitarbeiter, 'patient.hinweis'),
|
|
akteLesen = Rechte.Hat(mitarbeiter, 'akte.lesen'),
|
|
akteErstellen = Rechte.Hat(mitarbeiter, 'akte.erstellen'),
|
|
akteBearbeiten = Rechte.Hat(mitarbeiter, 'akte.bearbeiten'),
|
|
akteFreigeben = Rechte.Hat(mitarbeiter, 'akte.freigeben'),
|
|
akteSchliessen = Rechte.Hat(mitarbeiter, 'akte.schliessen'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- VERLAUF
|
|
--
|
|
-- Alle Vorgänge eines Patienten in einer Zeitleiste. Reine Anzeige —
|
|
-- aus der Abfolge wird nichts abgeleitet.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('mdt', 'verlauf', 'akte.lesen', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintraege = {}
|
|
|
|
--- Hängt einen Eintrag an die Zeitleiste
|
|
local function Anfuegen(art, zeitpunkt, titel, text, ziel, zielId, marker)
|
|
if not zeitpunkt then return end
|
|
eintraege[#eintraege + 1] = {
|
|
art = art, am = zeitpunkt, titel = titel, text = text,
|
|
ziel = ziel, zielId = zielId, marker = marker,
|
|
}
|
|
end
|
|
|
|
-- --- Aufnahmen und Entlassungen ---
|
|
local aufnahmen = MySQL.query.await([[
|
|
SELECT id, grund, station, status, aufgenommen_am, aufgenommen_von,
|
|
entlassen_am, entlassen_von, entlass_grund
|
|
FROM medical_aufnahme WHERE patient = ?
|
|
ORDER BY id DESC LIMIT 40
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(aufnahmen) do
|
|
Anfuegen('aufnahme', eintrag.aufgenommen_am, 'Aufnahme',
|
|
eintrag.grund, 'aufnahme', eintrag.id,
|
|
'aufgenommen von ' .. NameVon(eintrag.aufgenommen_von))
|
|
|
|
if eintrag.entlassen_am then
|
|
Anfuegen('entlassung', eintrag.entlassen_am, 'Entlassung',
|
|
eintrag.entlass_grund, 'aufnahme', eintrag.id,
|
|
'entlassen von ' .. NameVon(eintrag.entlassen_von))
|
|
end
|
|
end
|
|
|
|
-- --- Behandlungen ---
|
|
local behandlungen = MySQL.query.await([[
|
|
SELECT id, kennung, diagnose, massnahmen, status, erstellt_am, erstellt_von
|
|
FROM medical_behandlung WHERE patient = ?
|
|
ORDER BY id DESC LIMIT 40
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(behandlungen) do
|
|
Anfuegen('behandlung', eintrag.erstellt_am,
|
|
'Behandlung ' .. eintrag.kennung,
|
|
eintrag.diagnose or eintrag.massnahmen or 'Noch keine Diagnose eingetragen',
|
|
'behandlung', eintrag.id,
|
|
(eintrag.status == 'laufend' and 'läuft · ' or '') .. NameVon(eintrag.erstellt_von))
|
|
end
|
|
|
|
-- --- Operationen ---
|
|
local operationen = MySQL.query.await([[
|
|
SELECT id, kennung, art, status, geplant_fuer, beginn, ende, erstellt_von
|
|
FROM medical_operation WHERE patient = ?
|
|
ORDER BY id DESC LIMIT 20
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(operationen) do
|
|
Anfuegen('operation', eintrag.beginn or eintrag.geplant_fuer,
|
|
'Operation ' .. eintrag.kennung, eintrag.art,
|
|
'operation', eintrag.id,
|
|
eintrag.status .. ' · ' .. NameVon(eintrag.erstellt_von))
|
|
end
|
|
|
|
-- --- Laboraufträge ---
|
|
local labor = MySQL.query.await([[
|
|
SELECT id, kennung, art, status, fragestellung, auftrag_am, auftrag_von, befund
|
|
FROM medical_labor WHERE patient = ?
|
|
ORDER BY id DESC LIMIT 30
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(labor) do
|
|
Anfuegen('labor', eintrag.auftrag_am,
|
|
'Labor ' .. eintrag.kennung .. ' · ' .. eintrag.art,
|
|
eintrag.befund or eintrag.fragestellung or 'Ohne Fragestellung',
|
|
'labor', eintrag.id,
|
|
eintrag.status .. ' · ' .. NameVon(eintrag.auftrag_von))
|
|
end
|
|
|
|
-- --- Verabreichte Medikamente ---
|
|
local gaben = MySQL.query.await([[
|
|
SELECT g.id, g.dosierung, g.menge, g.gegeben_am, g.gegeben_von, g.hinweis,
|
|
m.name, m.einheit
|
|
FROM medical_behandlung_medikament g
|
|
JOIN medical_behandlung b ON b.id = g.behandlung
|
|
JOIN medical_medikament m ON m.id = g.medikament
|
|
WHERE b.patient = ?
|
|
ORDER BY g.id DESC LIMIT 40
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(gaben) do
|
|
Anfuegen('medikament', eintrag.gegeben_am,
|
|
'Medikamentengabe: ' .. eintrag.name,
|
|
('%s %s%s'):format(eintrag.menge, eintrag.einheit,
|
|
eintrag.dosierung and (' · ' .. eintrag.dosierung) or ''),
|
|
nil, nil,
|
|
'gegeben von ' .. NameVon(eintrag.gegeben_von))
|
|
end
|
|
|
|
-- --- Vermerke ---
|
|
local notizen = MySQL.query.await([[
|
|
SELECT id, text, von, am FROM medical_patient_notiz
|
|
WHERE patient = ? ORDER BY id DESC LIMIT 30
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(notizen) do
|
|
Anfuegen('notiz', eintrag.am, 'Vermerk', eintrag.text,
|
|
nil, nil, NameVon(eintrag.von))
|
|
end
|
|
|
|
-- Jüngstes zuerst
|
|
table.sort(eintraege, function(a, b) return tostring(a.am) > tostring(b.am) end)
|
|
|
|
-- Nur die letzten 60 Einträge übertragen
|
|
local gekuerzt = {}
|
|
for i = 1, math.min(#eintraege, 60) do gekuerzt[i] = eintraege[i] end
|
|
|
|
return { eintraege = gekuerzt, gesamt = #eintraege }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- STAMMDATEN PFLEGEN
|
|
--
|
|
-- Alles hier trägt ein Mediziner von Hand ein. Die Blutgruppe ist
|
|
-- bewusst ein Freitextfeld aus einer Liste — sie wird NICHT aus einem
|
|
-- Laborergebnis übernommen, denn das wäre eine Feststellung, die
|
|
-- jemand verantworten muss.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('mdt', 'stammdaten', 'patient.stammdaten', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local vorhanden = MySQL.scalar.await(
|
|
'SELECT id FROM aether_characters WHERE id = ? AND deleted = 0', { patientId })
|
|
if not vorhanden then return nil, 'Diese Person ist nicht erfasst.' end
|
|
|
|
-- Blutgruppe nur aus der bekannten Liste
|
|
local blutgruppe = nil
|
|
if daten.blutgruppe and daten.blutgruppe ~= '' then
|
|
for _, gruppe in ipairs(Medizin.Config.Blutgruppen) do
|
|
if gruppe == daten.blutgruppe then blutgruppe = gruppe break end
|
|
end
|
|
if not blutgruppe then return nil, 'Unbekannte Blutgruppe.' end
|
|
end
|
|
|
|
local organspender = ORGANSTATUS[daten.organspender] and daten.organspender or 'unbekannt'
|
|
|
|
-- Alten Stand für das Protokoll festhalten
|
|
local alt = MySQL.query.await(
|
|
'SELECT blutgruppe, allergien, hinweis FROM medical_patient WHERE `character` = ?',
|
|
{ patientId })
|
|
alt = alt and alt[1] or {}
|
|
|
|
MySQL.query.await([[
|
|
INSERT INTO medical_patient
|
|
(`character`, blutgruppe, allergien, vorerkrankungen, dauermedikation,
|
|
implantate, organspender, notfallkontakt, lichtbild,
|
|
aktualisiert_von, aktualisiert_am)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
blutgruppe = VALUES(blutgruppe),
|
|
allergien = VALUES(allergien),
|
|
vorerkrankungen = VALUES(vorerkrankungen),
|
|
dauermedikation = VALUES(dauermedikation),
|
|
implantate = VALUES(implantate),
|
|
organspender = VALUES(organspender),
|
|
notfallkontakt = VALUES(notfallkontakt),
|
|
lichtbild = VALUES(lichtbild),
|
|
aktualisiert_von = VALUES(aktualisiert_von),
|
|
aktualisiert_am = NOW()
|
|
]], {
|
|
patientId, blutgruppe,
|
|
Text(daten.allergien, 512),
|
|
Text(daten.vorerkrankungen, 1000),
|
|
Text(daten.dauermedikation, 1000),
|
|
Text(daten.implantate, 512),
|
|
organspender,
|
|
Text(daten.notfallkontakt, 160),
|
|
Text(daten.lichtbild, 512),
|
|
mitarbeiter.charakterId,
|
|
})
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'mdt', 'Stammdaten gepflegt',
|
|
{ typ = 'patient', id = patientId },
|
|
{ blutgruppeAlt = alt.blutgruppe, blutgruppeNeu = blutgruppe })
|
|
|
|
Kern.Loese('patient.stammdaten', patientId, mitarbeiter)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
--- Warnhinweis setzen oder entfernen — immer mit Begründung
|
|
Kern.RegistriereAktion('mdt', 'hinweisSetzen', 'patient.hinweis', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local hinweis = Text(daten.hinweis, 512)
|
|
local grund = Text(daten.grund, 255)
|
|
|
|
if hinweis and not grund then
|
|
return nil, 'Bitte begründen, warum der Warnhinweis gesetzt wird.'
|
|
end
|
|
|
|
MySQL.query.await([[
|
|
INSERT INTO medical_patient (`character`, hinweis, aktualisiert_von, aktualisiert_am)
|
|
VALUES (?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
hinweis = VALUES(hinweis),
|
|
aktualisiert_von = VALUES(aktualisiert_von),
|
|
aktualisiert_am = NOW()
|
|
]], { patientId, hinweis, mitarbeiter.charakterId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'mdt',
|
|
hinweis and 'Warnhinweis gesetzt' or 'Warnhinweis entfernt',
|
|
{ typ = 'patient', id = patientId }, { hinweis = hinweis }, grund)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- VERMERKE
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('mdt', 'notizen', 'akte.lesen', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local notizen = MySQL.query.await([[
|
|
SELECT n.id, n.text, n.am, n.von,
|
|
c.firstname, c.lastname, p.personalnummer
|
|
FROM medical_patient_notiz n
|
|
LEFT JOIN aether_characters c ON c.id = n.von
|
|
LEFT JOIN medical_personal p ON p.`character` = n.von
|
|
WHERE n.patient = ?
|
|
ORDER BY n.id DESC LIMIT 60
|
|
]], { patientId }) or {}
|
|
|
|
for _, eintrag in ipairs(notizen) do
|
|
eintrag.autor = (eintrag.firstname or 'Unbekannt') .. ' ' .. (eintrag.lastname or '')
|
|
eintrag.firstname, eintrag.lastname = nil, nil
|
|
eintrag.eigene = eintrag.von == mitarbeiter.charakterId
|
|
end
|
|
|
|
return { notizen = notizen }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('mdt', 'notizAnlegen', 'akte.lesen', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
local text = Text(daten.text, 1000)
|
|
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
if not text or #text < 3 then return nil, 'Der Vermerk ist zu kurz.' end
|
|
|
|
local id = MySQL.insert.await(
|
|
'INSERT INTO medical_patient_notiz (patient, text, von) VALUES (?, ?, ?)',
|
|
{ patientId, text, mitarbeiter.charakterId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'mdt', 'Vermerk angelegt',
|
|
{ typ = 'patient', id = patientId })
|
|
|
|
return { id = id }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- AKTEN
|
|
--
|
|
-- Jede Änderung erzeugt eine neue Version. Nichts wird überschrieben,
|
|
-- die Historie bleibt vollständig einsehbar.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('mdt', 'akten', 'akte.lesen', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
if not patientId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local akten = MySQL.query.await([[
|
|
SELECT a.id, a.aktenzeichen, a.typ, a.titel, a.status, a.version,
|
|
a.erstellt_am, a.erstellt_von, a.freigabe_am,
|
|
c.firstname, c.lastname
|
|
FROM medical_akte a
|
|
LEFT JOIN aether_characters c ON c.id = a.erstellt_von
|
|
WHERE a.patient = ?
|
|
ORDER BY a.id DESC LIMIT 60
|
|
]], { patientId }) or {}
|
|
|
|
local artNamen = {}
|
|
for _, eintrag in ipairs(Medizin.Config.Aktenarten) do artNamen[eintrag.id] = eintrag.name end
|
|
|
|
for _, akte in ipairs(akten) do
|
|
akte.typName = artNamen[akte.typ] or akte.typ
|
|
akte.autor = (akte.firstname or 'Unbekannt') .. ' ' .. (akte.lastname or '')
|
|
akte.firstname, akte.lastname = nil, nil
|
|
end
|
|
|
|
return { akten = akten, aktenarten = Medizin.Config.Aktenarten }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('mdt', 'akteOeffnen', 'akte.lesen', function(quelle, mitarbeiter, daten)
|
|
local akteId = tonumber(daten.id)
|
|
if not akteId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT a.*, c.firstname, c.lastname
|
|
FROM medical_akte a
|
|
LEFT JOIN aether_characters c ON c.id = a.erstellt_von
|
|
WHERE a.id = ?
|
|
]], { akteId })
|
|
|
|
if not zeilen or not zeilen[1] then return nil, 'Akte nicht gefunden.' end
|
|
local akte = zeilen[1]
|
|
|
|
-- Vollständige Versionshistorie
|
|
local versionen = MySQL.query.await([[
|
|
SELECT v.version, v.geaendert_am, v.geaendert_von, v.kommentar,
|
|
c.firstname, c.lastname
|
|
FROM medical_akte_version v
|
|
LEFT JOIN aether_characters c ON c.id = v.geaendert_von
|
|
WHERE v.akte = ?
|
|
ORDER BY v.version DESC
|
|
]], { akteId }) or {}
|
|
|
|
for _, version in ipairs(versionen) do
|
|
version.autor = (version.firstname or 'Unbekannt') .. ' ' .. (version.lastname or '')
|
|
version.firstname, version.lastname = nil, nil
|
|
end
|
|
|
|
return {
|
|
akte = {
|
|
id = akte.id, aktenzeichen = akte.aktenzeichen, patient = akte.patient,
|
|
typ = akte.typ, titel = akte.titel, status = akte.status,
|
|
inhalt = akte.inhalt, version = akte.version,
|
|
erstelltAm = akte.erstellt_am,
|
|
autor = (akte.firstname or 'Unbekannt') .. ' ' .. (akte.lastname or ''),
|
|
freigabeAm = akte.freigabe_am,
|
|
freigabeVon = akte.freigabe_von and NameVon(akte.freigabe_von) or nil,
|
|
},
|
|
versionen = versionen,
|
|
aktenarten = Medizin.Config.Aktenarten,
|
|
rechte = {
|
|
bearbeiten = Rechte.Hat(mitarbeiter, 'akte.bearbeiten'),
|
|
freigeben = Rechte.Hat(mitarbeiter, 'akte.freigeben'),
|
|
schliessen = Rechte.Hat(mitarbeiter, 'akte.schliessen'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
--- Liefert den Inhalt einer bestimmten Version
|
|
Kern.RegistriereAktion('mdt', 'akteVersion', 'akte.lesen', function(quelle, mitarbeiter, daten)
|
|
local akteId = tonumber(daten.id)
|
|
local version = tonumber(daten.version)
|
|
if not akteId or not version then return nil, 'Ungültige Kennung.' end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT v.version, v.inhalt, v.geaendert_am, v.geaendert_von, v.kommentar
|
|
FROM medical_akte_version v
|
|
WHERE v.akte = ? AND v.version = ?
|
|
]], { akteId, version })
|
|
|
|
if not zeilen or not zeilen[1] then return nil, 'Diese Version gibt es nicht.' end
|
|
|
|
local eintrag = zeilen[1]
|
|
eintrag.autor = NameVon(eintrag.geaendert_von)
|
|
eintrag.geaendert_von = nil
|
|
|
|
return eintrag
|
|
end)
|
|
|
|
Kern.RegistriereAktion('mdt', 'akteAnlegen', 'akte.erstellen', function(quelle, mitarbeiter, daten)
|
|
local patientId = tonumber(daten.patient)
|
|
local titel = Text(daten.titel, 160)
|
|
|
|
if not patientId then return nil, 'Kein Patient gewählt.' end
|
|
if not titel or #titel < 3 then return nil, 'Bitte einen Titel angeben.' end
|
|
|
|
-- Aktenart prüfen
|
|
local typ = nil
|
|
for _, eintrag in ipairs(Medizin.Config.Aktenarten) do
|
|
if eintrag.id == daten.typ then typ = eintrag.id break end
|
|
end
|
|
if not typ then return nil, 'Unbekannte Aktenart.' end
|
|
|
|
local inhalt = type(daten.inhalt) == 'string' and daten.inhalt:sub(1, 20000) or nil
|
|
local aktenzeichen = Kern.ErzeugeKennung('AKT', 'medical_akte', 'aktenzeichen')
|
|
|
|
local id = MySQL.insert.await([[
|
|
INSERT INTO medical_akte
|
|
(aktenzeichen, patient, typ, titel, status, inhalt, erstellt_von, version)
|
|
VALUES (?, ?, ?, ?, 'entwurf', ?, ?, 1)
|
|
]], { aktenzeichen, patientId, typ, titel, inhalt, mitarbeiter.charakterId })
|
|
|
|
-- Erste Version festhalten
|
|
MySQL.insert.await([[
|
|
INSERT INTO medical_akte_version (akte, version, inhalt, geaendert_von, kommentar)
|
|
VALUES (?, 1, ?, ?, 'Anlage')
|
|
]], { id, inhalt, mitarbeiter.charakterId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'mdt', 'Akte angelegt',
|
|
{ typ = 'akte', id = id }, { aktenzeichen = aktenzeichen, patient = patientId })
|
|
|
|
Kern.Loese('akte.angelegt', id, mitarbeiter)
|
|
|
|
return { id = id, aktenzeichen = aktenzeichen }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('mdt', 'akteSpeichern', 'akte.bearbeiten', function(quelle, mitarbeiter, daten)
|
|
local akteId = tonumber(daten.id)
|
|
if not akteId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local zeilen = MySQL.query.await(
|
|
'SELECT id, aktenzeichen, status, version FROM medical_akte WHERE id = ?', { akteId })
|
|
if not zeilen or not zeilen[1] then return nil, 'Akte nicht gefunden.' end
|
|
|
|
local akte = zeilen[1]
|
|
if akte.status == 'geschlossen' then
|
|
return nil, 'Eine geschlossene Akte lässt sich nicht mehr ändern.'
|
|
end
|
|
|
|
local titel = Text(daten.titel, 160)
|
|
if not titel or #titel < 3 then return nil, 'Bitte einen Titel angeben.' end
|
|
|
|
local inhalt = type(daten.inhalt) == 'string' and daten.inhalt:sub(1, 20000) or nil
|
|
local kommentar = Text(daten.kommentar, 255)
|
|
if not kommentar then return nil, 'Bitte kurz angeben, was geändert wurde.' end
|
|
|
|
local neueVersion = akte.version + 1
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_akte SET titel = ?, inhalt = ?, version = ? WHERE id = ?
|
|
]], { titel, inhalt, neueVersion, akteId })
|
|
|
|
-- Neue Version anhängen — die alte bleibt unangetastet
|
|
MySQL.insert.await([[
|
|
INSERT INTO medical_akte_version (akte, version, inhalt, geaendert_von, kommentar)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
]], { akteId, neueVersion, inhalt, mitarbeiter.charakterId, kommentar })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'mdt', 'Akte bearbeitet',
|
|
{ typ = 'akte', id = akteId },
|
|
{ aktenzeichen = akte.aktenzeichen, version = neueVersion }, kommentar)
|
|
|
|
return { version = neueVersion }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('mdt', 'akteStatus', nil, function(quelle, mitarbeiter, daten)
|
|
local akteId = tonumber(daten.id)
|
|
local status = tostring(daten.status or '')
|
|
|
|
if not akteId then return nil, 'Ungültige Kennung.' end
|
|
|
|
-- Welches Recht der Statuswechsel verlangt
|
|
local benoetigt = {
|
|
offen = 'akte.bearbeiten',
|
|
freigegeben = 'akte.freigeben',
|
|
geschlossen = 'akte.schliessen',
|
|
}
|
|
if not benoetigt[status] then return nil, 'Unbekannter Status.' end
|
|
if not Rechte.Hat(mitarbeiter, benoetigt[status]) then
|
|
return nil, 'Dir fehlt die Berechtigung für diesen Schritt.'
|
|
end
|
|
|
|
local zeilen = MySQL.query.await(
|
|
'SELECT id, aktenzeichen, status FROM medical_akte WHERE id = ?', { akteId })
|
|
if not zeilen or not zeilen[1] then return nil, 'Akte nicht gefunden.' end
|
|
|
|
if zeilen[1].status == 'geschlossen' then
|
|
return nil, 'Diese Akte ist bereits geschlossen.'
|
|
end
|
|
|
|
local grund = Text(daten.grund, 255)
|
|
|
|
if status == 'freigegeben' then
|
|
MySQL.update.await([[
|
|
UPDATE medical_akte SET status = ?, freigabe_von = ?, freigabe_am = NOW()
|
|
WHERE id = ?
|
|
]], { status, mitarbeiter.charakterId, akteId })
|
|
else
|
|
MySQL.update.await('UPDATE medical_akte SET status = ? WHERE id = ?', { status, akteId })
|
|
end
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'mdt', 'Aktenstatus geändert',
|
|
{ typ = 'akte', id = akteId },
|
|
{ aktenzeichen = zeilen[1].aktenzeichen, von = zeilen[1].status, nach = status }, grund)
|
|
|
|
Kern.Loese('akte.status', akteId, status, mitarbeiter)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Schnittstelle für andere Module
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Liefert die Stammdaten eines Patienten, so wie sie eingetragen sind.
|
|
--- Ausdrücklich ohne Bewertung: was hier steht, hat ein Mediziner
|
|
--- eingetragen, und was fehlt, ist schlicht nicht erhoben.
|
|
--- @param patientId number Charakter
|
|
--- @return table|nil
|
|
exports('HolePatientenStammdaten', function(patientId)
|
|
if not tonumber(patientId) then return nil end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT blutgruppe, allergien, vorerkrankungen, dauermedikation,
|
|
implantate, organspender, notfallkontakt, hinweis
|
|
FROM medical_patient WHERE `character` = ?
|
|
]], { tonumber(patientId) })
|
|
|
|
return zeilen and zeilen[1] or nil
|
|
end)
|