- 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>
783 lines
30 KiB
Lua
783 lines
30 KiB
Lua
-- =====================================================================
|
|
-- aether-medical / Modul: Berichte
|
|
--
|
|
-- Ein Bericht ist ein Dokument mit einer Unterschrift darunter.
|
|
--
|
|
-- Das System schreibt keinen Satz davon. Es formuliert nichts vor,
|
|
-- fasst nichts zusammen und leitet aus keiner Behandlung selbst eine
|
|
-- Diagnose ab. Wer Text aus einer Behandlung übernehmen will, löst
|
|
-- das ausdrücklich aus — und was dann im Bericht steht, verantwortet
|
|
-- er mit seinem Namen.
|
|
--
|
|
-- Der Weg vom Entwurf zur Freigabe wird Schritt für Schritt von
|
|
-- Menschen gegangen. Kein Bericht reift von selbst, keine Frist gibt
|
|
-- ihn frei, und ein freigegebener Bericht wird nie wieder geändert.
|
|
-- =====================================================================
|
|
|
|
-- Lesen darf, wer Akten lesen darf. Schreiben und Freigeben verlangen
|
|
-- die jeweils eigenen Rechte.
|
|
Kern.RegistriereModul({
|
|
id = 'bericht', name = 'Berichte', icon = '📄',
|
|
reihenfolge = 14, recht = 'akte.lesen',
|
|
beschreibung = 'Berichtsentwürfe, Freigabe und Unterschriften',
|
|
})
|
|
|
|
-- Die inhaltlichen Felder eines Berichts, in der Reihenfolge des Bogens
|
|
local FELDER = {
|
|
{ id = 'symptome', name = 'Symptome und Schilderung' },
|
|
{ id = 'untersuchung', name = 'Untersuchungsbefund' },
|
|
{ id = 'diagnose', name = 'Diagnose' },
|
|
{ id = 'massnahmen', name = 'Maßnahmen' },
|
|
{ id = 'empfehlung', name = 'Empfehlung' },
|
|
}
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 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 Charakters 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
|
|
|
|
--- Liest eine JSON-Liste aus der Datenbank
|
|
--- @param roh string|nil JSON
|
|
--- @return table Liste
|
|
local function AlsListe(roh)
|
|
if not roh then return {} end
|
|
local erfolg, liste = pcall(json.decode, roh)
|
|
if erfolg and type(liste) == 'table' then return liste end
|
|
return {}
|
|
end
|
|
|
|
--- Zeitstempel für Einträge in JSON-Feldern
|
|
--- @return string
|
|
local function Jetzt()
|
|
return os.date('%Y-%m-%d %H:%M:%S')
|
|
end
|
|
|
|
--- Baut einen Unterschrifteneintrag
|
|
--- @param mitarbeiter table Unterzeichnender
|
|
--- @param art string verfasser/freigabe/gegenzeichnung
|
|
--- @return table
|
|
local function Unterschrift(mitarbeiter, art)
|
|
return {
|
|
charakterId = mitarbeiter.charakterId,
|
|
name = mitarbeiter.name,
|
|
personalnummer = mitarbeiter.personalnummer,
|
|
rolle = mitarbeiter.rolleName,
|
|
art = art,
|
|
am = Jetzt(),
|
|
}
|
|
end
|
|
|
|
--- Prüft eine Adresse für eine Anlage
|
|
--- @param wert any Eingabe
|
|
--- @return string|nil
|
|
local function Adresse(wert)
|
|
local roh = Text(wert, 500)
|
|
if not roh then return nil end
|
|
if not roh:match('^https?://') then return nil end
|
|
return roh
|
|
end
|
|
|
|
--- Lädt einen Bericht mit seinen Begleitdaten
|
|
--- @param berichtId number Bericht
|
|
--- @return table|nil
|
|
local function LadeBericht(berichtId)
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT b.*,
|
|
c.firstname, c.lastname, c.dob,
|
|
beh.kennung AS behandlungKennung,
|
|
ein.nummer AS einsatzNummer, ein.titel AS einsatzTitel
|
|
FROM medical_bericht b
|
|
LEFT JOIN aether_characters c ON c.id = b.patient
|
|
LEFT JOIN medical_behandlung beh ON beh.id = b.behandlung
|
|
LEFT JOIN medical_einsatz ein ON ein.id = b.einsatz
|
|
WHERE b.id = ?
|
|
]], { berichtId })
|
|
return zeilen and zeilen[1] or nil
|
|
end
|
|
|
|
--- Darf dieser Mitarbeiter den Inhalt ändern?
|
|
--- Nur der Verfasser, und nur solange der Bericht ein Entwurf ist.
|
|
--- @param eintrag table Berichtszeile
|
|
--- @param mitarbeiter table Handelnder
|
|
--- @return boolean, string|nil Grund der Ablehnung
|
|
local function DarfAendern(eintrag, mitarbeiter)
|
|
if eintrag.status == 'freigegeben' then
|
|
return false, 'Ein freigegebener Bericht wird nicht mehr geändert.'
|
|
end
|
|
if eintrag.status == 'eingereicht' then
|
|
return false, 'Der Bericht liegt zur Freigabe vor. Dafür erst zurückziehen.'
|
|
end
|
|
if eintrag.erstellt_von ~= mitarbeiter.charakterId then
|
|
return false, 'Diesen Entwurf hat jemand anderes geschrieben.'
|
|
end
|
|
return true, nil
|
|
end
|
|
|
|
-- =====================================================================
|
|
-- ÜBERSICHT
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('bericht', 'uebersicht', nil, function(quelle, mitarbeiter, daten)
|
|
local ordner = tostring(daten.ordner or 'meine')
|
|
local ich = mitarbeiter.charakterId
|
|
|
|
local bedingung, werte
|
|
if ordner == 'freigabe' then
|
|
bedingung = "b.status = 'eingereicht'"
|
|
werte = {}
|
|
elseif ordner == 'freigegeben' then
|
|
bedingung = "b.status = 'freigegeben'"
|
|
werte = {}
|
|
elseif ordner == 'alle' then
|
|
bedingung = '1 = 1'
|
|
werte = {}
|
|
else
|
|
-- Eigene Entwürfe: was noch niemand ausser dem Verfasser gesehen hat
|
|
bedingung = "b.status = 'entwurf' AND b.erstellt_von = ?"
|
|
werte = { ich }
|
|
end
|
|
|
|
-- Freitextsuche über Nummer, Titel und Patientenname
|
|
local begriff = Text(daten.suche, 60)
|
|
if begriff then
|
|
local muster = '%' .. begriff .. '%'
|
|
bedingung = ('(%s) AND (b.nummer LIKE ? OR b.titel LIKE ? OR CONCAT(c.firstname, " ", c.lastname) LIKE ?)')
|
|
:format(bedingung)
|
|
werte[#werte + 1] = muster
|
|
werte[#werte + 1] = muster
|
|
werte[#werte + 1] = muster
|
|
end
|
|
|
|
local liste = MySQL.query.await(([[
|
|
SELECT b.id, b.nummer, b.titel, b.patient, b.behandlung, b.einsatz,
|
|
b.status, b.erstellt_von, b.erstellt_am, b.diagnose,
|
|
b.unterschriften,
|
|
c.firstname, c.lastname,
|
|
beh.kennung AS behandlungKennung,
|
|
ein.nummer AS einsatzNummer
|
|
FROM medical_bericht b
|
|
LEFT JOIN aether_characters c ON c.id = b.patient
|
|
LEFT JOIN medical_behandlung beh ON beh.id = b.behandlung
|
|
LEFT JOIN medical_einsatz ein ON ein.id = b.einsatz
|
|
WHERE %s
|
|
ORDER BY b.id DESC
|
|
LIMIT 150
|
|
]]):format(bedingung), werte) or {}
|
|
|
|
for _, eintrag in ipairs(liste) do
|
|
eintrag.patientName = eintrag.firstname
|
|
and (eintrag.firstname .. ' ' .. eintrag.lastname) or nil
|
|
eintrag.autor = NameVon(eintrag.erstellt_von)
|
|
eintrag.vonMir = eintrag.erstellt_von == ich
|
|
eintrag.unterschriften = #AlsListe(eintrag.unterschriften)
|
|
-- Nur ein Hinweis darauf, ob überhaupt eine Diagnose im Bericht steht
|
|
eintrag.hatDiagnose = eintrag.diagnose ~= nil and eintrag.diagnose ~= ''
|
|
eintrag.firstname, eintrag.lastname = nil, nil
|
|
eintrag.erstellt_von, eintrag.diagnose = nil, nil
|
|
end
|
|
|
|
local function Zaehle(sql, parameter)
|
|
return tonumber(MySQL.scalar.await(sql, parameter)) or 0
|
|
end
|
|
|
|
local zaehler = {
|
|
meine = Zaehle([[
|
|
SELECT COUNT(*) FROM medical_bericht
|
|
WHERE status = 'entwurf' AND erstellt_von = ?
|
|
]], { ich }),
|
|
freigabe = Zaehle("SELECT COUNT(*) FROM medical_bericht WHERE status = 'eingereicht'"),
|
|
freigegeben = Zaehle([[
|
|
SELECT COUNT(*) FROM medical_bericht
|
|
WHERE status = 'freigegeben' AND DATE(erstellt_am) = CURDATE()
|
|
]]),
|
|
-- Eigene Berichte, die noch bei der Freigabe liegen
|
|
meineOffen = Zaehle([[
|
|
SELECT COUNT(*) FROM medical_bericht
|
|
WHERE status = 'eingereicht' AND erstellt_von = ?
|
|
]], { ich }),
|
|
}
|
|
|
|
return {
|
|
ordner = ordner,
|
|
liste = liste,
|
|
zaehler = zaehler,
|
|
status = Medizin.Config.Berichtstatus,
|
|
felder = FELDER,
|
|
rechte = {
|
|
erstellen = Rechte.Hat(mitarbeiter, 'bericht.erstellen'),
|
|
freigeben = Rechte.Hat(mitarbeiter, 'bericht.freigeben'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- EINZELNER BERICHT
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('bericht', 'oeffnen', nil, function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
-- Ein fremder Entwurf ist noch niemandes Dokument ausser dem des Verfassers
|
|
if eintrag.status == 'entwurf' and eintrag.erstellt_von ~= mitarbeiter.charakterId then
|
|
return nil, 'Dieser Entwurf ist noch nicht eingereicht.'
|
|
end
|
|
|
|
-- Schreiben darf nur, wer den Entwurf verfasst hat und das Recht dazu hat
|
|
local aenderbar = DarfAendern(eintrag, mitarbeiter)
|
|
local darfAendern = aenderbar and Rechte.Hat(mitarbeiter, 'bericht.erstellen')
|
|
|
|
local unterschriften = AlsListe(eintrag.unterschriften)
|
|
local anlagen = AlsListe(eintrag.anlagen)
|
|
|
|
-- Steht Verfasser und Freigabe in einer Person? Das wird gezeigt,
|
|
-- nicht verhindert.
|
|
local selbstFreigabe = false
|
|
for _, zeile in ipairs(unterschriften) do
|
|
if zeile.art == 'freigabe' and zeile.charakterId == eintrag.erstellt_von then
|
|
selbstFreigabe = true
|
|
end
|
|
end
|
|
|
|
local spur = Protokoll.Lies({ zielTyp = 'bericht', zielId = berichtId, limit = 40 })
|
|
for _, zeile in ipairs(spur) do
|
|
zeile.autor = zeile.firstname
|
|
and (zeile.firstname .. ' ' .. zeile.lastname) or 'System'
|
|
zeile.firstname, zeile.lastname, zeile.details = nil, nil, nil
|
|
end
|
|
|
|
return {
|
|
bericht = {
|
|
id = eintrag.id, nummer = eintrag.nummer, titel = eintrag.titel,
|
|
status = eintrag.status,
|
|
patient = eintrag.patient,
|
|
patientName = eintrag.firstname
|
|
and (eintrag.firstname .. ' ' .. eintrag.lastname) or nil,
|
|
patientGeburt = eintrag.dob and tostring(eintrag.dob):sub(1, 10) or nil,
|
|
behandlung = eintrag.behandlung,
|
|
behandlungKennung = eintrag.behandlungKennung,
|
|
einsatz = eintrag.einsatz,
|
|
einsatzNummer = eintrag.einsatzNummer,
|
|
einsatzTitel = eintrag.einsatzTitel,
|
|
symptome = eintrag.symptome, untersuchung = eintrag.untersuchung,
|
|
diagnose = eintrag.diagnose, massnahmen = eintrag.massnahmen,
|
|
empfehlung = eintrag.empfehlung,
|
|
autor = NameVon(eintrag.erstellt_von),
|
|
vonMir = eintrag.erstellt_von == mitarbeiter.charakterId,
|
|
erstelltAm = eintrag.erstellt_am,
|
|
selbstFreigabe = selbstFreigabe,
|
|
},
|
|
unterschriften = unterschriften,
|
|
anlagen = anlagen,
|
|
spur = spur,
|
|
felder = FELDER,
|
|
status = Medizin.Config.Berichtstatus,
|
|
rechte = {
|
|
erstellen = Rechte.Hat(mitarbeiter, 'bericht.erstellen'),
|
|
freigeben = Rechte.Hat(mitarbeiter, 'bericht.freigeben'),
|
|
aendern = darfAendern,
|
|
},
|
|
}
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- ANLEGEN UND SCHREIBEN
|
|
-- =====================================================================
|
|
|
|
--- Patientensuche und die Vorgänge, an die ein Bericht hängen kann
|
|
Kern.RegistriereAktion('bericht', 'quellen', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local begriff = type(daten.begriff) == 'string' and daten.begriff or ''
|
|
local patientId = tonumber(daten.patient)
|
|
|
|
local treffer = {}
|
|
if #begriff >= 2 then
|
|
local muster = '%' .. begriff .. '%'
|
|
treffer = MySQL.query.await([[
|
|
SELECT c.id, c.firstname, c.lastname, c.dob, p.blutgruppe
|
|
FROM aether_characters c
|
|
LEFT JOIN medical_patient p ON p.`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.name = eintrag.firstname .. ' ' .. eintrag.lastname
|
|
eintrag.dob = tostring(eintrag.dob):sub(1, 10)
|
|
eintrag.firstname, eintrag.lastname = nil, nil
|
|
end
|
|
end
|
|
|
|
-- Behandlungen und Einsätze des gewählten Patienten
|
|
local behandlungen, einsaetze = {}, {}
|
|
if patientId then
|
|
behandlungen = MySQL.query.await([[
|
|
SELECT id, kennung, diagnose, status, erstellt_am
|
|
FROM medical_behandlung
|
|
WHERE patient = ?
|
|
ORDER BY id DESC LIMIT 25
|
|
]], { patientId }) or {}
|
|
|
|
einsaetze = MySQL.query.await([[
|
|
SELECT e.id, e.nummer, e.titel, e.kategorie, e.erstellt_am
|
|
FROM medical_einsatz e
|
|
WHERE EXISTS (SELECT 1 FROM medical_behandlung b
|
|
WHERE b.einsatz = e.id AND b.patient = ?)
|
|
ORDER BY e.id DESC LIMIT 15
|
|
]], { patientId }) or {}
|
|
end
|
|
|
|
return { treffer = treffer, behandlungen = behandlungen, einsaetze = einsaetze }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('bericht', 'anlegen', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local titel = Text(daten.titel, 160)
|
|
if not titel then return nil, 'Bitte einen Titel angeben.' end
|
|
|
|
-- Patient, Behandlung und Einsatz sind freiwillig — nicht jeder
|
|
-- Bericht gehört zu einem einzelnen Vorgang
|
|
local patientId = tonumber(daten.patient)
|
|
if patientId then
|
|
local person = MySQL.scalar.await(
|
|
'SELECT id FROM aether_characters WHERE id = ? AND deleted = 0', { patientId })
|
|
if not person then return nil, 'Diese Person ist nicht erfasst.' end
|
|
end
|
|
|
|
local behandlungId = tonumber(daten.behandlung)
|
|
if behandlungId then
|
|
local behandlung = MySQL.query.await(
|
|
'SELECT id, patient FROM medical_behandlung WHERE id = ?', { behandlungId })
|
|
if not behandlung or not behandlung[1] then return nil, 'Unbekannte Behandlung.' end
|
|
-- Die Behandlung bestimmt den Patienten, wenn keiner gewählt wurde
|
|
patientId = patientId or behandlung[1].patient
|
|
end
|
|
|
|
local einsatzId = tonumber(daten.einsatz)
|
|
if einsatzId then
|
|
local einsatz = MySQL.scalar.await(
|
|
'SELECT id FROM medical_einsatz WHERE id = ?', { einsatzId })
|
|
if not einsatz then return nil, 'Unbekannter Einsatz.' end
|
|
end
|
|
|
|
local nummer = Kern.ErzeugeKennung('BER', 'medical_bericht', 'nummer')
|
|
|
|
local berichtId = MySQL.insert.await([[
|
|
INSERT INTO medical_bericht
|
|
(nummer, patient, behandlung, einsatz, titel, status, erstellt_von)
|
|
VALUES (?, ?, ?, ?, ?, 'entwurf', ?)
|
|
]], { nummer, patientId, behandlungId, einsatzId, titel, mitarbeiter.charakterId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Bericht angelegt',
|
|
{ typ = 'bericht', id = berichtId },
|
|
{ nummer = nummer, titel = titel, patient = patientId,
|
|
behandlung = behandlungId, einsatz = einsatzId })
|
|
|
|
Kern.Loese('bericht.angelegt', berichtId, mitarbeiter)
|
|
|
|
return { id = berichtId, nummer = nummer }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('bericht', 'speichern', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
local darf, fehler = DarfAendern(eintrag, mitarbeiter)
|
|
if not darf then return nil, fehler end
|
|
|
|
local titel = Text(daten.titel, 160) or eintrag.titel
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_bericht
|
|
SET titel = ?, symptome = ?, untersuchung = ?, diagnose = ?,
|
|
massnahmen = ?, empfehlung = ?
|
|
WHERE id = ?
|
|
]], {
|
|
titel,
|
|
Text(daten.symptome, 4000),
|
|
Text(daten.untersuchung, 4000),
|
|
Text(daten.diagnose, 4000),
|
|
Text(daten.massnahmen, 4000),
|
|
Text(daten.empfehlung, 4000),
|
|
berichtId,
|
|
})
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Entwurf gespeichert',
|
|
{ typ = 'bericht', id = berichtId }, { nummer = eintrag.nummer })
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
--- Übernimmt den Wortlaut einer Behandlung in den Entwurf.
|
|
---
|
|
--- Das ist ein Kopiervorgang, den ein Mensch auslöst — das System
|
|
--- fasst nichts zusammen und formuliert nichts um. Vorhandener Text
|
|
--- im Bericht wird nicht überschrieben, sondern bleibt stehen.
|
|
Kern.RegistriereAktion('bericht', 'uebernehmen', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
local behandlungId = tonumber(daten.behandlung)
|
|
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
if not behandlungId then return nil, 'Keine Behandlung gewählt.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
local darf, fehler = DarfAendern(eintrag, mitarbeiter)
|
|
if not darf then return nil, fehler end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT id, kennung, patient, symptome, untersuchung, diagnose,
|
|
massnahmen, empfehlung
|
|
FROM medical_behandlung WHERE id = ?
|
|
]], { behandlungId })
|
|
if not zeilen or not zeilen[1] then return nil, 'Behandlung nicht gefunden.' end
|
|
|
|
local behandlung = zeilen[1]
|
|
|
|
-- Nur leere Felder füllen: was der Verfasser geschrieben hat, bleibt
|
|
local uebernommen = {}
|
|
local werte = {}
|
|
|
|
for _, feld in ipairs(FELDER) do
|
|
local vorhanden = Text(eintrag[feld.id], 4000)
|
|
local quelle_ = Text(behandlung[feld.id], 4000)
|
|
|
|
if not vorhanden and quelle_ then
|
|
werte[feld.id] = quelle_
|
|
uebernommen[#uebernommen + 1] = feld.name
|
|
else
|
|
werte[feld.id] = vorhanden
|
|
end
|
|
end
|
|
|
|
if #uebernommen == 0 then
|
|
return nil, 'Es gibt nichts zu übernehmen — alle Felder sind bereits gefüllt oder die Behandlung ist leer.'
|
|
end
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_bericht
|
|
SET symptome = ?, untersuchung = ?, diagnose = ?, massnahmen = ?,
|
|
empfehlung = ?, behandlung = COALESCE(behandlung, ?),
|
|
patient = COALESCE(patient, ?)
|
|
WHERE id = ?
|
|
]], { werte.symptome, werte.untersuchung, werte.diagnose, werte.massnahmen,
|
|
werte.empfehlung, behandlungId, behandlung.patient, berichtId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Wortlaut aus Behandlung übernommen',
|
|
{ typ = 'bericht', id = berichtId },
|
|
{ nummer = eintrag.nummer, behandlung = behandlung.kennung,
|
|
felder = uebernommen })
|
|
|
|
return { ok = true, uebernommen = uebernommen }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- ANLAGEN
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('bericht', 'anlage', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
local darf, fehler = DarfAendern(eintrag, mitarbeiter)
|
|
if not darf then return nil, fehler end
|
|
|
|
local anlagen = AlsListe(eintrag.anlagen)
|
|
local entfernen = tonumber(daten.entfernen)
|
|
|
|
if entfernen then
|
|
-- Entfernen ist nur im Entwurf möglich; ab der Einreichung
|
|
-- gehört die Anlage zum Dokument
|
|
if entfernen < 1 or entfernen > #anlagen then
|
|
return nil, 'Diese Anlage gibt es nicht.'
|
|
end
|
|
|
|
local weg = table.remove(anlagen, entfernen)
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Anlage entfernt',
|
|
{ typ = 'bericht', id = berichtId },
|
|
{ nummer = eintrag.nummer, titel = weg and weg.titel or nil })
|
|
else
|
|
local titel = Text(daten.titel, 120)
|
|
local adresse = Adresse(daten.adresse)
|
|
|
|
if not titel then return nil, 'Bitte die Anlage benennen.' end
|
|
if not adresse then return nil, 'Die Adresse muss mit http:// oder https:// beginnen.' end
|
|
if #anlagen >= 12 then return nil, 'Mehr als zwölf Anlagen sind nicht vorgesehen.' end
|
|
|
|
anlagen[#anlagen + 1] = {
|
|
titel = titel, adresse = adresse,
|
|
von = mitarbeiter.name, am = Jetzt(),
|
|
}
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Anlage hinzugefügt',
|
|
{ typ = 'bericht', id = berichtId },
|
|
{ nummer = eintrag.nummer, titel = titel })
|
|
end
|
|
|
|
MySQL.update.await('UPDATE medical_bericht SET anlagen = ? WHERE id = ?',
|
|
{ #anlagen > 0 and json.encode(anlagen) or nil, berichtId })
|
|
|
|
return { ok = true, anzahl = #anlagen }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- EINREICHEN UND ZURÜCKZIEHEN
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('bericht', 'einreichen', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
local darf, fehler = DarfAendern(eintrag, mitarbeiter)
|
|
if not darf then return nil, fehler end
|
|
|
|
-- Ein leerer Bericht ist kein Bericht. Welche Felder fehlen, wird
|
|
-- nur benannt — die Einreichung hält das nicht auf.
|
|
local gefuellt, leer = 0, {}
|
|
for _, feld in ipairs(FELDER) do
|
|
if Text(eintrag[feld.id], 4000) then gefuellt = gefuellt + 1
|
|
else leer[#leer + 1] = feld.name end
|
|
end
|
|
|
|
if gefuellt == 0 then
|
|
return nil, 'Der Bericht enthält noch keinen Text.'
|
|
end
|
|
|
|
local unterschriften = AlsListe(eintrag.unterschriften)
|
|
unterschriften[#unterschriften + 1] = Unterschrift(mitarbeiter, 'verfasser')
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_bericht SET status = 'eingereicht', unterschriften = ?
|
|
WHERE id = ?
|
|
]], { json.encode(unterschriften), berichtId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Bericht eingereicht',
|
|
{ typ = 'bericht', id = berichtId },
|
|
{ nummer = eintrag.nummer, leereFelder = leer })
|
|
|
|
-- Wer freigeben darf, erfährt davon
|
|
for _, person in pairs(Dienst.AlleImDienst()) do
|
|
if Rechte.Hat(person, 'bericht.freigeben')
|
|
and person.charakterId ~= mitarbeiter.charakterId then
|
|
Hinweise.Erstelle({ charakterId = person.charakterId }, {
|
|
kategorie = 'doku', prioritaet = 'normal',
|
|
titel = 'Bericht liegt zur Freigabe',
|
|
text = ('%s — %s, von %s.')
|
|
:format(eintrag.nummer, eintrag.titel, mitarbeiter.name),
|
|
zielTyp = 'bericht', zielId = berichtId,
|
|
})
|
|
end
|
|
end
|
|
|
|
Kern.Loese('bericht.eingereicht', berichtId, mitarbeiter)
|
|
|
|
return { ok = true, leereFelder = leer }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('bericht', 'zurueckziehen', 'bericht.erstellen', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local grund = Text(daten.grund, 255)
|
|
if not grund then return nil, 'Bitte angeben, warum der Bericht zurückgeht.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
if eintrag.status ~= 'eingereicht' then
|
|
return nil, 'Nur ein eingereichter Bericht lässt sich zurückziehen.'
|
|
end
|
|
if eintrag.erstellt_von ~= mitarbeiter.charakterId then
|
|
return nil, 'Zurückziehen kann nur, wer den Bericht geschrieben hat.'
|
|
end
|
|
|
|
-- Die geleistete Unterschrift bleibt stehen: sie wurde geleistet.
|
|
-- Dass der Bericht danach wieder geändert wurde, steht im Protokoll.
|
|
MySQL.update.await("UPDATE medical_bericht SET status = 'entwurf' WHERE id = ?",
|
|
{ berichtId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Bericht zurückgezogen',
|
|
{ typ = 'bericht', id = berichtId }, { nummer = eintrag.nummer }, grund)
|
|
|
|
Kern.Loese('bericht.zurueckgezogen', berichtId, mitarbeiter)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- FREIGABE
|
|
--
|
|
-- Die Freigabe ist eine Unterschrift. Sie geschieht nie automatisch,
|
|
-- nie nach Ablauf einer Frist und nie durch das System.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('bericht', 'freigeben', 'bericht.freigeben', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
if eintrag.status ~= 'eingereicht' then
|
|
return nil, 'Nur ein eingereichter Bericht wird freigegeben.'
|
|
end
|
|
|
|
local unterschriften = AlsListe(eintrag.unterschriften)
|
|
unterschriften[#unterschriften + 1] = Unterschrift(mitarbeiter, 'freigabe')
|
|
|
|
-- Verfasser und Freigabe in einer Person ist möglich und wird
|
|
-- festgehalten — verhindert wird es nicht.
|
|
local selbstFreigabe = eintrag.erstellt_von == mitarbeiter.charakterId
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_bericht SET status = 'freigegeben', unterschriften = ?
|
|
WHERE id = ?
|
|
]], { json.encode(unterschriften), berichtId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Bericht freigegeben',
|
|
{ typ = 'bericht', id = berichtId },
|
|
{ nummer = eintrag.nummer, selbstFreigabe = selbstFreigabe },
|
|
Text(daten.vermerk, 255))
|
|
|
|
if not selbstFreigabe then
|
|
Hinweise.Erstelle({ charakterId = eintrag.erstellt_von }, {
|
|
kategorie = 'doku', prioritaet = 'normal',
|
|
titel = 'Dein Bericht wurde freigegeben',
|
|
text = ('%s — %s, freigegeben von %s.')
|
|
:format(eintrag.nummer, eintrag.titel, mitarbeiter.name),
|
|
zielTyp = 'bericht', zielId = berichtId,
|
|
})
|
|
end
|
|
|
|
Kern.Loese('bericht.freigegeben', berichtId, mitarbeiter)
|
|
|
|
return { ok = true, selbstFreigabe = selbstFreigabe }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('bericht', 'zurueckweisen', 'bericht.freigeben', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local grund = Text(daten.grund, 500)
|
|
if not grund or #grund < 5 then
|
|
return nil, 'Eine Zurückweisung braucht eine Begründung.'
|
|
end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
if eintrag.status ~= 'eingereicht' then
|
|
return nil, 'Nur ein eingereichter Bericht lässt sich zurückweisen.'
|
|
end
|
|
|
|
MySQL.update.await("UPDATE medical_bericht SET status = 'entwurf' WHERE id = ?",
|
|
{ berichtId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Bericht zurückgewiesen',
|
|
{ typ = 'bericht', id = berichtId }, { nummer = eintrag.nummer }, grund)
|
|
|
|
Hinweise.Erstelle({ charakterId = eintrag.erstellt_von }, {
|
|
kategorie = 'doku', prioritaet = 'hoch',
|
|
titel = 'Bericht zurückgewiesen',
|
|
text = ('%s — %s: %s'):format(eintrag.nummer, mitarbeiter.name, grund:sub(1, 120)),
|
|
zielTyp = 'bericht', zielId = berichtId,
|
|
})
|
|
|
|
Kern.Loese('bericht.zurueckgewiesen', berichtId, mitarbeiter)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
--- Zeichnet einen Bericht gegen.
|
|
--- Eine Gegenzeichnung ändert nichts am Wortlaut — sie fügt nur eine
|
|
--- weitere Unterschrift hinzu. Zurücknehmen lässt sie sich nicht.
|
|
Kern.RegistriereAktion('bericht', 'gegenzeichnen', 'bericht.freigeben', function(quelle, mitarbeiter, daten)
|
|
local berichtId = tonumber(daten.id)
|
|
if not berichtId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local eintrag = LadeBericht(berichtId)
|
|
if not eintrag then return nil, 'Bericht nicht gefunden.' end
|
|
|
|
if eintrag.status == 'entwurf' then
|
|
return nil, 'Ein Entwurf wird nicht gegengezeichnet.'
|
|
end
|
|
|
|
local unterschriften = AlsListe(eintrag.unterschriften)
|
|
|
|
-- Zweimal dieselbe Unterschrift sagt nichts Zusätzliches
|
|
for _, zeile in ipairs(unterschriften) do
|
|
if zeile.charakterId == mitarbeiter.charakterId
|
|
and zeile.art == 'gegenzeichnung' then
|
|
return nil, 'Du hast diesen Bericht bereits gegengezeichnet.'
|
|
end
|
|
end
|
|
|
|
unterschriften[#unterschriften + 1] = Unterschrift(mitarbeiter, 'gegenzeichnung')
|
|
|
|
MySQL.update.await('UPDATE medical_bericht SET unterschriften = ? WHERE id = ?',
|
|
{ json.encode(unterschriften), berichtId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'bericht', 'Bericht gegengezeichnet',
|
|
{ typ = 'bericht', id = berichtId }, { nummer = eintrag.nummer },
|
|
Text(daten.vermerk, 255))
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Schnittstelle für andere Ressourcen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Liefert die freigegebenen Berichte eines Patienten.
|
|
--- Reine Auskunft — keine Ressource kann darüber einen Bericht
|
|
--- schreiben, ändern oder freigeben. Entwürfe bleiben aussen vor.
|
|
--- @param patientId number Charakter
|
|
--- @return table Liste
|
|
exports('HoleBerichte', function(patientId)
|
|
if not tonumber(patientId) then return {} end
|
|
|
|
return MySQL.query.await([[
|
|
SELECT nummer, titel, diagnose, erstellt_am
|
|
FROM medical_bericht
|
|
WHERE patient = ? AND status = 'freigegeben'
|
|
ORDER BY id DESC
|
|
LIMIT 50
|
|
]], { tonumber(patientId) }) or {}
|
|
end)
|