events() ->topLevel() ->latest('start_date'); if ($request->query('type')) { $query->where('event_type', $request->query('type')); } if ($request->boolean('include_children')) { $query->with('children'); } return EventResource::collection($query->paginate()); } public function show(Organisation $organisation, Event $event): JsonResponse { Gate::authorize('view', [$event, $organisation]); $event->load(['organisation', 'children', 'parent']) ->loadCount('children'); return $this->success(new EventResource($event)); } public function store(StoreEventRequest $request, Organisation $organisation): JsonResponse { Gate::authorize('create', [Event::class, $organisation]); $data = $request->validated(); if (!empty($data['parent_event_id'])) { $parentEvent = Event::where('id', $data['parent_event_id']) ->where('organisation_id', $organisation->id) ->first(); if (!$parentEvent) { return $this->error('Parent event does not belong to this organisation.', 422); } } if (!isset($data['event_type'])) { $data['event_type'] = 'event'; } $event = $organisation->events()->create($data); return $this->created(new EventResource($event)); } public function update(UpdateEventRequest $request, Organisation $organisation, Event $event): JsonResponse { Gate::authorize('update', [$event, $organisation]); $event->update($request->validated()); return $this->success(new EventResource($event->fresh())); } public function destroy(Organisation $organisation, Event $event): JsonResponse { Gate::authorize('delete', [$event, $organisation]); $event->delete(); return $this->success(null, 'Event deleted'); } public function transition(Request $request, Organisation $organisation, Event $event): JsonResponse { Gate::authorize('update', [$event, $organisation]); $request->validate(['status' => 'required|string']); $newStatus = $request->status; $result = $event->canTransitionToWithPrerequisites($newStatus); if (! empty($result['errors'])) { return response()->json([ 'message' => 'Status transition not possible.', 'errors' => $result['errors'], 'current_status' => $event->status, 'requested_status' => $newStatus, 'allowed_transitions' => Event::STATUS_TRANSITIONS[$event->status] ?? [], ], 422); } $event->transitionTo($newStatus); return $this->success(new EventResource($event->fresh())); } public function children(Organisation $organisation, Event $event): AnonymousResourceCollection { Gate::authorize('view', [$event, $organisation]); $children = $event->children() ->orderBy('start_date') ->orderBy('name') ->paginate(); return EventResource::collection($children); } public function uploadImage(UploadEventImageRequest $request, Organisation $organisation, Event $event): JsonResponse { Gate::authorize('update', [$event, $organisation]); $path = $request->file('image')->store( "events/{$event->id}", 'public' ); $field = $request->type === 'banner' ? 'registration_banner_url' : 'registration_logo_url'; $event->update([$field => Storage::disk('public')->url($path)]); return response()->json(['url' => $event->fresh()->{$field}]); } public function stats(Event $event): JsonResponse { Gate::authorize('view', $event); $personCounts = $event->persons() ->selectRaw(" COUNT(*) as total, SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as approved, SUM(CASE WHEN status IN ('pending', 'applied') THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected ") ->first(); $approvedWithoutShift = $event->persons() ->where('status', 'approved') ->whereDoesntHave('shiftAssignments') ->count(); $pendingMatches = PersonIdentityMatch::pending() ->whereHas('person', fn ($q) => $q->where('event_id', $event->id)) ->count(); $shifts = $event->festivalSections() ->with(['shifts' => fn ($q) => $q->withCount([ 'shiftAssignments' => fn ($q) => $q->where('status', 'approved'), ])]) ->get() ->flatMap->shifts; $shiftsTotal = $shifts->count(); $shiftsFilled = $shifts->filter( fn ($s) => $s->shift_assignments_count >= $s->slots_total )->count(); $total = (int) $personCounts->total; $approved = (int) $personCounts->approved; $pending = (int) $personCounts->pending; $rejected = (int) $personCounts->rejected; return response()->json(['data' => [ 'persons_total' => $total, 'persons_approved' => $approved, 'persons_pending' => $pending, 'persons_rejected' => $rejected, 'persons_other' => $total - $approved - $pending - $rejected, 'persons_approved_without_shift' => $approvedWithoutShift, 'pending_identity_matches' => $pendingMatches, 'shifts_total' => $shiftsTotal, 'shifts_filled' => $shiftsFilled, 'shifts_understaffed' => $shiftsTotal - $shiftsFilled, ]]); } }