Visitor management app with SSO, kiosk, badges, reports, and Gitea CI deploy to frontdesk.ladill.com. Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
3.2 KiB
PHP
101 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Frontdesk;
|
|
|
|
use App\Models\AuditLog;
|
|
use App\Models\Organization;
|
|
use App\Models\Visit;
|
|
use Illuminate\Support\Facades\DB;
|
|
class VisitLifecycleService
|
|
{
|
|
public function __construct(
|
|
protected VisitCheckInService $checkIns,
|
|
protected NotificationDispatcher $notifications,
|
|
) {}
|
|
|
|
public function checkInFromSchedule(Visit $visit, ?string $actorRef = null, bool $policiesAccepted = true): Visit
|
|
{
|
|
abort_unless($visit->canActivateCheckIn(), 422, 'This visit cannot be checked in.');
|
|
|
|
$visit->load('visitor');
|
|
app(WatchlistService::class)->assertCanCheckIn($visit->visitor);
|
|
|
|
return $this->checkIns->activateCheckIn($visit, $actorRef, $policiesAccepted);
|
|
}
|
|
|
|
public function markWaiting(Visit $visit, ?string $actorRef = null): Visit
|
|
{
|
|
abort_unless(in_array($visit->status, [
|
|
Visit::STATUS_EXPECTED,
|
|
Visit::STATUS_SCHEDULED,
|
|
Visit::STATUS_OVERDUE,
|
|
], true), 422, 'Visit is not awaiting arrival.');
|
|
|
|
$visit->update(['status' => Visit::STATUS_WAITING]);
|
|
|
|
AuditLog::record(
|
|
$visit->owner_ref,
|
|
'visit.waiting',
|
|
$visit->organization_id,
|
|
$actorRef,
|
|
Visit::class,
|
|
$visit->id,
|
|
['visitor' => $visit->visitor->full_name],
|
|
);
|
|
|
|
$this->notifications->visitorWaiting($visit);
|
|
|
|
return $visit->fresh(['visitor', 'host']);
|
|
}
|
|
|
|
public function cancel(Visit $visit, ?string $actorRef = null, ?string $reason = null): Visit
|
|
{
|
|
abort_if($visit->isInside(), 422, 'Checked-in visits must be checked out, not cancelled.');
|
|
abort_if($visit->status === Visit::STATUS_CANCELLED, 422, 'Visit is already cancelled.');
|
|
|
|
$visit->update([
|
|
'status' => Visit::STATUS_CANCELLED,
|
|
'notes' => trim(($visit->notes ?? '').($reason ? "\nCancelled: {$reason}" : '')),
|
|
]);
|
|
|
|
AuditLog::record(
|
|
$visit->owner_ref,
|
|
'visit.cancelled',
|
|
$visit->organization_id,
|
|
$actorRef,
|
|
Visit::class,
|
|
$visit->id,
|
|
['visitor' => $visit->visitor->full_name, 'reason' => $reason],
|
|
);
|
|
|
|
$this->notifications->visitCancelled($visit);
|
|
|
|
return $visit->fresh(['visitor', 'host']);
|
|
}
|
|
|
|
/** Mark expected/scheduled visits past their scheduled time as overdue. */
|
|
public function markOverdueVisits(?Organization $organization = null): int
|
|
{
|
|
$query = Visit::query()
|
|
->whereIn('status', [Visit::STATUS_EXPECTED, Visit::STATUS_SCHEDULED])
|
|
->whereNotNull('scheduled_at')
|
|
->where('scheduled_at', '<', now());
|
|
|
|
if ($organization) {
|
|
$query->where('organization_id', $organization->id);
|
|
}
|
|
|
|
return $query->update(['status' => Visit::STATUS_OVERDUE]);
|
|
}
|
|
|
|
public function approve(Visit $visit, ?string $actorRef = null): Visit
|
|
{
|
|
abort_unless($visit->awaitingApproval(), 422, 'This visit is not awaiting approval.');
|
|
|
|
$visit->load('visitor');
|
|
app(WatchlistService::class)->assertCanCheckIn($visit->visitor);
|
|
|
|
return $this->checkIns->activateCheckIn($visit, $actorRef, (bool) $visit->policies_accepted);
|
|
}
|
|
}
|