aether-framework/resources/[aether]/aether-medical/server/main.lua
Jerrit Fritzsche e4b82dd685 Aether-Framework: Fachmodule, Admin-Panel und Docker-Datenbank
- 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>
2026-07-27 18:55:32 +02:00

242 lines
8.8 KiB
Lua

-- =====================================================================
-- aether-medical — Server-Schnittstelle
--
-- Ein einziges Netz-Event bedient alle Module. Jede Anfrage durchläuft
-- Dienstprüfung und Rechteprüfung, bevor sie ein Modul erreicht.
-- =====================================================================
local Core = exports['aether-core']
--- Baut das Datenpaket für die Oberfläche
--- @param quelle number Server-ID
--- @param mitarbeiter table Mitarbeiter im Dienst
--- @return table
local function BaueTabletDaten(quelle, mitarbeiter)
return {
klinik = Medizin.Config.Klinik,
kuerzel = Medizin.Config.Kuerzel,
module = Kern.SichtbareModule(mitarbeiter),
mitarbeiter = {
name = mitarbeiter.name,
personalnummer = mitarbeiter.personalnummer,
rolle = mitarbeiter.rolle,
rolleName = mitarbeiter.rolleName,
station = mitarbeiter.station,
funktion = mitarbeiter.funktion,
status = mitarbeiter.status,
leitstelle = Dienst.Leitstelle() == quelle,
},
status = Medizin.Config.Dienststatus,
funktionen = Medizin.Config.Dienstfunktionen,
stationen = Medizin.Config.Stationen,
}
end
-- ---------------------------------------------------------------------
-- Oberfläche öffnen
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:medical:oeffnen', function()
local quelle = source
local mitarbeiter = Dienst.Hole(quelle)
if not mitarbeiter then
-- Noch nicht im Dienst: Personalakte prüfen und Anmeldung anbieten
local charakterId = Core:HoleCharakterId(quelle)
if not charakterId then return end
local geprueft, fehler = Dienst.LadeMitarbeiter(quelle, charakterId)
if not geprueft then
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
return
end
-- Nur Funktionen anbieten, für die auch das Recht vorliegt
local waehlbar = {}
for _, eintrag in ipairs(Medizin.Config.Dienstfunktionen) do
if not eintrag.recht or Rechte.Hat(geprueft, eintrag.recht) then
waehlbar[#waehlbar + 1] = { id = eintrag.id, name = eintrag.name }
end
end
TriggerClientEvent('aether:medical:anmeldung', quelle, {
name = geprueft.name,
personalnummer = geprueft.personalnummer,
rolleName = geprueft.rolleName,
station = geprueft.station,
klinik = Medizin.Config.Klinik,
funktionen = waehlbar,
})
return
end
TriggerClientEvent('aether:medical:zeige', quelle, BaueTabletDaten(quelle, mitarbeiter))
end)
-- ---------------------------------------------------------------------
-- Dienst
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:medical:dienstAntreten', function(funktion)
local quelle = source
local mitarbeiter, fehler = Dienst.Antreten(quelle,
type(funktion) == 'string' and funktion or nil)
if not mitarbeiter then
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
return
end
TriggerClientEvent('aether:medical:zeige', quelle, BaueTabletDaten(quelle, mitarbeiter))
end)
RegisterNetEvent('aether:medical:dienstBeenden', function()
local quelle = source
Dienst.Beenden(quelle)
TriggerClientEvent('aether:medical:schliessen', quelle)
end)
RegisterNetEvent('aether:medical:status', function(status)
local quelle = source
if type(status) ~= 'string' then return end
Dienst.SetzeStatus(quelle, status)
end)
RegisterNetEvent('aether:medical:funktion', function(funktion)
local quelle = source
if type(funktion) ~= 'string' then return end
local erfolg, fehler = Dienst.SetzeFunktion(quelle, funktion)
if not erfolg and fehler then
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
end
end)
RegisterNetEvent('aether:medical:leitstelle', function(zielQuelle)
local quelle = source
local erfolg, fehler = Dienst.SetzeLeitstelle(quelle,
type(zielQuelle) == 'number' and zielQuelle or nil)
if not erfolg and fehler then
TriggerClientEvent('aether:medical:fehler', quelle, fehler)
end
end)
-- ---------------------------------------------------------------------
-- Zentraler Modul-Aufruf
--
-- Die Oberfläche schickt { modul, aktion, daten, ruf }. Der `ruf` ist
-- eine laufende Nummer, damit die Antwort zugeordnet werden kann.
-- ---------------------------------------------------------------------
RegisterNetEvent('aether:medical:aufruf', function(modulId, aktion, daten, ruf)
local quelle = source
if type(modulId) ~= 'string' or type(aktion) ~= 'string' then return end
local mitarbeiter = Dienst.Hole(quelle)
if not mitarbeiter then
TriggerClientEvent('aether:medical:antwort', quelle, ruf, nil,
'Du bist nicht im Dienst.')
return
end
local ergebnis, fehler = Kern.FuehreAus(quelle, mitarbeiter, modulId, aktion, daten)
TriggerClientEvent('aether:medical:antwort', quelle, ruf, ergebnis, fehler)
end)
-- ---------------------------------------------------------------------
-- Eingehende Notrufe
--
-- Sie erzeugen einen Einsatz mit Status „offen". Zugewiesen wird
-- ausschliesslich von der Leitstelle — nie automatisch.
--
-- `EinsatzEingang` stellt das Einsatzmodul bereit. Solange es nicht
-- geladen ist, wird ein Notruf schlicht nicht angenommen, statt
-- irgendwo ins Leere zu laufen.
-- ---------------------------------------------------------------------
--- Nimmt einen Notruf entgegen, sofern das Einsatzmodul geladen ist
--- @param daten table Einsatzdaten
--- @return number|nil Einsatz-ID
local function NotrufAnnehmen(daten)
if type(EinsatzEingang) ~= 'function' then
Aether.Fehler('Notruf verworfen: Das Einsatzmodul ist nicht geladen.')
return nil
end
return EinsatzEingang(daten)
end
--- Notruf aus dem Handy (112)
AddEventHandler('aether:phone:notruf', function(quelle, fraktionId, nummer)
if fraktionId ~= 'lsmc' and fraktionId ~= 'medic' and fraktionId ~= 'ems' then return end
local position = GetEntityCoords(GetPlayerPed(quelle))
NotrufAnnehmen({
titel = 'Notruf über Telefon',
kategorie = 'notruf',
prioritaet = 2,
ort = 'Position des Anrufers',
beschreibung = ('Eingehender Anruf von %s.'):format(nummer or 'unbekannt'),
position = { x = position.x, y = position.y, z = position.z },
quelle = 'notruf',
})
end)
-- =====================================================================
-- EXPORTS — Schnittstelle für externe Systeme
--
-- Damit lassen sich Notrufe und Hinweise von aussen einspeisen, ohne
-- dass am Kern etwas geändert werden muss. Keiner dieser Exports
-- behandelt, diagnostiziert oder entlässt jemanden.
-- =====================================================================
--- Meldet einen Notfall an die Klinik.
--- Er erscheint als offener Einsatz — zugewiesen wird er nie automatisch.
exports('MeldeNotfall', function(daten)
if type(daten) ~= 'table' or not daten.titel then return nil end
return NotrufAnnehmen(daten)
end)
--- Erstellt einen Hinweis für alle im Dienst
exports('MeldeHinweis', function(daten)
if type(daten) ~= 'table' or not daten.titel then return false end
Hinweise.Erstelle(nil, daten)
return true
end)
--- Ist ein Spieler als Mediziner im Dienst?
exports('IstImDienst', function(quelle)
return Dienst.Hole(quelle) ~= nil
end)
--- Wie viele Mediziner sind gerade im Dienst?
exports('AnzahlImDienst', function()
local anzahl = 0
for _ in pairs(Dienst.AlleImDienst()) do anzahl = anzahl + 1 end
return anzahl
end)
--- Liefert die Dienstdaten eines Mitarbeiters (ohne Rechtetabelle)
exports('HoleMitarbeiter', function(quelle)
local mitarbeiter = Dienst.Hole(quelle)
if not mitarbeiter then return nil end
return {
charakterId = mitarbeiter.charakterId,
name = mitarbeiter.name,
personalnummer = mitarbeiter.personalnummer,
rolle = mitarbeiter.rolle,
rolleName = mitarbeiter.rolleName,
station = mitarbeiter.station,
funktion = mitarbeiter.funktion,
status = mitarbeiter.status,
}
end)
--- Erlaubt fremden Ressourcen, ein eigenes Modul zu registrieren
exports('RegistriereModul', function(beschreibung)
return Kern.RegistriereModul(beschreibung)
end)
--- Erlaubt fremden Ressourcen, eine Aktion beizusteuern
exports('RegistriereAktion', function(modulId, aktion, recht, funktion)
Kern.RegistriereAktion(modulId, aktion, recht, funktion)
return true
end)