Let guests check out by badge code or QR scan before leaving, with device and staff kiosk API routes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,9 +9,11 @@ use App\Models\Visit;
|
||||
use App\Services\Frontdesk\VisitCheckInService;
|
||||
use App\Services\Frontdesk\VisitCheckOutService;
|
||||
use App\Services\Frontdesk\VisitorTypeService;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class KioskController extends Controller
|
||||
{
|
||||
@@ -36,6 +38,7 @@ class KioskController extends Controller
|
||||
'typeConfigs' => $visitorTypes->configsForFrontend(),
|
||||
'resetSeconds' => (int) ($settings['kiosk_reset_seconds'] ?? config('frontdesk.kiosk.inactivity_reset_seconds', 120)),
|
||||
'visitorPolicy' => $settings['visitor_policy'] ?? null,
|
||||
'checkOutUrl' => route('frontdesk.kiosk.check-out'),
|
||||
// Staff sign-in requires a registered kiosk device (/kiosk/d/{token}).
|
||||
'employeeKioskEnabled' => false,
|
||||
'stepOutReasons' => collect(config('frontdesk.employee_step_out_reasons'))
|
||||
@@ -86,4 +89,46 @@ class KioskController extends Controller
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkOut(Request $request, VisitCheckOutService $checkOut): JsonResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'kiosk.use');
|
||||
$organization = $this->organization($request);
|
||||
|
||||
$validated = $request->validate([
|
||||
'badge_code' => ['nullable', 'string', 'max:32'],
|
||||
'qr_token' => ['nullable', 'string', 'max:500'],
|
||||
]);
|
||||
|
||||
if (trim((string) ($validated['badge_code'] ?? '')) === '' && trim((string) ($validated['qr_token'] ?? '')) === '') {
|
||||
throw ValidationException::withMessages([
|
||||
'credentials' => 'Enter your badge code or scan your badge QR code.',
|
||||
]);
|
||||
}
|
||||
|
||||
$branchScope = app(\App\Services\Frontdesk\OrganizationResolver::class)
|
||||
->branchScope($this->member($request));
|
||||
|
||||
try {
|
||||
$visit = $checkOut->checkOutFromKiosk(
|
||||
$this->ownerRef($request),
|
||||
$organization,
|
||||
$validated['badge_code'] ?? null,
|
||||
$validated['qr_token'] ?? null,
|
||||
$branchScope,
|
||||
);
|
||||
} catch (ModelNotFoundException) {
|
||||
throw ValidationException::withMessages([
|
||||
'credentials' => 'No active visit found for that badge. Ask reception if you need help.',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'visit' => [
|
||||
'visitor_name' => $visit->visitor->full_name,
|
||||
'badge_code' => $visit->badge_code,
|
||||
'checked_out_at' => $visit->checked_out_at?->toIso8601String(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use App\Models\Host;
|
||||
use App\Models\Organization;
|
||||
use App\Services\Frontdesk\EmployeePresenceService;
|
||||
use App\Services\Frontdesk\VisitCheckInService;
|
||||
use App\Services\Frontdesk\VisitCheckOutService;
|
||||
use App\Services\Frontdesk\VisitorTypeService;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -44,6 +46,7 @@ class KioskDeviceController extends Controller
|
||||
'resetSeconds' => (int) ($settings['kiosk_reset_seconds'] ?? config('frontdesk.kiosk.inactivity_reset_seconds', 120)),
|
||||
'visitorPolicy' => $settings['visitor_policy'] ?? null,
|
||||
'checkInUrl' => route('frontdesk.kiosk.device.check-in', $device->device_token),
|
||||
'checkOutUrl' => route('frontdesk.kiosk.device.check-out', $device->device_token),
|
||||
'employeeKioskEnabled' => $employeeKioskEnabled,
|
||||
'stepOutReasons' => collect(config('frontdesk.employee_step_out_reasons'))
|
||||
->map(fn ($label, $key) => ['key' => $key, 'label' => $label])
|
||||
@@ -160,6 +163,45 @@ class KioskDeviceController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkOut(Request $request, VisitCheckOutService $checkOut): JsonResponse
|
||||
{
|
||||
$device = $this->device($request);
|
||||
$organization = Organization::findOrFail($device->organization_id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'badge_code' => ['nullable', 'string', 'max:32'],
|
||||
'qr_token' => ['nullable', 'string', 'max:500'],
|
||||
]);
|
||||
|
||||
if (trim((string) ($validated['badge_code'] ?? '')) === '' && trim((string) ($validated['qr_token'] ?? '')) === '') {
|
||||
throw ValidationException::withMessages([
|
||||
'credentials' => 'Enter your badge code or scan your badge QR code.',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$visit = $checkOut->checkOutFromKiosk(
|
||||
$device->owner_ref,
|
||||
$organization,
|
||||
$validated['badge_code'] ?? null,
|
||||
$validated['qr_token'] ?? null,
|
||||
$device->branch_id,
|
||||
);
|
||||
} catch (ModelNotFoundException) {
|
||||
throw ValidationException::withMessages([
|
||||
'credentials' => 'No active visit found for that badge. Ask reception if you need help.',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'visit' => [
|
||||
'visitor_name' => $visit->visitor->full_name,
|
||||
'badge_code' => $visit->badge_code,
|
||||
'checked_out_at' => $visit->checked_out_at?->toIso8601String(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function device(Request $request): Device
|
||||
{
|
||||
return $request->attributes->get('frontdesk.device')
|
||||
|
||||
Reference in New Issue
Block a user