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
Usermit 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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
name | string | Vollständiger Name |
email | string | Eindeutige E-Mail |
password | hashed | Login-Passwort |
role | UserRole enum | admin oder driver |
phone | string | Telefonnummer |
locale | string | Oberflächensprache (nullable; null = App-Standard). Jede in LocaleRegistry registrierte Sprache. Seit 1.1.3 |
notes | text | Admin-Notizen |
default_vehicle_id | FK | Bevorzugtes Fahrzeug |
owntracks_username | string | OwnTracks-GPS-Benutzername |
owntracks_password_hash | string | OwnTracks-Passwort (bcrypt) |
dsgvo_informed_at | datetime | DSGVO-Bestätigung |
anonymized_at | datetime | Zeitpunkt 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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
name | string | Firmen-/Kundenname |
contact_name | string | Ansprechpartner |
email | string | Haupt-E-Mail |
phone | string | Telefon |
auto_notify_email | bool | Einsatz-Benachrichtigungen automatisch senden |
notification_email | string | Abweichende E-Mail für Benachrichtigungen |
locale | string | Bevorzugte Oberflächensprache — jede in LocaleRegistry registrierte Sprache |
password | hashed | Portal-Login-Passwort |
portal_enabled | bool | Portalzugang aktiv |
portal_show_gps | bool | GPS im Portal anzeigen |
portal_show_photos | bool | Fotos im Portal anzeigen |
portal_show_driver_name | bool | Fahrername 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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
customer_id | FK | Übergeordneter Kunde |
name | string | Objektname (z. B. „Haupteingang”) |
street, zip, city | string | Adresse |
lat, lon | decimal(7) | Koordinaten für Wetter/GPS |
contact_name, contact_email, contact_phone | string | Kontakt vor Ort |
price_amount_cents | int | Einsatzpreis in Cent |
price_unit | string | Preiseinheit |
plow_threshold_cm | int | Schneehöhen-Schwelle fürs Räumen |
salt_enabled | bool | Ob Streuen anwendbar ist |
site_notes, access_notes | text | Hinweise für Fahrer |
auto_notify_email | bool | Auto-Benachrichtigung für dieses Objekt |
notification_email | string | Objektspezifische 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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
work_shift_id | FK | Übergeordnete Schicht |
customer_id | FK | Kunde |
customer_object_id | FK | Einsatzort |
user_id | FK | Fahrer |
vehicle_id | FK | Eingesetztes Fahrzeug |
type | JobType enum | raumen, streuen, kontrolle, raumen_streuen |
started_at | datetime | Einsatzbeginn |
ended_at | datetime | Einsatzende |
notes | text | Fahrernotizen |
is_manual | bool | Manuell 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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
user_id | FK | Fahrer |
started_at | datetime | Schichtbeginn |
ended_at | datetime | Schichtende (null = laufend) |
notes | text | Schichtnotizen |
Beziehungen: user, jobs
Vehicle
Tabelle: vehicles
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
name | string | Fahrzeugname |
license_plate | string | Kennzeichen |
owntracks_device_id | string | OwnTracks-Geräte-Kennung |
notes | text | Fahrzeugnotizen |
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.
| Feld | Typ | Beschreibung |
|---|---|---|
id | int | Primärschlüssel |
job_id | FK | Übergeordneter Einsatz |
moment | WeatherMoment enum | start oder end |
provider | string | Anbieter-Slug |
temperature | decimal(2) | °C |
precipitation | decimal(2) | mm |
snow_depth | decimal(2) | cm |
wind_speed | decimal(2) | km/h |
humidity | int | % |
weather_code | int | WMO-Wettercode |
fetched_at | datetime | Abrufzeitpunkt |
raw_response | JSON | Vollstä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.
| Model | Tabelle | Zweck |
|---|---|---|
GpsPoint | gps_points | GPS-Koordinaten während der Einsätze |
JobPhoto | job_photos | Während der Einsätze aufgenommene Fotos |
JobAudit | job_audits | Änderungsprotokoll (insert-only) |
Setting | settings | Schlüssel-Wert-Konfiguration |
Role | roles | Autorisierungs-Rollen |
Permission | permissions | Autorisierungs-Berechtigungen |
Module | modules | Installierte Module |
ModuleApiToken | module_api_tokens | API-Authentifizierungs-Token |
ModLog | mod_logs | Modul-Log-Einträge (insert-only) |
NotificationLog | notification_logs | Benachrichtigungs-Protokoll |
AlertDismissal | alert_dismissals | Verworfene Einsatz-Hinweise |
MonthlyStatistic | monthly_statistics | Vorberechnete Monatsstatistiken |
DsgvoConfirmation | driver_dsgvo_confirmations | DSGVO-Einwilligungen (insert-only) |
OwntracksCredentialEvent | owntracks_credential_events | GPS-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.