- 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>
297 lines
10 KiB
Lua
297 lines
10 KiB
Lua
-- =====================================================================
|
|
-- aether-medical — Dienstsystem
|
|
--
|
|
-- Speichert ausschliesslich Statusinformationen. Es gibt keine
|
|
-- automatische Dienstkleidung und keine automatische Fahrzeugausgabe.
|
|
-- Auch die Funktion (RTW, Notarzt, OP, Leitstelle) wählt jeder selbst.
|
|
-- =====================================================================
|
|
|
|
Dienst = {}
|
|
|
|
-- Mitarbeiter im Dienst: [quelle] = mitarbeiter
|
|
local imDienst = {}
|
|
-- Wer hält die Leitstelle? (Server-ID oder nil)
|
|
local leitstelle = nil
|
|
|
|
local Core = exports['aether-core']
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Zugriff
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Liefert die Daten eines Mitarbeiters im Dienst
|
|
--- @param quelle number Server-ID
|
|
--- @return table|nil
|
|
function Dienst.Hole(quelle)
|
|
return imDienst[quelle]
|
|
end
|
|
|
|
--- Liefert alle Mitarbeiter im Dienst
|
|
--- @return table [quelle] = mitarbeiter
|
|
function Dienst.AlleImDienst()
|
|
return imDienst
|
|
end
|
|
|
|
--- Sucht die Server-ID zu einer Charakter-ID
|
|
--- @param charakterId number Charakter
|
|
--- @return number|nil
|
|
function Dienst.QuelleVonCharakter(charakterId)
|
|
for quelle, mitarbeiter in pairs(imDienst) do
|
|
if mitarbeiter.charakterId == charakterId then return quelle end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- Wer hält die Leitstelle?
|
|
--- @return number|nil Server-ID
|
|
function Dienst.Leitstelle()
|
|
return leitstelle
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Personalakte laden
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Lädt den Personaldatensatz und baut daraus den Mitarbeiter
|
|
--- @param quelle number Server-ID
|
|
--- @param charakterId number Charakter
|
|
--- @return table|nil Mitarbeiter, string|nil Fehler
|
|
function Dienst.LadeMitarbeiter(quelle, charakterId)
|
|
local zeilen = MySQL.query.await(
|
|
'SELECT * FROM medical_personal WHERE `character` = ? AND aktiv = 1', { charakterId })
|
|
|
|
if not zeilen or not zeilen[1] then
|
|
return nil, 'Du gehörst nicht zum Personal der Klinik.'
|
|
end
|
|
|
|
local personal = zeilen[1]
|
|
local person = Core:HoleSpielerInfo(quelle)
|
|
local rolle = Medizin.HoleRolle(personal.rolle)
|
|
|
|
return {
|
|
quelle = quelle,
|
|
charakterId = charakterId,
|
|
name = ((person and person.vorname or '') .. ' ' ..
|
|
(person and person.nachname or '')),
|
|
personalnummer = personal.personalnummer,
|
|
rolle = personal.rolle,
|
|
rolleName = rolle and rolle.name or personal.rolle,
|
|
stufe = Medizin.Stufe(personal.rolle),
|
|
station = personal.station,
|
|
rechte = Rechte.Berechne(personal),
|
|
status = 'verfuegbar',
|
|
funktion = 'station',
|
|
seit = os.time(),
|
|
dienstId = nil,
|
|
}
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Dienst antreten und beenden
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Meldet einen Mitarbeiter zum Dienst an
|
|
--- @param quelle number Server-ID
|
|
--- @param funktion string|nil Gewählte Dienstfunktion
|
|
--- @return table|nil Mitarbeiter, string|nil Fehler
|
|
function Dienst.Antreten(quelle, funktion)
|
|
if imDienst[quelle] then return imDienst[quelle] end
|
|
|
|
local charakterId = Core:HoleCharakterId(quelle)
|
|
if not charakterId then return nil, 'Kein Charakter geladen.' end
|
|
|
|
local mitarbeiter, fehler = Dienst.LadeMitarbeiter(quelle, charakterId)
|
|
if not mitarbeiter then return nil, fehler end
|
|
|
|
if not Rechte.Hat(mitarbeiter, 'dienst.antreten') then
|
|
return nil, 'Dir fehlt die Berechtigung, den Dienst anzutreten.'
|
|
end
|
|
|
|
-- Funktion prüfen; manche erfordern ein eigenes Recht
|
|
local gewaehlt = Dienst.PruefeFunktion(mitarbeiter, funktion)
|
|
mitarbeiter.funktion = gewaehlt
|
|
|
|
-- Dienstzeit protokollieren
|
|
mitarbeiter.dienstId = MySQL.insert.await(
|
|
'INSERT INTO medical_dienst (`character`, station, funktion) VALUES (?, ?, ?)',
|
|
{ charakterId, mitarbeiter.station, gewaehlt })
|
|
|
|
imDienst[quelle] = mitarbeiter
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'dienst', 'Dienst angetreten',
|
|
nil, { funktion = gewaehlt, station = mitarbeiter.station })
|
|
Kern.Loese('dienst.angetreten', quelle, mitarbeiter)
|
|
Dienst.Verteile()
|
|
|
|
return mitarbeiter
|
|
end
|
|
|
|
--- Prüft eine gewünschte Dienstfunktion und liefert eine zulässige zurück
|
|
--- @param mitarbeiter table Mitarbeiter
|
|
--- @param funktion string|nil Wunsch
|
|
--- @return string Zulässige Funktion
|
|
function Dienst.PruefeFunktion(mitarbeiter, funktion)
|
|
for _, eintrag in ipairs(Medizin.Config.Dienstfunktionen) do
|
|
if eintrag.id == funktion then
|
|
if not eintrag.recht or Rechte.Hat(mitarbeiter, eintrag.recht) then
|
|
return eintrag.id
|
|
end
|
|
break
|
|
end
|
|
end
|
|
-- Im Zweifel der einfache Stationsdienst
|
|
return 'station'
|
|
end
|
|
|
|
--- Meldet einen Mitarbeiter vom Dienst ab
|
|
--- @param quelle number Server-ID
|
|
function Dienst.Beenden(quelle)
|
|
local mitarbeiter = imDienst[quelle]
|
|
if not mitarbeiter then return end
|
|
|
|
if mitarbeiter.dienstId then
|
|
MySQL.update('UPDATE medical_dienst SET ende = NOW(), sekunden = ? WHERE id = ?',
|
|
{ os.time() - mitarbeiter.seit, mitarbeiter.dienstId })
|
|
end
|
|
|
|
imDienst[quelle] = nil
|
|
|
|
-- Leitstelle freigeben, wenn der Inhaber geht
|
|
if leitstelle == quelle then
|
|
leitstelle = nil
|
|
end
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'dienst', 'Dienst beendet')
|
|
Kern.Loese('dienst.beendet', quelle, mitarbeiter)
|
|
Dienst.Verteile()
|
|
end
|
|
|
|
--- Setzt den Status (verfügbar, am Patienten, Pause ...)
|
|
--- @param quelle number Server-ID
|
|
--- @param status string Statuskennung
|
|
--- @return boolean
|
|
function Dienst.SetzeStatus(quelle, status)
|
|
local mitarbeiter = imDienst[quelle]
|
|
if not mitarbeiter then return false end
|
|
|
|
local gueltig = false
|
|
for _, eintrag in ipairs(Medizin.Config.Dienststatus) do
|
|
if eintrag.id == status then gueltig = true break end
|
|
end
|
|
if not gueltig then return false end
|
|
|
|
mitarbeiter.status = status
|
|
Kern.Loese('dienst.status', quelle, mitarbeiter, status)
|
|
Dienst.Verteile()
|
|
return true
|
|
end
|
|
|
|
--- Wechselt die Dienstfunktion im laufenden Dienst
|
|
--- @param quelle number Server-ID
|
|
--- @param funktion string Gewünschte Funktion
|
|
--- @return boolean, string|nil
|
|
function Dienst.SetzeFunktion(quelle, funktion)
|
|
local mitarbeiter = imDienst[quelle]
|
|
if not mitarbeiter then return false end
|
|
|
|
local gewaehlt = Dienst.PruefeFunktion(mitarbeiter, funktion)
|
|
if gewaehlt ~= funktion then
|
|
return false, 'Dir fehlt die Berechtigung für diese Funktion.'
|
|
end
|
|
|
|
mitarbeiter.funktion = gewaehlt
|
|
|
|
if mitarbeiter.dienstId then
|
|
MySQL.update('UPDATE medical_dienst SET funktion = ? WHERE id = ?',
|
|
{ gewaehlt, mitarbeiter.dienstId })
|
|
end
|
|
|
|
Protokoll.Schreibe(mitarbeiter, 'dienst', 'Dienstfunktion gewechselt',
|
|
nil, { funktion = gewaehlt })
|
|
Kern.Loese('dienst.funktion', quelle, mitarbeiter, gewaehlt)
|
|
Dienst.Verteile()
|
|
return true
|
|
end
|
|
|
|
--- Übernimmt oder überträgt die Leitstelle
|
|
--- @param quelle number Handelnder
|
|
--- @param zielQuelle number|nil Ziel (nil = selbst übernehmen)
|
|
--- @return boolean, string|nil
|
|
function Dienst.SetzeLeitstelle(quelle, zielQuelle)
|
|
local mitarbeiter = imDienst[quelle]
|
|
if not mitarbeiter then return false end
|
|
|
|
if not Rechte.Hat(mitarbeiter, 'dienst.leitstelle') then
|
|
return false, 'Dir fehlt die Berechtigung für die Leitstelle.'
|
|
end
|
|
|
|
if zielQuelle and zielQuelle ~= quelle then
|
|
-- Zuweisen an andere setzt voraus, dass man über ihnen steht
|
|
local ziel = imDienst[zielQuelle]
|
|
if not ziel then return false, 'Diese Person ist nicht im Dienst.' end
|
|
if not Rechte.StehtUeber(mitarbeiter, ziel.rolle) then
|
|
return false, 'Du kannst die Leitstelle nur an rangniedrigere Kollegen übertragen.'
|
|
end
|
|
leitstelle = zielQuelle
|
|
Protokoll.Schreibe(mitarbeiter, 'dienst', 'Leitstelle übertragen',
|
|
{ typ = 'person', id = ziel.charakterId })
|
|
else
|
|
leitstelle = quelle
|
|
Protokoll.Schreibe(mitarbeiter, 'dienst', 'Leitstelle übernommen')
|
|
end
|
|
|
|
Kern.Loese('dienst.leitstelle', leitstelle)
|
|
Dienst.Verteile()
|
|
return true
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Verteilung an die Clients
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Baut die Liste der Mitarbeiter im Dienst
|
|
--- @return table
|
|
function Dienst.BaueListe()
|
|
local liste = {}
|
|
for quelle, mitarbeiter in pairs(imDienst) do
|
|
local position = GetEntityCoords(GetPlayerPed(quelle))
|
|
liste[#liste + 1] = {
|
|
quelle = quelle,
|
|
charakterId = mitarbeiter.charakterId,
|
|
name = mitarbeiter.name,
|
|
personalnummer = mitarbeiter.personalnummer,
|
|
rolle = mitarbeiter.rolle,
|
|
rolleName = mitarbeiter.rolleName,
|
|
station = mitarbeiter.station,
|
|
funktion = mitarbeiter.funktion,
|
|
status = mitarbeiter.status,
|
|
leitstelle = leitstelle == quelle,
|
|
seit = mitarbeiter.seit,
|
|
position = { x = position.x, y = position.y, z = position.z },
|
|
}
|
|
end
|
|
table.sort(liste, function(a, b) return a.personalnummer < b.personalnummer end)
|
|
return liste
|
|
end
|
|
|
|
--- Schickt die Dienstliste an alle im Dienst
|
|
function Dienst.Verteile()
|
|
local liste = Dienst.BaueListe()
|
|
for quelle in pairs(imDienst) do
|
|
TriggerClientEvent('aether:medical:dienstliste', quelle, liste)
|
|
end
|
|
end
|
|
|
|
-- Positionen regelmäßig auffrischen (für die Einsatzkarte)
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(5000)
|
|
if next(imDienst) then Dienst.Verteile() end
|
|
end
|
|
end)
|
|
|
|
-- Beim Verlassen sauber abmelden
|
|
AddEventHandler('playerDropped', function()
|
|
Dienst.Beenden(source)
|
|
end)
|