Extract Ladill Events as a standalone events suite at events.ladill.com.
Deploy Ladill QR Plus / deploy (push) Successful in 28s
Deploy Ladill QR Plus / deploy (push) Successful in 28s
Full control center for ticketed events, contributions, attendees, badges, and programmes — not a QR utility clone. Includes SSO shell, import command, and platform cutover runbook. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Events;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\QrCode;
|
||||
use App\Models\QrEventRegistration;
|
||||
use App\Notifications\EventProgrammeSharedNotification;
|
||||
use App\Services\Billing\SmsService;
|
||||
use App\Support\Events\EventBadgeZpl;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AttendeeController extends Controller
|
||||
{
|
||||
public function __construct(private readonly SmsService $sms) {}
|
||||
|
||||
public function index(Request $request, QrCode $event): View
|
||||
{
|
||||
$this->authorize('view', $event);
|
||||
abort_unless($event->type === QrCode::TYPE_EVENT, 404);
|
||||
|
||||
$query = $event->eventRegistrations()->latest();
|
||||
|
||||
if ($search = trim((string) $request->query('search', ''))) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('attendee_name', 'like', "%{$search}%")
|
||||
->orWhere('attendee_email', 'like', "%{$search}%")
|
||||
->orWhere('badge_code', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
if ($status = $request->query('status')) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
$registrations = $query->paginate(40)->withQueryString();
|
||||
|
||||
$stats = [
|
||||
'total' => $event->eventRegistrations()->where('status', QrEventRegistration::STATUS_CONFIRMED)->count(),
|
||||
'checked_in' => $event->eventRegistrations()->whereNotNull('checked_in_at')->count(),
|
||||
'revenue' => $event->eventRegistrations()->where('status', QrEventRegistration::STATUS_CONFIRMED)->sum('amount_minor') / 100,
|
||||
];
|
||||
|
||||
$programme = $this->programmeFor($event);
|
||||
|
||||
return view('events.attendees', [
|
||||
'qrCode' => $event,
|
||||
'event' => $event,
|
||||
'registrations' => $registrations,
|
||||
'stats' => $stats,
|
||||
'programme' => $programme,
|
||||
]);
|
||||
}
|
||||
|
||||
public function shareProgramme(QrCode $event): RedirectResponse
|
||||
{
|
||||
$this->authorize('view', $event);
|
||||
abort_unless($event->type === QrCode::TYPE_EVENT, 404);
|
||||
|
||||
$programme = $this->programmeFor($event);
|
||||
if (! $programme) {
|
||||
return back()->with('error', 'No programme outline is attached to this event yet.');
|
||||
}
|
||||
|
||||
$recipients = $event->eventRegistrations()
|
||||
->where('status', QrEventRegistration::STATUS_CONFIRMED)
|
||||
->get();
|
||||
|
||||
$eventName = $event->content()['name'] ?? $event->label;
|
||||
$programmeUrl = $programme->publicUrl();
|
||||
$emailed = 0;
|
||||
$texted = 0;
|
||||
|
||||
foreach ($recipients as $reg) {
|
||||
if ($reg->attendee_email) {
|
||||
Notification::route('mail', $reg->attendee_email)
|
||||
->notify(new EventProgrammeSharedNotification($event, $programme, $reg->attendee_name));
|
||||
$emailed++;
|
||||
}
|
||||
|
||||
if ($reg->attendee_phone) {
|
||||
$this->sms->send(
|
||||
$reg->attendee_phone,
|
||||
sprintf(
|
||||
'Hi %s, the programme for %s is here: %s',
|
||||
explode(' ', $reg->attendee_name ?? 'there')[0],
|
||||
$eventName,
|
||||
$programmeUrl
|
||||
)
|
||||
);
|
||||
$texted++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($emailed === 0 && $texted === 0) {
|
||||
return back()->with('error', 'No confirmed attendees with contact details to share with yet.');
|
||||
}
|
||||
|
||||
return back()->with('success', "Programme shared — {$emailed} email(s) and {$texted} SMS sent.");
|
||||
}
|
||||
|
||||
private function programmeFor(QrCode $event): ?QrCode
|
||||
{
|
||||
$programmeId = (int) ($event->content()['programme_qr_id'] ?? 0);
|
||||
if ($programmeId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return QrCode::where('id', $programmeId)
|
||||
->where('user_id', $event->user_id)
|
||||
->where('type', QrCode::TYPE_ITINERARY)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function badges(Request $request, QrCode $event): View
|
||||
{
|
||||
$this->authorize('view', $event);
|
||||
abort_unless($event->type === QrCode::TYPE_EVENT, 404);
|
||||
|
||||
$registrations = $this->selectedRegistrations($request, $event);
|
||||
|
||||
return view('events.badges', [
|
||||
'qrCode' => $event,
|
||||
'event' => $event,
|
||||
'registrations' => $registrations,
|
||||
'auto' => $request->boolean('auto'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function badgesZpl(Request $request, QrCode $event): Response
|
||||
{
|
||||
$this->authorize('view', $event);
|
||||
abort_unless($event->type === QrCode::TYPE_EVENT, 404);
|
||||
|
||||
$registrations = $this->selectedRegistrations($request, $event);
|
||||
$zpl = EventBadgeZpl::forRegistrations($event, $registrations);
|
||||
|
||||
return response($zpl, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="badges-' . $event->short_code . '.zpl"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkIn(QrCode $event, QrEventRegistration $registration): RedirectResponse
|
||||
{
|
||||
$this->authorize('view', $event);
|
||||
abort_unless($registration->qr_code_id === $event->id, 404);
|
||||
|
||||
$registration->update([
|
||||
'checked_in_at' => $registration->checked_in_at ? null : now(),
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/** @return \Illuminate\Support\Collection<int, QrEventRegistration> */
|
||||
private function selectedRegistrations(Request $request, QrCode $event)
|
||||
{
|
||||
$query = $event->eventRegistrations()
|
||||
->where('status', QrEventRegistration::STATUS_CONFIRMED)
|
||||
->latest();
|
||||
|
||||
$ids = array_filter((array) $request->query('ids', []));
|
||||
if (! empty($ids)) {
|
||||
$query->whereIn('id', $ids);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user