Files
ladill-frontdesk/app/Services/Frontdesk/WatchlistService.php
T
isaaccladandCursor 9e2d79936c
Deploy Ladill Frontdesk / deploy (push) Failing after 35s
Test / test (push) Failing after 2m45s
Initial Ladill Frontdesk release with deploy pipeline.
Visitor management app with SSO, kiosk, badges, reports, and Gitea CI deploy to frontdesk.ladill.com.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 20:37:15 +00:00

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;
}
}