Zum Hauptinhalt springen

OrderRatingResource

Die OrderRatingResource zeigt alle Bewertungen für abgeschlossene Aufträge. Diese Resource ist Read-Only - Bewertungen werden automatisch erstellt und können nur von den Nutzern selbst abgegeben werden.

Konfiguration

File: OrderRatingResource.php

EigenschaftWert
ModelApp\Models\OrderRating
Navigation GroupAuftragsverwaltung
Navigation LabelBewertungen
Navigation Iconheroicon-o-star
Navigation Sort15

Berechtigungen

public static function canCreate(): bool
{
return false; // Bewertungen werden automatisch erstellt
}

public static function canEdit(Model $record): bool
{
return false; // Nur Anzeige
}

public static function canDelete(Model $record): bool
{
return false; // Bewertungen sind unveränderlich
}

Bewertungs-Workflow

Bewertungs-Token

Jede Bewertung erhält einen einmaligen Token:

// Bei Erstellung
$rating = OrderRating::create([
'order_id' => $order->id,
'rater_id' => $user->id,
'rater_type' => $user->hasRole('requester') ? 'requester' : 'interpreter',
'token' => Str::uuid(),
'token_expires_at' => now()->addDays(7),
]);

// Bewertungslink
$url = config('app.frontend_url') . "/rate-order?token={$rating->token}";

Table-Konfiguration

Spalten

SpalteTypFormatierung
order.order_numberTextColumnLink zur Order
rater_nameTextColumnfirst_name + last_name
rater_typeBadgeColumnrequester=info, interpreter=success
starsTextColumnMit ⭐ Emoji
commentTextColumnMax 50 Zeichen, Tooltip für Rest
is_submittedIconColumn✓ (check) oder 🕐 (clock)
submitted_atTextColumnFormatiertes Datum
token_expires_atTextColumnRot wenn abgelaufen

Sterne-Anzeige

Tables\Columns\TextColumn::make('stars')
->formatStateUsing(fn (int $state) => str_repeat('⭐', $state))
->sortable()

Token-Ablauf-Formatierung

Tables\Columns\TextColumn::make('token_expires_at')
->label('Token läuft ab')
->dateTime('d.m.Y H:i')
->color(fn ($state) => $state && $state < now() ? 'danger' : null)
->description(fn ($state) => $state && $state < now() ? 'Abgelaufen' : null)

Filter

FilterTypBeschreibung
rater_typeSelectFilterrequester/interpreter
starsSelectFilter1-5 Sterne
submitted_atTernaryFilterEingereicht/Ausstehend
Tables\Filters\TernaryFilter::make('submitted_at')
->label('Status')
->placeholder('Alle')
->trueLabel('Eingereicht')
->falseLabel('Ausstehend')
->queries(
true: fn (Builder $query) => $query->whereNotNull('submitted_at'),
false: fn (Builder $query) => $query->whereNull('submitted_at'),
)

Actions

viewRating

Öffnet die Bewertungsseite in der Web-App (nur für ausstehende, nicht abgelaufene Bewertungen):

Action::make('viewRating')
->label('Bewertungsseite öffnen')
->icon('heroicon-o-arrow-top-right-on-square')
->url(fn (OrderRating $record) => config('app.frontend_url') . "/rate-order?token={$record->token}")
->openUrlInNewTab()
->visible(fn (OrderRating $record) =>
$record->submitted_at === null &&
$record->token_expires_at > now()
)

Statistiken

Die Bewertungen werden in verschiedenen Widgets aggregiert:

Durchschnittsbewertung pro Sprachmittler

// In User Model
public function getAverageRatingAttribute(): ?float
{
return $this->receivedRatings()
->whereNotNull('submitted_at')
->avg('stars');
}

Bewertungsverteilung

// In RecentReviewsWidget
OrderRating::query()
->whereNotNull('submitted_at')
->whereBetween('submitted_at', [$start, $end])
->selectRaw('stars, COUNT(*) as count')
->groupBy('stars')
->get();

Model-Struktur

class OrderRating extends Model
{
protected $fillable = [
'order_id',
'rater_id',
'rater_type', // 'requester' oder 'interpreter'
'stars', // 1-5
'comment',
'token',
'token_expires_at',
'submitted_at',
];

protected $casts = [
'stars' => 'integer',
'token_expires_at' => 'datetime',
'submitted_at' => 'datetime',
];

// Beziehungen
public function order(): BelongsTo
{
return $this->belongsTo(Order::class);
}

public function rater(): BelongsTo
{
return $this->belongsTo(User::class, 'rater_id');
}

// Scopes
public function scopeSubmitted(Builder $query): Builder
{
return $query->whereNotNull('submitted_at');
}

public function scopePending(Builder $query): Builder
{
return $query->whereNull('submitted_at')
->where('token_expires_at', '>', now());
}
}

Bewertungs-Abgabe (API)

Die Bewertung wird über die Web-App abgegeben:

// POST /api/ratings/token/{token}
public function submitRating(Request $request, string $token)
{
$rating = OrderRating::where('token', $token)
->whereNull('submitted_at')
->where('token_expires_at', '>', now())
->firstOrFail();

$validated = $request->validate([
'stars' => 'required|integer|min:1|max:5',
'comment' => 'nullable|string|max:1000',
]);

$rating->update([
'stars' => $validated['stars'],
'comment' => $validated['comment'],
'submitted_at' => now(),
]);

return response()->json(['message' => 'Bewertung abgegeben']);
}