-- ===================================================================== -- aether-police / Modul: Beweisverwaltung -- -- Es gibt KEINE automatische Spurenerkennung. Jeder Beweis wird von -- einem Beamten erfasst, beschrieben und zugeordnet. Das System -- vergibt lediglich eine Kennung und hält die Verknüpfungen zusammen. -- ===================================================================== Kern.RegistriereModul({ id = 'beweis', name = 'Beweise', icon = '🔬', reihenfolge = 6, recht = 'beweis.erfassen', beschreibung = 'Beweismittel erfassen und zuordnen', }) -- --------------------------------------------------------------------- -- Hilfsfunktionen -- --------------------------------------------------------------------- --- Prüft, ob eine Beweisart bekannt ist local function ArtGueltig(art) for _, eintrag in ipairs(Polizei.Config.Beweisarten) do if eintrag.id == art then return true end end return false end --- Lädt die Verknüpfungen eines Beweises (welche Akten nutzen ihn?) --- @param beweisId number --- @return table local function LadeVerknuepfungen(beweisId) return MySQL.query.await([[ SELECT v.id, a.id AS akte, a.aktenzeichen, a.titel, a.typ, a.status FROM police_verknuepfung v JOIN police_akte a ON a.id = v.akte WHERE v.typ = 'beweis' AND v.ziel_id = ? ORDER BY a.id DESC ]], { tostring(beweisId) }) or {} end -- ===================================================================== -- ÜBERSICHT -- ===================================================================== Kern.RegistriereAktion('beweis', 'liste', nil, function(quelle, beamter, daten) local bedingungen, werte = { '1=1' }, {} if daten.art and daten.art ~= '' then bedingungen[#bedingungen + 1] = 'b.art = ?' werte[#werte + 1] = daten.art end if type(daten.begriff) == 'string' and #daten.begriff >= 2 then bedingungen[#bedingungen + 1] = '(b.bezeichnung LIKE ? OR b.kennung LIKE ? OR b.fundort LIKE ?)' local muster = '%' .. daten.begriff .. '%' werte[#werte + 1] = muster werte[#werte + 1] = muster werte[#werte + 1] = muster end if daten.akte then bedingungen[#bedingungen + 1] = 'b.akte = ?' werte[#werte + 1] = tonumber(daten.akte) end local beweise = MySQL.query.await(([[ SELECT b.id, b.kennung, b.art, b.bezeichnung, b.beschreibung, b.fundort, b.gefunden_am, b.datei, b.akte, b.erfasst_am, c.firstname, c.lastname, p.dienstnummer, a.aktenzeichen FROM police_beweis b LEFT JOIN aether_characters c ON c.id = b.erfasst_von LEFT JOIN police_personal p ON p.`character` = b.erfasst_von LEFT JOIN police_akte a ON a.id = b.akte WHERE %s ORDER BY b.id DESC LIMIT 80 ]]):format(table.concat(bedingungen, ' AND ')), werte) or {} return { beweise = beweise, arten = Polizei.Config.Beweisarten, rechte = { erfassen = Rechte.Hat(beamter, 'beweis.erfassen'), loeschen = Rechte.Hat(beamter, 'beweis.loeschen'), akte = Rechte.Hat(beamter, 'akte.bearbeiten'), }, } end) Kern.RegistriereAktion('beweis', 'oeffnen', nil, function(quelle, beamter, daten) local beweisId = tonumber(daten.id) if not beweisId then return nil, 'Ungültige Kennung.' end local beweis = MySQL.query.await([[ SELECT b.*, c.firstname, c.lastname, p.dienstnummer, a.aktenzeichen, a.titel AS akte_titel FROM police_beweis b LEFT JOIN aether_characters c ON c.id = b.erfasst_von LEFT JOIN police_personal p ON p.`character` = b.erfasst_von LEFT JOIN police_akte a ON a.id = b.akte WHERE b.id = ? ]], { beweisId }) if not beweis or not beweis[1] then return nil, 'Beweis nicht gefunden.' end -- Asservate, die zu diesem Beweis gehören local asservate = MySQL.query.await([[ SELECT id, kennung, kategorie, bezeichnung, status, lagerort FROM police_asservat WHERE beweis = ? ]], { beweisId }) or {} return { beweis = beweis[1], akten = LadeVerknuepfungen(beweisId), asservate = asservate, historie = Protokoll.Lies({ zielTyp = 'beweis', zielId = beweisId, limit = 30 }), rechte = { loeschen = Rechte.Hat(beamter, 'beweis.loeschen'), akte = Rechte.Hat(beamter, 'akte.bearbeiten'), }, } end) -- ===================================================================== -- ERFASSEN -- ===================================================================== Kern.RegistriereAktion('beweis', 'erfassen', 'beweis.erfassen', function(quelle, beamter, daten) if not ArtGueltig(daten.art) then return nil, 'Unbekannte Beweisart.' end local bezeichnung = type(daten.bezeichnung) == 'string' and daten.bezeichnung:gsub('^%s+', ''):gsub('%s+$', '') or '' if #bezeichnung < 3 or #bezeichnung > 160 then return nil, 'Die Bezeichnung muss 3 bis 160 Zeichen lang sein.' end local fundort = type(daten.fundort) == 'string' and daten.fundort:gsub('^%s+', ''):gsub('%s+$', '') or '' if #fundort < 2 then return nil, 'Bitte einen Fundort angeben — er gehört zur Beweiskette.' end -- Akte prüfen, falls angegeben local akteId = tonumber(daten.akte) if akteId then local vorhanden = MySQL.scalar.await('SELECT id FROM police_akte WHERE id = ?', { akteId }) if not vorhanden then return nil, 'Die angegebene Akte existiert nicht.' end end local kennung = Kern.ErzeugeKennung('BM', 'police_beweis', 'kennung') local beweisId = MySQL.insert.await([[ INSERT INTO police_beweis (kennung, akte, art, bezeichnung, beschreibung, fundort, datei, erfasst_von) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ]], { kennung, akteId, daten.art, bezeichnung, type(daten.beschreibung) == 'string' and daten.beschreibung or nil, fundort, type(daten.datei) == 'string' and daten.datei:sub(1, 2000) or nil, beamter.charakterId, }) -- Ist eine Akte angegeben, gleich verknüpfen if akteId then MySQL.insert('INSERT INTO police_verknuepfung (akte, typ, ziel_id) VALUES (?, ?, ?)', { akteId, 'beweis', tostring(beweisId) }) end Protokoll.Schreibe(beamter, 'beweis', 'Beweis erfasst', { typ = 'beweis', id = beweisId }, { kennung = kennung, art = daten.art, fundort = fundort }) Kern.Loese('beweis.erfasst', beweisId, beamter) return { id = beweisId, kennung = kennung } end) -- ===================================================================== -- BEARBEITEN -- ===================================================================== Kern.RegistriereAktion('beweis', 'bearbeiten', 'beweis.erfassen', function(quelle, beamter, daten) local beweisId = tonumber(daten.id) if not beweisId then return nil, 'Ungültige Kennung.' end local beweis = MySQL.query.await( 'SELECT erfasst_von, kennung FROM police_beweis WHERE id = ?', { beweisId }) if not beweis or not beweis[1] then return nil, 'Beweis nicht gefunden.' end -- Fremde Beweise darf nur ändern, wer auch löschen darf if beweis[1].erfasst_von ~= beamter.charakterId and not Rechte.Hat(beamter, 'beweis.loeschen') then return nil, 'Du kannst nur eigene Beweise bearbeiten.' end local grund = type(daten.grund) == 'string' and daten.grund:sub(1, 255) or '' if #grund < 3 then return nil, 'Änderungen an Beweismitteln erfordern einen Grund.' end MySQL.update.await([[ UPDATE police_beweis SET bezeichnung = ?, beschreibung = ?, fundort = ?, datei = ? WHERE id = ? ]], { type(daten.bezeichnung) == 'string' and daten.bezeichnung:sub(1, 160) or nil, type(daten.beschreibung) == 'string' and daten.beschreibung or nil, type(daten.fundort) == 'string' and daten.fundort:sub(1, 160) or nil, type(daten.datei) == 'string' and daten.datei:sub(1, 2000) or nil, beweisId, }) Protokoll.Schreibe(beamter, 'beweis', 'Beweis geändert', { typ = 'beweis', id = beweisId }, { kennung = beweis[1].kennung }, grund) return { ok = true } end) -- ===================================================================== -- ENTFERNEN -- -- Beweise verschwinden nicht still — das Entfernen verlangt ein -- eigenes Recht und eine Begründung und bleibt im Protokoll sichtbar. -- ===================================================================== Kern.RegistriereAktion('beweis', 'entfernen', 'beweis.loeschen', function(quelle, beamter, daten) local beweisId = tonumber(daten.id) if not beweisId then return nil, 'Ungültige Kennung.' end local grund = type(daten.grund) == 'string' and daten.grund:sub(1, 255) or '' if #grund < 5 then return nil, 'Das Entfernen eines Beweismittels erfordert eine ausführliche Begründung.' end local beweis = MySQL.query.await( 'SELECT kennung, bezeichnung, art FROM police_beweis WHERE id = ?', { beweisId }) if not beweis or not beweis[1] then return nil, 'Beweis nicht gefunden.' end -- Verknüpfte Asservate müssen zuerst geklärt werden local asservate = MySQL.scalar.await( "SELECT COUNT(*) FROM police_asservat WHERE beweis = ? AND status = 'eingelagert'", { beweisId }) or 0 if asservate > 0 then return nil, ('Zu diesem Beweis sind noch %d Asservate eingelagert. Bitte zuerst klären.') :format(asservate) end MySQL.update.await('DELETE FROM police_verknuepfung WHERE typ = ? AND ziel_id = ?', { 'beweis', tostring(beweisId) }) MySQL.update.await('DELETE FROM police_beweis WHERE id = ?', { beweisId }) Protokoll.Schreibe(beamter, 'beweis', 'Beweis entfernt', { typ = 'beweis', id = beweisId }, { kennung = beweis[1].kennung, bezeichnung = beweis[1].bezeichnung }, grund) Hinweise.Erstelle(nil, { kategorie = 'beweis', prioritaet = 'hoch', titel = 'Beweismittel entfernt', text = ('%s (%s) wurde von %s entfernt: %s') :format(beweis[1].kennung, beweis[1].bezeichnung, beamter.dienstnummer, grund), }) return { ok = true } end) -- ===================================================================== -- MIT AKTE VERKNÜPFEN -- ===================================================================== Kern.RegistriereAktion('beweis', 'akteVerknuepfen', 'akte.bearbeiten', function(quelle, beamter, daten) local beweisId = tonumber(daten.beweisId) local akteId = tonumber(daten.akteId) if not beweisId or not akteId then return nil, 'Unvollständige Angaben.' end local akte = MySQL.query.await( 'SELECT aktenzeichen FROM police_akte WHERE id = ?', { akteId }) if not akte or not akte[1] then return nil, 'Akte nicht gefunden.' end local vorhanden = MySQL.scalar.await( 'SELECT id FROM police_verknuepfung WHERE akte = ? AND typ = ? AND ziel_id = ?', { akteId, 'beweis', tostring(beweisId) }) if vorhanden then return nil, 'Diese Verknüpfung besteht bereits.' end MySQL.insert.await('INSERT INTO police_verknuepfung (akte, typ, ziel_id) VALUES (?, ?, ?)', { akteId, 'beweis', tostring(beweisId) }) Protokoll.Schreibe(beamter, 'beweis', 'Mit Akte verknüpft', { typ = 'beweis', id = beweisId }, { akte = akte[1].aktenzeichen }) return { ok = true } end) --- Aktensuche für die Verknüpfungsauswahl Kern.RegistriereAktion('beweis', 'aktenSuche', 'akte.lesen', function(quelle, beamter, daten) local begriff = type(daten.begriff) == 'string' and daten.begriff or '' if #begriff < 2 then return { akten = {} } end local muster = '%' .. begriff .. '%' local akten = MySQL.query.await([[ SELECT id, aktenzeichen, typ, titel, status FROM police_akte WHERE aktenzeichen LIKE ? OR titel LIKE ? ORDER BY id DESC LIMIT 20 ]], { muster, muster }) or {} return { akten = akten } end)