-- ===================================================================== -- aether-police / Modul: Fahrzeugverwaltung -- -- Das System dokumentiert Tatsachen: Standort, Tankstand, Zustand und -- wer zuletzt gefahren ist. Es entscheidet nichts — es gibt keine -- automatische Fahrzeugvergabe und keinen automatischen Statuswechsel. -- Zuweisung, Freigabe und Wartungsstatus setzt immer ein Beamter. -- ===================================================================== Kern.RegistriereModul({ id = 'fahrzeug', name = 'Fuhrpark', icon = '🚓', reihenfolge = 10, recht = 'dienstfahrzeug.sehen', beschreibung = 'Dienstfahrzeuge und Wartung', }) -- Zulaessige Werte local STATUS = { verfuegbar = 'Verfügbar', im_einsatz = 'Im Einsatz', werkstatt = 'In der Werkstatt', ausser_dienst= 'Außer Dienst', } local WARTUNG = { ok = 'In Ordnung', faellig = 'Wartung fällig', werkstatt = 'In Reparatur' } -- --------------------------------------------------------------------- -- Übersicht -- --------------------------------------------------------------------- Kern.RegistriereAktion('fahrzeug', 'liste', nil, function(quelle, beamter, daten) local bedingungen, werte = { '1=1' }, {} if daten.abteilung and daten.abteilung ~= '' then bedingungen[#bedingungen + 1] = 'f.abteilung = ?' werte[#werte + 1] = daten.abteilung end if daten.status and daten.status ~= '' then bedingungen[#bedingungen + 1] = 'f.status = ?' werte[#werte + 1] = daten.status end if daten.wartung and daten.wartung ~= '' then bedingungen[#bedingungen + 1] = 'f.wartung = ?' werte[#werte + 1] = daten.wartung end if type(daten.begriff) == 'string' and #daten.begriff >= 2 then bedingungen[#bedingungen + 1] = '(f.kennzeichen LIKE ? OR f.bezeichnung LIKE ? OR f.modell LIKE ?)' local muster = '%' .. daten.begriff .. '%' werte[#werte + 1] = muster werte[#werte + 1] = muster werte[#werte + 1] = muster end local fahrzeuge = MySQL.query.await(([[ SELECT f.*, z.firstname AS z_vorname, z.lastname AS z_nachname, zp.dienstnummer AS z_nummer, l.firstname AS l_vorname, l.lastname AS l_nachname, lp.dienstnummer AS l_nummer FROM police_fahrzeug f LEFT JOIN aether_characters z ON z.id = f.zugewiesen_an LEFT JOIN police_personal zp ON zp.`character` = f.zugewiesen_an LEFT JOIN aether_characters l ON l.id = f.letzter_fahrer LEFT JOIN police_personal lp ON lp.`character` = f.letzter_fahrer WHERE %s ORDER BY f.abteilung, f.kennzeichen LIMIT 100 ]]):format(table.concat(bedingungen, ' AND ')), werte) or {} -- Bestandszahlen local zahlen = MySQL.query.await( 'SELECT status, COUNT(*) AS anzahl FROM police_fahrzeug GROUP BY status') or {} local bestand = { verfuegbar = 0, im_einsatz = 0, werkstatt = 0, ausser_dienst = 0 } for _, zeile in ipairs(zahlen) do bestand[zeile.status] = zeile.anzahl end local wartungFaellig = MySQL.scalar.await( "SELECT COUNT(*) FROM police_fahrzeug WHERE wartung <> 'ok'") or 0 return { fahrzeuge = fahrzeuge, bestand = bestand, wartungFaellig= wartungFaellig, abteilungen = Polizei.Config.Abteilungen, statusliste = STATUS, wartungsliste = WARTUNG, rechte = { zuweisen = Rechte.Hat(beamter, 'dienstfahrzeug.zuweisen'), wartung = Rechte.Hat(beamter, 'dienstfahrzeug.wartung'), }, } end) Kern.RegistriereAktion('fahrzeug', 'oeffnen', nil, function(quelle, beamter, daten) local fahrzeugId = tonumber(daten.id) if not fahrzeugId then return nil, 'Ungültige Kennung.' end local zeilen = MySQL.query.await([[ SELECT f.*, z.firstname AS z_vorname, z.lastname AS z_nachname, zp.dienstnummer AS z_nummer, l.firstname AS l_vorname, l.lastname AS l_nachname, lp.dienstnummer AS l_nummer FROM police_fahrzeug f LEFT JOIN aether_characters z ON z.id = f.zugewiesen_an LEFT JOIN police_personal zp ON zp.`character` = f.zugewiesen_an LEFT JOIN aether_characters l ON l.id = f.letzter_fahrer LEFT JOIN police_personal lp ON lp.`character` = f.letzter_fahrer WHERE f.id = ? ]], { fahrzeugId }) if not zeilen or not zeilen[1] then return nil, 'Fahrzeug nicht gefunden.' end return { fahrzeug = zeilen[1], historie = Protokoll.Lies({ zielTyp = 'fahrzeug', zielId = fahrzeugId, limit = 30 }), beamteImDienst= Dienst.BaueListe(), statusliste = STATUS, wartungsliste = WARTUNG, rechte = { zuweisen = Rechte.Hat(beamter, 'dienstfahrzeug.zuweisen'), wartung = Rechte.Hat(beamter, 'dienstfahrzeug.wartung'), }, } end) -- --------------------------------------------------------------------- -- Zuweisung — immer manuell -- --------------------------------------------------------------------- Kern.RegistriereAktion('fahrzeug', 'zuweisen', 'dienstfahrzeug.zuweisen', function(quelle, beamter, daten) local fahrzeugId = tonumber(daten.id) if not fahrzeugId then return nil, 'Ungültige Kennung.' end local fahrzeug = MySQL.query.await( 'SELECT kennzeichen, bezeichnung, status, zugewiesen_an FROM police_fahrzeug WHERE id = ?', { fahrzeugId }) if not fahrzeug or not fahrzeug[1] then return nil, 'Fahrzeug nicht gefunden.' end -- Freigeben if daten.freigeben then MySQL.update.await('UPDATE police_fahrzeug SET zugewiesen_an = NULL WHERE id = ?', { fahrzeugId }) Protokoll.Schreibe(beamter, 'fahrzeug', 'Zuweisung aufgehoben', { typ = 'fahrzeug', id = fahrzeugId }, { kennzeichen = fahrzeug[1].kennzeichen }) return { ok = true } end local zielCharakter = tonumber(daten.charakterId) if not zielCharakter then return nil, 'Keine Person ausgewählt.' end -- Nur Personal der Behörde local personal = MySQL.query.await( 'SELECT dienstnummer FROM police_personal WHERE `character` = ? AND aktiv = 1', { zielCharakter }) if not personal or not personal[1] then return nil, 'Diese Person gehört nicht zum Personalbestand.' end if fahrzeug[1].status == 'werkstatt' or fahrzeug[1].status == 'ausser_dienst' then return nil, ('Das Fahrzeug ist %s und kann nicht zugewiesen werden.') :format(STATUS[fahrzeug[1].status]) end MySQL.update.await('UPDATE police_fahrzeug SET zugewiesen_an = ? WHERE id = ?', { zielCharakter, fahrzeugId }) Protokoll.Schreibe(beamter, 'fahrzeug', 'Fahrzeug zugewiesen', { typ = 'fahrzeug', id = fahrzeugId }, { kennzeichen = fahrzeug[1].kennzeichen, an = personal[1].dienstnummer }, daten.grund) -- Den Betroffenen informieren, falls im Dienst local zielQuelle = Dienst.QuelleVonCharakter(zielCharakter) if zielQuelle then Hinweise.Erstelle({ charakterId = zielCharakter }, { kategorie = 'system', prioritaet = 'normal', titel = 'Dienstfahrzeug zugewiesen', text = ('%s (%s) wurde dir von %s zugewiesen.') :format(fahrzeug[1].bezeichnung, fahrzeug[1].kennzeichen, beamter.dienstnummer), zielTyp = 'fahrzeug', zielId = fahrzeugId, }) end return { ok = true } end) -- --------------------------------------------------------------------- -- Status und Wartung — ebenfalls manuell -- --------------------------------------------------------------------- Kern.RegistriereAktion('fahrzeug', 'status', 'dienstfahrzeug.zuweisen', function(quelle, beamter, daten) local fahrzeugId = tonumber(daten.id) local status = tostring(daten.status or '') if not fahrzeugId or not STATUS[status] then return nil, 'Unbekannter Status.' end local fahrzeug = MySQL.query.await( 'SELECT kennzeichen FROM police_fahrzeug WHERE id = ?', { fahrzeugId }) if not fahrzeug or not fahrzeug[1] then return nil, 'Fahrzeug nicht gefunden.' end MySQL.update.await('UPDATE police_fahrzeug SET status = ? WHERE id = ?', { status, fahrzeugId }) Protokoll.Schreibe(beamter, 'fahrzeug', 'Status geändert', { typ = 'fahrzeug', id = fahrzeugId }, { kennzeichen = fahrzeug[1].kennzeichen, status = status }, daten.grund) return { ok = true } end) Kern.RegistriereAktion('fahrzeug', 'wartung', 'dienstfahrzeug.wartung', function(quelle, beamter, daten) local fahrzeugId = tonumber(daten.id) local wartung = tostring(daten.wartung or '') if not fahrzeugId or not WARTUNG[wartung] then return nil, 'Unbekannter Wartungsstatus.' end local fahrzeug = MySQL.query.await( 'SELECT kennzeichen, wartung FROM police_fahrzeug WHERE id = ?', { fahrzeugId }) if not fahrzeug or not fahrzeug[1] then return nil, 'Fahrzeug nicht gefunden.' end MySQL.update.await('UPDATE police_fahrzeug SET wartung = ? WHERE id = ?', { wartung, fahrzeugId }) Protokoll.Schreibe(beamter, 'fahrzeug', 'Wartungsstatus gesetzt', { typ = 'fahrzeug', id = fahrzeugId }, { kennzeichen = fahrzeug[1].kennzeichen, von = fahrzeug[1].wartung, nach = wartung }, daten.vermerk) -- Bei fälliger Wartung die Fuhrparkverantwortlichen erinnern if wartung ~= 'ok' then Hinweise.Erstelle(nil, { kategorie = 'system', prioritaet = 'niedrig', titel = 'Wartung: ' .. fahrzeug[1].kennzeichen, text = ('%s — %s'):format(WARTUNG[wartung], daten.vermerk or 'ohne Vermerk'), zielTyp = 'fahrzeug', zielId = fahrzeugId, }) end return { ok = true } end) -- --------------------------------------------------------------------- -- Fuhrpark pflegen -- --------------------------------------------------------------------- Kern.RegistriereAktion('fahrzeug', 'anlegen', 'dienstfahrzeug.wartung', function(quelle, beamter, daten) local kennzeichen = type(daten.kennzeichen) == 'string' and daten.kennzeichen:gsub('%s', ''):upper() or '' if #kennzeichen < 2 or #kennzeichen > 16 then return nil, 'Das Kennzeichen muss 2 bis 16 Zeichen lang sein.' end local vorhanden = MySQL.scalar.await( 'SELECT id FROM police_fahrzeug WHERE kennzeichen = ?', { kennzeichen }) if vorhanden then return nil, 'Dieses Kennzeichen ist bereits im Fuhrpark.' end local modell = type(daten.modell) == 'string' and daten.modell:gsub('%s', '') or '' if #modell < 2 then return nil, 'Bitte das Fahrzeugmodell angeben.' end local bezeichnung = type(daten.bezeichnung) == 'string' and daten.bezeichnung:gsub('^%s+', ''):gsub('%s+$', '') or '' if #bezeichnung < 2 then return nil, 'Bitte eine Bezeichnung angeben.' end -- Abteilung prüfen local abteilung = 'streife' for _, eintrag in ipairs(Polizei.Config.Abteilungen) do if eintrag.id == daten.abteilung then abteilung = daten.abteilung break end end local fahrzeugId = MySQL.insert.await([[ INSERT INTO police_fahrzeug (kennzeichen, modell, bezeichnung, abteilung) VALUES (?, ?, ?, ?) ]], { kennzeichen, modell:sub(1, 48), bezeichnung:sub(1, 64), abteilung }) Protokoll.Schreibe(beamter, 'fahrzeug', 'Fahrzeug aufgenommen', { typ = 'fahrzeug', id = fahrzeugId }, { kennzeichen = kennzeichen, modell = modell, abteilung = abteilung }) return { id = fahrzeugId } end) Kern.RegistriereAktion('fahrzeug', 'entfernen', 'dienstfahrzeug.wartung', function(quelle, beamter, daten) local fahrzeugId = tonumber(daten.id) if not fahrzeugId then return nil, 'Ungültige Kennung.' end local grund = type(daten.grund) == 'string' and daten.grund:sub(1, 255) or '' if #grund < 3 then return nil, 'Bitte einen Grund für die Ausmusterung angeben.' end local fahrzeug = MySQL.query.await( 'SELECT kennzeichen, bezeichnung FROM police_fahrzeug WHERE id = ?', { fahrzeugId }) if not fahrzeug or not fahrzeug[1] then return nil, 'Fahrzeug nicht gefunden.' end MySQL.update.await('DELETE FROM police_fahrzeug WHERE id = ?', { fahrzeugId }) Protokoll.Schreibe(beamter, 'fahrzeug', 'Fahrzeug ausgemustert', { typ = 'fahrzeug', id = fahrzeugId }, { kennzeichen = fahrzeug[1].kennzeichen, bezeichnung = fahrzeug[1].bezeichnung }, grund) return { ok = true } end) -- ===================================================================== -- TELEMETRIE -- -- Der Client meldet Tatsachen: wo das Fahrzeug steht, wie voll der Tank -- ist, wie es um den Zustand steht und wer zuletzt gefahren ist. -- Das ist Dokumentation, keine Entscheidung — Status und Wartung -- bleiben davon unberührt. -- ===================================================================== RegisterNetEvent('aether:police:fahrzeugMelden', function(daten) local quelle = source local beamter = Dienst.Hole(quelle) if not beamter then return end if type(daten) ~= 'table' or type(daten.kennzeichen) ~= 'string' then return end local kennzeichen = daten.kennzeichen:gsub('%s', ''):upper() local fahrzeug = MySQL.scalar.await( 'SELECT id FROM police_fahrzeug WHERE kennzeichen = ?', { kennzeichen }) if not fahrzeug then return end -- kein Dienstfahrzeug local tank = math.max(0, math.min(100, tonumber(daten.tank) or 0)) local zustand = math.max(0, math.min(1000, tonumber(daten.zustand) or 1000)) MySQL.update([[ UPDATE police_fahrzeug SET tank = ?, zustand = ?, letzter_fahrer = ?, pos_x = ?, pos_y = ?, pos_z = ? WHERE id = ? ]], { tank, zustand, beamter.charakterId, daten.x, daten.y, daten.z, fahrzeug, }) end) -- ===================================================================== -- EXPORT -- ===================================================================== --- Ist ein Kennzeichen ein Dienstfahrzeug? exports('IstDienstfahrzeug', function(kennzeichen) if type(kennzeichen) ~= 'string' then return false end local treffer = MySQL.scalar.await( 'SELECT id FROM police_fahrzeug WHERE kennzeichen = ?', { kennzeichen:gsub('%s', ''):upper() }) return treffer ~= nil end)