aether-framework/resources/[aether]/aether-police/server/module/mdt.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

636 lines
26 KiB
Lua

-- =====================================================================
-- aether-police / Modul: MDT
--
-- Personensuche, Fahrzeugsuche und Aktenverwaltung.
-- Das MDT zeigt und dokumentiert — es bewertet nichts und ergreift
-- keine Maßnahme. Jede Änderung wird protokolliert.
-- =====================================================================
Kern.RegistriereModul({
id = 'mdt', name = 'MDT', icon = '🔎',
reihenfolge = 2, recht = 'person.suchen',
beschreibung = 'Personen, Fahrzeuge und Akten',
})
-- ---------------------------------------------------------------------
-- Hilfsfunktionen
-- ---------------------------------------------------------------------
--- Liest den Namen eines Beamten anhand der Charakter-ID
--- @param charakterId number|nil
--- @return string
local function NameVon(charakterId)
if not charakterId then return '—' end
local zeile = MySQL.query.await(
'SELECT firstname, lastname FROM aether_characters WHERE id = ?', { charakterId })
if not zeile or not zeile[1] then return '#' .. tostring(charakterId) end
return zeile[1].firstname .. ' ' .. zeile[1].lastname
end
--- Prüft, ob eine Zeichenkette als Suchbegriff taugt
local function SuchbegriffGueltig(begriff)
return type(begriff) == 'string' and #begriff:gsub('%s', '') >= 2
end
-- =====================================================================
-- PERSONENSUCHE
-- =====================================================================
Kern.RegistriereAktion('mdt', 'personSuchen', 'person.suchen', function(quelle, beamter, daten)
if not SuchbegriffGueltig(daten.begriff) then
return nil, 'Bitte mindestens zwei Zeichen eingeben.'
end
local muster = '%' .. daten.begriff .. '%'
-- Sucht über Name, Geburtsdatum und Telefonnummer
local treffer = MySQL.query.await([[
SELECT c.id, c.firstname, c.lastname, c.dob, c.gender, c.job,
p.warnhinweis, p.gefahrenstufe, p.lichtbild,
t.number AS telefon,
(SELECT COUNT(*) FROM police_fahndung f
WHERE f.typ = 'person' AND f.ziel_id = c.id AND f.aktiv = 1) AS fahndung
FROM aether_characters c
LEFT JOIN police_person p ON p.`character` = c.id
LEFT JOIN aether_phone t ON t.`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 ? OR t.number LIKE ?)
ORDER BY c.lastname, c.firstname
LIMIT 40
]], { muster, muster, muster, muster, muster }) or {}
return { treffer = treffer }
end)
-- =====================================================================
-- PERSONENAKTE
-- =====================================================================
Kern.RegistriereAktion('mdt', 'personAkte', 'person.suchen', function(quelle, beamter, daten)
local charakterId = tonumber(daten.charakterId)
if not charakterId then return nil, 'Ungültige Kennung.' end
-- --- Stammdaten ---
local person = MySQL.query.await([[
SELECT c.id, c.firstname, c.lastname, c.dob, c.gender, c.nationality, c.job,
c.iban, p.lichtbild, p.fingerabdruck, p.dna, p.warnhinweis, p.gefahrenstufe
FROM aether_characters c
LEFT JOIN police_person p ON p.`character` = c.id
WHERE c.id = ? AND c.deleted = 0
]], { charakterId })
if not person or not person[1] then return nil, 'Person nicht gefunden.' end
-- --- Telefonnummer ---
local telefon = MySQL.scalar.await(
'SELECT number FROM aether_phone WHERE `character` = ?', { charakterId })
-- --- Dokumente (Führerschein, Waffenschein ...) ---
local dokumente = MySQL.query.await([[
SELECT type, data, issued_by, issued_at, valid_until, revoked
FROM aether_documents
WHERE `character` = ?
]], { charakterId }) or {}
local lizenzen = {}
for _, dokument in ipairs(dokumente) do
local zusatz = {}
if dokument.data then
local erfolg, entschluesselt = pcall(json.decode, dokument.data)
if erfolg and type(entschluesselt) == 'table' then zusatz = entschluesselt end
end
-- Gültig, wenn nicht entzogen und (unbefristet oder noch nicht abgelaufen)
local gueltig = dokument.revoked == 0
if gueltig and dokument.valid_until then
gueltig = tostring(dokument.valid_until):sub(1, 10) >= os.date('%Y-%m-%d')
end
lizenzen[#lizenzen + 1] = {
typ = dokument.type, nummer = zusatz.nummer, klassen = zusatz.klassen,
waffen = zusatz.waffen, aussteller = dokument.issued_by,
gueltigBis = dokument.valid_until and tostring(dokument.valid_until):sub(1, 10) or nil,
entzogen = dokument.revoked == 1, gueltig = gueltig,
}
end
-- --- Bekannte Fahrzeuge ---
local fahrzeuge = MySQL.query.await([[
SELECT v.plate, v.model, v.props, v.state, v.garage,
(SELECT COUNT(*) FROM police_fahndung f
WHERE f.typ = 'fahrzeug' AND f.ziel_id = v.plate AND f.aktiv = 1) AS fahndung
FROM aether_vehicles v WHERE v.owner = ?
]], { charakterId }) or {}
for _, fahrzeug in ipairs(fahrzeuge) do
if fahrzeug.props then
local erfolg, props = pcall(json.decode, fahrzeug.props)
if erfolg and type(props) == 'table' then
fahrzeug.name = props.name
fahrzeug.farbe = props.farbe
end
end
fahrzeug.props = nil
end
-- --- Bekannte Kontakte (aus dem Telefonbuch) ---
local kontakte = MySQL.query.await([[
SELECT k.name, k.number,
(SELECT c2.id FROM aether_phone p2
JOIN aether_characters c2 ON c2.id = p2.`character`
WHERE p2.number = k.number LIMIT 1) AS charakterId
FROM aether_phone_contacts k
WHERE k.`character` = ?
ORDER BY k.name LIMIT 40
]], { charakterId }) or {}
-- --- Notizen ---
local notizen = MySQL.query.await([[
SELECT n.id, n.text, n.am, c.firstname, c.lastname
FROM police_person_notiz n
LEFT JOIN aether_characters c ON c.id = n.von
WHERE n.`character` = ?
ORDER BY n.id DESC LIMIT 50
]], { charakterId }) or {}
-- --- Verknüpfte Akten ---
local akten = MySQL.query.await([[
SELECT a.id, a.aktenzeichen, a.typ, a.titel, a.status, a.erstellt_am, v.rolle
FROM police_verknuepfung v
JOIN police_akte a ON a.id = v.akte
WHERE v.typ = 'person' AND v.ziel_id = ?
ORDER BY a.id DESC LIMIT 50
]], { tostring(charakterId) }) or {}
-- --- Anzeigen (nur bestätigte zählen als Vorgeschichte) ---
local anzeigen = MySQL.query.await([[
SELECT id, delikte, haft_final, geld_final, status, erstellt_am, bestaetigt_am
FROM police_anzeige
WHERE beschuldigter = ?
ORDER BY id DESC LIMIT 30
]], { charakterId }) or {}
for _, anzeige in ipairs(anzeigen) do
local erfolg, liste = pcall(json.decode, anzeige.delikte)
anzeige.delikte = (erfolg and type(liste) == 'table') and liste or {}
end
-- --- Offene Fahndung ---
local fahndung = MySQL.query.await([[
SELECT id, grund, gefahr, prioritaet, hinweise, erstellt_am
FROM police_fahndung
WHERE typ = 'person' AND ziel_id = ? AND aktiv = 1
LIMIT 1
]], { tostring(charakterId) })
-- --- Laufender Gewahrsam ---
local gewahrsam = MySQL.query.await([[
SELECT id, grund, zelle, faellig_am,
TIMESTAMPDIFF(MINUTE, NOW(), faellig_am) AS rest
FROM police_gewahrsam
WHERE `character` = ? AND entlassen_am IS NULL
LIMIT 1
]], { charakterId })
return {
person = person[1],
telefon = telefon,
lizenzen = lizenzen,
fahrzeuge = fahrzeuge,
kontakte = kontakte,
notizen = notizen,
akten = akten,
anzeigen = anzeigen,
fahndung = fahndung and fahndung[1] or nil,
gewahrsam = gewahrsam and gewahrsam[1] or nil,
}
end)
-- ---------------------------------------------------------------------
-- Notiz zu einer Person
-- ---------------------------------------------------------------------
Kern.RegistriereAktion('mdt', 'personNotiz', 'person.bearbeiten', function(quelle, beamter, daten)
local charakterId = tonumber(daten.charakterId)
local text = type(daten.text) == 'string' and daten.text:gsub('^%s+', ''):gsub('%s+$', '') or ''
if not charakterId then return nil, 'Ungültige Kennung.' end
if #text < 3 or #text > 1000 then return nil, 'Die Notiz muss 3 bis 1000 Zeichen lang sein.' end
local id = MySQL.insert.await(
'INSERT INTO police_person_notiz (`character`, text, von) VALUES (?, ?, ?)',
{ charakterId, text, beamter.charakterId })
Protokoll.Schreibe(beamter, 'mdt', 'Notiz hinzugefügt',
{ typ = 'person', id = charakterId }, { notizId = id })
return { id = id }
end)
Kern.RegistriereAktion('mdt', 'personNotizLoeschen', 'person.bearbeiten', function(quelle, beamter, daten)
local notizId = tonumber(daten.id)
if not notizId then return nil, 'Ungültige Kennung.' end
-- Eigene Notizen darf jeder löschen; fremde nur ab Captain
local notiz = MySQL.query.await(
'SELECT `character`, von FROM police_person_notiz WHERE id = ?', { notizId })
if not notiz or not notiz[1] then return nil, 'Notiz nicht gefunden.' end
if notiz[1].von ~= beamter.charakterId and not Rechte.Hat(beamter, 'protokoll.lesen') then
return nil, 'Du kannst nur eigene Notizen entfernen.'
end
MySQL.update.await('DELETE FROM police_person_notiz WHERE id = ?', { notizId })
Protokoll.Schreibe(beamter, 'mdt', 'Notiz entfernt',
{ typ = 'person', id = notiz[1].character }, { notizId = notizId })
return { ok = true }
end)
-- ---------------------------------------------------------------------
-- Warnhinweis und Gefahrenstufe
-- ---------------------------------------------------------------------
Kern.RegistriereAktion('mdt', 'personWarnhinweis', 'person.warnhinweis', function(quelle, beamter, daten)
local charakterId = tonumber(daten.charakterId)
if not charakterId then return nil, 'Ungültige Kennung.' end
local hinweis = type(daten.hinweis) == 'string' and daten.hinweis:sub(1, 255) or nil
if hinweis == '' then hinweis = nil end
local stufe = math.max(0, math.min(3, tonumber(daten.stufe) or 0))
MySQL.query.await([[
INSERT INTO police_person (`character`, warnhinweis, gefahrenstufe)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE warnhinweis = VALUES(warnhinweis), gefahrenstufe = VALUES(gefahrenstufe)
]], { charakterId, hinweis, stufe })
Protokoll.Schreibe(beamter, 'mdt',
hinweis and 'Warnhinweis gesetzt' or 'Warnhinweis entfernt',
{ typ = 'person', id = charakterId }, { hinweis = hinweis, stufe = stufe },
daten.grund)
return { ok = true }
end)
-- ---------------------------------------------------------------------
-- Erkennungsdienstliche Daten (Fingerabdruck, DNA, Lichtbild)
-- ---------------------------------------------------------------------
Kern.RegistriereAktion('mdt', 'personStammdaten', 'person.bearbeiten', function(quelle, beamter, daten)
local charakterId = tonumber(daten.charakterId)
if not charakterId then return nil, 'Ungültige Kennung.' end
-- Vorhandene Werte lesen, damit leere Felder nichts überschreiben
local vorhanden = MySQL.query.await(
'SELECT lichtbild, fingerabdruck, dna FROM police_person WHERE `character` = ?',
{ charakterId })
local alt = (vorhanden and vorhanden[1]) or {}
local lichtbild = daten.lichtbild ~= nil and tostring(daten.lichtbild):sub(1, 2000) or alt.lichtbild
local finger = daten.fingerabdruck ~= nil and tostring(daten.fingerabdruck):sub(1, 64) or alt.fingerabdruck
local dna = daten.dna ~= nil and tostring(daten.dna):sub(1, 64) or alt.dna
MySQL.query.await([[
INSERT INTO police_person (`character`, lichtbild, fingerabdruck, dna)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
lichtbild = VALUES(lichtbild), fingerabdruck = VALUES(fingerabdruck), dna = VALUES(dna)
]], { charakterId, lichtbild, finger, dna })
Protokoll.Schreibe(beamter, 'mdt', 'Erkennungsdienstliche Daten geändert',
{ typ = 'person', id = charakterId })
return { ok = true }
end)
-- =====================================================================
-- FAHRZEUGSUCHE
-- =====================================================================
Kern.RegistriereAktion('mdt', 'fahrzeugSuchen', 'fahrzeug.suchen', function(quelle, beamter, daten)
if not SuchbegriffGueltig(daten.begriff) then
return nil, 'Bitte mindestens zwei Zeichen eingeben.'
end
local muster = '%' .. daten.begriff:upper() .. '%'
local namensmuster = '%' .. daten.begriff .. '%'
local treffer = MySQL.query.await([[
SELECT v.plate, v.model, v.props, v.state, v.garage, v.owner,
c.firstname, c.lastname,
(SELECT COUNT(*) FROM police_fahndung f
WHERE f.typ = 'fahrzeug' AND f.ziel_id = v.plate AND f.aktiv = 1) AS fahndung
FROM aether_vehicles v
LEFT JOIN aether_characters c ON c.id = v.owner
WHERE UPPER(v.plate) LIKE ?
OR v.model LIKE ?
OR CONCAT(c.firstname, ' ', c.lastname) LIKE ?
ORDER BY v.plate LIMIT 40
]], { muster, namensmuster, namensmuster }) or {}
for _, fahrzeug in ipairs(treffer) do
if fahrzeug.props then
local erfolg, props = pcall(json.decode, fahrzeug.props)
if erfolg and type(props) == 'table' then
fahrzeug.name = props.name
fahrzeug.farbe = props.farbe
fahrzeug.klasse = props.klasse
end
end
fahrzeug.props = nil
end
return { treffer = treffer }
end)
Kern.RegistriereAktion('mdt', 'fahrzeugAkte', 'fahrzeug.suchen', function(quelle, beamter, daten)
local kennzeichen = type(daten.kennzeichen) == 'string' and daten.kennzeichen:upper() or nil
if not kennzeichen then return nil, 'Ungültiges Kennzeichen.' end
local fahrzeug = MySQL.query.await([[
SELECT v.*, c.firstname, c.lastname, c.dob
FROM aether_vehicles v
LEFT JOIN aether_characters c ON c.id = v.owner
WHERE UPPER(v.plate) = ?
]], { kennzeichen })
if not fahrzeug or not fahrzeug[1] then return nil, 'Kein Fahrzeug mit diesem Kennzeichen.' end
local eintrag = fahrzeug[1]
if eintrag.props then
local erfolg, props = pcall(json.decode, eintrag.props)
if erfolg and type(props) == 'table' then
eintrag.name = props.name
eintrag.farbe = props.farbe
eintrag.klasse = props.klasse
end
eintrag.props = nil
end
-- Fahndungsstatus
local fahndung = MySQL.query.await([[
SELECT id, grund, gefahr, prioritaet, hinweise, erstellt_am
FROM police_fahndung
WHERE typ = 'fahrzeug' AND ziel_id = ? AND aktiv = 1 LIMIT 1
]], { eintrag.plate })
-- Verknüpfte Akten
local akten = MySQL.query.await([[
SELECT a.id, a.aktenzeichen, a.typ, a.titel, a.status, a.erstellt_am
FROM police_verknuepfung v
JOIN police_akte a ON a.id = v.akte
WHERE v.typ = 'fahrzeug' AND v.ziel_id = ?
ORDER BY a.id DESC LIMIT 30
]], { eintrag.plate }) or {}
-- Protokollhistorie zu diesem Fahrzeug
local historie = Protokoll.Lies({ zielTyp = 'fahrzeug', zielId = eintrag.plate, limit = 30 })
return {
fahrzeug = eintrag,
fahndung = fahndung and fahndung[1] or nil,
akten = akten,
historie = historie,
}
end)
-- =====================================================================
-- AKTEN
-- =====================================================================
Kern.RegistriereAktion('mdt', 'aktenListe', 'akte.lesen', function(quelle, beamter, daten)
local bedingungen, werte = { '1=1' }, {}
if daten.typ and daten.typ ~= '' then
bedingungen[#bedingungen + 1] = 'a.typ = ?'
werte[#werte + 1] = daten.typ
end
if daten.status and daten.status ~= '' then
bedingungen[#bedingungen + 1] = 'a.status = ?'
werte[#werte + 1] = daten.status
end
if SuchbegriffGueltig(daten.begriff) then
bedingungen[#bedingungen + 1] = '(a.titel LIKE ? OR a.aktenzeichen LIKE ?)'
werte[#werte + 1] = '%' .. daten.begriff .. '%'
werte[#werte + 1] = '%' .. daten.begriff .. '%'
end
local akten = MySQL.query.await(([[
SELECT a.id, a.aktenzeichen, a.typ, a.titel, a.status, a.erstellt_am, a.version,
c.firstname, c.lastname
FROM police_akte a
LEFT JOIN aether_characters c ON c.id = a.erstellt_von
WHERE %s
ORDER BY a.id DESC LIMIT 60
]]):format(table.concat(bedingungen, ' AND ')), werte) or {}
return { akten = akten }
end)
Kern.RegistriereAktion('mdt', 'akteOeffnen', 'akte.lesen', function(quelle, beamter, daten)
local akteId = tonumber(daten.id)
if not akteId then return nil, 'Ungültige Kennung.' end
local akte = MySQL.query.await([[
SELECT a.*, c.firstname, c.lastname
FROM police_akte a
LEFT JOIN aether_characters c ON c.id = a.erstellt_von
WHERE a.id = ?
]], { akteId })
if not akte or not akte[1] then return nil, 'Akte nicht gefunden.' end
-- Verknüpfungen mit lesbaren Bezeichnungen anreichern
local verknuepfungen = MySQL.query.await(
'SELECT * FROM police_verknuepfung WHERE akte = ? ORDER BY id ASC', { akteId }) or {}
for _, verknuepfung in ipairs(verknuepfungen) do
if verknuepfung.typ == 'person' then
verknuepfung.bezeichnung = NameVon(tonumber(verknuepfung.ziel_id))
elseif verknuepfung.typ == 'fahrzeug' then
local modell = MySQL.scalar.await(
'SELECT model FROM aether_vehicles WHERE plate = ?', { verknuepfung.ziel_id })
verknuepfung.bezeichnung = verknuepfung.ziel_id .. (modell and (' · ' .. modell) or '')
else
verknuepfung.bezeichnung = verknuepfung.ziel_id
end
end
-- Versionshistorie
local versionen = MySQL.query.await([[
SELECT v.version, v.geaendert_am, v.grund, c.firstname, c.lastname
FROM police_akte_version v
LEFT JOIN aether_characters c ON c.id = v.geaendert_von
WHERE v.akte = ? ORDER BY v.version DESC
]], { akteId }) or {}
return {
akte = akte[1],
verknuepfungen = verknuepfungen,
versionen = versionen,
darfBearbeiten = Rechte.Hat(beamter, 'akte.bearbeiten'),
darfFreigeben = Rechte.Hat(beamter, 'akte.freigeben'),
darfSchliessen = Rechte.Hat(beamter, 'akte.schliessen'),
}
end)
Kern.RegistriereAktion('mdt', 'akteErstellen', 'akte.erstellen', function(quelle, beamter, daten)
local titel = type(daten.titel) == 'string' and daten.titel:gsub('^%s+', ''):gsub('%s+$', '') or ''
if #titel < 3 or #titel > 160 then return nil, 'Der Titel muss 3 bis 160 Zeichen lang sein.' end
-- Aktenart gegen die Konfiguration prüfen
local gueltig = false
for _, art in ipairs(Polizei.Config.Aktenarten) do
if art.id == daten.typ then gueltig = true break end
end
if not gueltig then return nil, 'Unbekannte Aktenart.' end
local aktenzeichen = Kern.ErzeugeKennung('AZ', 'police_akte', 'aktenzeichen')
local akteId = MySQL.insert.await([[
INSERT INTO police_akte (aktenzeichen, typ, titel, inhalt, erstellt_von)
VALUES (?, ?, ?, ?, ?)
]], { aktenzeichen, daten.typ, titel,
type(daten.inhalt) == 'string' and daten.inhalt or nil, beamter.charakterId })
-- Erste Version festhalten
MySQL.insert('INSERT INTO police_akte_version (akte, version, inhalt, geaendert_von, grund) VALUES (?, 1, ?, ?, ?)',
{ akteId, daten.inhalt, beamter.charakterId, 'Akte angelegt' })
-- Optionale Erstverknüpfung (z. B. direkt aus einer Personenakte heraus)
if daten.verknuepfeTyp and daten.verknuepfeId then
MySQL.insert('INSERT INTO police_verknuepfung (akte, typ, ziel_id, rolle) VALUES (?, ?, ?, ?)',
{ akteId, tostring(daten.verknuepfeTyp), tostring(daten.verknuepfeId), daten.verknuepfeRolle })
end
Protokoll.Schreibe(beamter, 'mdt', 'Akte angelegt',
{ typ = 'akte', id = akteId }, { aktenzeichen = aktenzeichen, typ = daten.typ })
Kern.Loese('akte.erstellt', akteId, beamter)
return { id = akteId, aktenzeichen = aktenzeichen }
end)
Kern.RegistriereAktion('mdt', 'akteBearbeiten', 'akte.bearbeiten', function(quelle, beamter, daten)
local akteId = tonumber(daten.id)
if not akteId then return nil, 'Ungültige Kennung.' end
local akte = MySQL.query.await('SELECT * FROM police_akte WHERE id = ?', { akteId })
if not akte or not akte[1] then return nil, 'Akte nicht gefunden.' end
if akte[1].status == 'geschlossen' and not Rechte.Hat(beamter, 'akte.freigeben') then
return nil, 'Diese Akte ist geschlossen und kann nicht mehr bearbeitet werden.'
end
local grund = type(daten.grund) == 'string' and daten.grund:sub(1, 255) or nil
if not grund or #grund < 3 then
return nil, 'Bitte gib einen Grund für die Änderung an.'
end
local neueVersion = (akte[1].version or 1) + 1
MySQL.update.await('UPDATE police_akte SET titel = ?, inhalt = ?, version = ? WHERE id = ?',
{ type(daten.titel) == 'string' and daten.titel:sub(1, 160) or akte[1].titel,
daten.inhalt, neueVersion, akteId })
MySQL.insert('INSERT INTO police_akte_version (akte, version, inhalt, geaendert_von, grund) VALUES (?, ?, ?, ?, ?)',
{ akteId, neueVersion, daten.inhalt, beamter.charakterId, grund })
Protokoll.Schreibe(beamter, 'mdt', 'Akte bearbeitet',
{ typ = 'akte', id = akteId }, { version = neueVersion }, grund)
return { version = neueVersion }
end)
--- Status einer Akte ändern — jede Stufe braucht ein eigenes Recht
Kern.RegistriereAktion('mdt', 'akteStatus', 'akte.lesen', function(quelle, beamter, daten)
local akteId = tonumber(daten.id)
local status = tostring(daten.status or '')
if not akteId then return nil, 'Ungültige Kennung.' end
if status ~= 'offen' and status ~= 'pruefung' and status ~= 'geschlossen' then
return nil, 'Unbekannter Status.'
end
-- Freigabe und Schließen sind gesondert geschützt
if status == 'geschlossen' and not Rechte.Hat(beamter, 'akte.schliessen') then
return nil, 'Dir fehlt die Berechtigung, Akten zu schließen.'
end
if status == 'pruefung' and not Rechte.Hat(beamter, 'akte.bearbeiten') then
return nil, 'Dir fehlt die Berechtigung dafür.'
end
-- Beim Schließen wird festgehalten, WER es entschieden hat
if status == 'geschlossen' then
MySQL.update.await(
'UPDATE police_akte SET status = ?, freigabe_von = ?, freigabe_am = NOW() WHERE id = ?',
{ status, beamter.charakterId, akteId })
else
MySQL.update.await('UPDATE police_akte SET status = ? WHERE id = ?', { status, akteId })
end
Protokoll.Schreibe(beamter, 'mdt', 'Aktenstatus geändert',
{ typ = 'akte', id = akteId }, { status = status }, daten.grund)
return { ok = true }
end)
-- ---------------------------------------------------------------------
-- Verknüpfungen
-- ---------------------------------------------------------------------
Kern.RegistriereAktion('mdt', 'akteVerknuepfen', 'akte.bearbeiten', function(quelle, beamter, daten)
local akteId = tonumber(daten.akteId)
local typ = tostring(daten.typ or '')
local zielId = tostring(daten.zielId or '')
if not akteId or zielId == '' then return nil, 'Unvollständige Angaben.' end
local erlaubt = { person = true, fahrzeug = true, beweis = true, einsatz = true, bericht = true }
if not erlaubt[typ] then return nil, 'Unbekannte Verknüpfungsart.' end
-- Doppelte Verknüpfungen vermeiden
local vorhanden = MySQL.scalar.await(
'SELECT id FROM police_verknuepfung WHERE akte = ? AND typ = ? AND ziel_id = ?',
{ akteId, typ, zielId })
if vorhanden then return nil, 'Diese Verknüpfung besteht bereits.' end
local id = MySQL.insert.await(
'INSERT INTO police_verknuepfung (akte, typ, ziel_id, rolle) VALUES (?, ?, ?, ?)',
{ akteId, typ, zielId, daten.rolle })
Protokoll.Schreibe(beamter, 'mdt', 'Verknüpfung hinzugefügt',
{ typ = 'akte', id = akteId }, { ziel = typ .. ':' .. zielId, rolle = daten.rolle })
return { id = id }
end)
Kern.RegistriereAktion('mdt', 'akteVerknuepfungLoesen', 'akte.bearbeiten', function(quelle, beamter, daten)
local id = tonumber(daten.id)
if not id then return nil, 'Ungültige Kennung.' end
local eintrag = MySQL.query.await('SELECT akte, typ, ziel_id FROM police_verknuepfung WHERE id = ?', { id })
if not eintrag or not eintrag[1] then return nil, 'Verknüpfung nicht gefunden.' end
MySQL.update.await('DELETE FROM police_verknuepfung WHERE id = ?', { id })
Protokoll.Schreibe(beamter, 'mdt', 'Verknüpfung entfernt',
{ typ = 'akte', id = eintrag[1].akte },
{ ziel = eintrag[1].typ .. ':' .. eintrag[1].ziel_id })
return { ok = true }
end)
-- ---------------------------------------------------------------------
-- Konfiguration für die Oberfläche
-- ---------------------------------------------------------------------
Kern.RegistriereAktion('mdt', 'stammdaten', nil, function()
return {
aktenarten = Polizei.Config.Aktenarten,
rollen = {
{ id = 'beschuldigt', name = 'Beschuldigt' },
{ id = 'zeuge', name = 'Zeuge' },
{ id = 'geschaedigt', name = 'Geschädigt' },
{ id = 'beteiligt', name = 'Beteiligt' },
},
}
end)