tenant = Tenant::create([ 'slug' => 'tv', 'name' => 'Test', 'email' => 't@example.com', 'email_finance' => 'f@example.com', 'url' => 'test.local', 'account_name' => 'Test e.V.', 'account_iban' => 'DE00', 'account_bic' => 'XY', 'city' => 'Stadt', 'postcode' => '00000', 'is_active_local_group' => true, 'has_active_instance' => true, ]); app()->instance('tenant', $this->tenant); PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]); DB::table('participation_fee_types')->insert(['slug' => 'FIXED', 'name' => 'Fixed', 'created_at' => now(), 'updated_at' => now()]); ParticipationType::create(['slug' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, 'name' => 'Teilnehmende']); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']); SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'name' => 'Darf baden', 'short' => 'Erteilt']); SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_DENIED, 'name' => 'Darf nicht baden', 'short' => 'Keine']); FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, 'name' => 'Zugestimmt', 'description' => '']); FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_DENIED, 'name' => 'Abgelehnt', 'description' => '']); EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_OMNIVOR, 'name' => 'Omnivor']); } private function createEvent( bool $withFee = true, bool $swimmingPermissionRequired = true, ?float $amountReduced = 30.0, ?float $amountSolidarity = 70.0, ): Event { $event = Event::create([ 'tenant' => $this->tenant->slug, 'name' => 'Event', 'identifier' => 'evt-1', 'location' => 'Ort', 'postal_code' => '00000', 'email' => 'e@example.com', 'start_date' => '2026-08-01', 'end_date' => '2026-08-05', 'early_bird_end' => '2026-07-01', 'registration_final_end' => '2026-07-20', 'early_bird_end_amount_increase' => 0, 'account_owner' => 'Owner', 'account_iban' => 'DE00', 'participation_fee_type' => 'FIXED', 'pay_per_day' => false, 'pay_direct' => false, 'short_registration' => true, 'swimming_permission_required' => $swimmingPermissionRequired, ]); if ($withFee) { $fee = EventParticipationFee::create([ 'tenant' => $this->tenant->slug, 'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, 'name' => 'Teilnahme', 'description' => '', 'amount_standard' => 50.0, 'amount_reduced' => $amountReduced, 'amount_solidarity' => $amountSolidarity, ]); $event->participation_fee_1 = $fee->id; $event->save(); $event->refresh(); } return $event; } private function shortRequest(Event $event, array $overrides = []): ShortSignUpRequest { return new ShortSignUpRequest( event: $event, user_id: null, firstname: $overrides['firstname'] ?? 'Hans', lastname: $overrides['lastname'] ?? 'Muster', nickname: $overrides['nickname'] ?? null, // Volljährig, sofern nicht anders angegeben. birthday: $overrides['birthday'] ?? new \DateTime('2000-01-01'), email: $overrides['email'] ?? 'h@example.com', phone: $overrides['phone'] ?? '123', localGroupId: $overrides['localGroupId'] ?? null, contactPerson: $overrides['contactPerson'] ?? null, contactEmail: $overrides['contactEmail'] ?? null, allergies: $overrides['allergies'] ?? null, intolerances: $overrides['intolerances'] ?? null, firstAidPermission: $overrides['firstAidPermission'] ?? null, swimmingPermission: $overrides['swimmingPermission'] ?? null, foto_socialmedia: true, foto_print: false, foto_webseite: false, foto_partner: false, foto_intern: false, paymentMethod: $overrides['paymentMethod'] ?? PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION, paymentOptions: [], participationOptions: $overrides['participationOptions'] ?? [], feeType: $overrides['feeType'] ?? 'standard', ); } public function test_kurzanmeldung_fuellt_nicht_erhobene_pflichtfelder(): void { $event = $this->createEvent(); $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $participant = $response->participant; $this->assertSame('Nicht erforderlich', $participant->address_1); $this->assertSame('Nicht erforderlich', $participant->city); $this->assertSame('00000', $participant->postcode); $this->assertNull($participant->local_group); $this->assertNull($participant->address_2); $this->assertNull($participant->medications); // Zeitraum und Mahlzeiten kommen aus der Veranstaltung. $this->assertSame('2026-08-01', $participant->arrival_date->format('Y-m-d')); $this->assertSame('2026-08-05', $participant->departure_date->format('Y-m-d')); $this->assertSame(1, $participant->arrival_eating); $this->assertSame(2, $participant->departure_eating); // Die Gruppe steht fest, ohne Wahl gilt der Standardbeitrag. $this->assertSame(ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, $participant->participation_type); $this->assertSame('standard', $participant->fee_type); $this->assertSame(50.0, $participant->amount->getAmount()); $this->assertSame(EatingHabit::EATING_HABIT_OMNIVOR, $participant->eating_habit); // Volljährige brauchen keine Erlaubnisse. $this->assertSame(SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, $participant->swimming_permission); $this->assertSame(FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, $participant->first_aid_permission); $this->assertSame(1, $participant->invoice_sequence); $this->assertTrue($participant->foto_socialmedia); $this->assertFalse($participant->foto_print); } public function test_gewaehlter_reduzierter_beitrag_wird_berechnet(): void { $event = $this->createEvent(); $response = new ShortSignUpCommand($this->shortRequest($event, ['feeType' => 'reduced']))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $this->assertSame('reduced', $response->participant->fee_type); $this->assertSame(30.0, $response->participant->amount->getAmount()); } public function test_gewaehlter_solidaritaetsbeitrag_wird_berechnet(): void { $event = $this->createEvent(); $response = new ShortSignUpCommand($this->shortRequest($event, ['feeType' => 'solidarity']))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $this->assertSame('solidarity', $response->participant->fee_type); $this->assertSame(70.0, $response->participant->amount->getAmount()); } public function test_nicht_hinterlegte_beitragsstufe_wird_abgelehnt(): void { $event = $this->createEvent(amountReduced: null); $response = new ShortSignUpCommand($this->shortRequest($event, ['feeType' => 'reduced']))->execute(); $this->assertFalse($response->success); $this->assertNotNull($response->message); $this->assertSame(0, EventParticipant::count()); } public function test_waehlbar_sind_nur_hinterlegte_beitragsstufen(): void { $this->assertSame( ['standard', 'reduced', 'solidarity'], $this->createEvent()->firstParticipationFee()->availableFeeTypes() ); $withoutReduced = EventParticipationFee::create([ 'tenant' => $this->tenant->slug, 'type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, 'name' => 'Teilnahme', 'description' => '', 'amount_standard' => 50.0, 'amount_reduced' => null, 'amount_solidarity' => 70.0, ]); $this->assertSame(['standard', 'solidarity'], $withoutReduced->availableFeeTypes()); } public function test_u18_ohne_abgefragte_badeerlaubnis_bekommt_keine(): void { $event = $this->createEvent(swimmingPermissionRequired: false); $response = new ShortSignUpCommand($this->shortRequest($event, [ 'birthday' => new \DateTime('2015-01-01'), 'contactPerson' => 'Muster, Maria', 'contactEmail' => 'maria@example.com', 'firstAidPermission' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, ]))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $this->assertSame(SwimmingPermission::SWIMMING_PERMISSION_DENIED, $response->participant->swimming_permission); $this->assertSame(EfzStatus::EFZ_STATUS_NOT_REQUIRED, $response->participant->efz_status); $this->assertSame('Muster, Maria', $response->participant->contact_person); $this->assertSame('maria@example.com', $response->participant->email_2); } public function test_u18_uebernimmt_gewaehlte_badeerlaubnis(): void { $event = $this->createEvent(); $response = new ShortSignUpCommand($this->shortRequest($event, [ 'birthday' => new \DateTime('2015-01-01'), 'contactPerson' => 'Muster, Maria', 'contactEmail' => 'maria@example.com', 'firstAidPermission' => FirstAidPermission::FIRST_AID_PERMISSION_DENIED, 'swimmingPermission' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, ]))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $this->assertSame(SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, $response->participant->swimming_permission); $this->assertSame(FirstAidPermission::FIRST_AID_PERMISSION_DENIED, $response->participant->first_aid_permission); } public function test_u18_ohne_kontaktperson_wird_abgelehnt(): void { $event = $this->createEvent(); $response = new ShortSignUpCommand($this->shortRequest($event, [ 'birthday' => new \DateTime('2015-01-01'), ]))->execute(); $this->assertFalse($response->success); $this->assertSame(0, EventParticipant::count()); } public function test_ohne_teilnahmegruppe_wird_abgelehnt(): void { $event = $this->createEvent(withFee: false); $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); $this->assertFalse($response->success); $this->assertNotNull($response->message); $this->assertSame(0, EventParticipant::count()); } public function test_teilnahmeoptionen_werden_gespeichert(): void { $event = $this->createEvent(); $event->participation_options = [[ 'key' => 'shirt', 'title' => 'T-Shirt', 'label' => 'T-Shirt-Größe', 'options' => [['value' => 'm', 'label' => 'M'], ['value' => 'l', 'label' => 'L']], ]]; $event->save(); $response = new ShortSignUpCommand($this->shortRequest($event, [ 'participationOptions' => ['shirt' => 'l'], ]))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $options = $response->participant->selectedOptions()->get(); $this->assertCount(1, $options); $this->assertSame('shirt', $options->first()->question_key); $this->assertSame('L', $options->first()->value_label); } public function test_unbeantwortete_teilnahmeoption_wird_abgelehnt(): void { $event = $this->createEvent(); $event->participation_options = [[ 'key' => 'shirt', 'title' => 'T-Shirt', 'label' => 'T-Shirt-Größe', 'options' => [['value' => 'm', 'label' => 'M']], ]]; $event->save(); $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); $this->assertFalse($response->success); $this->assertSame(0, EventParticipant::count()); } private function createLocalGroup(string $slug, string $name): Tenant { return Tenant::create([ 'slug' => $slug, 'name' => $name, 'email' => $slug . '@example.com', 'email_finance' => $slug . '-f@example.com', 'url' => $slug . '.local', 'account_name' => $name, 'account_iban' => 'DE00', 'account_bic' => 'XY', 'city' => 'Stadt', 'postcode' => '00000', 'is_active_local_group' => true, 'has_active_instance' => false, ]); } public function test_einziger_stamm_wird_ohne_auswahl_zugeordnet(): void { $event = $this->createEvent(); $group = $this->createLocalGroup('stamm-a', 'Stamm A'); $event->localGroups()->attach($group->id); $event->refresh(); $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $this->assertSame('stamm-a', $response->participant->local_group); } public function test_bei_mehreren_staemmen_wird_die_auswahl_uebernommen(): void { $event = $this->createEvent(); $first = $this->createLocalGroup('stamm-a', 'Stamm A'); $second = $this->createLocalGroup('stamm-b', 'Stamm B'); $event->localGroups()->attach([$first->id, $second->id]); $event->refresh(); $response = new ShortSignUpCommand($this->shortRequest($event, [ 'localGroupId' => $second->id, ]))->execute(); $this->assertTrue($response->success, $response->message ?? ''); $this->assertSame('stamm-b', $response->participant->local_group); } public function test_bei_mehreren_staemmen_ist_die_auswahl_pflicht(): void { $event = $this->createEvent(); $first = $this->createLocalGroup('stamm-a', 'Stamm A'); $second = $this->createLocalGroup('stamm-b', 'Stamm B'); $event->localGroups()->attach([$first->id, $second->id]); $event->refresh(); $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); $this->assertFalse($response->success); $this->assertSame(0, EventParticipant::count()); } public function test_nicht_teilnehmender_stamm_wird_abgelehnt(): void { $event = $this->createEvent(); $first = $this->createLocalGroup('stamm-a', 'Stamm A'); $second = $this->createLocalGroup('stamm-b', 'Stamm B'); $event->localGroups()->attach([$first->id, $second->id]); $event->refresh(); // Stamm existiert, nimmt aber nicht an der Veranstaltung teil. $foreign = $this->createLocalGroup('stamm-c', 'Stamm C'); $response = new ShortSignUpCommand($this->shortRequest($event, [ 'localGroupId' => $foreign->id, ]))->execute(); $this->assertFalse($response->success); $this->assertSame(0, EventParticipant::count()); } public function test_resource_ueberlebt_kurzanmeldung(): void { $event = $this->createEvent(); $response = new ShortSignUpCommand($this->shortRequest($event))->execute(); $this->assertTrue($response->success, $response->message ?? ''); // Regressionswächter für Teilnehmerliste, Detailansicht und CSV-Export. $array = $response->participant->toResource()->toArray(request()); $this->assertSame('Nicht im LV', $array['localgroup']); $this->assertSame('Erteilt', $array['swimmingPermission']); $this->assertSame('Zugestimmt', $array['extendedFirstAid']); $this->assertSame('Omnivor', $array['eatingHabit']); $this->assertSame('Teilnehmende', $array['participationType']); } }