62 lines
3.3 KiB
PHP
62 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\{AppointmentStatus, NotificationType, Permission};
|
|
use App\Models\Appointment;
|
|
use App\Services\Loyalty\LoyaltyService;
|
|
use App\Services\Notifications\NotificationService;
|
|
use App\Services\Tenant\TenantContext;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use RuntimeException;
|
|
|
|
class StaffAppointmentController extends Controller
|
|
{
|
|
public function status(Request $request, TenantContext $tenant, Appointment $appointment, LoyaltyService $loyalty, NotificationService $notifications): RedirectResponse
|
|
{
|
|
$business = $tenant->require();
|
|
abort_unless($appointment->businessId === $business->id, 404);
|
|
$staff = $request->user()->staffProfiles()->where('businessId', $business->id)->with('role.permissions')->firstOrFail();
|
|
abort_unless($appointment->staffId === $staff->id || $staff->hasPermission(Permission::ManageAllAppointments), 403);
|
|
|
|
$validated = $request->validate(['status' => ['required', 'in:PENDING,CONFIRMED,CHECKED_IN,IN_PROGRESS,COMPLETED,CANCELLED,NO_SHOW'], 'reason' => ['nullable', 'string', 'max:255']]);
|
|
$next = AppointmentStatus::from($validated['status']);
|
|
$this->assertTransition($appointment->status, $next);
|
|
$appointment->loadMissing(['customer', 'services', 'business']);
|
|
|
|
$appointment->status = $next;
|
|
if ($next === AppointmentStatus::Cancelled) {
|
|
$appointment->cancellationReason = $validated['reason'] ?? null;
|
|
$appointment->cancelledAt = now();
|
|
$appointment->cancelledByUserId = $request->user()->id;
|
|
}
|
|
$appointment->save();
|
|
|
|
if ($next === AppointmentStatus::Completed) {
|
|
try { $loyalty->awardForCompletedAppointment($appointment, $staff); } catch (RuntimeException) { /* loyalty is optional and must not block completion */ }
|
|
}
|
|
|
|
$type = match ($next) {
|
|
AppointmentStatus::Cancelled => NotificationType::BookingCancelled,
|
|
AppointmentStatus::Completed => NotificationType::BookingCompleted,
|
|
AppointmentStatus::Confirmed => NotificationType::BookingApproved,
|
|
default => null,
|
|
};
|
|
if ($type) $notifications->create($business, $type, "Your appointment status is now ".strtolower(str_replace('_', ' ', $next->value)).'.', "appointment-status:{$appointment->id}:{$next->value}", $appointment->customer->userId, $appointment->customer->id, $appointment->id, '/account', $business->name);
|
|
|
|
return back()->with('status', 'Appointment updated.');
|
|
}
|
|
|
|
private function assertTransition(AppointmentStatus $current, AppointmentStatus $next): void
|
|
{
|
|
$allowed = [
|
|
AppointmentStatus::Pending->value => [AppointmentStatus::Confirmed, AppointmentStatus::Cancelled],
|
|
AppointmentStatus::Confirmed->value => [AppointmentStatus::CheckedIn, AppointmentStatus::Cancelled, AppointmentStatus::NoShow],
|
|
AppointmentStatus::CheckedIn->value => [AppointmentStatus::InProgress, AppointmentStatus::Cancelled],
|
|
AppointmentStatus::InProgress->value => [AppointmentStatus::Completed],
|
|
];
|
|
if (! in_array($next, $allowed[$current->value] ?? [], true)) throw new RuntimeException('That appointment status transition is not allowed.');
|
|
}
|
|
}
|