Files
NextChair/app/Http/Controllers/StaffAppointmentController.php
T
Stefan Lazic 2ef3654841
CI / ci (push) Canceled after 0s
PHP Rebuild
Initial PHP rebuild of the app
2026-09-05 08:21:33 +01:00

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.');
}
}