- 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>
572 lines
24 KiB
Lua
572 lines
24 KiB
Lua
-- =====================================================================
|
|
-- aether-medical / Modul: Medikamentenverwaltung
|
|
--
|
|
-- Bestandsführung und Katalogpflege. Nichts weiter.
|
|
--
|
|
-- Das System gibt kein Medikament aus, verordnet keines, bestellt
|
|
-- nichts nach und entsorgt nichts. Es zählt, was Menschen gebucht
|
|
-- haben, und erinnert, wenn ein Bestand knapp wird oder ein Datum
|
|
-- abläuft. Was daraufhin geschieht, entscheidet die Apotheke.
|
|
--
|
|
-- Abgelaufene Medikamente werden AUFFÄLLIG MARKIERT, aber nicht
|
|
-- gesperrt. Ob ein Präparat noch verwendbar ist, ist eine fachliche
|
|
-- Beurteilung — keine Datumsrechnung.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereModul({
|
|
id = 'medikament', name = 'Medikamente', icon = '💊',
|
|
reihenfolge = 8, recht = 'medikament.sehen',
|
|
beschreibung = 'Bestand, Buchungen, Verfallsdaten und BTM-Buch',
|
|
})
|
|
|
|
-- Buchungsarten und ihre Wirkung auf den Bestand
|
|
local BUCHUNGSARTEN = {
|
|
eingang = { name = 'Wareneingang', richtung = 1 },
|
|
ruecknahme = { name = 'Rücknahme', richtung = 1 },
|
|
ausgabe = { name = 'Ausgabe', richtung = -1 },
|
|
verfall = { name = 'Verfall entnommen', richtung = -1 },
|
|
korrektur = { name = 'Korrektur', richtung = 0 },
|
|
}
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 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 Kategorie gegen die Konfiguration
|
|
local function Kategorie(kennung)
|
|
for _, eintrag in ipairs(Medizin.Config.Medikamentkategorien) do
|
|
if eintrag.id == kennung then return eintrag.id end
|
|
end
|
|
return 'sonstiges'
|
|
end
|
|
|
|
-- =====================================================================
|
|
-- BESTANDSÜBERSICHT
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('medikament', 'uebersicht', nil, function(quelle, mitarbeiter, daten)
|
|
local bedingungen, werte = {}, {}
|
|
|
|
if daten.stillgelegt == true then
|
|
bedingungen[#bedingungen + 1] = 'm.aktiv = 0'
|
|
else
|
|
bedingungen[#bedingungen + 1] = 'm.aktiv = 1'
|
|
end
|
|
|
|
if daten.kategorie and daten.kategorie ~= '' then
|
|
bedingungen[#bedingungen + 1] = 'm.kategorie = ?'
|
|
werte[#werte + 1] = daten.kategorie
|
|
end
|
|
|
|
-- Schnellfilter der Übersicht
|
|
local filter = tostring(daten.filter or '')
|
|
if filter == 'knapp' then
|
|
bedingungen[#bedingungen + 1] = 'm.bestand <= m.mindestbestand'
|
|
elseif filter == 'leer' then
|
|
bedingungen[#bedingungen + 1] = 'm.bestand <= 0'
|
|
elseif filter == 'verfall' then
|
|
bedingungen[#bedingungen + 1] =
|
|
'm.verfaellt_am IS NOT NULL AND m.verfaellt_am <= DATE_ADD(CURDATE(), INTERVAL ? DAY)'
|
|
werte[#werte + 1] = Medizin.Config.Erinnerungen.medikamentVorlaufTage
|
|
elseif filter == 'btm' then
|
|
bedingungen[#bedingungen + 1] = 'm.betaeubungsmittel = 1'
|
|
end
|
|
|
|
if type(daten.begriff) == 'string' and #daten.begriff >= 2 then
|
|
bedingungen[#bedingungen + 1] =
|
|
'(m.kennung LIKE ? OR m.name LIKE ? OR m.wirkstoff LIKE ?)'
|
|
local muster = '%' .. daten.begriff .. '%'
|
|
for _ = 1, 3 do werte[#werte + 1] = muster end
|
|
end
|
|
|
|
local liste = MySQL.query.await(([[
|
|
SELECT m.id, m.kennung, m.name, m.wirkstoff, m.kategorie,
|
|
m.dosierung_standard, m.einheit, m.bestand, m.mindestbestand,
|
|
m.lagerort, m.verfaellt_am, m.hinweis, m.betaeubungsmittel, m.aktiv,
|
|
DATEDIFF(m.verfaellt_am, CURDATE()) AS tageBisVerfall,
|
|
(SELECT COUNT(*) FROM medical_medikament_buchung b
|
|
WHERE b.medikament = m.id AND b.art = 'ausgabe'
|
|
AND b.gebucht_am >= DATE_SUB(NOW(), INTERVAL 7 DAY)) AS ausgabenWoche
|
|
FROM medical_medikament m
|
|
WHERE %s
|
|
ORDER BY m.kategorie, m.sortierung, m.name
|
|
LIMIT 300
|
|
]]):format(table.concat(bedingungen, ' AND ')), werte) or {}
|
|
|
|
local kategorieNamen = {}
|
|
for _, eintrag in ipairs(Medizin.Config.Medikamentkategorien) do
|
|
kategorieNamen[eintrag.id] = eintrag.name
|
|
end
|
|
|
|
for _, eintrag in ipairs(liste) do
|
|
eintrag.kategorieName = kategorieNamen[eintrag.kategorie] or eintrag.kategorie
|
|
eintrag.btm = eintrag.betaeubungsmittel == 1
|
|
eintrag.aktiv = eintrag.aktiv == 1
|
|
eintrag.knapp = eintrag.bestand <= eintrag.mindestbestand
|
|
eintrag.leer = eintrag.bestand <= 0
|
|
local tage = tonumber(eintrag.tageBisVerfall)
|
|
eintrag.abgelaufen = tage ~= nil and tage < 0
|
|
eintrag.laeuftAb = tage ~= nil and tage >= 0
|
|
and tage <= Medizin.Config.Erinnerungen.medikamentVorlaufTage
|
|
eintrag.betaeubungsmittel = nil
|
|
end
|
|
|
|
return {
|
|
liste = liste,
|
|
kategorien = Medizin.Config.Medikamentkategorien,
|
|
buchungsarten = BUCHUNGSARTEN,
|
|
filter = filter,
|
|
stillgelegt = daten.stillgelegt == true,
|
|
zaehler = {
|
|
gesamt = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_medikament WHERE aktiv = 1')) or 0,
|
|
knapp = tonumber(MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM medical_medikament
|
|
WHERE aktiv = 1 AND bestand > 0 AND bestand <= mindestbestand
|
|
]])) or 0,
|
|
leer = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_medikament WHERE aktiv = 1 AND bestand <= 0')) or 0,
|
|
verfall = tonumber(MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM medical_medikament
|
|
WHERE aktiv = 1 AND verfaellt_am IS NOT NULL
|
|
AND verfaellt_am <= DATE_ADD(CURDATE(), INTERVAL ? DAY)
|
|
]], { Medizin.Config.Erinnerungen.medikamentVorlaufTage })) or 0,
|
|
btm = tonumber(MySQL.scalar.await(
|
|
'SELECT COUNT(*) FROM medical_medikament WHERE aktiv = 1 AND betaeubungsmittel = 1')) or 0,
|
|
},
|
|
rechte = {
|
|
bestand = Rechte.Hat(mitarbeiter, 'medikament.bestand'),
|
|
btm = Rechte.Hat(mitarbeiter, 'medikament.btm'),
|
|
katalog = Rechte.Hat(mitarbeiter, 'katalog.bearbeiten'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
Kern.RegistriereAktion('medikament', 'oeffnen', nil, function(quelle, mitarbeiter, daten)
|
|
local medikamentId = tonumber(daten.id)
|
|
if not medikamentId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT m.*, DATEDIFF(m.verfaellt_am, CURDATE()) AS tageBisVerfall
|
|
FROM medical_medikament m WHERE m.id = ?
|
|
]], { medikamentId })
|
|
if not zeilen or not zeilen[1] then return nil, 'Medikament nicht gefunden.' end
|
|
|
|
local m = zeilen[1]
|
|
|
|
-- Buchungshistorie
|
|
local buchungen = MySQL.query.await([[
|
|
SELECT b.id, b.art, b.menge, b.bestand_danach, b.grund, b.gebucht_am,
|
|
b.gebucht_von, b.patient,
|
|
c.firstname, c.lastname
|
|
FROM medical_medikament_buchung b
|
|
LEFT JOIN aether_characters c ON c.id = b.patient
|
|
WHERE b.medikament = ?
|
|
ORDER BY b.id DESC LIMIT 60
|
|
]], { medikamentId }) or {}
|
|
|
|
for _, buchung in ipairs(buchungen) do
|
|
buchung.artName = BUCHUNGSARTEN[buchung.art] and BUCHUNGSARTEN[buchung.art].name
|
|
or buchung.art
|
|
buchung.autor = NameVon(buchung.gebucht_von)
|
|
buchung.patientName = buchung.firstname
|
|
and (buchung.firstname .. ' ' .. buchung.lastname) or nil
|
|
buchung.firstname, buchung.lastname, buchung.gebucht_von = nil, nil, nil
|
|
end
|
|
|
|
-- Zuletzt dokumentierte Gaben
|
|
local gaben = MySQL.query.await([[
|
|
SELECT g.id, g.menge, g.dosierung, g.gegeben_am, g.gegeben_von, g.hinweis,
|
|
b.kennung AS behandlung, c.firstname, c.lastname
|
|
FROM medical_behandlung_medikament g
|
|
JOIN medical_behandlung b ON b.id = g.behandlung
|
|
LEFT JOIN aether_characters c ON c.id = b.patient
|
|
WHERE g.medikament = ?
|
|
ORDER BY g.id DESC LIMIT 30
|
|
]], { medikamentId }) or {}
|
|
|
|
for _, gabe in ipairs(gaben) do
|
|
gabe.autor = NameVon(gabe.gegeben_von)
|
|
gabe.patientName = (gabe.firstname or 'Unbekannt') .. ' ' .. (gabe.lastname or '')
|
|
gabe.ohneBuchung = gabe.hinweis
|
|
and gabe.hinweis:find('ohne Bestandsbuchung', 1, true) ~= nil or false
|
|
gabe.firstname, gabe.lastname, gabe.gegeben_von = nil, nil, nil
|
|
end
|
|
|
|
local tage = tonumber(m.tageBisVerfall)
|
|
|
|
return {
|
|
medikament = {
|
|
id = m.id, kennung = m.kennung, name = m.name, wirkstoff = m.wirkstoff,
|
|
kategorie = m.kategorie, dosierung = m.dosierung_standard,
|
|
einheit = m.einheit, bestand = m.bestand, mindestbestand = m.mindestbestand,
|
|
lagerort = m.lagerort, verfaelltAm = m.verfaellt_am,
|
|
tageBisVerfall = tage,
|
|
abgelaufen = tage ~= nil and tage < 0,
|
|
hinweis = m.hinweis,
|
|
btm = m.betaeubungsmittel == 1,
|
|
aktiv = m.aktiv == 1,
|
|
sortierung = m.sortierung,
|
|
},
|
|
buchungen = buchungen,
|
|
gaben = gaben,
|
|
kategorien = Medizin.Config.Medikamentkategorien,
|
|
buchungsarten = BUCHUNGSARTEN,
|
|
rechte = {
|
|
bestand = Rechte.Hat(mitarbeiter, 'medikament.bestand'),
|
|
btm = Rechte.Hat(mitarbeiter, 'medikament.btm'),
|
|
katalog = Rechte.Hat(mitarbeiter, 'katalog.bearbeiten'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- BESTAND BUCHEN
|
|
--
|
|
-- Jede Buchung ist die Aufzeichnung dessen, was ein Mensch im Schrank
|
|
-- getan hat. Das System bewegt nichts von selbst.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('medikament', 'buchen', 'medikament.bestand', function(quelle, mitarbeiter, daten)
|
|
local medikamentId = tonumber(daten.id)
|
|
local art = tostring(daten.art or '')
|
|
local menge = math.floor(tonumber(daten.menge) or 0)
|
|
|
|
if not medikamentId then return nil, 'Ungültige Kennung.' end
|
|
if not BUCHUNGSARTEN[art] then return nil, 'Unbekannte Buchungsart.' end
|
|
if art == 'ausgabe' then
|
|
return nil, 'Eine Ausgabe entsteht nur aus einer dokumentierten Gabe.'
|
|
end
|
|
if menge < 1 or menge > 100000 then return nil, 'Ungültige Menge.' end
|
|
|
|
local grund = Text(daten.grund, 255)
|
|
if not grund then return nil, 'Bitte einen Grund für die Buchung angeben.' end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT id, kennung, name, einheit, bestand, mindestbestand, betaeubungsmittel
|
|
FROM medical_medikament WHERE id = ?
|
|
]], { medikamentId })
|
|
if not zeilen or not zeilen[1] then return nil, 'Medikament nicht gefunden.' end
|
|
|
|
local medikament = zeilen[1]
|
|
|
|
-- Betäubungsmittel erfordern zusätzlich das eigene Recht
|
|
if medikament.betaeubungsmittel == 1 and not Rechte.Hat(mitarbeiter, 'medikament.btm') then
|
|
return nil, 'Betäubungsmittel darfst du nicht buchen.'
|
|
end
|
|
|
|
local richtung = BUCHUNGSARTEN[art].richtung
|
|
local neuerBestand
|
|
|
|
if art == 'korrektur' then
|
|
-- Eine Korrektur setzt den Bestand auf den gezählten Wert
|
|
neuerBestand = menge
|
|
else
|
|
neuerBestand = medikament.bestand + (richtung * menge)
|
|
if neuerBestand < 0 then
|
|
return nil, ('Das ergäbe einen Bestand von %d. Vorrätig sind %d %s.')
|
|
:format(neuerBestand, medikament.bestand, medikament.einheit)
|
|
end
|
|
end
|
|
|
|
MySQL.update.await('UPDATE medical_medikament SET bestand = ? WHERE id = ?',
|
|
{ neuerBestand, medikamentId })
|
|
|
|
MySQL.insert.await([[
|
|
INSERT INTO medical_medikament_buchung
|
|
(medikament, art, menge, bestand_danach, grund, gebucht_von)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
]], { medikamentId, art, menge, neuerBestand, grund, mitarbeiter.charakterId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'medikament',
|
|
('Bestand gebucht: %s'):format(BUCHUNGSARTEN[art].name),
|
|
{ typ = 'medikament', id = medikamentId },
|
|
{ kennung = medikament.kennung, menge = menge,
|
|
vorher = medikament.bestand, nachher = neuerBestand,
|
|
btm = medikament.betaeubungsmittel == 1 }, grund)
|
|
|
|
-- Erinnern, wenn es knapp wird — nachbestellt wird nichts
|
|
if neuerBestand <= medikament.mindestbestand then
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'medikament',
|
|
prioritaet = neuerBestand <= 0 and 'hoch' or 'normal',
|
|
titel = neuerBestand <= 0 and 'Medikament nicht mehr vorrätig'
|
|
or 'Medikament unter Mindestbestand',
|
|
text = ('%s — noch %d %s.'):format(medikament.name, neuerBestand,
|
|
medikament.einheit),
|
|
zielTyp = 'medikament', zielId = medikamentId,
|
|
})
|
|
end
|
|
|
|
Kern.Loese('medikament.gebucht', medikamentId, art, mitarbeiter)
|
|
|
|
return { bestand = neuerBestand }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- KATALOG PFLEGEN
|
|
--
|
|
-- Werte hier sind Stammdaten und Vorschläge. Die Standarddosierung
|
|
-- ist ein Anhaltspunkt, keine Verordnung.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('medikament', 'speichern', 'katalog.bearbeiten', function(quelle, mitarbeiter, daten)
|
|
local kennung = Text(daten.kennung, 24)
|
|
local name = Text(daten.name, 120)
|
|
|
|
if not kennung or #kennung < 2 then return nil, 'Bitte eine Kennung angeben.' end
|
|
if not name or #name < 2 then return nil, 'Bitte einen Namen angeben.' end
|
|
|
|
kennung = kennung:gsub('%s', ''):upper()
|
|
|
|
local id = tonumber(daten.id)
|
|
local btm = daten.btm == true
|
|
|
|
-- Betäubungsmittel darf nur kennzeichnen, wer sie auch buchen darf
|
|
if btm and not Rechte.Hat(mitarbeiter, 'medikament.btm') then
|
|
return nil, 'Nur mit BTM-Recht lässt sich ein Präparat als Betäubungsmittel führen.'
|
|
end
|
|
|
|
local belegt = MySQL.scalar.await(
|
|
'SELECT id FROM medical_medikament WHERE kennung = ? AND (? IS NULL OR id <> ?)',
|
|
{ kennung, id, id })
|
|
if belegt then return nil, 'Diese Kennung ist bereits vergeben.' end
|
|
|
|
local mindestbestand = math.max(0, math.floor(tonumber(daten.mindestbestand) or 0))
|
|
local sortierung = math.floor(tonumber(daten.sortierung) or 0)
|
|
local verfaellt = Text(daten.verfaelltAm, 10) -- Format JJJJ-MM-TT
|
|
|
|
if verfaellt and not verfaellt:match('^%d%d%d%d%-%d%d%-%d%d$') then
|
|
return nil, 'Das Verfallsdatum muss im Format JJJJ-MM-TT stehen.'
|
|
end
|
|
|
|
if id then
|
|
local alt = MySQL.query.await(
|
|
'SELECT kennung, name, mindestbestand, verfaellt_am FROM medical_medikament WHERE id = ?',
|
|
{ id })
|
|
if not alt or not alt[1] then return nil, 'Medikament nicht gefunden.' end
|
|
|
|
MySQL.update.await([[
|
|
UPDATE medical_medikament
|
|
SET kennung = ?, name = ?, wirkstoff = ?, kategorie = ?,
|
|
dosierung_standard = ?, einheit = ?, mindestbestand = ?,
|
|
lagerort = ?, verfaellt_am = ?, hinweis = ?,
|
|
betaeubungsmittel = ?, sortierung = ?
|
|
WHERE id = ?
|
|
]], {
|
|
kennung, name, Text(daten.wirkstoff, 120), Kategorie(daten.kategorie),
|
|
Text(daten.dosierung, 64), Text(daten.einheit, 24) or 'Stück',
|
|
mindestbestand, Text(daten.lagerort, 64), verfaellt,
|
|
Text(daten.hinweis, 255), btm and 1 or 0, sortierung, id,
|
|
})
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'medikament', 'Medikament bearbeitet',
|
|
{ typ = 'medikament', id = id },
|
|
{ kennung = kennung,
|
|
verfallAlt = alt[1].verfaellt_am, verfallNeu = verfaellt,
|
|
mindestAlt = alt[1].mindestbestand, mindestNeu = mindestbestand })
|
|
else
|
|
-- Ein neues Präparat startet mit Bestand 0. Der Zugang wird gebucht.
|
|
id = MySQL.insert.await([[
|
|
INSERT INTO medical_medikament
|
|
(kennung, name, wirkstoff, kategorie, dosierung_standard, einheit,
|
|
bestand, mindestbestand, lagerort, verfaellt_am, hinweis,
|
|
betaeubungsmittel, sortierung)
|
|
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)
|
|
]], {
|
|
kennung, name, Text(daten.wirkstoff, 120), Kategorie(daten.kategorie),
|
|
Text(daten.dosierung, 64), Text(daten.einheit, 24) or 'Stück',
|
|
mindestbestand, Text(daten.lagerort, 64), verfaellt,
|
|
Text(daten.hinweis, 255), btm and 1 or 0, sortierung,
|
|
})
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'medikament', 'Medikament angelegt',
|
|
{ typ = 'medikament', id = id }, { kennung = kennung, btm = btm })
|
|
end
|
|
|
|
Kern.Loese('medikament.katalog', id, mitarbeiter)
|
|
|
|
return { id = id, kennung = kennung }
|
|
end)
|
|
|
|
--- Stilllegen statt löschen: alte Gaben bleiben nachvollziehbar
|
|
Kern.RegistriereAktion('medikament', 'schalten', 'katalog.bearbeiten', function(quelle, mitarbeiter, daten)
|
|
local medikamentId = tonumber(daten.id)
|
|
if not medikamentId then return nil, 'Ungültige Kennung.' end
|
|
|
|
local aktiv = daten.aktiv == true
|
|
|
|
local zeilen = MySQL.query.await(
|
|
'SELECT id, kennung, name, bestand, einheit FROM medical_medikament WHERE id = ?',
|
|
{ medikamentId })
|
|
if not zeilen or not zeilen[1] then return nil, 'Medikament nicht gefunden.' end
|
|
|
|
local medikament = zeilen[1]
|
|
|
|
-- Ein Restbestand wird gemeldet, aber nicht weggebucht
|
|
if not aktiv and medikament.bestand > 0 then
|
|
Hinweise.Erstelle(nil, {
|
|
kategorie = 'medikament', prioritaet = 'normal',
|
|
titel = 'Stillgelegtes Medikament mit Restbestand',
|
|
text = ('%s wurde stillgelegt, im Schrank liegen noch %d %s. Die Entnahme muss gebucht werden.')
|
|
:format(medikament.name, medikament.bestand, medikament.einheit),
|
|
zielTyp = 'medikament', zielId = medikamentId,
|
|
})
|
|
end
|
|
|
|
MySQL.update.await('UPDATE medical_medikament SET aktiv = ? WHERE id = ?',
|
|
{ aktiv and 1 or 0, medikamentId })
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'medikament',
|
|
aktiv and 'Medikament wieder freigegeben' or 'Medikament stillgelegt',
|
|
{ typ = 'medikament', id = medikamentId },
|
|
{ kennung = medikament.kennung, restbestand = medikament.bestand },
|
|
Text(daten.grund, 255))
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- BTM-BUCH
|
|
--
|
|
-- Lückenlose Bewegungsliste aller Betäubungsmittel. Reine Auskunft;
|
|
-- hier lässt sich nichts ändern.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('medikament', 'btmBuch', 'medikament.btm', function(quelle, mitarbeiter, daten)
|
|
local medikamentId = tonumber(daten.medikament)
|
|
|
|
local bedingungen = { 'm.betaeubungsmittel = 1' }
|
|
local werte = {}
|
|
|
|
if medikamentId then
|
|
bedingungen[#bedingungen + 1] = 'm.id = ?'
|
|
werte[#werte + 1] = medikamentId
|
|
end
|
|
|
|
local bewegungen = MySQL.query.await(([[
|
|
SELECT b.id, b.art, b.menge, b.bestand_danach, b.grund, b.gebucht_am,
|
|
b.gebucht_von, b.patient,
|
|
m.id AS medikamentId, m.kennung, m.name, m.einheit,
|
|
c.firstname, c.lastname
|
|
FROM medical_medikament_buchung b
|
|
JOIN medical_medikament m ON m.id = b.medikament
|
|
LEFT JOIN aether_characters c ON c.id = b.patient
|
|
WHERE %s
|
|
ORDER BY b.id DESC
|
|
LIMIT 200
|
|
]]):format(table.concat(bedingungen, ' AND ')), werte) or {}
|
|
|
|
for _, eintrag in ipairs(bewegungen) do
|
|
eintrag.artName = BUCHUNGSARTEN[eintrag.art] and BUCHUNGSARTEN[eintrag.art].name
|
|
or eintrag.art
|
|
eintrag.autor = NameVon(eintrag.gebucht_von)
|
|
eintrag.patientName = eintrag.firstname
|
|
and (eintrag.firstname .. ' ' .. eintrag.lastname) or nil
|
|
eintrag.firstname, eintrag.lastname, eintrag.gebucht_von = nil, nil, nil
|
|
end
|
|
|
|
-- Gaben, die ohne Bestandsbuchung dokumentiert wurden.
|
|
-- Das ist keine Unregelmässigkeit, sondern ein bewusster Vermerk —
|
|
-- er gehört aber ins Buch, damit die Zahlen erklärbar bleiben.
|
|
local ohneBuchung = MySQL.query.await([[
|
|
SELECT g.id, g.menge, g.gegeben_am, g.gegeben_von, g.hinweis,
|
|
m.kennung, m.name, m.einheit,
|
|
bh.kennung AS behandlung,
|
|
c.firstname, c.lastname
|
|
FROM medical_behandlung_medikament g
|
|
JOIN medical_medikament m ON m.id = g.medikament
|
|
JOIN medical_behandlung bh ON bh.id = g.behandlung
|
|
LEFT JOIN aether_characters c ON c.id = bh.patient
|
|
WHERE m.betaeubungsmittel = 1
|
|
AND g.hinweis LIKE '%ohne Bestandsbuchung%'
|
|
ORDER BY g.id DESC LIMIT 60
|
|
]]) or {}
|
|
|
|
for _, eintrag in ipairs(ohneBuchung) do
|
|
eintrag.autor = NameVon(eintrag.gegeben_von)
|
|
eintrag.patientName = (eintrag.firstname or 'Unbekannt')
|
|
.. ' ' .. (eintrag.lastname or '')
|
|
eintrag.firstname, eintrag.lastname, eintrag.gegeben_von = nil, nil, nil
|
|
end
|
|
|
|
-- Aktueller Stand aller Betäubungsmittel
|
|
local bestaende = MySQL.query.await([[
|
|
SELECT id, kennung, name, einheit, bestand, mindestbestand, lagerort,
|
|
verfaellt_am, DATEDIFF(verfaellt_am, CURDATE()) AS tageBisVerfall
|
|
FROM medical_medikament
|
|
WHERE betaeubungsmittel = 1 AND aktiv = 1
|
|
ORDER BY name
|
|
]]) or {}
|
|
|
|
for _, eintrag in ipairs(bestaende) do
|
|
local tage = tonumber(eintrag.tageBisVerfall)
|
|
eintrag.abgelaufen = tage ~= nil and tage < 0
|
|
end
|
|
|
|
return {
|
|
bewegungen = bewegungen,
|
|
ohneBuchung = ohneBuchung,
|
|
bestaende = bestaende,
|
|
gewaehlt = medikamentId,
|
|
}
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Schnittstelle für andere Ressourcen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Liefert den Bestand eines Medikaments
|
|
--- @param kennung string Kennung, z. B. 'ANA-01'
|
|
--- @return table|nil
|
|
exports('HoleBestand', function(kennung)
|
|
if type(kennung) ~= 'string' then return nil end
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT kennung, name, einheit, bestand, mindestbestand, verfaellt_am, aktiv
|
|
FROM medical_medikament WHERE kennung = ?
|
|
]], { kennung })
|
|
return zeilen and zeilen[1] or nil
|
|
end)
|
|
|
|
--- Meldet einen Wareneingang aus einer anderen Ressource.
|
|
--- Der buchende Mitarbeiter muss im Dienst sein — das System bucht nie
|
|
--- aus eigenem Antrieb.
|
|
--- @param charakterId number Buchender Mitarbeiter
|
|
--- @param kennung string Medikament
|
|
--- @param menge number Anzahl
|
|
--- @param grund string Begründung
|
|
--- @return number|nil Neuer Bestand
|
|
exports('BucheEingang', function(charakterId, kennung, menge, grund)
|
|
local quelle = Dienst.QuelleVonCharakter(tonumber(charakterId) or 0)
|
|
local mitarbeiter = quelle and Dienst.Hole(quelle)
|
|
if not mitarbeiter then return nil end
|
|
|
|
local medikament = MySQL.scalar.await(
|
|
'SELECT id FROM medical_medikament WHERE kennung = ?', { tostring(kennung) })
|
|
if not medikament then return nil end
|
|
|
|
local ergebnis = Kern.FuehreAus(quelle, mitarbeiter, 'medikament', 'buchen', {
|
|
id = medikament, art = 'eingang',
|
|
menge = menge, grund = grund or 'Wareneingang',
|
|
})
|
|
|
|
return ergebnis and ergebnis.bestand or nil
|
|
end)
|