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
| Eigenschaft | Wert |
|---|---|
| Model | App\Models\OrderRating |
| Navigation Group | Auftragsverwaltung |
| Navigation Label | Bewertungen |
| Navigation Icon | heroicon-o-star |
| Navigation Sort | 15 |
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
| Spalte | Typ | Formatierung |
|---|---|---|
| order.order_number | TextColumn | Link zur Order |
| rater_name | TextColumn | first_name + last_name |
| rater_type | BadgeColumn | requester=info, interpreter=success |
| stars | TextColumn | Mit ⭐ Emoji |
| comment | TextColumn | Max 50 Zeichen, Tooltip für Rest |
| is_submitted | IconColumn | ✓ (check) oder 🕐 (clock) |
| submitted_at | TextColumn | Formatiertes Datum |
| token_expires_at | TextColumn | Rot 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
| Filter | Typ | Beschreibung |
|---|---|---|
| rater_type | SelectFilter | requester/interpreter |
| stars | SelectFilter | 1-5 Sterne |
| submitted_at | TernaryFilter | Eingereicht/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']);
}