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>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user