Deploy Ladill Frontdesk / deploy (push) Failing after 26s
Enables staff roster, PIN/code kiosk flow, attendance reports, evacuation staff roll call, webhooks, branch mismatch warnings, and a linked-user My presence portal. Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
7.4 KiB
PHP
196 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Frontdesk;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Controllers\Frontdesk\Concerns\ScopesToAccount;
|
|
use App\Models\EmployeePresence;
|
|
use App\Models\Visit;
|
|
use App\Services\Frontdesk\VisitCheckOutService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class SecurityController extends Controller
|
|
{
|
|
use ScopesToAccount;
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
$this->authorizeAbility($request, 'security.view');
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
|
|
$occupancy = Visit::owned($owner)
|
|
->where('organization_id', $organization->id)
|
|
->currentlyInside()
|
|
->with(['visitor', 'host']);
|
|
$this->scopeToBranch($request, $occupancy);
|
|
$occupancy = $occupancy->orderBy('checked_in_at')->get();
|
|
|
|
$staffOnSite = $this->staffOccupancy($request, $owner, $organization);
|
|
|
|
$expiredBadges = $occupancy->filter(fn (Visit $v) => $v->isBadgeExpired());
|
|
|
|
return view('frontdesk.security.index', [
|
|
'occupancy' => $occupancy,
|
|
'staffOnSite' => $staffOnSite,
|
|
'expiredBadges' => $expiredBadges,
|
|
'organization' => $organization,
|
|
'canCheckout' => app(\App\Services\Frontdesk\FrontdeskPermissions::class)
|
|
->can($this->member($request), 'security.checkout'),
|
|
]);
|
|
}
|
|
|
|
public function evacuation(Request $request): View
|
|
{
|
|
$this->authorizeAbility($request, 'security.view');
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
|
|
$occupancy = Visit::owned($owner)
|
|
->where('organization_id', $organization->id)
|
|
->currentlyInside()
|
|
->with(['visitor', 'host', 'branch']);
|
|
$this->scopeToBranch($request, $occupancy);
|
|
$occupancy = $occupancy->orderBy('checked_in_at')->get();
|
|
|
|
$staffOnSite = $this->staffOccupancy($request, $owner, $organization, withBranch: true);
|
|
|
|
return view('frontdesk.security.evacuation', compact('occupancy', 'staffOnSite', 'organization'));
|
|
}
|
|
|
|
public function evacuationExport(Request $request): StreamedResponse
|
|
{
|
|
$this->authorizeAbility($request, 'security.view');
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
|
|
$occupancy = Visit::owned($owner)
|
|
->where('organization_id', $organization->id)
|
|
->currentlyInside()
|
|
->with(['visitor', 'host', 'branch']);
|
|
$this->scopeToBranch($request, $occupancy);
|
|
$occupancy = $occupancy->orderBy('checked_in_at')->get();
|
|
|
|
$staffOnSite = $this->staffOccupancy($request, $owner, $organization, withBranch: true);
|
|
|
|
$filename = 'evacuation-'.now()->format('Y-m-d-His').'.csv';
|
|
|
|
return response()->streamDownload(function () use ($occupancy, $staffOnSite) {
|
|
$handle = fopen('php://output', 'w');
|
|
fputcsv($handle, ['Type', 'Name', 'Detail', 'Branch', 'Since', 'Phone', 'Status']);
|
|
|
|
foreach ($occupancy as $visit) {
|
|
fputcsv($handle, [
|
|
'Visitor',
|
|
$visit->visitor->full_name,
|
|
ucfirst(str_replace('_', ' ', $visit->visitor_type)).($visit->host ? ' · Host: '.$visit->host->name : ''),
|
|
$visit->branch?->name ?? '—',
|
|
$visit->checked_in_at?->format('Y-m-d g:i A') ?? '—',
|
|
$visit->visitor->phone ?? '—',
|
|
'On site',
|
|
]);
|
|
}
|
|
|
|
foreach ($staffOnSite as $presence) {
|
|
fputcsv($handle, [
|
|
'Staff',
|
|
$presence->employee->full_name,
|
|
$presence->employee->department ?? '—',
|
|
$presence->branch?->name ?? $presence->employee->branch?->name ?? '—',
|
|
$presence->signed_in_at?->format('Y-m-d g:i A') ?? '—',
|
|
$presence->employee->phone ?? '—',
|
|
config('frontdesk.employee_presence_statuses.'.$presence->status, $presence->status)
|
|
.($presence->destination ? ' · '.$presence->destination : ''),
|
|
]);
|
|
}
|
|
|
|
fclose($handle);
|
|
}, $filename, ['Content-Type' => 'text/csv']);
|
|
}
|
|
|
|
public function evacuationBadges(Request $request, \App\Services\Frontdesk\BadgeRenderService $badges): View
|
|
{
|
|
$this->authorizeAbility($request, 'security.view');
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
|
|
$visits = Visit::owned($owner)
|
|
->where('organization_id', $organization->id)
|
|
->currentlyInside()
|
|
->with(['visitor', 'host', 'organization']);
|
|
$this->scopeToBranch($request, $visits);
|
|
$visits = $visits->orderBy('checked_in_at')->get();
|
|
|
|
$rendered = $visits->map(fn (Visit $visit) => $badges->renderHtml($visit));
|
|
|
|
return view('frontdesk.security.evacuation-badges', compact('visits', 'rendered', 'organization'));
|
|
}
|
|
|
|
public function checkOut(Request $request, Visit $visit, VisitCheckOutService $checkOut): RedirectResponse
|
|
{
|
|
$this->authorizeAbility($request, 'security.checkout');
|
|
$this->authorizeOwner($request, $visit);
|
|
$checkOut->checkOut($visit, $this->ownerRef($request));
|
|
|
|
return back()->with('success', 'Visitor checked out.');
|
|
}
|
|
|
|
public function verifyForm(Request $request): View
|
|
{
|
|
$this->authorizeAbility($request, 'security.verify');
|
|
|
|
return view('frontdesk.security.verify', [
|
|
'organization' => $this->organization($request),
|
|
]);
|
|
}
|
|
|
|
public function verify(Request $request): View
|
|
{
|
|
$this->authorizeAbility($request, 'security.verify');
|
|
$organization = $this->organization($request);
|
|
$owner = $this->ownerRef($request);
|
|
|
|
$validated = $request->validate([
|
|
'lookup' => ['required', 'string', 'max:64'],
|
|
]);
|
|
|
|
$lookup = trim($validated['lookup']);
|
|
|
|
$visit = Visit::owned($owner)
|
|
->where('organization_id', $organization->id)
|
|
->where(function ($q) use ($lookup) {
|
|
$q->where('badge_code', strtoupper($lookup))
|
|
->orWhere('qr_token', $lookup);
|
|
})
|
|
->with(['visitor', 'host', 'branch'])
|
|
->latest('checked_in_at')
|
|
->first();
|
|
|
|
return view('frontdesk.security.verify', [
|
|
'organization' => $organization,
|
|
'lookup' => $lookup,
|
|
'visit' => $visit,
|
|
]);
|
|
}
|
|
|
|
protected function staffOccupancy(Request $request, string $owner, $organization, bool $withBranch = false)
|
|
{
|
|
$query = EmployeePresence::query()
|
|
->whereIn('status', [EmployeePresence::STATUS_ON_SITE, EmployeePresence::STATUS_STEPPED_OUT])
|
|
->whereHas('employee', function ($q) use ($owner, $organization, $request) {
|
|
$q->owned($owner)->where('organization_id', $organization->id)->where('active', true);
|
|
$this->scopeToBranch($request, $q, 'branch_id');
|
|
})
|
|
->with(['employee']);
|
|
|
|
if ($withBranch) {
|
|
$query->with(['branch', 'employee.branch']);
|
|
}
|
|
|
|
return $query->orderBy('employee_id')->get();
|
|
}
|
|
}
|