Add visitor self-checkout on the kiosk.
Deploy Ladill Frontdesk / deploy (push) Successful in 46s

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:
isaacclad
2026-06-29 09:07:39 +00:00
co-authored by Cursor
parent fedc1b2763
commit 0c092b7711
8 changed files with 505 additions and 3 deletions
@@ -3,6 +3,7 @@
namespace App\Services\Frontdesk;
use App\Models\AuditLog;
use App\Models\Organization;
use App\Models\Visit;
class VisitCheckOutService
@@ -47,4 +48,63 @@ class VisitCheckOutService
return $this->checkOut($visit, $actorRef);
}
public function checkOutFromKiosk(
string $ownerRef,
Organization $organization,
?string $badgeCode = null,
?string $qrInput = null,
?int $branchId = null,
): Visit {
$visit = $this->resolveCheckedInVisitForKiosk($ownerRef, $organization, $badgeCode, $qrInput, $branchId);
return $this->checkOut($visit);
}
protected function resolveCheckedInVisitForKiosk(
string $ownerRef,
Organization $organization,
?string $badgeCode,
?string $qrInput,
?int $branchId,
): Visit {
$query = Visit::owned($ownerRef)
->where('organization_id', $organization->id)
->currentlyInside()
->with('visitor');
if ($branchId !== null) {
$query->where(function ($q) use ($branchId) {
$q->whereNull('branch_id')->orWhere('branch_id', $branchId);
});
}
$badgeCode = strtoupper(trim((string) $badgeCode));
if ($badgeCode !== '') {
return (clone $query)->where('badge_code', $badgeCode)->firstOrFail();
}
$qrToken = $this->parseVisitQrToken((string) $qrInput);
abort_unless($qrToken !== null, 422, 'Invalid badge scan.');
return (clone $query)->where('qr_token', $qrToken)->firstOrFail();
}
protected function parseVisitQrToken(string $input): ?string
{
$trimmed = trim($input);
if ($trimmed === '') {
return null;
}
if (preg_match('#/q/([A-Za-z0-9]+)#', $trimmed, $matches)) {
return $matches[1];
}
if (preg_match('/^[A-Za-z0-9]{16,64}$/', $trimmed)) {
return $trimmed;
}
return null;
}
}