Zum Hauptinhalt springen

OrderRequestResource

Die OrderRequestResource verwaltet das Matching zwischen Aufträgen und Sprachmittlern.

Konfiguration

File: OrderRequestResource.php

EigenschaftWert
ModelApp\Models\OrderRequest
Navigation GroupAuftragsverwaltung
Navigation LabelSprachmittler-Matching
Navigation Iconheroicon-o-chat-bubble-left-right
Navigation Sort3
Polling Interval30 Sekunden
public static function getNavigationBadge(): ?string
{
return static::getModel()::query()
->where('state', Requested::class)
->where('timeout_at', '>', now())
->count();
}

public static function getNavigationBadgeColor(): ?string
{
return 'warning';
}

State Machine

State-Farben

StateFarbe
Requestedwarning
Acceptedsuccess
Rejecteddanger
Expiredgray
AutomaticallyCancelledgray

Form-Struktur

Basis-Daten

FeldTypBeschreibung
order_idSelectZugehöriger Auftrag
interpreter_idSelectSprachmittler (mit Distanz)
stateSelectStatus
request_typeSelectadhoc/planned
Forms\Components\Select::make('interpreter_id')
->relationship('interpreter', 'full_name')
->getOptionLabelFromRecordUsing(function (User $record, Get $get) {
$order = Order::find($get('order_id'));
if ($order && $record->hasCoordinates()) {
$distance = $record->distanceFrom(
$order->location_latitude,
$order->location_longitude
);
return "{$record->full_name} ({$distance} km)";
}
return $record->full_name;
})

Sprache & Service

FeldTyp
source_languageSelect
target_languageSelect
service_typeSelect (interpretation/translation/both)

Matching & Timeouts

FeldTypBeschreibung
scoreTextInputMatching-Score (0-100)
is_first_choiceToggleErste Wahl
is_urgentToggleDringend
timeout_atDateTimePickerTimeout-Zeitpunkt
timeout_minutesTextInputTimeout in Minuten
attempt_numberTextInputVersuchsnummer

Response & Admin Notes

FeldTyp
responded_atDateTimePicker
rejection_reasonTextarea
admin_notesTextarea

Infolist-Konfiguration

Für die View-Seite wird ein Infolist verwendet:

public static function infolist(Infolist $infolist): Infolist
{
return $infolist->schema([
Infolists\Components\Section::make('Links')
->columns(3)
->schema([
Infolists\Components\TextEntry::make('order.order_number')
->url(fn ($record) => OrderResource::getUrl('view', ['record' => $record->order_id])),
Infolists\Components\TextEntry::make('order.requester.full_name')
->url(fn ($record) => UserResource::getUrl('view', ['record' => $record->order->requester_id])),
Infolists\Components\TextEntry::make('interpreter.full_name')
->url(fn ($record) => UserResource::getUrl('view', ['record' => $record->interpreter_id])),
]),

Infolists\Components\Section::make('Status')
->schema([
Infolists\Components\TextEntry::make('state')
->badge()
->color(fn (string $state) => match ($state) {
'requested' => 'warning',
'accepted' => 'success',
'rejected' => 'danger',
default => 'gray',
}),
Infolists\Components\TextEntry::make('score')
->badge()
->color(fn (int $score) => match (true) {
$score >= 80 => 'success',
$score >= 60 => 'warning',
default => 'danger',
}),
Infolists\Components\TextEntry::make('distance')
->suffix(' km'),
]),
]);
}

Table-Konfiguration

Spalten

SpalteTypFormatierung
order.order_numberTextColumnLink zur Order
order.requester.full_nameTextColumnLink zum User
interpreter.full_nameTextColumnLink zum User
stateBadgeColumnFarbcodiert
request_typeBadgeColumnadhoc=info, planned=success
source_languageBadgeColumn-
target_languageBadgeColumn-
scoreTextColumnFarbcodiert (≥80=success, ≥60=warning, sonst danger)
distanceTextColumnMit "km" Suffix
timeout_atTextColumnMit Expired-Indikator
start_timeTextColumnFormatiert
created_atTextColumnSortiert desc

