Files
ladill-frontdesk/app/Http/Controllers/Frontdesk/VisitorController.php
T
isaaccladandCursor 0d74330e44
Deploy Ladill Frontdesk / deploy (push) Successful in 50s
Add Visitors page hero and drop dark-mode classes from Reports.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 20:53:53 +00:00

213 lines
7.8 KiB
PHP

<?php
namespace App\Http\Controllers\Frontdesk;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Frontdesk\Concerns\ScopesToAccount;
use App\Models\AuditLog;
use App\Models\Branch;
use App\Models\Host;
use App\Models\Visitor;
use App\Services\Frontdesk\FrontdeskPermissions;
use App\Services\Frontdesk\VisitCheckInService;
use App\Services\Frontdesk\WatchlistService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
class VisitorController extends Controller
{
use ScopesToAccount;
public function index(Request $request): View
{
$this->authorizeAbility($request, 'visitors.view');
$organization = $this->organization($request);
$visitors = Visitor::owned($this->ownerRef($request))
->where('organization_id', $organization->id)
->when($request->q, fn ($q, $search) => $q->where('full_name', 'like', "%{$search}%"))
->orderByDesc('is_frequent')
->orderBy('full_name')
->paginate(25)
->withQueryString();
$statsQuery = Visitor::owned($this->ownerRef($request))
->where('organization_id', $organization->id);
$heroStats = [
'total' => (clone $statsQuery)->count(),
'frequent' => (clone $statsQuery)->where('is_frequent', true)->count(),
'flagged' => (clone $statsQuery)->whereIn('watchlist_status', [
Visitor::WATCHLIST_REQUIRES_APPROVAL,
Visitor::WATCHLIST_BLACKLISTED,
])->count(),
];
return view('frontdesk.visitors.index', compact('visitors', 'organization', 'heroStats'));
}
public function show(Request $request, Visitor $visitor): View
{
$this->authorizeAbility($request, 'visitors.view');
$this->authorizeOwner($request, $visitor);
$visitor->load(['visits' => fn ($q) => $q->latest()->limit(20), 'visits.host']);
$visitIds = $visitor->visits()->pluck('id');
$activity = AuditLog::owned($this->ownerRef($request))
->where('organization_id', $visitor->organization_id)
->where(function ($q) use ($visitor, $visitIds) {
$q->where(function ($inner) use ($visitor) {
$inner->where('subject_type', Visitor::class)
->where('subject_id', $visitor->id);
});
if ($visitIds->isNotEmpty()) {
$q->orWhere(function ($inner) use ($visitIds) {
$inner->where('subject_type', Visit::class)
->whereIn('subject_id', $visitIds);
});
}
})
->orderByDesc('created_at')
->limit(30)
->get();
return view('frontdesk.visitors.show', [
'visitor' => $visitor,
'activity' => $activity,
'auditActions' => config('frontdesk.audit_actions'),
'watchlistStatuses' => config('frontdesk.watchlist_statuses'),
'canManage' => app(FrontdeskPermissions::class)->can(
$this->member($request),
'visitors.manage'
),
]);
}
public function checkInForm(Request $request, Visitor $visitor): View
{
$this->authorizeAbility($request, 'visits.manage');
$this->authorizeOwner($request, $visitor);
$organization = $this->organization($request);
$hosts = Host::owned($this->ownerRef($request))
->where('organization_id', $organization->id);
$this->scopeToBranch($request, $hosts);
$hosts = $hosts->orderBy('name')->get();
$branches = Branch::owned($this->ownerRef($request))
->where('organization_id', $organization->id)
->where('is_active', true)
->orderBy('name')
->get();
return view('frontdesk.visitors.check-in', compact('visitor', 'organization', 'hosts', 'branches'));
}
public function checkIn(Request $request, Visitor $visitor, VisitCheckInService $checkIn): RedirectResponse
{
$this->authorizeAbility($request, 'visits.manage');
$this->authorizeOwner($request, $visitor);
$organization = $this->organization($request);
$validated = $request->validate([
'host_id' => ['nullable', 'integer'],
'branch_id' => ['nullable', 'integer'],
'visitor_type' => ['required', 'string', 'in:'.implode(',', array_keys(config('frontdesk.visitor_types')))],
'purpose' => ['nullable', 'string', 'max:500'],
'policies_accepted' => ['accepted'],
]);
$branchScope = app(\App\Services\Frontdesk\OrganizationResolver::class)
->branchScope($this->member($request));
if ($branchScope !== null) {
$validated['branch_id'] = $branchScope;
}
$validated['visitor_id'] = $visitor->id;
$visit = $checkIn->checkIn(
$this->ownerRef($request),
$organization,
$validated,
$this->ownerRef($request),
);
return redirect()
->route('frontdesk.visits.show', $visit)
->with('success', 'Returning visitor checked in.');
}
public function update(Request $request, Visitor $visitor): RedirectResponse
{
$this->authorizeAbility($request, 'visitors.manage');
$this->authorizeOwner($request, $visitor);
$validated = $request->validate([
'full_name' => ['required', 'string', 'max:255'],
'company' => ['nullable', 'string', 'max:255'],
'phone' => ['nullable', 'string', 'max:50'],
'email' => ['nullable', 'email', 'max:255'],
'notes' => ['nullable', 'string', 'max:2000'],
'photo' => ['nullable', 'image', 'max:5120'],
'id_document' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'],
]);
if ($request->hasFile('photo')) {
if ($visitor->photo_path) {
Storage::disk('public')->delete($visitor->photo_path);
}
$validated['photo_path'] = $request->file('photo')->store('frontdesk/visitors/photos', 'public');
}
if ($request->hasFile('id_document')) {
if ($visitor->id_document_path) {
Storage::disk('public')->delete($visitor->id_document_path);
}
$validated['id_document_path'] = $request->file('id_document')->store('frontdesk/visitors/documents', 'public');
}
unset($validated['photo'], $validated['id_document']);
$visitor->update($validated);
return back()->with('success', 'Visitor profile updated.');
}
public function updateWatchlist(Request $request, Visitor $visitor, WatchlistService $watchlist): RedirectResponse
{
$this->authorizeAbility($request, 'visitors.manage');
$this->authorizeOwner($request, $visitor);
$organization = $this->organization($request);
$validated = $request->validate([
'watchlist_status' => ['required', 'string', 'in:'.implode(',', array_keys(config('frontdesk.watchlist_statuses')))],
'notes' => ['nullable', 'string', 'max:2000'],
]);
$visitor->update($validated);
$watchlist->syncEntryForVisitor(
$visitor,
$validated['watchlist_status'],
$validated['notes'] ?? null,
$this->ownerRef($request),
);
AuditLog::record(
$this->ownerRef($request),
'watchlist.entry_created',
$organization->id,
$this->ownerRef($request),
Visitor::class,
$visitor->id,
['watchlist_status' => $validated['watchlist_status']],
);
return back()->with('success', 'Visitor watchlist status updated.');
}
}