Zum Hauptinhalt springen
Schneespur
Dokumentation durchblättern

Module entwickeln

Kern-Datenmodell

Die Datenbank-Modelle des Kerns — Customer, Job, User, WorkShift und mehr — mit ihren Feldern, Beziehungen und dem direkten Zugriff aus einem Modul.

Ein Modul erweitert den Kern, indem es auf dessen Daten aufsetzt. Diese Seite beschreibt die zentralen Datenbank-Modelle, ihre Felder und Beziehungen — und zeigt, wie Sie aus einem eigenen Modul darauf zugreifen. Wer das Datenmodell kennt, baut Module, die mit dem Kern zusammenspielen, statt eigene Parallelwelten anzulegen.

Die Domäne in Begriffen

Schneespur verwaltet den Ablauf eines Winterdienstes. Die folgenden Begriffe ziehen sich durch das gesamte Datenmodell — sie zu kennen, erspart später viel Suchen:

  • Customers sind die Auftraggeber, die den Winterdienst beauftragen.
  • Customer Objects sind die konkreten Orte (Adressen), die betreut werden.
  • Drivers sind die Personen, die den Dienst ausführen (als User mit Fahrer-Rolle).
  • Vehicles sind die eingesetzten Fahrzeuge oder Maschinen.
  • Work Shifts halten fest, wann ein Fahrer im Einsatz ist.
  • Jobs (service_jobs) sind die einzelnen Einsätze an einem Customer Object.
  • GPS Points zeichnen den Standort des Fahrers während eines Einsatzes auf.
  • Weather Snapshots halten die Wetterlage zu Beginn und Ende eines Einsatzes fest.

Der Job ist die zentrale Einheit: An ihm hängen Fahrer, Fahrzeug, Kunde, Ort, GPS-Track, Wetter und Fotos zusammen. Fast jede Auswertung im System läuft über ihn.

Beziehungen im Überblick

Customer ─┬─ has many ──→ CustomerObject ──→ has many ──→ Job
           └─ has many ──→ Job                              │
                                                             ├─ belongs to ─→ User (driver)
User (driver) ──→ has many ──→ WorkShift ──→ has many ──→ Job│
                                                             ├─ belongs to ─→ Vehicle
Vehicle ──────→ has many ──→ Job                             │
                                                             ├─ has many ──→ GpsPoint
                                                             ├─ has many ──→ WeatherSnapshot
                                                             ├─ has many ──→ JobPhoto
                                                             └─ has many ──→ JobAudit

Ein Customer kann mehrere CustomerObject-Standorte haben, an jedem Standort entstehen viele Job-Einsätze. Jeder Job verweist auf genau einen Fahrer, ein Fahrzeug, einen Kunden und einen Ort — und sammelt seine GPS-Punkte, Wetter-Snapshots, Fotos und Audit-Einträge.

Models im Detail

User

Tabelle: users

Ein User ist entweder Administrator oder Fahrer — die Rolle steht im Feld role. Fahrer bekommen zusätzlich OwnTracks-Zugangsdaten für das GPS-Tracking.

FeldTypBeschreibung
idintPrimärschlüssel
namestringVollständiger Name
emailstringEindeutige E-Mail
passwordhashedLogin-Passwort
roleUserRole enumadmin oder driver
phonestringTelefonnummer
localestringOberflächensprache (nullable; null = App-Standard). Jede in LocaleRegistry registrierte Sprache. Seit 1.1.3
notestextAdmin-Notizen
default_vehicle_idFKBevorzugtes Fahrzeug
owntracks_usernamestringOwnTracks-GPS-Benutzername
owntracks_password_hashstringOwnTracks-Passwort (bcrypt)
dsgvo_informed_atdatetimeDSGVO-Bestätigung
anonymized_atdatetimeZeitpunkt der Fahrer-Anonymisierung

Wichtige Methoden:

$user->isAdmin(): bool
$user->isDriver(): bool
$user->isAnonymized(): bool
$user->displayName(): string        // returns anonymized label if anonymized
$user->hasRole('admin'): bool
$user->hasPermission('jobs.view'): bool

Scopes: drivers(), admins(), withAnonymized(), onlyAnonymized()

Globaler Scope: ExcludeAnonymizedScope blendet anonymisierte Nutzer standardmäßig aus. Das ist wichtig, wenn Sie in einem Modul mit Fahrerdaten arbeiten: Ein anonymisierter Fahrer taucht in normalen Abfragen nicht mehr auf — wer ihn trotzdem braucht (etwa für Altdaten), nutzt withAnonymized().

Beziehungen: roles, workShifts, serviceJobs, gpsPoints, defaultVehicle, dsgvoConfirmations

Customer

Tabelle: customers

