- 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>
574 lines
23 KiB
Lua
574 lines
23 KiB
Lua
-- =====================================================================
|
|
-- aether-police / Modul: Interne Kommunikation
|
|
--
|
|
-- Zwei Wege: Nachrichten zwischen Beamten und Aushänge der Leitung.
|
|
--
|
|
-- Das System stellt zu, sortiert und erinnert. Es verfasst nichts,
|
|
-- leitet nichts weiter und bestätigt keine Kenntnisnahme im Namen
|
|
-- eines Beamten. Ein gelesener Posteingang ist reine Anzeige — eine
|
|
-- Kenntnisnahme dagegen ist eine Erklärung und braucht einen Klick.
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereModul({
|
|
id = 'kommunikation', name = 'Kommunikation', icon = '✉',
|
|
reihenfolge = 12, recht = nil,
|
|
beschreibung = 'Nachrichten, Aushänge und Dienstanweisungen',
|
|
})
|
|
|
|
-- Erlaubte Kategorien eines Aushangs
|
|
local AUSHANG_KATEGORIEN = {
|
|
anweisung = 'Dienstanweisung',
|
|
information = 'Information',
|
|
warnung = 'Warnung',
|
|
}
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Hilfsfunktionen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Baut die Bedingung, welche Nachrichten einen Beamten erreichen
|
|
--- @param beamter table Empfänger
|
|
--- @return string Bedingung, table Werte
|
|
local function EmpfangsBedingung(beamter)
|
|
return [[(
|
|
(n.empf_typ = 'person' AND n.empfaenger = ?) OR
|
|
(n.empf_typ = 'abteilung' AND n.empfaenger = ?) OR
|
|
(n.empf_typ = 'rundmeldung')
|
|
)]], { tostring(beamter.charakterId), beamter.abteilung }
|
|
end
|
|
|
|
--- Zählt ungelesene Nachrichten eines Beamten
|
|
--- @param beamter table Empfänger
|
|
--- @return number
|
|
local function ZaehleUngelesen(beamter)
|
|
local bedingung, werte = EmpfangsBedingung(beamter)
|
|
|
|
return MySQL.scalar.await(([[
|
|
SELECT COUNT(*) FROM police_nachricht n
|
|
WHERE %s
|
|
AND n.absender <> ?
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM police_nachricht_gelesen g
|
|
WHERE g.nachricht = n.id AND g.`character` = ?
|
|
)
|
|
]]):format(bedingung),
|
|
{ werte[1], werte[2], beamter.charakterId, beamter.charakterId }) or 0
|
|
end
|
|
|
|
--- Zählt offene Kenntnisnahmen eines Beamten
|
|
--- @param beamter table Empfänger
|
|
--- @return number
|
|
local function ZaehleOffeneKenntnis(beamter)
|
|
return MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM police_aushang a
|
|
WHERE a.aktiv = 1 AND a.kenntnispflicht = 1
|
|
AND (a.abteilung IS NULL OR a.abteilung = ?)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM police_aushang_kenntnis k
|
|
WHERE k.aushang = a.id AND k.`character` = ?
|
|
)
|
|
]], { beamter.abteilung, beamter.charakterId }) or 0
|
|
end
|
|
|
|
--- Liefert den Namen einer Abteilung
|
|
local function AbteilungsName(id)
|
|
for _, eintrag in ipairs(Polizei.Config.Abteilungen) do
|
|
if eintrag.id == id then return eintrag.name end
|
|
end
|
|
return id
|
|
end
|
|
|
|
-- =====================================================================
|
|
-- POSTEINGANG
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'uebersicht', nil, function(quelle, beamter, daten)
|
|
local ordner = tostring(daten.ordner or 'eingang')
|
|
local bedingung, werte = EmpfangsBedingung(beamter)
|
|
local nachrichten
|
|
|
|
if ordner == 'gesendet' then
|
|
nachrichten = MySQL.query.await([[
|
|
SELECT n.id, n.empfaenger, n.empf_typ, n.betreff, n.text, n.gesendet,
|
|
n.absender, c.firstname, c.lastname, p.dienstnummer,
|
|
1 AS gelesen
|
|
FROM police_nachricht n
|
|
JOIN aether_characters c ON c.id = n.absender
|
|
LEFT JOIN police_personal p ON p.`character` = n.absender
|
|
WHERE n.absender = ?
|
|
ORDER BY n.id DESC LIMIT 100
|
|
]], { beamter.charakterId }) or {}
|
|
else
|
|
nachrichten = MySQL.query.await(([[
|
|
SELECT n.id, n.empfaenger, n.empf_typ, n.betreff, n.text, n.gesendet,
|
|
n.absender, c.firstname, c.lastname, p.dienstnummer,
|
|
(SELECT COUNT(*) FROM police_nachricht_gelesen g
|
|
WHERE g.nachricht = n.id AND g.`character` = ?) AS gelesen
|
|
FROM police_nachricht n
|
|
JOIN aether_characters c ON c.id = n.absender
|
|
LEFT JOIN police_personal p ON p.`character` = n.absender
|
|
WHERE %s AND n.absender <> ?
|
|
ORDER BY n.id DESC LIMIT 100
|
|
]]):format(bedingung),
|
|
{ beamter.charakterId, werte[1], werte[2], beamter.charakterId }) or {}
|
|
end
|
|
|
|
-- Empfängernamen und Vorschau ergänzen
|
|
for _, eintrag in ipairs(nachrichten) do
|
|
eintrag.absenderName = eintrag.firstname .. ' ' .. eintrag.lastname
|
|
eintrag.gelesen = tonumber(eintrag.gelesen) > 0
|
|
|
|
if eintrag.empf_typ == 'rundmeldung' then
|
|
eintrag.empfaengerName = 'Alle Einheiten'
|
|
elseif eintrag.empf_typ == 'abteilung' then
|
|
eintrag.empfaengerName = AbteilungsName(eintrag.empfaenger)
|
|
else
|
|
local ziel = MySQL.query.await([[
|
|
SELECT c.firstname, c.lastname, p.dienstnummer
|
|
FROM aether_characters c
|
|
LEFT JOIN police_personal p ON p.`character` = c.id
|
|
WHERE c.id = ?
|
|
]], { tonumber(eintrag.empfaenger) or 0 })
|
|
eintrag.empfaengerName = ziel and ziel[1]
|
|
and (ziel[1].firstname .. ' ' .. ziel[1].lastname) or 'Unbekannt'
|
|
end
|
|
|
|
-- Vorschau kurz halten, der volle Text kommt beim Öffnen
|
|
eintrag.vorschau = tostring(eintrag.text):gsub('%s+', ' '):sub(1, 120)
|
|
eintrag.text = nil
|
|
eintrag.firstname, eintrag.lastname = nil, nil
|
|
end
|
|
|
|
return {
|
|
ordner = ordner,
|
|
nachrichten = nachrichten,
|
|
ungelesen = ZaehleUngelesen(beamter),
|
|
offeneKenntnis = ZaehleOffeneKenntnis(beamter),
|
|
abteilungen = Polizei.Config.Abteilungen,
|
|
meineAbteilung = beamter.abteilung,
|
|
rechte = {
|
|
senden = Rechte.Hat(beamter, 'nachricht.senden'),
|
|
rundmeldung = Rechte.Hat(beamter, 'nachricht.rundmeldung'),
|
|
aushang = Rechte.Hat(beamter, 'aushang.schreiben'),
|
|
archivieren = Rechte.Hat(beamter, 'aushang.archivieren'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'nachrichtOeffnen', nil, function(quelle, beamter, daten)
|
|
local id = tonumber(daten.id)
|
|
if not id then return nil, 'Ungültige Kennung.' end
|
|
|
|
local zeilen = MySQL.query.await([[
|
|
SELECT n.*, c.firstname, c.lastname, p.dienstnummer, p.rolle
|
|
FROM police_nachricht n
|
|
JOIN aether_characters c ON c.id = n.absender
|
|
LEFT JOIN police_personal p ON p.`character` = n.absender
|
|
WHERE n.id = ?
|
|
]], { id })
|
|
if not zeilen or not zeilen[1] then return nil, 'Nachricht nicht gefunden.' end
|
|
|
|
local nachricht = zeilen[1]
|
|
|
|
-- Zustellprüfung: nur Absender und tatsächliche Empfänger dürfen lesen
|
|
local darf = nachricht.absender == beamter.charakterId
|
|
or nachricht.empf_typ == 'rundmeldung'
|
|
or (nachricht.empf_typ == 'abteilung' and nachricht.empfaenger == beamter.abteilung)
|
|
or (nachricht.empf_typ == 'person'
|
|
and tonumber(nachricht.empfaenger) == beamter.charakterId)
|
|
if not darf then return nil, 'Diese Nachricht ist nicht an dich gerichtet.' end
|
|
|
|
-- Lesestatus vermerken: das ist reine Anzeige, keine Erklärung
|
|
if nachricht.absender ~= beamter.charakterId then
|
|
MySQL.query([[
|
|
INSERT IGNORE INTO police_nachricht_gelesen (nachricht, `character`)
|
|
VALUES (?, ?)
|
|
]], { id, beamter.charakterId })
|
|
end
|
|
|
|
local rolle = Polizei.HoleRolle(nachricht.rolle)
|
|
|
|
return {
|
|
id = nachricht.id,
|
|
betreff = nachricht.betreff,
|
|
text = nachricht.text,
|
|
gesendet = nachricht.gesendet,
|
|
absender = nachricht.absender,
|
|
absenderName = nachricht.firstname .. ' ' .. nachricht.lastname,
|
|
dienstnummer = nachricht.dienstnummer,
|
|
rolleName = rolle and rolle.name or nachricht.rolle,
|
|
empfTyp = nachricht.empf_typ,
|
|
empfaengerName = nachricht.empf_typ == 'rundmeldung' and 'Alle Einheiten'
|
|
or nachricht.empf_typ == 'abteilung' and AbteilungsName(nachricht.empfaenger)
|
|
or nil,
|
|
-- Antworten geht nur bei einer Nachricht von einer Person
|
|
antwortAn = nachricht.absender ~= beamter.charakterId and nachricht.absender or nil,
|
|
}
|
|
end)
|
|
|
|
--- Auswahlliste möglicher Empfänger
|
|
Kern.RegistriereAktion('kommunikation', 'empfaenger', 'nachricht.senden', function(quelle, beamter)
|
|
local beamte = MySQL.query.await([[
|
|
SELECT p.`character`, p.dienstnummer, p.rolle, p.abteilung,
|
|
c.firstname, c.lastname
|
|
FROM police_personal p
|
|
JOIN aether_characters c ON c.id = p.`character`
|
|
WHERE p.aktiv = 1 AND p.`character` <> ?
|
|
ORDER BY p.dienstnummer
|
|
]], { beamter.charakterId }) or {}
|
|
|
|
-- Wer gerade erreichbar ist, wird markiert — mehr nicht
|
|
local imDienst = {}
|
|
for _, eintrag in pairs(Dienst.AlleImDienst()) do
|
|
imDienst[eintrag.charakterId] = true
|
|
end
|
|
|
|
for _, eintrag in ipairs(beamte) do
|
|
local rolle = Polizei.HoleRolle(eintrag.rolle)
|
|
eintrag.rolleName = rolle and rolle.name or eintrag.rolle
|
|
eintrag.imDienst = imDienst[eintrag.character] == true
|
|
end
|
|
|
|
return {
|
|
beamte = beamte,
|
|
abteilungen = Polizei.Config.Abteilungen,
|
|
rundmeldung = Rechte.Hat(beamter, 'nachricht.rundmeldung'),
|
|
}
|
|
end)
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'senden', 'nachricht.senden', function(quelle, beamter, daten)
|
|
local typ = tostring(daten.empfTyp or 'person')
|
|
local text = type(daten.text) == 'string' and daten.text or ''
|
|
local betreff = type(daten.betreff) == 'string' and daten.betreff:sub(1, 160) or ''
|
|
|
|
if #text < 2 then return nil, 'Die Nachricht ist leer.' end
|
|
if #text > 4000 then return nil, 'Die Nachricht ist zu lang.' end
|
|
if #betreff < 2 then return nil, 'Bitte einen Betreff angeben.' end
|
|
|
|
local empfaenger, hinweisText
|
|
|
|
if typ == 'rundmeldung' then
|
|
if not Rechte.Hat(beamter, 'nachricht.rundmeldung') then
|
|
return nil, 'Du darfst keine Rundmeldung absetzen.'
|
|
end
|
|
empfaenger = 'alle'
|
|
hinweisText = 'Rundmeldung von ' .. beamter.name
|
|
|
|
elseif typ == 'abteilung' then
|
|
local gefunden = false
|
|
for _, eintrag in ipairs(Polizei.Config.Abteilungen) do
|
|
if eintrag.id == daten.empfaenger then gefunden = true break end
|
|
end
|
|
if not gefunden then return nil, 'Unbekannte Abteilung.' end
|
|
empfaenger = daten.empfaenger
|
|
hinweisText = 'Nachricht an ' .. AbteilungsName(empfaenger)
|
|
|
|
else
|
|
typ = 'person'
|
|
local zielId = tonumber(daten.empfaenger)
|
|
if not zielId then return nil, 'Kein Empfänger gewählt.' end
|
|
|
|
local vorhanden = MySQL.scalar.await(
|
|
'SELECT `character` FROM police_personal WHERE `character` = ? AND aktiv = 1',
|
|
{ zielId })
|
|
if not vorhanden then return nil, 'Dieser Empfänger ist nicht im Dienstbestand.' end
|
|
|
|
empfaenger = tostring(zielId)
|
|
hinweisText = 'Nachricht von ' .. beamter.name
|
|
end
|
|
|
|
local id = MySQL.insert.await([[
|
|
INSERT INTO police_nachricht (absender, empfaenger, empf_typ, betreff, text)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
]], { beamter.charakterId, empfaenger, typ, betreff, text })
|
|
|
|
-- Zustellung: nur benachrichtigen, nicht öffnen
|
|
if typ == 'person' then
|
|
Hinweise.Erstelle({ charakterId = tonumber(empfaenger) }, {
|
|
kategorie = 'system', prioritaet = 'normal',
|
|
titel = hinweisText, text = betreff,
|
|
zielTyp = 'nachricht', zielId = id,
|
|
})
|
|
else
|
|
for _, ziel in pairs(Dienst.AlleImDienst()) do
|
|
local passt = typ == 'rundmeldung' or ziel.abteilung == empfaenger
|
|
if passt and ziel.charakterId ~= beamter.charakterId then
|
|
Hinweise.Erstelle({ charakterId = ziel.charakterId }, {
|
|
kategorie = 'system',
|
|
prioritaet = typ == 'rundmeldung' and 'hoch' or 'normal',
|
|
titel = hinweisText, text = betreff,
|
|
zielTyp = 'nachricht', zielId = id,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
Protokoll.Schreibe(beamter, 'kommunikation', 'Nachricht gesendet',
|
|
{ typ = 'nachricht', id = id }, { empfTyp = typ, empfaenger = empfaenger })
|
|
|
|
Kern.Loese('nachricht.gesendet', id, beamter)
|
|
|
|
return { id = id }
|
|
end)
|
|
|
|
-- =====================================================================
|
|
-- AUSHÄNGE
|
|
-- =====================================================================
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'aushaenge', nil, function(quelle, beamter, daten)
|
|
local archiv = daten.archiv == true
|
|
|
|
local aushaenge = MySQL.query.await([[
|
|
SELECT a.id, a.titel, a.text, a.kategorie, a.abteilung, a.angeheftet,
|
|
a.kenntnispflicht, a.frist, a.erstellt_am, a.erstellt_von, a.aktiv,
|
|
a.archiviert_am,
|
|
c.firstname, c.lastname, p.dienstnummer,
|
|
(SELECT COUNT(*) FROM police_aushang_kenntnis k WHERE k.aushang = a.id)
|
|
AS kenntnisse,
|
|
(SELECT COUNT(*) FROM police_aushang_kenntnis k
|
|
WHERE k.aushang = a.id AND k.`character` = ?) AS meineKenntnis
|
|
FROM police_aushang a
|
|
JOIN aether_characters c ON c.id = a.erstellt_von
|
|
LEFT JOIN police_personal p ON p.`character` = a.erstellt_von
|
|
WHERE a.aktiv = ?
|
|
AND (? = 1 OR a.abteilung IS NULL OR a.abteilung = ?)
|
|
ORDER BY a.angeheftet DESC, a.id DESC
|
|
LIMIT 100
|
|
]], {
|
|
beamter.charakterId, archiv and 0 or 1,
|
|
-- Wer Aushänge veröffentlichen darf, sieht auch die anderer Abteilungen
|
|
Rechte.Hat(beamter, 'aushang.schreiben') and 1 or 0,
|
|
beamter.abteilung,
|
|
}) or {}
|
|
|
|
-- Wie viele Beamte kämen für eine Kenntnisnahme überhaupt in Frage?
|
|
for _, eintrag in ipairs(aushaenge) do
|
|
eintrag.autor = eintrag.firstname .. ' ' .. eintrag.lastname
|
|
eintrag.kategorieName = AUSHANG_KATEGORIEN[eintrag.kategorie] or eintrag.kategorie
|
|
eintrag.abteilungName = eintrag.abteilung and AbteilungsName(eintrag.abteilung) or nil
|
|
eintrag.bestaetigt = tonumber(eintrag.meineKenntnis) > 0
|
|
eintrag.angeheftet = eintrag.angeheftet == 1
|
|
eintrag.kenntnispflicht = eintrag.kenntnispflicht == 1
|
|
|
|
eintrag.betroffen = MySQL.scalar.await([[
|
|
SELECT COUNT(*) FROM police_personal
|
|
WHERE aktiv = 1 AND (? IS NULL OR abteilung = ?)
|
|
]], { eintrag.abteilung, eintrag.abteilung }) or 0
|
|
|
|
eintrag.firstname, eintrag.lastname, eintrag.meineKenntnis = nil, nil, nil
|
|
end
|
|
|
|
return {
|
|
aushaenge = aushaenge,
|
|
archiv = archiv,
|
|
kategorien = AUSHANG_KATEGORIEN,
|
|
abteilungen= Polizei.Config.Abteilungen,
|
|
rechte = {
|
|
schreiben = Rechte.Hat(beamter, 'aushang.schreiben'),
|
|
archivieren = Rechte.Hat(beamter, 'aushang.archivieren'),
|
|
},
|
|
}
|
|
end)
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'aushangErstellen', 'aushang.schreiben', function(quelle, beamter, daten)
|
|
local titel = type(daten.titel) == 'string' and daten.titel:sub(1, 160) or ''
|
|
local text = type(daten.text) == 'string' and daten.text or ''
|
|
|
|
if #titel < 3 then return nil, 'Bitte einen Titel angeben.' end
|
|
if #text < 5 then return nil, 'Der Aushang hat keinen Inhalt.' end
|
|
if #text > 8000 then return nil, 'Der Aushang ist zu lang.' end
|
|
|
|
local kategorie = AUSHANG_KATEGORIEN[daten.kategorie] and daten.kategorie or 'information'
|
|
|
|
-- Abteilung: leer bedeutet gesamte Behörde
|
|
local abteilung = nil
|
|
if daten.abteilung and daten.abteilung ~= '' then
|
|
for _, eintrag in ipairs(Polizei.Config.Abteilungen) do
|
|
if eintrag.id == daten.abteilung then abteilung = eintrag.id break end
|
|
end
|
|
if not abteilung then return nil, 'Unbekannte Abteilung.' end
|
|
end
|
|
|
|
-- Frist nur sinnvoll, wenn eine Kenntnisnahme verlangt wird
|
|
local kenntnispflicht = daten.kenntnispflicht == true
|
|
local fristMinuten = tonumber(daten.fristMinuten)
|
|
if fristMinuten and (fristMinuten < 5 or fristMinuten > 20160) then
|
|
return nil, 'Die Frist muss zwischen 5 Minuten und 14 Tagen liegen.'
|
|
end
|
|
if not kenntnispflicht then fristMinuten = nil end
|
|
|
|
local id = MySQL.insert.await([[
|
|
INSERT INTO police_aushang
|
|
(titel, text, kategorie, abteilung, angeheftet, kenntnispflicht, frist, erstellt_von)
|
|
VALUES (?, ?, ?, ?, ?, ?,
|
|
CASE WHEN ? IS NULL THEN NULL ELSE DATE_ADD(NOW(), INTERVAL ? MINUTE) END, ?)
|
|
]], {
|
|
titel, text, kategorie, abteilung,
|
|
daten.angeheftet == true and 1 or 0,
|
|
kenntnispflicht and 1 or 0,
|
|
fristMinuten, fristMinuten,
|
|
beamter.charakterId,
|
|
})
|
|
|
|
-- Betroffene informieren, aber nichts für sie bestätigen
|
|
for _, ziel in pairs(Dienst.AlleImDienst()) do
|
|
if (abteilung == nil or ziel.abteilung == abteilung)
|
|
and ziel.charakterId ~= beamter.charakterId then
|
|
Hinweise.Erstelle({ charakterId = ziel.charakterId }, {
|
|
kategorie = 'system',
|
|
prioritaet = kategorie == 'warnung' and 'hoch' or 'normal',
|
|
titel = 'Neuer Aushang: ' .. (AUSHANG_KATEGORIEN[kategorie] or kategorie),
|
|
text = titel,
|
|
zielTyp = 'aushang', zielId = id,
|
|
})
|
|
end
|
|
end
|
|
|
|
Protokoll.Schreibe(beamter, 'kommunikation', 'Aushang veröffentlicht',
|
|
{ typ = 'aushang', id = id },
|
|
{ kategorie = kategorie, abteilung = abteilung, kenntnispflicht = kenntnispflicht })
|
|
|
|
Kern.Loese('aushang.erstellt', id, beamter)
|
|
|
|
return { id = id }
|
|
end)
|
|
|
|
--- Kenntnisnahme: bestätigt ausschließlich der Beamte selbst
|
|
Kern.RegistriereAktion('kommunikation', 'kenntnisnahme', nil, function(quelle, beamter, daten)
|
|
local id = tonumber(daten.id)
|
|
if not id then return nil, 'Ungültige Kennung.' end
|
|
|
|
local aushang = MySQL.query.await(
|
|
'SELECT id, titel, abteilung, aktiv FROM police_aushang WHERE id = ?', { id })
|
|
if not aushang or not aushang[1] then return nil, 'Aushang nicht gefunden.' end
|
|
if aushang[1].aktiv ~= 1 then return nil, 'Dieser Aushang ist archiviert.' end
|
|
|
|
if aushang[1].abteilung and aushang[1].abteilung ~= beamter.abteilung then
|
|
return nil, 'Dieser Aushang betrifft deine Abteilung nicht.'
|
|
end
|
|
|
|
MySQL.query.await([[
|
|
INSERT IGNORE INTO police_aushang_kenntnis (aushang, `character`)
|
|
VALUES (?, ?)
|
|
]], { id, beamter.charakterId })
|
|
|
|
Protokoll.Schreibe(beamter, 'kommunikation', 'Kenntnisnahme bestätigt',
|
|
{ typ = 'aushang', id = id }, { titel = aushang[1].titel })
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
--- Wer hat bestätigt, wer noch nicht — reine Auskunft für den Verfasser
|
|
Kern.RegistriereAktion('kommunikation', 'kenntnisStand', 'aushang.schreiben', function(quelle, beamter, daten)
|
|
local id = tonumber(daten.id)
|
|
if not id then return nil, 'Ungültige Kennung.' end
|
|
|
|
local aushang = MySQL.query.await(
|
|
'SELECT id, titel, abteilung, frist FROM police_aushang WHERE id = ?', { id })
|
|
if not aushang or not aushang[1] then return nil, 'Aushang nicht gefunden.' end
|
|
|
|
local abteilung = aushang[1].abteilung
|
|
|
|
local beamte = MySQL.query.await([[
|
|
SELECT p.`character`, p.dienstnummer, p.abteilung,
|
|
c.firstname, c.lastname,
|
|
k.bestaetigt_am
|
|
FROM police_personal p
|
|
JOIN aether_characters c ON c.id = p.`character`
|
|
LEFT JOIN police_aushang_kenntnis k
|
|
ON k.aushang = ? AND k.`character` = p.`character`
|
|
WHERE p.aktiv = 1 AND (? IS NULL OR p.abteilung = ?)
|
|
ORDER BY k.bestaetigt_am IS NULL DESC, p.dienstnummer
|
|
]], { id, abteilung, abteilung }) or {}
|
|
|
|
local offen = 0
|
|
for _, eintrag in ipairs(beamte) do
|
|
eintrag.name = eintrag.firstname .. ' ' .. eintrag.lastname
|
|
eintrag.firstname, eintrag.lastname = nil, nil
|
|
if not eintrag.bestaetigt_am then offen = offen + 1 end
|
|
end
|
|
|
|
return {
|
|
titel = aushang[1].titel,
|
|
frist = aushang[1].frist,
|
|
beamte = beamte,
|
|
offen = offen,
|
|
gesamt = #beamte,
|
|
}
|
|
end)
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'aushangArchivieren', 'aushang.archivieren', function(quelle, beamter, daten)
|
|
local id = tonumber(daten.id)
|
|
if not id then return nil, 'Ungültige Kennung.' end
|
|
|
|
local aushang = MySQL.query.await(
|
|
'SELECT id, titel, aktiv FROM police_aushang WHERE id = ?', { id })
|
|
if not aushang or not aushang[1] then return nil, 'Aushang nicht gefunden.' end
|
|
if aushang[1].aktiv ~= 1 then return nil, 'Dieser Aushang ist bereits archiviert.' end
|
|
|
|
MySQL.update.await([[
|
|
UPDATE police_aushang
|
|
SET aktiv = 0, archiviert_von = ?, archiviert_am = NOW()
|
|
WHERE id = ?
|
|
]], { beamter.charakterId, id })
|
|
|
|
Protokoll.Schreibe(beamter, 'kommunikation', 'Aushang archiviert',
|
|
{ typ = 'aushang', id = id }, { titel = aushang[1].titel }, daten.grund)
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
Kern.RegistriereAktion('kommunikation', 'anheften', 'aushang.schreiben', function(quelle, beamter, daten)
|
|
local id = tonumber(daten.id)
|
|
if not id then return nil, 'Ungültige Kennung.' end
|
|
|
|
local angeheftet = daten.angeheftet == true
|
|
|
|
MySQL.update.await('UPDATE police_aushang SET angeheftet = ? WHERE id = ? AND aktiv = 1',
|
|
{ angeheftet and 1 or 0, id })
|
|
|
|
Protokoll.Schreibe(beamter, 'kommunikation',
|
|
angeheftet and 'Aushang angeheftet' or 'Aushang losgelöst',
|
|
{ typ = 'aushang', id = id })
|
|
|
|
return { ok = true }
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Schnittstelle für andere Ressourcen
|
|
-- ---------------------------------------------------------------------
|
|
|
|
--- Zählt offene Post eines Beamten im Dienst
|
|
--- @param quelle number Server-ID
|
|
--- @return table { nachrichten, kenntnisnahmen }
|
|
exports('OffenePost', function(quelle)
|
|
local beamter = Dienst.Hole(quelle)
|
|
if not beamter then return { nachrichten = 0, kenntnisnahmen = 0 } end
|
|
return {
|
|
nachrichten = ZaehleUngelesen(beamter),
|
|
kenntnisnahmen = ZaehleOffeneKenntnis(beamter),
|
|
}
|
|
end)
|
|
|
|
--- Schickt eine Nachricht aus einer anderen Ressource in den Posteingang.
|
|
--- Der Absender muss ein Beamter sein — das System schreibt nicht selbst.
|
|
--- @param absenderCharakterId number Verfassender Beamter
|
|
--- @param empfTyp string person/abteilung/rundmeldung
|
|
--- @param empfaenger string|number Ziel
|
|
--- @param betreff string Betreff
|
|
--- @param text string Inhalt
|
|
--- @return number|nil Nachrichten-ID
|
|
exports('SendeNachricht', function(absenderCharakterId, empfTyp, empfaenger, betreff, text)
|
|
if not absenderCharakterId or not betreff or not text then return nil end
|
|
|
|
local quelle = Dienst.QuelleVonCharakter(absenderCharakterId)
|
|
local beamter = quelle and Dienst.Hole(quelle)
|
|
if not beamter then return nil end
|
|
|
|
local ergebnis = Kern.FuehreAus(quelle, beamter, 'kommunikation', 'senden', {
|
|
empfTyp = empfTyp, empfaenger = empfaenger,
|
|
betreff = betreff, text = text,
|
|
})
|
|
return ergebnis and ergebnis.id or nil
|
|
end)
|