Score-Farbcodierung

Tables\Columns\TextColumn::make('score')
->badge()
->color(fn (int $state): string => match (true) {
$state >= 80 => 'success',
$state >= 60 => 'warning',
default => 'danger',
})

Filter

FilterTypBeschreibung
stateSelectFilterStatus-Auswahl
request_typeSelectFilterAnfragetyp
is_urgentTernaryFilterNur dringende
is_activeTernaryFilterRequested + nicht abgelaufen
Tables\Filters\Filter::make('is_active')
->label('Nur aktive')
->query(fn (Builder $query) => $query
->where('state', Requested::class)
->where('timeout_at', '>', now())
)

Actions

resend_notification

Sendet die Benachrichtigung erneut an den Sprachmittler:

Action::make('resend_notification')
->label('Benachrichtigung erneut senden')
->icon('heroicon-o-bell-alert')
->requiresConfirmation()
->action(function (OrderRequest $record) {
if ($record->is_urgent) {
$record->interpreter->notify(new UrgentOrderAvailable($record->order));
} else {
$record->interpreter->notify(new RegularOrderAvailable($record->order));
}

Notification::make()
->success()
->title('Benachrichtigung gesendet')
->body("An {$record->interpreter->full_name}")
->send();
})

accept

Akzeptiert die Anfrage im Namen des Sprachmittlers:

Action::make('accept')
->label('Akzeptieren')
->icon('heroicon-o-check')
->color('success')
->requiresConfirmation()
->modalHeading('Anfrage akzeptieren')
->modalDescription('Der Auftrag wird dem Sprachmittler zugewiesen.')
->action(function (OrderRequest $record) {
ProcessOrderRequestResponse::run($record, 'accepted');

Notification::make()
->success()
->title('Anfrage akzeptiert')
->send();
})

reject

Lehnt die Anfrage ab (mit Begründung):

Action::make('reject')
->label('Ablehnen')
->icon('heroicon-o-x-mark')
->color('danger')
->form([
Forms\Components\Textarea::make('rejection_reason')
->label('Ablehnungsgrund')
->required(),
])
->action(function (OrderRequest $record, array $data) {
$record->update(['rejection_reason' => $data['rejection_reason']]);
ProcessOrderRequestResponse::run($record, 'rejected');

Notification::make()
->success()
->title('Anfrage abgelehnt')
->send();
})

Auto-Refresh

Die Tabelle aktualisiert sich automatisch alle 30 Sekunden:

protected static ?string $pollingInterval = '30s';

Matching-Algorithmus

Der Matching-Score wird basierend auf folgenden Faktoren berechnet:

FaktorGewichtung
Distanz40%
Sprachkenntnisse30%
Verfügbarkeit20%
Bisherige Bewertungen10%
// In MatchInterpretersForOrder Action
$score = 0;

// Distanz (max 40 Punkte, 0km = 40, 100km = 0)
$distanceScore = max(0, 40 - ($distance / 2.5));
$score += $distanceScore;

// Sprachkenntnisse (max 30 Punkte)
if ($interpreter->hasLanguage($order->target_language)) {
$score += 30;
}

// Verfügbarkeit (max 20 Punkte)
if ($interpreter->isAvailableAt($order->scheduled_at)) {
$score += 20;
}

// Bewertungen (max 10 Punkte)
$avgRating = $interpreter->average_rating; // 1-5
$score += ($avgRating / 5) * 10;

Timeout-System

Anfragen haben ein konfigurierbares Timeout:

// In SystemSettings
'order_request_timeout_minutes' => 30

// Bei Erstellung
$orderRequest->timeout_at = now()->addMinutes(
app(SystemSettings::class)->order_request_timeout_minutes
);

// Scheduled Command prüft abgelaufene Anfragen
// ExpireOrderRequests::class runs every minute

Pages

PageBeschreibung
indexÜbersichtstabelle mit Auto-Refresh
viewInfolist-Ansicht
editBearbeitung (selten benötigt)