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,200 @@
|
||||
<?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();
|
||||
|
||||
return view('frontdesk.visitors.index', compact('visitors', 'organization'));
|
||||
}
|
||||
|
||||
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.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user