-- ===================================================================== -- aether-police — Benachrichtigungen und Fristenüberwachung -- -- WICHTIG: Dieses Modul ERINNERT ausschliesslich. Es entlässt niemanden, -- schliesst nichts ab und ergreift keine Maßnahme. Jede Meldung endet -- in einer Aufforderung an einen Beamten, selbst zu entscheiden. -- ===================================================================== Hinweise = {} -- Bereits gemeldete Fristen, damit nicht im Minutentakt gewarnt wird: -- [gewahrsamId] = { [stufe] = true } local gemeldet = {} --- Erstellt einen Hinweis --- @param ziel table { charakterId? } — ohne charakterId geht er an alle im Dienst --- @param daten table { kategorie, prioritaet?, titel, text?, zielTyp?, zielId? } function Hinweise.Erstelle(ziel, daten) local id = MySQL.insert.await([[ INSERT INTO police_hinweis (`character`, kategorie, prioritaet, titel, text, ziel_typ, ziel_id) VALUES (?, ?, ?, ?, ?, ?, ?) ]], { ziel and ziel.charakterId or nil, daten.kategorie or 'system', daten.prioritaet or 'normal', daten.titel, daten.text, daten.zielTyp, daten.zielId and tostring(daten.zielId) or nil, }) -- Betroffene Clients sofort informieren. -- Die Feldnamen entsprechen bewusst denen aus Hinweise.Lade, damit ein -- frisch gemeldeter Hinweis genauso anklickbar ist wie ein geladener. local paket = { id = id, kategorie = daten.kategorie or 'system', prioritaet = daten.prioritaet or 'normal', titel = daten.titel, text = daten.text, zielTyp = daten.zielTyp, zielId = daten.zielId, } if ziel and ziel.charakterId then local quelle = Dienst.QuelleVonCharakter(ziel.charakterId) if quelle then TriggerClientEvent('aether:police:hinweis', quelle, paket) end else for quelle in pairs(Dienst.AlleImDienst()) do TriggerClientEvent('aether:police:hinweis', quelle, paket) end end end --- Lädt ungelesene Hinweise eines Beamten --- @param charakterId number Charakter --- @param limit number|nil Anzahl --- @return table function Hinweise.Lade(charakterId, limit) local zeilen = MySQL.query.await([[ SELECT * FROM police_hinweis WHERE (`character` = ? OR `character` IS NULL) ORDER BY id DESC LIMIT ? ]], { charakterId, limit or 30 }) or {} -- Sprungziel auf die Schreibweise der Oberfläche bringen for _, eintrag in ipairs(zeilen) do eintrag.zielTyp = eintrag.ziel_typ eintrag.zielId = eintrag.ziel_id eintrag.ziel_typ, eintrag.ziel_id = nil, nil end return zeilen end --- Markiert einen Hinweis als gelesen function Hinweise.MarkiereGelesen(charakterId, hinweisId) MySQL.update('UPDATE police_hinweis SET gelesen = 1 WHERE id = ? AND (`character` = ? OR `character` IS NULL)', { hinweisId, charakterId }) end -- --------------------------------------------------------------------- -- Fristenüberwachung -- -- Läuft im Hintergrund und erzeugt NUR Erinnerungen. Ein Gewahrsam -- endet niemals von selbst — er bleibt offen, bis ein Beamter die -- Entlassung ausführt. -- --------------------------------------------------------------------- CreateThread(function() Wait(5000) while true do Wait(Polizei.Config.Erinnerungen.pruefIntervall) -- --- Gewahrsam: auslaufende Fristen --- local offene = MySQL.query.await([[ SELECT g.id, g.character, g.faellig_am, g.grund, TIMESTAMPDIFF(MINUTE, NOW(), g.faellig_am) AS rest, c.firstname, c.lastname FROM police_gewahrsam g JOIN aether_characters c ON c.id = g.character WHERE g.entlassen_am IS NULL ]]) or {} for _, eintrag in ipairs(offene) do local rest = tonumber(eintrag.rest) or 0 gemeldet[eintrag.id] = gemeldet[eintrag.id] or {} for _, stufe in ipairs(Polizei.Config.Erinnerungen.gewahrsamStufen) do -- Erinnern, sobald die Restzeit die Stufe erreicht oder unterschreitet if rest <= stufe and not gemeldet[eintrag.id][stufe] then gemeldet[eintrag.id][stufe] = true local name = eintrag.firstname .. ' ' .. eintrag.lastname local titel, text, prio if stufe > 0 then titel = ('Gewahrsam: noch %d Minuten'):format(stufe) text = ('%s — Haftzeit endet bald. Entlassung muss manuell erfolgen.') :format(name) prio = 'normal' else titel = 'Haftzeit abgelaufen' text = ('%s — die festgelegte Haftzeit ist abgelaufen. Bitte über die Entlassung entscheiden.') :format(name) prio = 'hoch' end Hinweise.Erstelle(nil, { kategorie = 'gewahrsam', prioritaet = prio, titel = titel, text = text, zielTyp = 'gewahrsam', zielId = eintrag.id, }) break -- pro Durchlauf nur eine Stufe melden end end end -- --- Berichte, die zu lange im Entwurf liegen --- local entwuerfe = MySQL.query.await([[ SELECT b.id, b.nummer, b.titel, b.erstellt_von FROM police_bericht b WHERE b.status = 'entwurf' AND b.erstellt_am < DATE_SUB(NOW(), INTERVAL ? MINUTE) ]], { Polizei.Config.Erinnerungen.berichtOffenNach }) or {} for _, bericht in ipairs(entwuerfe) do -- Nur einmal je Bericht erinnern local schluessel = 'bericht_' .. bericht.id if not gemeldet[schluessel] then gemeldet[schluessel] = true Hinweise.Erstelle({ charakterId = bericht.erstellt_von }, { kategorie = 'bericht', prioritaet = 'normal', titel = 'Offener Bericht', text = ('%s „%s" liegt noch als Entwurf vor.'):format(bericht.nummer, bericht.titel), zielTyp = 'bericht', zielId = bericht.id, }) end end -- --- Aushänge mit Kenntnisnahmefrist --- -- Das System erinnert nur; es bestätigt keine Kenntnisnahme selbst. local fristen = MySQL.query.await([[ SELECT a.id, a.titel, a.abteilung, TIMESTAMPDIFF(MINUTE, NOW(), a.frist) AS rest FROM police_aushang a WHERE a.aktiv = 1 AND a.kenntnispflicht = 1 AND a.frist IS NOT NULL AND a.frist > DATE_SUB(NOW(), INTERVAL 1 DAY) ]]) or {} for _, aushang in ipairs(fristen) do local rest = tonumber(aushang.rest) or 0 local schluessel = 'aushang_' .. aushang.id -- Einmal vorwarnen, einmal beim Ablauf local stufe = rest <= 0 and 'abgelaufen' or (rest <= Polizei.Config.Erinnerungen.aushangVorwarnung and 'bald') or nil if stufe then gemeldet[schluessel] = gemeldet[schluessel] or {} if not gemeldet[schluessel][stufe] then gemeldet[schluessel][stufe] = true -- Nur Beamte ansprechen, die noch nicht bestätigt haben for quelle, beamter in pairs(Dienst.AlleImDienst()) do local passt = aushang.abteilung == nil or aushang.abteilung == beamter.abteilung if passt then local schon = MySQL.scalar.await([[ SELECT 1 FROM police_aushang_kenntnis WHERE aushang = ? AND `character` = ? ]], { aushang.id, beamter.charakterId }) if not schon then Hinweise.Erstelle({ charakterId = beamter.charakterId }, { kategorie = 'system', prioritaet = stufe == 'abgelaufen' and 'hoch' or 'normal', titel = stufe == 'abgelaufen' and 'Kenntnisnahme überfällig' or 'Kenntnisnahme offen', text = ('„%s" — bitte zur Kenntnis nehmen.'):format(aushang.titel), zielTyp = 'aushang', zielId = aushang.id, }) end end end end end end end end) --- Räumt gemeldete Fristen auf, wenn ein Gewahrsam abgeschlossen wurde Kern.Abonniere('gewahrsam.entlassen', function(gewahrsamId) gemeldet[gewahrsamId] = nil end)