Der Customer ist der Auftraggeber. Einige Felder steuern das optionale Kundenportal — etwa, ob der Kunde GPS-Track, Fotos oder den Fahrernamen sehen darf.

FeldTypBeschreibung
idintPrimärschlüssel
namestringFirmen-/Kundenname
contact_namestringAnsprechpartner
emailstringHaupt-E-Mail
phonestringTelefon
auto_notify_emailboolEinsatz-Benachrichtigungen automatisch senden
notification_emailstringAbweichende E-Mail für Benachrichtigungen
localestringBevorzugte Oberflächensprache — jede in LocaleRegistry registrierte Sprache
passwordhashedPortal-Login-Passwort
portal_enabledboolPortalzugang aktiv
portal_show_gpsboolGPS im Portal anzeigen
portal_show_photosboolFotos im Portal anzeigen
portal_show_driver_nameboolFahrername im Portal anzeigen

Beziehungen: objects (CustomerObject), serviceJobs, notificationLogs

CustomerObject

Tabelle: customer_objects

Ein CustomerObject ist ein konkreter Standort eines Kunden — mit Adresse, Koordinaten und einsatzrelevanten Vorgaben wie Streu-Schwelle oder Hinweisen für die Fahrer. Die Koordinaten lat/lon sind die Grundlage für Wetterabfragen und GPS-Zuordnung.

FeldTypBeschreibung
idintPrimärschlüssel
customer_idFKÜbergeordneter Kunde
namestringObjektname (z. B. „Haupteingang”)
street, zip, citystringAdresse
lat, londecimal(7)Koordinaten für Wetter/GPS
contact_name, contact_email, contact_phonestringKontakt vor Ort
price_amount_centsintEinsatzpreis in Cent
price_unitstringPreiseinheit
plow_threshold_cmintSchneehöhen-Schwelle fürs Räumen
salt_enabledboolOb Streuen anwendbar ist
site_notes, access_notestextHinweise für Fahrer
auto_notify_emailboolAuto-Benachrichtigung für dieses Objekt
notification_emailstringObjektspezifische Benachrichtigungs-E-Mail

Geldbeträge liegen bewusst als Ganzzahl in Cent (price_amount_cents) vor — das vermeidet Rundungsfehler, die bei Fließkomma-Beträgen entstehen würden.

Beziehungen: customer, serviceJobs

Job (service_jobs)

Tabelle: service_jobs

Der Job ist der einzelne Einsatz. Beachten Sie: Das Model heißt Job, die Tabelle aber service_jobs — daher heißt die Beziehung auf anderen Models meist serviceJobs.

FeldTypBeschreibung
idintPrimärschlüssel
work_shift_idFKÜbergeordnete Schicht
customer_idFKKunde
customer_object_idFKEinsatzort
user_idFKFahrer
vehicle_idFKEingesetztes Fahrzeug
typeJobType enumraumen, streuen, kontrolle, raumen_streuen
started_atdatetimeEinsatzbeginn
ended_atdatetimeEinsatzende
notestextFahrernotizen
is_manualboolManuell erfasst (nicht GPS-getrackt)

Wichtige Methoden:

$job->isCompleted(): bool      // has ended_at
$job->isInGracePeriod(): bool  // within 24h of completion
$job->isLocked(): bool         // past grace period
$job->isGpsLocked(): bool      // GPS locked after completion
$job->graceDeadline(): ?Carbon
$job->durationFormatted(): string  // "2h 15min"
$job->localStartedAt(): Carbon
$job->localEndedAt(): Carbon

Hinter diesen Methoden steckt das Sperr-Konzept des Einsatznachweises: Nach Abschluss bleibt ein Einsatz noch eine begrenzte Zeit (isInGracePeriod()) änderbar, danach gilt er als gesperrt (isLocked()). So bleibt der dokumentierte Nachweis nachträglich unverändert — wichtig, wenn er später als Beleg dienen soll. Verlassen Sie sich in einem Modul nicht auf eigene Datums-Vergleiche, sondern auf diese Methoden.

Beziehungen: workShift, customer, customerObject, user, vehicle, gpsPoints, weatherSnapshots, jobPhotos, audits, notificationLogs, alertDismissals

WorkShift

Tabelle: work_shifts

Eine WorkShift klammert die Einsätze eines Fahrers zu einer Schicht. Ist ended_at noch null, läuft die Schicht.

FeldTypBeschreibung
idintPrimärschlüssel
user_idFKFahrer
started_atdatetimeSchichtbeginn
ended_atdatetimeSchichtende (null = laufend)
notestextSchichtnotizen

Beziehungen: user, jobs

Vehicle

Tabelle: vehicles

FeldTypBeschreibung
idintPrimärschlüssel
namestringFahrzeugname
license_platestringKennzeichen
owntracks_device_idstringOwnTracks-Geräte-Kennung
notestextFahrzeugnotizen

