$validated * * @throws ValidationException */ public function register(Event $event, array $validated, ?User $user): Person { if ($event->status !== 'registration_open') { throw ValidationException::withMessages([ 'event' => ['This event is not accepting registrations.'], ]); } $festivalEvent = $this->resolveFestivalEvent($event); $email = $user?->email ?? $validated['email']; $this->checkDuplicateRegistration($festivalEvent, $email); $volunteerCrowdType = $this->resolveVolunteerCrowdType($event); return DB::transaction(function () use ($festivalEvent, $validated, $user, $email, $volunteerCrowdType): Person { $person = Person::updateOrCreate( [ 'event_id' => $festivalEvent->id, 'email' => $email, ], [ 'user_id' => $user?->id, 'crowd_type_id' => $volunteerCrowdType->id, 'first_name' => $user?->first_name ?? $validated['first_name'], 'last_name' => $user?->last_name ?? $validated['last_name'], 'phone' => $validated['phone'] ?? null, 'date_of_birth' => $validated['date_of_birth'] ?? null, 'status' => PersonStatus::PENDING, 'custom_fields' => [ 'tshirt_size' => $validated['tshirt_size'] ?? null, 'first_aid' => $validated['first_aid'] ?? false, 'allergies' => $validated['allergies'] ?? null, 'driving_licence' => $validated['driving_licence'] ?? false, 'motivation' => $validated['motivation'] ?? null, 'motivation_other' => $validated['motivation_other'] ?? null, ], ] ); $this->syncAvailabilities($person, $festivalEvent, $validated['availabilities'] ?? []); if (!empty($validated['field_values'])) { $this->registrationFormFieldService->upsertPersonValues( $person, $validated['field_values'] ); } if (!empty($validated['section_preferences'])) { $this->personSectionPreferenceService->replacePreferences( $person, $validated['section_preferences'] ); } if ($user === null) { $this->detectIdentityMatch($person); } $source = $user !== null ? 'authenticated_form' : 'public_form'; $activityLogger = activity('volunteer_registration') ->performedOn($person) ->withProperties([ 'source' => $source, 'event_id' => $festivalEvent->id, 'person_id' => $person->id, 'email' => $email, ]); if ($user !== null) { $activityLogger->causedBy($user); } $activityLogger->log('person.registered'); return $person; }); } private function resolveFestivalEvent(Event $event): Event { if ($event->isSubEvent()) { return $event->parent; } return $event; } /** * @throws ValidationException */ private function checkDuplicateRegistration(Event $festivalEvent, string $email): void { $existing = Person::where('event_id', $festivalEvent->id) ->where('email', $email) ->first(); if ($existing === null) { return; } if ($existing->status !== PersonStatus::REJECTED->value) { throw ValidationException::withMessages([ 'email' => ['Already registered for this event.'], ]); } } /** * @throws \RuntimeException */ private function resolveVolunteerCrowdType(Event $event): CrowdType { $crowdType = CrowdType::where('organisation_id', $event->organisation_id) ->where('system_type', 'VOLUNTEER') ->first(); if ($crowdType === null) { Log::error('No volunteer crowd type configured', [ 'organisation_id' => $event->organisation_id, 'event_id' => $event->id, ]); abort(500, 'No volunteer crowd type configured for this organisation.'); } return $crowdType; } /** * @param array> $availabilities */ private function syncAvailabilities(Person $person, Event $festivalEvent, array $availabilities): void { if (empty($availabilities)) { return; } VolunteerAvailability::where('person_id', $person->id)->delete(); $validTimeSlotIds = $festivalEvent->getAllRelevantTimeSlots() ->where('person_type', 'VOLUNTEER') ->pluck('id') ->toArray(); foreach ($availabilities as $availability) { if (! in_array($availability['time_slot_id'], $validTimeSlotIds, true)) { continue; } VolunteerAvailability::create([ 'person_id' => $person->id, 'time_slot_id' => $availability['time_slot_id'], 'preference_level' => $availability['preference_level'] ?? 3, 'submitted_at' => now(), ]); } } private function detectIdentityMatch(Person $person): void { if (! Schema::hasTable('person_identity_matches')) { activity('volunteer_registration') ->performedOn($person) ->withProperties(['email' => $person->email]) ->log('person.identity_match_skipped_table_missing'); return; } $this->identityService->detectMatchForPerson($person); } }