-- ===================================================================== -- aether-medical / Modul: Blutbank -- -- Ein Lagerbuch für Blutkonserven. Nichts weiter. -- -- Das System transfundiert nicht, wählt keine Konserve aus und -- verwirft keine. Eine abgelaufene Konserve bleibt so lange im -- Bestand, bis ein Mensch sie herausnimmt — das Ablaufdatum ist -- eine Erinnerung, keine Sperre. -- -- Die Verträglichkeitstabelle ist eine Nachschlagehilfe. Passt eine -- Konserve laut Tabelle nicht zum Empfänger, wird das deutlich -- angezeigt und mitprotokolliert — verhindert wird es nicht. Ob -- transfundiert wird, entscheidet ausschliesslich ein Arzt. -- ===================================================================== Kern.RegistriereModul({ id = 'blutbank', name = 'Blutbank', icon = '🩸', reihenfolge = 11, recht = 'blut.sehen', beschreibung = 'Bestand, Reservierungen, Ausgabe und Verfallsdaten', }) -- Status und ihre erlaubten Übergänge. Jeder Schritt braucht einen -- Menschen — es gibt keinen Weg, den das System von selbst geht. local UEBERGAENGE = { vorraetig = { reserviert = true, ausgegeben = true, verworfen = true }, reserviert = { vorraetig = true, ausgegeben = true, verworfen = true }, ausgegeben = {}, verworfen = {}, } -- --------------------------------------------------------------------- -- 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 --- Prüft eine Blutgruppe gegen die Konfiguration --- @param wert any Eingabe --- @return string|nil Gültige Blutgruppe local function Blutgruppe(wert) local gesucht = tostring(wert or '') for _, gruppe in ipairs(Medizin.Config.Blutgruppen) do if gruppe == gesucht then return gruppe end end return nil end --- Prüft einen Lagerort gegen die Konfiguration --- @param wert any Eingabe --- @return string|nil Gültiger Lagerort local function Lagerort(wert) local gesucht = tostring(wert or '') for _, ort in ipairs(Medizin.Config.Blutlagerorte) do if ort == gesucht then return ort end end return nil end --- Prüft ein Datum im Format JJJJ-MM-TT --- @param wert any Eingabe --- @return string|nil local function Datum(wert) local roh = Text(wert, 10) if not roh then return nil end if not roh:match('^%d%d%d%d%-%d%d%-%d%d$') then return nil end return roh end --- Passt eine Konserve laut Nachschlagetabelle zum Empfänger? --- --- Liefert `true`, wenn die Kombination in der Tabelle steht, und --- `nil`, wenn sich dazu nichts sagen lässt — etwa weil die --- Blutgruppe des Patienten nie bestimmt wurde. Der Rückgabewert --- sperrt nichts; er wird nur angezeigt und protokolliert. --- @param empfaenger string|nil Blutgruppe des Patienten --- @param konserve string Blutgruppe der Konserve --- @return boolean|nil local function Vertraeglich(empfaenger, konserve) if not empfaenger or empfaenger == '' then return nil end local erlaubt = Medizin.Config.Blutvertraeglichkeit[empfaenger] if not erlaubt then return nil end for _, gruppe in ipairs(erlaubt) do if gruppe == konserve then return true end end return false end --- Lädt eine Konserve mit den Daten des Empfängers --- @param konserveId number Konserve --- @return table|nil local function LadeKonserve(konserveId) local zeilen = MySQL.query.await([[ SELECT b.*, DATEDIFF(b.verfaellt_am, CURDATE()) AS tage, c.firstname, c.lastname, p.blutgruppe AS empfaengerGruppe FROM medical_blut b LEFT JOIN aether_characters c ON c.id = b.reserviert_fuer LEFT JOIN medical_patient p ON p.`character` = b.reserviert_fuer WHERE b.id = ? ]], { konserveId }) return zeilen and zeilen[1] or nil end --- Prüft, ob ein Statuswechsel überhaupt vorgesehen ist --- @param von string Aktueller Status --- @param nach string Gewünschter Status --- @return boolean local function DarfWechseln(von, nach) return (UEBERGAENGE[von] or {})[nach] == true end -- ===================================================================== -- ÜBERSICHT -- ===================================================================== Kern.RegistriereAktion('blutbank', 'uebersicht', nil, function(quelle, mitarbeiter, daten) local ordner = tostring(daten.ordner or 'vorraetig') local vorlauf = Medizin.Config.Erinnerungen.blutVorlaufTage -- Welche Status gehören in diesen Ordner? local bedingung, werte if ordner == 'reserviert' then bedingung = "b.status = 'reserviert'" elseif ordner == 'archiv' then bedingung = "b.status IN ('ausgegeben', 'verworfen')" else bedingung = "b.status = 'vorraetig'" end werte = {} if daten.gruppe and daten.gruppe ~= '' then bedingung = bedingung .. ' AND b.blutgruppe = ?' werte[#werte + 1] = tostring(daten.gruppe) end -- Im Bestand steht die älteste Konserve oben. Das ist eine -- Sortierung, keine Vorgabe — welche genommen wird, entscheidet -- weiterhin ein Mensch. local sortierung = ordner == 'archiv' and 'b.id DESC' or 'b.verfaellt_am IS NULL, b.verfaellt_am, b.konserve' local liste = MySQL.query.await(([[ SELECT b.id, b.konserve, b.blutgruppe, b.menge_ml, b.lagerort, b.status, b.eingelagert_am, b.eingelagert_von, b.verfaellt_am, b.reserviert_fuer, b.reserviert_von, b.ausgegeben_von, b.ausgegeben_am, DATEDIFF(b.verfaellt_am, CURDATE()) AS tage, c.firstname, c.lastname, p.blutgruppe AS empfaengerGruppe FROM medical_blut b LEFT JOIN aether_characters c ON c.id = b.reserviert_fuer LEFT JOIN medical_patient p ON p.`character` = b.reserviert_fuer WHERE %s ORDER BY %s LIMIT 200 ]]):format(bedingung, sortierung), werte) or {} for _, eintrag in ipairs(liste) do eintrag.tage = eintrag.verfaellt_am and tonumber(eintrag.tage) or nil -- Abgelaufen heisst hier nur: das Datum ist vorbei. Sonst nichts. eintrag.abgelaufen = eintrag.tage ~= nil and eintrag.tage <= 0 eintrag.laeuftBaldAb = eintrag.tage ~= nil and eintrag.tage > 0 and eintrag.tage <= vorlauf eintrag.empfaenger = eintrag.firstname and (eintrag.firstname .. ' ' .. eintrag.lastname) or nil -- Bewusst ohne "and ... or": ein `false` darf hier nicht zu `nil` -- werden, sonst verschwindet genau die Abweichung, um die es geht. if eintrag.reserviert_fuer then eintrag.vertraeglich = Vertraeglich(eintrag.empfaengerGruppe, eintrag.blutgruppe) end eintrag.eingelagertVon = NameVon(eintrag.eingelagert_von) eintrag.reserviertVon = eintrag.reserviert_von and NameVon(eintrag.reserviert_von) or nil eintrag.ausgegebenVon = eintrag.ausgegeben_von and NameVon(eintrag.ausgegeben_von) or nil eintrag.firstname, eintrag.lastname = nil, nil eintrag.eingelagert_von, eintrag.reserviert_von, eintrag.ausgegeben_von = nil, nil, nil end -- Bestand je Blutgruppe local bestand = {} for _, gruppe in ipairs(Medizin.Config.Blutgruppen) do local zeilen = MySQL.query.await([[ SELECT status, COUNT(*) AS anzahl FROM medical_blut WHERE blutgruppe = ? AND status IN ('vorraetig', 'reserviert') GROUP BY status ]], { gruppe }) or {} local eintrag = { gruppe = gruppe, vorraetig = 0, reserviert = 0 } for _, zeile in ipairs(zeilen) do eintrag[zeile.status] = tonumber(zeile.anzahl) or 0 end eintrag.baldAb = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_blut WHERE blutgruppe = ? AND status IN ('vorraetig', 'reserviert') AND verfaellt_am IS NOT NULL AND verfaellt_am <= DATE_ADD(CURDATE(), INTERVAL ? DAY) ]], { gruppe, vorlauf })) or 0 eintrag.knapp = eintrag.vorraetig <= Medizin.Config.Erinnerungen.blutMindestbestand bestand[#bestand + 1] = eintrag end -- Zählwerte über das ganze Lager local function Zaehle(sql, parameter) return tonumber(MySQL.scalar.await(sql, parameter)) or 0 end local zaehler = { vorraetig = Zaehle("SELECT COUNT(*) FROM medical_blut WHERE status = 'vorraetig'"), reserviert = Zaehle("SELECT COUNT(*) FROM medical_blut WHERE status = 'reserviert'"), baldAb = Zaehle([[ SELECT COUNT(*) FROM medical_blut WHERE status IN ('vorraetig', 'reserviert') AND verfaellt_am IS NOT NULL AND verfaellt_am <= DATE_ADD(CURDATE(), INTERVAL ? DAY) AND verfaellt_am >= CURDATE() ]], { vorlauf }), abgelaufen = Zaehle([[ SELECT COUNT(*) FROM medical_blut WHERE status IN ('vorraetig', 'reserviert') AND verfaellt_am IS NOT NULL AND verfaellt_am < CURDATE() ]]), ausgegebenHeute = Zaehle([[ SELECT COUNT(*) FROM medical_blut WHERE status = 'ausgegeben' AND DATE(ausgegeben_am) = CURDATE() ]]), } return { ordner = ordner, liste = liste, bestand = bestand, zaehler = zaehler, gruppen = Medizin.Config.Blutgruppen, status = Medizin.Config.Blutstatus, lagerorte = Medizin.Config.Blutlagerorte, vertraeglichkeit = Medizin.Config.Blutvertraeglichkeit, vorlaufTage = vorlauf, mindestbestand = Medizin.Config.Erinnerungen.blutMindestbestand, rechte = { verwalten = Rechte.Hat(mitarbeiter, 'blut.verwalten'), }, } end) -- ===================================================================== -- EINZELNE KONSERVE -- ===================================================================== Kern.RegistriereAktion('blutbank', 'konserve', nil, function(quelle, mitarbeiter, daten) local konserveId = tonumber(daten.id) if not konserveId then return nil, 'Ungültige Kennung.' end local eintrag = LadeKonserve(konserveId) if not eintrag then return nil, 'Konserve nicht gefunden.' end local tage = eintrag.verfaellt_am and tonumber(eintrag.tage) or nil -- Auch hier gilt: `false` ist eine Aussage und darf nicht zu `nil` werden local passt = nil if eintrag.reserviert_fuer then passt = Vertraeglich(eintrag.empfaengerGruppe, eintrag.blutgruppe) end -- Der Weg der Konserve steht im Protokoll und wird nur gelesen local spur = Protokoll.Lies({ zielTyp = 'blut', zielId = konserveId, limit = 50 }) for _, zeile in ipairs(spur) do zeile.autor = zeile.firstname and (zeile.firstname .. ' ' .. zeile.lastname) or 'System' zeile.firstname, zeile.lastname = nil, nil zeile.details = nil end return { konserve = { id = eintrag.id, kennung = eintrag.konserve, blutgruppe = eintrag.blutgruppe, menge = eintrag.menge_ml, lagerort = eintrag.lagerort, status = eintrag.status, eingelagertAm = eintrag.eingelagert_am, eingelagertVon = NameVon(eintrag.eingelagert_von), verfaelltAm = eintrag.verfaellt_am, tage = tage, abgelaufen = tage ~= nil and tage <= 0, empfaenger = eintrag.reserviert_fuer, empfaengerName = eintrag.firstname and (eintrag.firstname .. ' ' .. eintrag.lastname) or nil, empfaengerGruppe = eintrag.empfaengerGruppe, vertraeglich = passt, reserviertVon = eintrag.reserviert_von and NameVon(eintrag.reserviert_von) or nil, ausgegebenAm = eintrag.ausgegeben_am, ausgegebenVon = eintrag.ausgegeben_von and NameVon(eintrag.ausgegeben_von) or nil, }, spur = spur, rechte = { verwalten = Rechte.Hat(mitarbeiter, 'blut.verwalten'), }, } end) -- ===================================================================== -- EINLAGERN -- ===================================================================== Kern.RegistriereAktion('blutbank', 'einlagern', 'blut.verwalten', function(quelle, mitarbeiter, daten) local gruppe = Blutgruppe(daten.blutgruppe) if not gruppe then return nil, 'Unbekannte Blutgruppe.' end local menge = math.floor(tonumber(daten.menge) or 0) if menge < 50 or menge > 1000 then return nil, 'Eine Konserve fasst zwischen 50 und 1000 ml.' end local anzahl = math.floor(tonumber(daten.anzahl) or 1) if anzahl < 1 or anzahl > 20 then return nil, 'Es lassen sich 1 bis 20 Konserven auf einmal einlagern.' end local lagerort = Lagerort(daten.lagerort) if not lagerort then return nil, 'Unbekannter Lagerort.' end -- Ein Verfallsdatum ist freiwillig; ohne eines erinnert später nichts local verfaellt = Datum(daten.verfaellt) if daten.verfaellt and daten.verfaellt ~= '' and not verfaellt then return nil, 'Das Verfallsdatum braucht die Form JJJJ-MM-TT.' end local angelegt = {} for _ = 1, anzahl do local kennung = Kern.ErzeugeKennung('BK', 'medical_blut', 'konserve') local id = MySQL.insert.await([[ INSERT INTO medical_blut (konserve, blutgruppe, menge_ml, lagerort, eingelagert_von, verfaellt_am) VALUES (?, ?, ?, ?, ?, ?) ]], { kennung, gruppe, menge, lagerort, mitarbeiter.charakterId, verfaellt }) angelegt[#angelegt + 1] = { id = id, kennung = kennung } Protokoll.Schreibe(mitarbeiter, 'blutbank', 'Konserve eingelagert', { typ = 'blut', id = id }, { konserve = kennung, blutgruppe = gruppe, menge = menge, lagerort = lagerort, verfaellt = verfaellt }, Text(daten.vermerk, 255)) end Kern.Loese('blut.eingelagert', gruppe, anzahl, mitarbeiter) return { angelegt = angelegt, anzahl = #angelegt } end) -- ===================================================================== -- RESERVIEREN -- -- Eine Reservierung legt eine Konserve für einen Patienten beiseite. -- Sie transfundiert nichts und verpflichtet zu nichts. -- ===================================================================== --- Personensuche für die Reservierung Kern.RegistriereAktion('blutbank', 'patientSuche', 'blut.verwalten', function(quelle, mitarbeiter, daten) local begriff = type(daten.begriff) == 'string' and daten.begriff or '' if #begriff < 2 then return { treffer = {} } end local muster = '%' .. begriff .. '%' local treffer = MySQL.query.await([[ SELECT c.id, c.firstname, c.lastname, c.dob, p.blutgruppe, p.allergien, p.hinweis, (SELECT a.status FROM medical_aufnahme a WHERE a.patient = c.id AND a.status IN ('wartend', 'aufgenommen') LIMIT 1) AS laufend 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.dob = tostring(eintrag.dob):sub(1, 10) end return { treffer = treffer } end) Kern.RegistriereAktion('blutbank', 'reservieren', 'blut.verwalten', function(quelle, mitarbeiter, daten) local konserveId = tonumber(daten.id) local patientId = tonumber(daten.patient) if not konserveId then return nil, 'Ungültige Kennung.' end if not patientId then return nil, 'Kein Patient gewählt.' end local eintrag = LadeKonserve(konserveId) if not eintrag then return nil, 'Konserve nicht gefunden.' end if not DarfWechseln(eintrag.status, 'reserviert') then return nil, ('Eine Konserve im Status "%s" lässt sich nicht reservieren.') :format(eintrag.status) end local person = MySQL.query.await([[ SELECT c.firstname, c.lastname, p.blutgruppe FROM aether_characters c LEFT JOIN medical_patient p ON p.`character` = c.id WHERE c.id = ? AND c.deleted = 0 ]], { patientId }) if not person or not person[1] then return nil, 'Diese Person ist nicht erfasst.' end -- Die Tabelle sagt etwas dazu — entschieden wird trotzdem hier oben local passt = Vertraeglich(person[1].blutgruppe, eintrag.blutgruppe) MySQL.update.await([[ UPDATE medical_blut SET status = 'reserviert', reserviert_fuer = ?, reserviert_von = ? WHERE id = ? ]], { patientId, mitarbeiter.charakterId, konserveId }) Protokoll.Schreibe(mitarbeiter, 'blutbank', 'Konserve reserviert', { typ = 'blut', id = konserveId }, { konserve = eintrag.konserve, blutgruppe = eintrag.blutgruppe, patient = patientId, patientGruppe = person[1].blutgruppe, lautTabelleVertraeglich = passt }, Text(daten.vermerk, 255)) -- Eine Abweichung von der Tabelle wird deutlich gemeldet, nicht verhindert if passt == false then Hinweise.Erstelle(nil, { kategorie = 'blut', prioritaet = 'hoch', titel = 'Reservierung weicht von der Verträglichkeitstabelle ab', text = ('%s (%s) für %s %s mit Blutgruppe %s — bitte prüfen.') :format(eintrag.konserve, eintrag.blutgruppe, person[1].firstname, person[1].lastname, person[1].blutgruppe), zielTyp = 'blut', zielId = konserveId, }) end Kern.Loese('blut.reserviert', konserveId, patientId, mitarbeiter) return { ok = true, vertraeglich = passt } end) Kern.RegistriereAktion('blutbank', 'reservierungLoesen', 'blut.verwalten', function(quelle, mitarbeiter, daten) local konserveId = tonumber(daten.id) if not konserveId then return nil, 'Ungültige Kennung.' end local grund = Text(daten.grund, 255) if not grund then return nil, 'Bitte angeben, warum die Reservierung entfällt.' end local eintrag = LadeKonserve(konserveId) if not eintrag then return nil, 'Konserve nicht gefunden.' end if eintrag.status ~= 'reserviert' then return nil, 'Diese Konserve ist nicht reserviert.' end MySQL.update.await([[ UPDATE medical_blut SET status = 'vorraetig', reserviert_fuer = NULL, reserviert_von = NULL WHERE id = ? ]], { konserveId }) Protokoll.Schreibe(mitarbeiter, 'blutbank', 'Reservierung aufgehoben', { typ = 'blut', id = konserveId }, { konserve = eintrag.konserve, patient = eintrag.reserviert_fuer }, grund) Kern.Loese('blut.freigegeben', konserveId, mitarbeiter) return { ok = true } end) -- ===================================================================== -- AUSGABE -- -- Dokumentiert, dass eine Konserve das Lager verlassen hat. Was mit -- ihr geschieht, entscheidet und verantwortet der Arzt am Patienten. -- ===================================================================== Kern.RegistriereAktion('blutbank', 'ausgeben', 'blut.verwalten', function(quelle, mitarbeiter, daten) local konserveId = tonumber(daten.id) if not konserveId then return nil, 'Ungültige Kennung.' end local eintrag = LadeKonserve(konserveId) if not eintrag then return nil, 'Konserve nicht gefunden.' end if not DarfWechseln(eintrag.status, 'ausgegeben') then return nil, ('Eine Konserve im Status "%s" lässt sich nicht ausgeben.') :format(eintrag.status) end -- Ohne Reservierung muss bei der Ausgabe gesagt werden, für wen local patientId = tonumber(daten.patient) or eintrag.reserviert_fuer if not patientId then return nil, 'Bitte angeben, für welchen Patienten.' end local vermerk = Text(daten.vermerk, 255) if not vermerk then return nil, 'Bitte einen Vermerk zur Ausgabe hinterlassen.' end local person = MySQL.query.await([[ SELECT c.firstname, c.lastname, p.blutgruppe FROM aether_characters c LEFT JOIN medical_patient p ON p.`character` = c.id WHERE c.id = ? AND c.deleted = 0 ]], { patientId }) if not person or not person[1] then return nil, 'Diese Person ist nicht erfasst.' end local passt = Vertraeglich(person[1].blutgruppe, eintrag.blutgruppe) local tage = eintrag.verfaellt_am and tonumber(eintrag.tage) or nil local abgelaufen = tage ~= nil and tage <= 0 MySQL.update.await([[ UPDATE medical_blut SET status = 'ausgegeben', reserviert_fuer = ?, ausgegeben_von = ?, ausgegeben_am = NOW() WHERE id = ? ]], { patientId, mitarbeiter.charakterId, konserveId }) Protokoll.Schreibe(mitarbeiter, 'blutbank', 'Konserve ausgegeben', { typ = 'blut', id = konserveId }, { konserve = eintrag.konserve, blutgruppe = eintrag.blutgruppe, patient = patientId, patientGruppe = person[1].blutgruppe, lautTabelleVertraeglich = passt, abgelaufen = abgelaufen }, vermerk) if passt == false then Hinweise.Erstelle(nil, { kategorie = 'blut', prioritaet = 'kritisch', titel = 'Ausgabe weicht von der Verträglichkeitstabelle ab', text = ('%s (%s) an %s %s mit Blutgruppe %s — ausgegeben von %s.') :format(eintrag.konserve, eintrag.blutgruppe, person[1].firstname, person[1].lastname, person[1].blutgruppe, mitarbeiter.name), zielTyp = 'blut', zielId = konserveId, }) end if abgelaufen then Hinweise.Erstelle(nil, { kategorie = 'blut', prioritaet = 'hoch', titel = 'Abgelaufene Konserve ausgegeben', text = ('%s war seit %d Tagen über dem Datum — ausgegeben von %s.') :format(eintrag.konserve, math.abs(tage), mitarbeiter.name), zielTyp = 'blut', zielId = konserveId, }) end Kern.Loese('blut.ausgegeben', konserveId, patientId, mitarbeiter) return { ok = true, vertraeglich = passt, abgelaufen = abgelaufen } end) -- ===================================================================== -- VERWERFEN -- -- Auch abgelaufene Konserven verschwinden nicht von selbst. Ob eine -- Konserve noch brauchbar ist, ist eine fachliche Beurteilung — -- keine Datumsrechnung. -- ===================================================================== Kern.RegistriereAktion('blutbank', 'verwerfen', 'blut.verwalten', function(quelle, mitarbeiter, daten) local konserveId = tonumber(daten.id) if not konserveId then return nil, 'Ungültige Kennung.' end local grund = Text(daten.grund, 255) if not grund or #grund < 3 then return nil, 'Das Verwerfen einer Konserve erfordert eine Begründung.' end local eintrag = LadeKonserve(konserveId) if not eintrag then return nil, 'Konserve nicht gefunden.' end if not DarfWechseln(eintrag.status, 'verworfen') then return nil, ('Eine Konserve im Status "%s" lässt sich nicht verwerfen.') :format(eintrag.status) end local warReserviert = eintrag.status == 'reserviert' MySQL.update.await("UPDATE medical_blut SET status = 'verworfen' WHERE id = ?", { konserveId }) Protokoll.Schreibe(mitarbeiter, 'blutbank', 'Konserve verworfen', { typ = 'blut', id = konserveId }, { konserve = eintrag.konserve, blutgruppe = eintrag.blutgruppe, vorherigerStatus = eintrag.status, verfaellt = eintrag.verfaellt_am }, grund) -- Eine verworfene Reservierung darf niemandem entgehen if warReserviert and eintrag.reserviert_fuer then Hinweise.Erstelle(nil, { kategorie = 'blut', prioritaet = 'hoch', titel = 'Reservierte Konserve verworfen', text = ('%s war für %s %s zurückgelegt.') :format(eintrag.konserve, eintrag.firstname or '', eintrag.lastname or ''), zielTyp = 'blut', zielId = konserveId, }) end Kern.Loese('blut.verworfen', konserveId, mitarbeiter) return { ok = true } end) -- ===================================================================== -- LAGERORT -- ===================================================================== Kern.RegistriereAktion('blutbank', 'umlagern', 'blut.verwalten', function(quelle, mitarbeiter, daten) local konserveId = tonumber(daten.id) if not konserveId then return nil, 'Ungültige Kennung.' end local lagerort = Lagerort(daten.lagerort) if not lagerort then return nil, 'Unbekannter Lagerort.' end local eintrag = LadeKonserve(konserveId) if not eintrag then return nil, 'Konserve nicht gefunden.' end if eintrag.status ~= 'vorraetig' and eintrag.status ~= 'reserviert' then return nil, 'Diese Konserve liegt nicht mehr im Lager.' end MySQL.update.await('UPDATE medical_blut SET lagerort = ? WHERE id = ?', { lagerort, konserveId }) Protokoll.Schreibe(mitarbeiter, 'blutbank', 'Konserve umgelagert', { typ = 'blut', id = konserveId }, { konserve = eintrag.konserve, von = eintrag.lagerort, nach = lagerort }, Text(daten.vermerk, 255)) return { ok = true } end) -- --------------------------------------------------------------------- -- Schnittstelle für andere Ressourcen -- --------------------------------------------------------------------- --- Liefert den Bestand je Blutgruppe. --- Reine Auskunft — keine Ressource kann darüber eine Konserve --- reservieren, ausgeben oder verwerfen. --- @return table Liste { blutgruppe, vorraetig, reserviert } exports('HoleBlutbestand', function() local liste = {} for _, gruppe in ipairs(Medizin.Config.Blutgruppen) do liste[#liste + 1] = { blutgruppe = gruppe, vorraetig = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_blut WHERE blutgruppe = ? AND status = 'vorraetig' ]], { gruppe })) or 0, reserviert = tonumber(MySQL.scalar.await([[ SELECT COUNT(*) FROM medical_blut WHERE blutgruppe = ? AND status = 'reserviert' ]], { gruppe })) or 0, } end return liste end)