diff --git a/app/Member/FilterScope.php b/app/Member/FilterScope.php index c5a2d219..2bed86a9 100644 --- a/app/Member/FilterScope.php +++ b/app/Member/FilterScope.php @@ -32,6 +32,8 @@ class FilterScope extends Filter public array $groupIds = [], public array $include = [], public array $exclude = [], + public ?bool $hasFullAddress = null, + public ?bool $hasBirthday = null, ) { } @@ -60,6 +62,22 @@ class FilterScope extends Filter $query->where('bill_kind', BillKind::fromValue($this->billKind)); } + if (true === $this->hasFullAddress) { + $query->whereNotNull('address')->whereNotNull('zip')->whereNotNull('location')->where('address', '!=', '')->where('zip', '!=', '')->where('location', '!=', ''); + } + + if (false === $this->hasFullAddress) { + $query->where(fn ($q) => $q + ->orWhere('address', '')->orWhereNull('address') + ->orWhere('zip', '')->orWhereNull('zip') + ->orWhere('location', '')->orWhereNull('location') + ); + } + + if (true === $this->hasBirthday) { + $query->whereNotNull('birthday'); + } + if (count($this->groupIds)) { $query->whereIn('group_id', $this->groupIds); } diff --git a/app/Member/Member.php b/app/Member/Member.php index 13998e20..0d74a23d 100644 --- a/app/Member/Member.php +++ b/app/Member/Member.php @@ -161,7 +161,7 @@ class Member extends Model implements Geolocatable public function getEfzLink(): ?string { - return $this->isLeader() + return $this->isLeader() && $this->address && $this->zip && $this->location && $this->birthday ? route('efz', ['member' => $this]) : null; } @@ -436,11 +436,14 @@ class Member extends Model implements Geolocatable 'VERSION' => '3.0', 'FN' => $this->fullname, 'N' => [$this->lastname, $this->firstname, '', '', ''], - 'BDAY' => $this->birthday->format('Ymd'), 'CATEGORIES' => 'Scoutrobot', 'UID' => $this->slug, ]); + if ($this->birthday) { + $card->add('BDAY', $this->birthday->format('Ymd')); + } + if ($this->main_phone) { $card->add('TEL', $this->main_phone, ['type' => 'voice']); } diff --git a/app/Member/MemberRequest.php b/app/Member/MemberRequest.php index 12cb8397..4db6adda 100644 --- a/app/Member/MemberRequest.php +++ b/app/Member/MemberRequest.php @@ -12,6 +12,7 @@ use App\Subactivity; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Str; use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; use Zoomyboy\Phone\ValidPhoneRule; class MemberRequest extends FormRequest @@ -51,15 +52,10 @@ class MemberRequest extends FormRequest }), 'firstname' => 'required', 'lastname' => 'required', - 'address' => 'required', - 'zip' => 'required|numeric', - 'location' => 'required', - 'birthday' => 'date|required', 'country_id' => 'required|exists:countries,id', 'email' => 'nullable|email', 'email_parents' => 'nullable|email', 'bill_kind' => ['nullable', Rule::in(BillKind::values())], - 'joined_at' => 'date|required', 'confession_id' => 'nullable|exists:confessions,id', 'ps_at' => 'nullable|date_format:Y-m-d', 'more_ps_at' => 'nullable|date_format:Y-m-d', @@ -76,7 +72,6 @@ class MemberRequest extends FormRequest 'invoice_address' => '', 'gender_id' => 'nullable|exists:genders,id', 'region_id' => 'nullable|exists:regions,id', - 'nationality_id' => 'required|exists:nationalities,id', 'children_phone' => ['nullable', new ValidPhoneRule('Telefon (Kind)')], 'fax' => ['nullable', new ValidPhoneRule('Fax')], 'other_country' => '', @@ -122,4 +117,23 @@ class MemberRequest extends FormRequest } ResyncAction::dispatch(); } + + public function withValidator(Validator $validator): void + { + $this->namiIfElse($validator, 'birthday', 'date|required'); + $this->namiIfElse($validator, 'nationality_id', 'required|exists:nationalities,id'); + $this->namiIfElse($validator, 'address', 'required|max:255'); + $this->namiIfElse($validator, 'zip', 'required|numeric'); + $this->namiIfElse($validator, 'location', 'required|max:255'); + $this->namiIfElse($validator, 'joined_at', 'date|required'); + } + + public function namiIfElse(Validator $validator, string $attribute, string $rules): void + { + $request = request(); + $when = fn () => true === $request->input('has_nami'); + $notWhen = fn () => true !== $request->input('has_nami'); + $validator->sometimes($attribute, $rules, $when); + $validator->sometimes($attribute, 'present', $notWhen); + } } diff --git a/database/migrations/2023_07_23_203851_edit_members_table.php b/database/migrations/2023_07_23_203851_edit_members_table.php new file mode 100644 index 00000000..7df859f3 --- /dev/null +++ b/database/migrations/2023_07_23_203851_edit_members_table.php @@ -0,0 +1,41 @@ +unsignedBigInteger('nationality_id')->nullable(true)->change(); + $table->date('birthday')->nullable(true)->change(); + $table->string('address')->nullable(true)->change(); + $table->string('zip')->nullable(true)->change(); + $table->string('location')->nullable(true)->change(); + $table->date('joined_at')->nullable(true)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('members', function (Blueprint $table) { + $table->unsignedBigInteger('nationality_id')->nullable(false)->change(); + $table->date('birthday')->nullable(false)->change(); + $table->string('address')->nullable(false)->change(); + $table->string('zip')->nullable(false)->change(); + $table->string('location')->nullable(false)->change(); + $table->date('joined_at')->nullable(false)->change(); + }); + } +}; diff --git a/resources/js/views/contribution/VIndex.vue b/resources/js/views/contribution/VIndex.vue index 99995c7c..e5a34c60 100644 --- a/resources/js/views/contribution/VIndex.vue +++ b/resources/js/views/contribution/VIndex.vue @@ -86,6 +86,8 @@ export default { var response = await this.axios.post('/api/member/search', { filter: { search: event, + hasBirthday: true, + hasFullAddress: true, }, }); diff --git a/resources/js/views/member/VForm.vue b/resources/js/views/member/VForm.vue index c2a916c4..ba27c954 100644 --- a/resources/js/views/member/VForm.vue +++ b/resources/js/views/member/VForm.vue @@ -27,14 +27,14 @@ - + - + - - - + + + diff --git a/resources/js/views/member/boxes/Stamm.vue b/resources/js/views/member/boxes/Stamm.vue index f4ceb428..e35c2d7c 100644 --- a/resources/js/views/member/boxes/Stamm.vue +++ b/resources/js/views/member/boxes/Stamm.vue @@ -5,7 +5,7 @@ - + diff --git a/tests/EndToEnd/MemberIndexTest.php b/tests/EndToEnd/MemberIndexTest.php index 8e1a716c..24953fc7 100644 --- a/tests/EndToEnd/MemberIndexTest.php +++ b/tests/EndToEnd/MemberIndexTest.php @@ -25,6 +25,33 @@ class MemberIndexTest extends TestCase $this->assertCount(1, $this->inertia($response, 'data.data')); } + public function testItHandlesAddress(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + $group = Group::factory()->create(); + Member::factory()->defaults()->for(Group::factory())->create(['address' => '']); + Member::factory()->defaults()->for(Group::factory())->create(['zip' => '']); + Member::factory()->defaults()->for(Group::factory())->create(['location' => '']); + + $response = $this->callFilter('member.index', ['has_full_address' => true]); + $noResponse = $this->callFilter('member.index', ['has_full_address' => false]); + + $this->assertCount(0, $this->inertia($response, 'data.data')); + $this->assertCount(3, $this->inertia($noResponse, 'data.data')); + } + + public function testItHandlesBirthday(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + $group = Group::factory()->create(); + $member = Member::factory()->defaults()->for(Group::factory())->create(['birthday' => null]); + + $response = $this->callFilter('member.index', ['has_birthday' => true]); + + $this->assertCount(0, $this->inertia($response, 'data.data')); + $member->delete(); + } + public function testItFiltersForSearchButNotForPayments(): void { $this->withoutExceptionHandling()->login()->loginNami(); diff --git a/tests/Feature/Member/DavTest.php b/tests/Feature/Member/DavTest.php index 73cc1b25..45e85e6e 100644 --- a/tests/Feature/Member/DavTest.php +++ b/tests/Feature/Member/DavTest.php @@ -76,6 +76,15 @@ VCARD; $this->assertEquals('Max Muster', $card->FN->getValue()); } + public function testItDoesntNeedBirthday(): void + { + $member = Member::factory()->defaults()->create(['birthday' => null]); + + $card = $member->toVcard(); + + $this->assertNull($card->BDAY); + } + public function testItSetsTheBirthday(): void { $member = Member::factory()->defaults()->create(['birthday' => '1993-05-06']); diff --git a/tests/Feature/Member/IndexTest.php b/tests/Feature/Member/IndexTest.php index 17c38130..4f02ae6c 100644 --- a/tests/Feature/Member/IndexTest.php +++ b/tests/Feature/Member/IndexTest.php @@ -80,6 +80,19 @@ class IndexTest extends TestCase $this->assertInertiaHas(false, $response, 'data.data.2.is_leader'); } + public function testItHasNoEfzLinkWhenAddressIsMissing(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + $member = Member::factory() + ->defaults() + ->has(Membership::factory()->in('€ LeiterIn', 455, 'Pfadfinder', 15)) + ->create(['address' => null]); + + $response = $this->get('/member'); + + $this->assertInertiaHas(null, $response, 'data.data.0.efz_link'); + } + public function testItShowsAgeGroupIcon(): void { $this->withoutExceptionHandling()->login()->loginNami(); @@ -93,6 +106,17 @@ class IndexTest extends TestCase $this->assertInertiaHas('woelfling', $response, 'data.data.0.age_group_icon'); } + public function testAgeIsNullWhenBirthdayIsNull(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + $member = Member::factory()->defaults()->create(['birthday' => null]); + + $response = $this->get('/member'); + + $this->assertInertiaHas(null, $response, 'data.data.0.age'); + $this->assertInertiaHas(null, $response, 'data.data.0.birthday'); + } + public function testItShowsActivitiesAndSubactivities(): void { $this->withoutExceptionHandling()->login()->loginNami(); diff --git a/tests/Feature/Member/SearchTest.php b/tests/Feature/Member/SearchTest.php index db17f8e1..2be536ea 100644 --- a/tests/Feature/Member/SearchTest.php +++ b/tests/Feature/Member/SearchTest.php @@ -24,5 +24,4 @@ class SearchTest extends TestCase $this->assertEquals('::firstname:: ::lastname:: Kölner Str 3, 33333 Hilden', $member->search_text); } - } diff --git a/tests/Feature/Member/StoreTest.php b/tests/Feature/Member/StoreTest.php index 8deaf70e..268cfd8f 100644 --- a/tests/Feature/Member/StoreTest.php +++ b/tests/Feature/Member/StoreTest.php @@ -47,7 +47,7 @@ class StoreTest extends TestCase 'bill_kind' => 'Post', 'salutation' => 'Doktor', 'comment' => 'Lorem bla', - ])); + ]))->assertSessionHasNoErrors(); $response->assertRedirect('/member')->assertSessionHasNoErrors(); $member = Member::firstWhere('firstname', 'Joe'); @@ -140,6 +140,30 @@ class StoreTest extends TestCase ]); } + public function testItDoesntRequireBirthdayWhenNotInNami(): void + { + $this->login()->loginNami(); + + $this + ->post('/member', $this->attributes([ + 'nationality_id' => null, + 'birthday' => null, + 'has_nami' => false, + 'address' => null, + 'zip' => null, + 'location' => null, + 'joined_at' => null, + ])); + $this->assertDatabaseHas('members', [ + 'nationality_id' => null, + 'birthday' => null, + 'address' => null, + 'zip' => null, + 'location' => null, + 'joined_at' => null, + ]); + } + public function testItRequiresFields(): void { $this->login()->loginNami(); @@ -147,8 +171,13 @@ class StoreTest extends TestCase $this ->post('/member', $this->attributes([ 'nationality_id' => null, + 'birthday' => '', + 'address' => '', + 'zip' => '', + 'location' => '', + 'joined_at' => '', ])) - ->assertSessionHasErrors(['nationality_id']); + ->assertSessionHasErrors(['nationality_id', 'birthday', 'address', 'zip', 'location', 'joined_at']); } public function testSubscriptionIsRequiredIfFirstActivityIsPaid(): void diff --git a/tests/Feature/Member/UpdateTest.php b/tests/Feature/Member/UpdateTest.php index 854e8fb9..e63286a5 100644 --- a/tests/Feature/Member/UpdateTest.php +++ b/tests/Feature/Member/UpdateTest.php @@ -73,6 +73,22 @@ class UpdateTest extends TestCase ]); } + public function testItSetsLocationToNull(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + $member = $this->member(['location' => 'Hilden', 'nami_id' => null]); + $this->fakeRequest(); + NamiPutMemberAction::allowToRun(); + + $this->patch("/member/{$member->id}", array_merge($member->getAttributes(), [ + 'location' => null, + ])); + + $this->assertDatabaseHas('members', [ + 'location' => null, + ]); + } + public function testItUpdatesContact(): void { $this->withoutExceptionHandling()->login()->loginNami();