Wichtige Methode: displayLabel(): string — liefert „name (license_plate)”.

Beziehungen: serviceJobs

WeatherSnapshot

Tabelle: weather_snapshots

Pro Einsatz entstehen typischerweise zwei WeatherSnapshot-Einträge: einer zu Beginn (start) und einer am Ende (end). Das Feld raw_response bewahrt die vollständige Antwort des Wetteranbieters auf — so bleibt die Wetterlage auch dann nachvollziehbar, wenn sich die Auswertung später ändert.

FeldTypBeschreibung
idintPrimärschlüssel
job_idFKÜbergeordneter Einsatz
momentWeatherMoment enumstart oder end
providerstringAnbieter-Slug
temperaturedecimal(2)°C
precipitationdecimal(2)mm
snow_depthdecimal(2)cm
wind_speeddecimal(2)km/h
humidityint%
weather_codeintWMO-Wettercode
fetched_atdatetimeAbrufzeitpunkt
raw_responseJSONVollständige API-Antwort

Wichtige Methoden: providerLabel(), weatherLabel() — liefern lokalisierte Anzeigetexte.

Enums

Mehrere Felder sind als typsichere Enums modelliert. Das verhindert ungültige Werte in der Datenbank und gibt Ihnen lesbare Konstanten statt roher Strings.

enum JobType: string {
    case Raumen = 'raumen';           // Snow removal/plowing
    case Streuen = 'streuen';         // Salt spreading
    case Kontrolle = 'kontrolle';     // Inspection
    case RaumenStreuen = 'raumen_streuen'; // Combined
}

enum WeatherMoment: string {
    case Start = 'start';
    case End = 'end';
}

enum UserRole: string {
    case Admin = 'admin';
    case Driver = 'driver';
}

Jedes Enum hat eine Methode label(): string, die einen lokalisierten Anzeigenamen liefert. Verwenden Sie in einem Modul diese label()-Methode statt eigener Übersetzungen, damit die Bezeichnungen mit dem Rest der Oberfläche übereinstimmen.

Weitere Models

Neben den zentralen Modellen gibt es eine Reihe unterstützender Models. Einige davon sind bewusst insert-only (nur einfügen, nie ändern oder löschen) — sie bilden den revisionssicheren Teil des Systems, etwa Audit- und Einwilligungs-Spuren.

ModelTabelleZweck
GpsPointgps_pointsGPS-Koordinaten während der Einsätze
JobPhotojob_photosWährend der Einsätze aufgenommene Fotos
JobAuditjob_auditsÄnderungsprotokoll (insert-only)
SettingsettingsSchlüssel-Wert-Konfiguration
RolerolesAutorisierungs-Rollen
PermissionpermissionsAutorisierungs-Berechtigungen
ModulemodulesInstallierte Module
ModuleApiTokenmodule_api_tokensAPI-Authentifizierungs-Token
ModLogmod_logsModul-Log-Einträge (insert-only)
NotificationLognotification_logsBenachrichtigungs-Protokoll
AlertDismissalalert_dismissalsVerworfene Einsatz-Hinweise
MonthlyStatisticmonthly_statisticsVorberechnete Monatsstatistiken
DsgvoConfirmationdriver_dsgvo_confirmationsDSGVO-Einwilligungen (insert-only)
OwntracksCredentialEventowntracks_credential_eventsGPS-Zugangsdaten-Protokoll (insert-only)

Die Konfiguration läuft über Setting (Schlüssel-Wert) — wie Sie eigene Einstellungen mit Modul-Präfix registrieren und lesen, steht unter ServiceProvider. Rollen und Berechtigungen behandelt Berechtigungen und Rollen.

Models aus dem Modul nutzen

Sie greifen auf die Kern-Models direkt zu, indem Sie sie aus dem App\Models-Namespace importieren. Es gibt keine gesonderte Modul-Schnittstelle für Lesezugriffe — Sie arbeiten mit denselben Eloquent-Models wie der Kern:

use App\Models\Customer;
use App\Models\Job;
use App\Models\User;
use App\Models\Setting;

$customers = Customer::with('objects')->get();
$recentJobs = Job::where('ended_at', '>=', now()->subDays(7))->get();
$drivers = User::drivers()->get();

Beachten Sie dabei zwei Dinge: Laden Sie Beziehungen über with(...) vor, wenn Sie sie in einer Schleife brauchen — sonst entstehen viele Einzelabfragen. Und denken Sie an die globalen Scopes: User::drivers() liefert dank ExcludeAnonymizedScope von sich aus keine anonymisierten Fahrer. Wer die volle Datenbank-Migration eines Moduls aufsetzen will, findet die Grundlagen unter Datenbank und Migrationen.