tenant = $this->createTenant('tv', 'Test'); app()->instance('tenant', $this->tenant); DB::table('cost_unit_types')->insert(['slug' => CostUnitType::COST_UNIT_TYPE_EVENT, 'name' => 'Veranstaltung']); DB::table('participation_types')->insert(['slug' => 'participant', 'name' => 'Teilnehmer']); DB::table('participation_fee_types')->insert(['slug' => 'fixed', 'name' => 'Fix']); EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_VEGAN, 'name' => 'Vegan']); EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_VEGETARIAN, 'name' => 'Vegetarisch']); $this->repository = new AdminParentGroupRepository(); } private function createTenant(string $slug, string $name, ?ParentGroup $parentGroup = null): 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' => true, 'tax_liable' => false, 'vat_rate' => 0, 'vat_pricing_mode' => 'inclusive', 'parent_group_id' => $parentGroup?->id, ]); } private function parentGroup(string $name): ParentGroup { return ParentGroup::where('name', $name)->firstOrFail(); } private function createEvent(ParticipantGroupType $type = ParticipantGroupType::LocalGroup): Event { $response = new CreateEventCommand(new CreateEventRequest( name: 'Sommerlager', location: 'Leipzig', postalCode: '04103', email: 'e@example.com', begin: new DateTime('2026-09-01'), end: new DateTime('2026-09-05'), earlyBirdEnd: new DateTime('2026-08-20'), registrationFinalEnd: new DateTime('2026-08-25'), earlyBirdEndAmountIncrease: 0, participationFeeType: ParticipationFeeType::where('slug', 'fixed')->first(), accountOwner: 'Owner', accountIban: 'DE00', payPerDay: false, participantGroupType: $type, ))->execute(); $this->assertTrue($response->success); return $response->event->fresh(); } private function updateGroupType(Event $event, ParticipantGroupType $type): Event { new UpdateEventCommand(new UpdateEventRequest( $event, $event->name, $event->location, $event->postal_code, $event->email, new DateTime('2026-08-20'), new DateTime('2026-08-25'), 16, true, true, Amount::fromString('0,00'), Amount::fromString('0,00'), $event->localGroups->pluck('id')->all(), [], participantGroupType: $type, ))->execute(); return $event->fresh(); } // --- Vorbelegung und Tenant -------------------------------------------------------------------------- public function test_vorbelegung_ist_vorhanden(): void { $this->assertEqualsCanonicalizing( ['Ohne Zuordnung', 'BdP', 'DPV', 'DPSG', 'VCP'], ParentGroup::pluck('name')->all(), ); $this->assertSame('Ohne Zuordnung', ParentGroup::fallback()->name); } public function test_neuer_tenant_ohne_angabe_landet_bei_ohne_zuordnung(): void { $this->assertSame(ParentGroup::fallback()->id, $this->tenant->fresh()->parent_group_id); } // --- Verwaltung ---------------------------------------------------------------------------------------- public function test_anlegen_und_umbenennen(): void { $created = new CreateParentGroupAction(new CreateParentGroupRequest(' Pfadfinderbund '))->execute(); $this->assertTrue($created->success); $this->assertSame('Pfadfinderbund', $created->parentGroup->name); $renamed = new RenameParentGroupAction(new RenameParentGroupRequest($created->parentGroup, 'Neuer Name'))->execute(); $this->assertTrue($renamed->success); $this->assertSame('Neuer Name', $created->parentGroup->fresh()->name); } public function test_leerer_name_wird_abgelehnt(): void { $this->assertFalse(new CreateParentGroupAction(new CreateParentGroupRequest(' '))->execute()->success); $this->assertFalse( new RenameParentGroupAction(new RenameParentGroupRequest($this->parentGroup('BdP'), ''))->execute()->success ); $this->assertSame('BdP', $this->parentGroup('BdP')->name); } public function test_unbenutzte_gruppe_ist_loeschbar(): void { $dpv = $this->parentGroup('DPV'); $response = new DeleteParentGroupAction(new DeleteParentGroupRequest($dpv, $this->repository->isInUse($dpv)))->execute(); $this->assertTrue($response->success); $this->assertNull(ParentGroup::find($dpv->id)); } public function test_zugeordnete_gruppe_ist_nicht_loeschbar(): void { $bdp = $this->parentGroup('BdP'); $this->createTenant('stamm-a', 'Stamm A', $bdp); $this->assertTrue($this->repository->isInUse($bdp)); $response = new DeleteParentGroupAction(new DeleteParentGroupRequest($bdp, $this->repository->isInUse($bdp)))->execute(); $this->assertFalse($response->success); $this->assertNotNull(ParentGroup::find($bdp->id)); } public function test_fallback_ist_nie_loeschbar(): void { $fallback = ParentGroup::fallback(); $response = new DeleteParentGroupAction(new DeleteParentGroupRequest($fallback, false))->execute(); $this->assertFalse($response->success); $this->assertNotNull(ParentGroup::find($fallback->id)); } // --- Bezeichnungen ------------------------------------------------------------------------------------- public function test_bezeichnungen_haben_vorgaben(): void { $this->assertSame( ['localGroup' => 'Stamm', 'localGroupPlural' => 'Stämme', 'parentGroup' => 'Bund', 'parentGroupPlural' => 'Bünde'], ParticipantGroupType::labels(), ); } public function test_geaenderte_bezeichnung_wirkt_auf_enum_und_event(): void { $response = new UpdateGlobalSettingsAction(new UpdateGlobalSettingsRequest( app(GlobalSettingRepository::class), 'Ortsgruppe', 'Ortsgruppen', 'Verband', 'Verbände', ))->execute(); $this->assertTrue($response->success); $this->assertSame('Verband', ParticipantGroupType::ParentGroup->label()); $this->assertSame('Ortsgruppen', ParticipantGroupType::LocalGroup->pluralLabel()); } public function test_leere_bezeichnung_wird_abgelehnt(): void { $response = new UpdateGlobalSettingsAction(new UpdateGlobalSettingsRequest( app(GlobalSettingRepository::class), 'Ortsgruppe', '', 'Verband', 'Verbände', ))->execute(); $this->assertFalse($response->success); $this->assertSame('Stamm', ParticipantGroupType::LocalGroup->label()); } public function test_fehlende_einstellung_faellt_auf_vorgabe_zurueck(): void { DB::table('global_settings')->delete(); $this->assertSame('Bund', app(GlobalSettingRepository::class)->get('parent_group_label', 'Bund')); $this->assertSame('Stämme', ParticipantGroupType::LocalGroup->pluralLabel()); } // --- Event ------------------------------------------------------------------------------------------- public function test_anlage_speichert_abfrage_nach_bund(): void { $this->assertSame('parent_group', $this->createEvent(ParticipantGroupType::ParentGroup)->participant_group_type); $this->assertSame('local_group', $this->createEvent()->participant_group_type); } public function test_optionen_sind_die_buende_der_teilnehmenden_staemme(): void { $event = $this->createEvent(ParticipantGroupType::ParentGroup); $event->localGroups()->sync([ $this->createTenant('stamm-a', 'Stamm A', $this->parentGroup('VCP'))->id, $this->createTenant('stamm-b', 'Stamm B', $this->parentGroup('BdP'))->id, $this->createTenant('stamm-c', 'Stamm C', $this->parentGroup('BdP'))->id, ]); // Jeder Bund nur einmal, alphabetisch. $this->assertSame(['BdP', 'VCP'], $event->participantGroupOptions()->pluck('name')->all()); } public function test_optionen_listen_ohne_zuordnung_zuletzt(): void { $event = $this->createEvent(ParticipantGroupType::ParentGroup); $event->localGroups()->sync([ $this->createTenant('stamm-a', 'Stamm A', $this->parentGroup('VCP'))->id, $this->createTenant('stamm-b', 'Stamm B', ParentGroup::fallback())->id, $this->createTenant('stamm-c', 'Stamm C', $this->parentGroup('BdP'))->id, ]); $this->assertSame(['BdP', 'VCP', 'Ohne Zuordnung'], $event->participantGroupOptions()->pluck('name')->all()); } public function test_verwaltung_listet_alphabetisch_mit_ohne_zuordnung_zuletzt(): void { new CreateParentGroupAction(new CreateParentGroupRequest('Pfadfinderbund'))->execute(); $this->assertSame( ['BdP', 'DPSG', 'DPV', 'Pfadfinderbund', 'VCP', 'Ohne Zuordnung'], $this->repository->all()->pluck('name')->all(), ); } public function test_optionen_sind_bei_stamm_abfrage_die_staemme(): void { $event = $this->createEvent(); $event->localGroups()->sync([$this->createTenant('stamm-a', 'Stamm A', $this->parentGroup('BdP'))->id]); $this->assertSame(['Stamm A'], $event->participantGroupOptions()->pluck('name')->all()); } public function test_umstellen_ist_ohne_anmeldungen_moeglich(): void { $event = $this->updateGroupType($this->createEvent(), ParticipantGroupType::ParentGroup); $this->assertSame('parent_group', $event->participant_group_type); } public function test_umstellen_ist_nach_der_ersten_anmeldung_gesperrt(): void { $event = $this->createEvent(); // Eine Anmeldung genügt -- direkt geschrieben, der Anmeldeprozess ist hier nicht Gegenstand. EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']); SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'name' => 'Darf baden', 'short' => 'Erteilt']); FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, 'name' => 'Zugestimmt', 'description' => '']); DB::table('event_participants')->insert([ 'efz_status' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'swimming_permission' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'first_aid_permission' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, 'tenant' => $this->tenant->slug, 'event_id' => $event->id, 'identifier' => 'p-1', 'firstname' => 'Hans', 'lastname' => 'Muster', 'participation_type' => 'participant', 'birthday' => '2000-01-01', 'address_1' => 'Weg 1', 'postcode' => '04103', 'city' => 'Leipzig', 'email_1' => 'h@example.com', 'phone_1' => '123', 'payment_purpose' => 'Beitrag', 'eating_habit' => EatingHabit::EATING_HABIT_VEGAN, 'arrival_date' => '2026-09-01', 'departure_date' => '2026-09-05', 'arrival_eating' => 1, 'departure_eating' => 2, 'amount' => 0, 'created_at' => now(), 'updated_at' => now(), ]); $event = $this->updateGroupType($event, ParticipantGroupType::ParentGroup); $this->assertSame('local_group', $event->participant_group_type); } }