- 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>
636 lines
26 KiB
Lua
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)
|