Visitor management app with SSO, kiosk, badges, reports, and Gitea CI deploy to frontdesk.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
3.7 KiB
PHP
124 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Frontdesk;
|
|
|
|
use App\Models\AuditLog;
|
|
use App\Models\Organization;
|
|
use App\Models\Visitor;
|
|
use App\Models\WatchlistEntry;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
class WatchlistService
|
|
{
|
|
public function __construct(
|
|
protected NotificationDispatcher $notifications,
|
|
) {}
|
|
|
|
public function assertCanCheckIn(Visitor $visitor, ?Organization $organization = null, ?string $actorRef = null): void
|
|
{
|
|
if ($visitor->isBlacklisted()) {
|
|
if ($organization) {
|
|
$this->recordBlockedAttempt($visitor, $organization, $actorRef);
|
|
}
|
|
|
|
throw new HttpException(403, 'This visitor is blacklisted and cannot check in.');
|
|
}
|
|
}
|
|
|
|
public function visitorNeedsApprovalQueue(Visitor $visitor): bool
|
|
{
|
|
return $visitor->requiresApproval();
|
|
}
|
|
|
|
/** @param array<string, mixed> $context */
|
|
public function recordBlockedAttempt(
|
|
Visitor $visitor,
|
|
Organization $organization,
|
|
?string $actorRef = null,
|
|
array $context = [],
|
|
): void {
|
|
AuditLog::record(
|
|
$organization->owner_ref,
|
|
'watchlist.blocked_attempt',
|
|
$organization->id,
|
|
$actorRef,
|
|
Visitor::class,
|
|
$visitor->id,
|
|
array_merge([
|
|
'visitor' => $visitor->full_name,
|
|
'watchlist_status' => $visitor->watchlist_status,
|
|
], $context),
|
|
);
|
|
|
|
$this->notifications->watchlistBlockedAttempt($visitor, $organization);
|
|
}
|
|
|
|
/** @param array<string, mixed> $context */
|
|
public function recordFlaggedCheckIn(
|
|
Visitor $visitor,
|
|
Organization $organization,
|
|
?string $actorRef = null,
|
|
array $context = [],
|
|
): void {
|
|
AuditLog::record(
|
|
$organization->owner_ref,
|
|
'watchlist.flagged_checkin',
|
|
$organization->id,
|
|
$actorRef,
|
|
Visitor::class,
|
|
$visitor->id,
|
|
array_merge([
|
|
'visitor' => $visitor->full_name,
|
|
'watchlist_status' => $visitor->watchlist_status,
|
|
], $context),
|
|
);
|
|
|
|
$this->notifications->watchlistFlaggedCheckIn($visitor, $organization);
|
|
}
|
|
|
|
public function syncEntryForVisitor(Visitor $visitor, string $status, ?string $reason, ?string $actorRef): WatchlistEntry
|
|
{
|
|
$entry = WatchlistEntry::query()
|
|
->where('owner_ref', $visitor->owner_ref)
|
|
->where('organization_id', $visitor->organization_id)
|
|
->where('visitor_id', $visitor->id)
|
|
->first();
|
|
|
|
if ($status === Visitor::WATCHLIST_ALLOWED) {
|
|
$entry?->delete();
|
|
|
|
return $entry ?? new WatchlistEntry;
|
|
}
|
|
|
|
return WatchlistEntry::updateOrCreate(
|
|
[
|
|
'owner_ref' => $visitor->owner_ref,
|
|
'organization_id' => $visitor->organization_id,
|
|
'visitor_id' => $visitor->id,
|
|
],
|
|
[
|
|
'full_name' => $visitor->full_name,
|
|
'company' => $visitor->company,
|
|
'status' => $status,
|
|
'reason' => $reason,
|
|
'created_by' => $actorRef,
|
|
],
|
|
);
|
|
}
|
|
|
|
public function applyApprovalMetadata(array $data, Visitor $visitor): array
|
|
{
|
|
if (! $this->visitorNeedsApprovalQueue($visitor)) {
|
|
return $data;
|
|
}
|
|
|
|
$key = isset($data['delivery_details']) ? 'delivery_details' : 'contractor_details';
|
|
$details = $data[$key] ?? [];
|
|
$details['_awaiting_approval'] = true;
|
|
$details['_watchlist_flagged'] = true;
|
|
$data[$key] = $details;
|
|
|
|
return $data;
|
|
}
|
|
}
|