diff --git a/app/Activity/Actions/ActivityUpdateAction.php b/app/Activity/Actions/ActivityUpdateAction.php index ce7f7f5f..41b18c89 100644 --- a/app/Activity/Actions/ActivityUpdateAction.php +++ b/app/Activity/Actions/ActivityUpdateAction.php @@ -3,6 +3,7 @@ namespace App\Activity\Actions; use App\Activity; +use App\Member\Membership; use App\Subactivity; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\DB; @@ -46,6 +47,12 @@ class ActivityUpdateAction $this->validateNami($activity, $request->validated()); } + $removingSubactivities = $activity->subactivities()->whereNotIn('id', $request->validated('subactivities'))->pluck('id'); + + if ($removingSubactivities->first(fn ($subactivity) => Membership::firstWhere(['activity_id' => $activity->id, 'subactivity_id' => $subactivity]))) { + throw ValidationException::withMessages(['subactivities' => 'Untergliederung hat noch Mitglieder.']); + } + $this->handle($activity, $request->validated()); return redirect()->route('activity.index'); diff --git a/app/Activity/Api/SubactivityUpdateAction.php b/app/Activity/Api/SubactivityUpdateAction.php new file mode 100644 index 00000000..82f8ebba --- /dev/null +++ b/app/Activity/Api/SubactivityUpdateAction.php @@ -0,0 +1,93 @@ +> $payload + */ + public function handle(Subactivity $subactivity, array $payload): Subactivity + { + $subactivity->update(Arr::except($payload, 'activities')); + + if (null !== data_get($payload, 'activities')) { + $subactivity->activities()->sync($payload['activities']); + } + + return $subactivity; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'max:255', Rule::unique('subactivities', 'name')->ignore(request()->route('subactivity')->id)], + 'activities' => ['present', 'array', 'min:1'], + 'is_filterable' => 'present|boolean', + ]; + } + + /** + * @return array + */ + public function getValidationAttributes(): array + { + return [ + 'activities' => 'Tätigkeiten', + ]; + } + + public function asController(ActionRequest $request, Subactivity $subactivity): JsonResponse + { + if ($subactivity->hasNami) { + $this->validateNami($subactivity, $request->validated()); + } + + $removingActivities = $subactivity->activities()->whereNotIn('id', $request->validated('activities'))->pluck('id'); + + if ($removingActivities->first(fn ($activity) => Membership::firstWhere(['activity_id' => $activity, 'subactivity_id' => $subactivity->id]))) { + throw ValidationException::withMessages(['activities' => 'Tätigkeit hat noch Mitglieder.']); + } + + return response()->json($this->handle($subactivity, $request->validated())); + } + + /** + * @todo handle this with a model event on the pivot model + * + * @param Payload $payload + */ + private function validateNami(Subactivity $subactivity, array $payload): void + { + if ($subactivity->name !== $payload['name']) { + throw ValidationException::withMessages(['name' => 'Untertätigkeit ist in NaMi. Update des Namens nicht möglich.']); + } + + $removingActivities = $subactivity->activities()->whereNotIn('id', $payload['activities'])->pluck('id'); + + if ($removingActivities->first(fn ($activity) => Activity::find($activity)->hasNami)) { + throw ValidationException::withMessages(['activities' => 'Tätigkeit kann nicht entfernt werden.']); + } + + $addingActivities = collect($payload['activities'])->filter(fn ($activityId) => $subactivity->activities->doesntContain($activityId)); + + if ($addingActivities->first(fn ($activity) => Activity::find($activity)->hasNami)) { + throw ValidationException::withMessages(['activities' => 'Tätigkeit kann nicht hinzugefügt werden.']); + } + } +} diff --git a/routes/web.php b/routes/web.php index f1e80122..29279cc3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,6 +7,7 @@ use App\Activity\Actions\DestroyAction as ActivityDestroyAction; use App\Activity\Actions\EditAction as ActivityEditAction; use App\Activity\Actions\IndexAction as ActivityIndexAction; use App\Activity\Api\SubactivityStoreAction; +use App\Activity\Api\SubactivityUpdateAction; use App\Contribution\Actions\FormAction as ContributionFormAction; use App\Contribution\ContributionController; use App\Course\Controllers\CourseController; @@ -62,4 +63,5 @@ Route::group(['middleware' => 'auth:web'], function (): void { Route::patch('/activity/{activity}', ActivityUpdateAction::class)->name('activity.update'); Route::delete('/activity/{activity}', ActivityDestroyAction::class)->name('activity.destroy'); Route::post('/subactivity', SubactivityStoreAction::class)->name('api.subactivity.store'); + Route::patch('/subactivity/{subactivity}', SubactivityUpdateAction::class)->name('api.subactivity.update'); }); diff --git a/tests/Feature/Activity/SubactivityTest.php b/tests/Feature/Activity/SubactivityTest.php index 4cf14c33..3ce15525 100644 --- a/tests/Feature/Activity/SubactivityTest.php +++ b/tests/Feature/Activity/SubactivityTest.php @@ -3,6 +3,8 @@ namespace Tests\Feature\Activity; use App\Activity; +use App\Member\Member; +use App\Member\Membership; use App\Subactivity; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\TestCase; @@ -86,4 +88,191 @@ class SubactivityTest extends TestCase ]); $this->assertDatabaseMissing('subactivities', ['nami_id' => 556]); } + + public function testItCannotUpdateNameIfInNami(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->inNami(123)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->inNami(777)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'aaaa', + 'nami_id' => 777, + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $response->assertJsonValidationErrors(['name' => 'Untertätigkeit ist in NaMi. Update des Namens nicht möglich.']); + } + + public function testItCannotUpdateNamiId(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->inNami(123)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->inNami(777)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'nami_id' => 556, + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $this->assertDatabaseHas('subactivities', [ + 'id' => $subactivity->id, + 'nami_id' => 777, + ]); + } + + public function testItCannotSetNamiId(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->inNami(123)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'nami_id' => 556, + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $this->assertDatabaseHas('subactivities', [ + 'id' => $subactivity->id, + 'nami_id' => null, + ]); + } + + public function testItCanUpdateIsFilterableIfInNami(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->inNami(123)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->filterable()->inNami(777)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $this->assertDatabaseHas('subactivities', [ + 'id' => $subactivity->id, + 'is_filterable' => false, + ]); + } + + public function testItCanUpdateNameWhenNotInNami(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->inNami(123)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'def', + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $this->assertDatabaseHas('subactivities', [ + 'id' => $subactivity->id, + 'name' => 'def', + ]); + } + + public function testNameShouldBeUnique(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->inNami(123)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->create(); + Subactivity::factory()->hasAttached($activity)->name('def')->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'def', + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $response->assertJsonValidationErrors(['name' => 'Name ist bereits vergeben']); + } + + public function testItCanSetAnotherActivity(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->create(); + $newActivity = Activity::factory()->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'activities' => [$activity->id, $newActivity->id], + 'is_filterable' => false, + ]); + + $this->assertDatabaseHas('activity_subactivity', ['activity_id' => $activity->id, 'subactivity_id' => $subactivity->id]); + $this->assertDatabaseHas('activity_subactivity', ['activity_id' => $newActivity->id, 'subactivity_id' => $subactivity->id]); + } + + public function testItCannotSetAnotherNamiActivity(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->create(); + $newActivity = Activity::factory()->inNami(556)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->name('abc')->inNami(667)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'activities' => [$activity->id, $newActivity->id], + 'is_filterable' => false, + ]); + + $response->assertJsonValidationErrors(['activities' => 'Tätigkeit kann nicht hinzugefügt werden.']); + } + + public function testItCannotRemoveANamiActivity(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->create(); + $newActivity = Activity::factory()->inNami(556)->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->hasAttached($newActivity)->name('abc')->inNami(667)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'activities' => [$activity->id], + 'is_filterable' => false, + ]); + + $response->assertJsonValidationErrors(['activities' => 'Tätigkeit kann nicht entfernt werden.']); + } + + public function testItCannotRemoveActivityIfMembershipsHasMembers(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->create(); + $newActivity = Activity::factory()->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->create(); + Member::factory()->defaults()->has(Membership::factory()->for($activity)->for($subactivity))->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'activities' => [$newActivity->id], + 'is_filterable' => false, + ]); + + $response->assertJsonValidationErrors(['activities' => 'Tätigkeit hat noch Mitglieder.']); + } + + public function testItCannotSetNoActivity(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->create(); + + $response = $this->patchJson(route('api.subactivity.update', ['subactivity' => $subactivity->id]), [ + 'name' => 'abc', + 'activities' => [], + 'is_filterable' => false, + ]); + + $response->assertJsonValidationErrors(['activities' => 'Tätigkeiten muss mindestens 1 Elemente haben.']); + } } diff --git a/tests/Feature/Activity/UpdateTest.php b/tests/Feature/Activity/UpdateTest.php index 75eba9be..cce95b1a 100644 --- a/tests/Feature/Activity/UpdateTest.php +++ b/tests/Feature/Activity/UpdateTest.php @@ -3,6 +3,8 @@ namespace Tests\Feature\Activity; use App\Activity; +use App\Member\Member; +use App\Member\Membership; use App\Subactivity; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\TestCase; @@ -158,4 +160,21 @@ class UpdateTest extends TestCase $this->assertDatabaseEmpty('activity_subactivity'); } + + public function testItCannotSetSubactivityIfItStillHasMembers(): void + { + $this->login()->loginNami(); + $activity = Activity::factory()->create(); + $subactivity = Subactivity::factory()->hasAttached($activity)->create(); + $newSubactivity = Subactivity::factory()->create(); + Member::factory()->defaults()->has(Membership::factory()->for($activity)->for($subactivity))->create(); + + $response = $this->patch(route('activity.update', ['activity' => $activity]), [ + 'name' => 'abc', + 'is_filterable' => false, + 'subactivities' => [$newSubactivity->id], + ]); + + $response->assertSessionHasErrors(['subactivities' => 'Untergliederung hat noch Mitglieder.']); + } }