dispatchVisitEvent( $visit, 'visitor_arrived', 'Visitor has arrived', "{$visit->visitor->full_name} has checked in.", ['icon' => 'user-check'], ); } public function visitorCheckedOut(Visit $visit): void { $this->dispatchVisitEvent( $visit, 'visitor_checked_out', 'Visitor checked out', "{$visit->visitor->full_name} has checked out.", ['icon' => 'logout'], ); } public function visitorExpected(Visit $visit): void { $when = $visit->scheduled_at?->format('M j, g:i A') ?? 'today'; $this->dispatchVisitEvent( $visit, 'visitor_expected', 'Visitor expected', "{$visit->visitor->full_name} is expected on {$when}.", ['icon' => 'calendar'], ); } public function visitorWaiting(Visit $visit): void { $this->dispatchVisitEvent( $visit, 'visitor_waiting', 'Visitor waiting', "{$visit->visitor->full_name} is waiting in reception.", ['icon' => 'clock'], ); } public function visitCancelled(Visit $visit): void { $this->dispatchVisitEvent( $visit, 'visit_cancelled', 'Visit cancelled', "The visit for {$visit->visitor->full_name} has been cancelled.", ['icon' => 'x-circle'], ); } public function approvalNeeded(Visit $visit): void { $visit->load(['visitor', 'host', 'organization']); $this->dispatchVisitEvent( $visit, 'approval_needed', 'Visitor awaiting approval', "{$visit->visitor->full_name} ({$visit->visitor_type}) is waiting for approval.", ['icon' => 'shield-alert'], ); $this->notifyStaffUsers( $visit->organization, 'approval_needed', 'Approval required', "{$visit->visitor->full_name} needs reception approval.", $visit, ); } public function watchlistBlockedAttempt(Visitor $visitor, Organization $organization): void { Log::alert('Watchlist blocked check-in attempt', [ 'visitor_id' => $visitor->id, 'visitor' => $visitor->full_name, 'organization_id' => $organization->id, ]); $this->notifyStaffUsers( $organization, 'watchlist_alert', 'Blocked check-in attempt', "{$visitor->full_name} (blacklisted) attempted to check in.", ); } public function watchlistFlaggedCheckIn(Visitor $visitor, Organization $organization): void { Log::warning('Watchlist flagged visitor submitted for approval', [ 'visitor_id' => $visitor->id, 'visitor' => $visitor->full_name, ]); $this->notifyStaffUsers( $organization, 'watchlist_alert', 'Flagged visitor check-in', "{$visitor->full_name} requires approval before badge issue.", ); } public function badgeExpired(Visit $visit): void { $visit->load(['visitor', 'organization']); $this->notifyStaffUsers( $visit->organization, 'badge_expired', 'Expired badge', "{$visit->visitor->full_name}'s badge expired while still checked in.", $visit, ); } /** @param array $meta */ protected function dispatchVisitEvent( Visit $visit, string $event, string $title, string $message, array $meta = [], ): void { $visit->load(['visitor', 'host', 'organization']); $channels = $this->preferences->channelsForEvent($visit->organization, $event); if ($channels === [] || ! $visit->host) { return; } $this->notifyHost($visit->host, $visit->organization, $channels, $title, $message, $visit, $event, $meta); } /** @param list $channels */ protected function notifyHost( Host $host, Organization $organization, array $channels, string $title, string $message, ?Visit $visit = null, ?string $event = null, array $meta = [], ): void { if (in_array('email', $channels, true) && $host->email) { try { $this->email->send($host->email, $title, $message); } catch (\Throwable $e) { Log::warning('Frontdesk host email failed', ['host_id' => $host->id, 'error' => $e->getMessage()]); } } if (in_array('sms', $channels, true) && $host->phone) { $this->sms->send($host->phone, "{$title}: {$message}"); } if ($host->user_ref) { $user = User::where('public_id', $host->user_ref)->first(); if ($user) { $user->notify(new FrontdeskAlertNotification($title, $message, array_merge($meta, [ 'event' => $event, 'visit_id' => $visit?->id, 'url' => $visit ? route('frontdesk.visits.show', $visit) : null, ]))); } } } protected function notifyStaffUsers( Organization $organization, string $event, string $title, string $message, ?Visit $visit = null, ): void { if (! $this->preferences->isEventEnabled($organization, $event)) { return; } $roles = ['org_admin', 'branch_admin', 'receptionist', 'security_officer']; $userRefs = Member::query() ->where('organization_id', $organization->id) ->whereIn('role', $roles) ->pluck('user_ref') ->unique(); foreach ($userRefs as $userRef) { $user = User::where('public_id', $userRef)->first(); if (! $user) { continue; } $user->notify(new FrontdeskAlertNotification($title, $message, [ 'event' => $event, 'visit_id' => $visit?->id, 'url' => $visit ? route('frontdesk.visits.show', $visit) : route('frontdesk.dashboard'), 'icon' => 'bell', ])); } } }