From 12c37b6250477bf149196a3ee73ad4502f43107b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 17 Jul 2024 15:52:34 -0500 Subject: [PATCH] Created UpdateEntry action #29 Creating an entry should check on the status of the draw and respond appropriately Also changed name of CreateEntryException to MangeEntryException Also addresses #37 --- app/Actions/{ => Entries}/CreateEntry.php | 28 +-- app/Actions/Entries/UpdateEntry.php | 142 ++++++++++++ ...Exception.php => ManageEntryException.php} | 2 +- .../Controllers/Admin/EntryController.php | 6 +- app/Http/Controllers/EntryController.php | 6 +- tests/Feature/Actions/CreateEntryTest.php | 18 +- tests/Feature/Actions/UpdateEntryTest.php | 219 ++++++++++++++++++ 7 files changed, 391 insertions(+), 30 deletions(-) rename app/Actions/{ => Entries}/CreateEntry.php (76%) create mode 100644 app/Actions/Entries/UpdateEntry.php rename app/Exceptions/{CreateEntryException.php => ManageEntryException.php} (54%) create mode 100644 tests/Feature/Actions/UpdateEntryTest.php diff --git a/app/Actions/CreateEntry.php b/app/Actions/Entries/CreateEntry.php similarity index 76% rename from app/Actions/CreateEntry.php rename to app/Actions/Entries/CreateEntry.php index 1cfacc6..daa6825 100644 --- a/app/Actions/CreateEntry.php +++ b/app/Actions/Entries/CreateEntry.php @@ -1,8 +1,8 @@ exists()) { - throw new CreateEntryException('Invalid student provided'); + if (! $student || ! $student->exists()) { + throw new ManageEntryException('Invalid student provided'); } // Make sure the audition is valid - if (! $audition->exists()) { - throw new CreateEntryException('Invalid audition provided'); + if (! $audition || ! $audition->exists()) { + throw new ManageEntryException('Invalid audition provided'); } // A student can't enter the same audition twice if (Entry::where('student_id', $student->id)->where('audition_id', $audition->id)->exists()) { - throw new CreateEntryException('That student is already entered in that audition'); + throw new ManageEntryException('That student is already entered in that audition'); } // Can't enter a published audition if ($audition->hasFlag('seats_published')) { - throw new CreateEntryException('Cannot add an entry to an audition where seats are published'); + throw new ManageEntryException('Cannot add an entry to an audition where seats are published'); } if ($audition->hasFlag('advancement_published')) { - throw new CreateEntryException('Cannot add an entry to an audition where advancement is published'); + throw new ManageEntryException('Cannot add an entry to an audition where advancement is published'); } // Verify the grade of the student is in range for the audition if ($student->grade > $audition->maximum_grade) { - throw new CreateEntryException('The grade of the student exceeds the maximum for that audition'); + throw new ManageEntryException('The grade of the student exceeds the maximum for that audition'); } if ($student->grade < $audition->minimum_grade) { - throw new CreateEntryException('The grade of the student does not meet the minimum for that audition'); + throw new ManageEntryException('The grade of the student does not meet the minimum for that audition'); } } } diff --git a/app/Actions/Entries/UpdateEntry.php b/app/Actions/Entries/UpdateEntry.php new file mode 100644 index 0000000..4d44650 --- /dev/null +++ b/app/Actions/Entries/UpdateEntry.php @@ -0,0 +1,142 @@ +updateEntry($entry, $updateData); + } + + /** + * @throws ManageEntryException + */ + public function updateEntry(Entry|int $entry, array $updateData): void + { + if (is_int($entry)) { + $entry = Entry::find($entry); + } + if (! $entry || ! $entry->exists) { + throw new ManageEntryException('Invalid entry provided'); + } + $this->entry = $entry; + if (array_key_exists('for_seating', $updateData)) { + $this->updateForSeating($updateData['for_seating']); + } + if (array_key_exists('for_advancement', $updateData)) { + $this->updateForAdvancement($updateData['for_advancement']); + } + if (array_key_exists('audition', $updateData)) { + $this->updateAudition($updateData['audition']); + } + + $this->entry->save(); + } + + /** + * @throws ManageEntryException + */ + private function updateAudition(Audition|int $audition): void + { + + if (is_int($audition)) { + $audition = Audition::find($audition); + } + if (! $audition || ! $audition->exists) { + throw new ManageEntryException('Invalid audition provided'); + } + + if ($this->entry->audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot change the audition for an entry where seating for that entry\'s current audition is published'); + } + if ($this->entry->audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot change the audition for an entry where advancement for that entry\'s current audition is published'); + } + if ($audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot change the entry to an audition with published seating'); + } + if ($audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot change the entry to an audition with published advancement'); + } + if ($this->entry->student->grade > $audition->maximum_grade) { + throw new ManageEntryException('The grade of the student exceeds the maximum for that audition'); + } + if ($this->entry->student->grade < $audition->minimum_grade) { + throw new ManageEntryException('The grade of the student does not meet the minimum for that audition'); + } + if ($this->entry->scoreSheets()->count() > 0) { + throw new ManageEntryException('Cannot change the audition for an entry with scores'); + } + if (Entry::where('student_id', $this->entry->student_id)->where('audition_id', $audition->id)->exists()) { + throw new ManageEntryException('That student is already entered in that audition'); + } + // OK we're allowed to change the audition + $this->entry->audition_id = $audition->id; + // Deal with our draw number + if ($audition->hasFlag('drawn')) { + $draw_number = $audition->entries()->max('draw_number'); + $this->entry->draw_number = $draw_number + 1; + } else { + $this->entry->draw_number = null; + } + } + + /** + * @throws ManageEntryException + */ + private function updateForSeating($forSeating): void + { + if ($this->entry->for_seating == $forSeating) { + return; + } + if ($forSeating) { + if ($this->entry->audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot add seating to an entry in an audition where seats are published'); + } + $this->entry->for_seating = 1; + } else { + if ($this->entry->audition->hasFlag('seats_published')) { + throw new ManageEntryException('Cannot remove seating from an entry in an audition where seats are published'); + } + $this->entry->for_seating = 0; + } + } + + /** + * @throws ManageEntryException + */ + private function updateForAdvancement($forAdvancement): void + { + if ($this->entry->for_advancement == $forAdvancement) { + return; + } + if ($forAdvancement) { + if ($this->entry->audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot add advancement to an entry in an audition where advancement is published'); + } + $this->entry->for_advancement = 1; + } else { + if ($this->entry->audition->hasFlag('advancement_published')) { + throw new ManageEntryException('Cannot remove advancement from an entry in an audition where advancement is published'); + } + $this->entry->for_advancement = 0; + } + + } +} diff --git a/app/Exceptions/CreateEntryException.php b/app/Exceptions/ManageEntryException.php similarity index 54% rename from app/Exceptions/CreateEntryException.php rename to app/Exceptions/ManageEntryException.php index 8dd356f..6d5c3cf 100644 --- a/app/Exceptions/CreateEntryException.php +++ b/app/Exceptions/ManageEntryException.php @@ -4,6 +4,6 @@ namespace App\Exceptions; use Exception; -class CreateEntryException extends Exception +class ManageEntryException extends Exception { } diff --git a/app/Http/Controllers/Admin/EntryController.php b/app/Http/Controllers/Admin/EntryController.php index ab55ea0..2edead0 100644 --- a/app/Http/Controllers/Admin/EntryController.php +++ b/app/Http/Controllers/Admin/EntryController.php @@ -2,9 +2,9 @@ namespace App\Http\Controllers\Admin; -use App\Actions\CreateEntry; +use App\Actions\Entries\CreateEntry; use App\Actions\Tabulation\CalculateEntryScore; -use App\Exceptions\CreateEntryException; +use App\Exceptions\ManageEntryException; use App\Http\Controllers\Controller; use App\Models\Audition; use App\Models\Entry; @@ -111,7 +111,7 @@ class EntryController extends Controller try { $creator($validData['student_id'], $validData['audition_id'], $enter_for); - } catch (CreateEntryException $ex) { + } catch (ManageEntryException $ex) { return redirect()->route('admin.entries.index')->with('error', $ex->getMessage()); } diff --git a/app/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php index f2e7ca4..cdb992e 100644 --- a/app/Http/Controllers/EntryController.php +++ b/app/Http/Controllers/EntryController.php @@ -2,8 +2,8 @@ namespace App\Http\Controllers; -use App\Actions\CreateEntry; -use App\Exceptions\CreateEntryException; +use App\Actions\Entries\CreateEntry; +use App\Exceptions\ManageEntryException; use App\Models\Audition; use App\Models\Entry; use Illuminate\Http\Request; @@ -53,7 +53,7 @@ class EntryController extends Controller try { $creator($validData['student_id'], $validData['audition_id'], $enter_for); - } catch (CreateEntryException $ex) { + } catch (ManageEntryException $ex) { return redirect()->route('entries.index')->with('error', $ex->getMessage()); } diff --git a/tests/Feature/Actions/CreateEntryTest.php b/tests/Feature/Actions/CreateEntryTest.php index ab7fce4..cf1601a 100644 --- a/tests/Feature/Actions/CreateEntryTest.php +++ b/tests/Feature/Actions/CreateEntryTest.php @@ -1,7 +1,7 @@ create(); $student = Student::factory()->make(); $this->createEntry->__invoke($student, $audition); -})->throws(CreateEntryException::class, 'Invalid student provided'); +})->throws(ManageEntryException::class, 'Invalid student provided'); it('throws an exception if the audition does not exist', function () { $audition = Audition::factory()->make(); $student = Student::factory()->create(); $this->createEntry->__invoke($student, $audition); -})->throws(CreateEntryException::class, 'Invalid audition provided'); +})->throws(ManageEntryException::class, 'Invalid audition provided'); it('throws an exception if the student is already entered in the audition', function () { // Arrange $audition = Audition::factory()->create(); @@ -34,7 +34,7 @@ it('throws an exception if the student is already entered in the audition', func ]); // Act & Assert $this->createEntry->createEntry($student, $audition); -})->throws(CreateEntryException::class, 'That student is already entered in that audition'); +})->throws(ManageEntryException::class, 'That student is already entered in that audition'); it('throws an exception if seats are published for the audition', function () { // Arrange $audition = Audition::factory()->create(); @@ -42,7 +42,7 @@ it('throws an exception if seats are published for the audition', function () { $audition->addFlag('seats_published'); // Act & Assert $this->createEntry->createEntry($student, $audition); -})->throws(CreateEntryException::class, 'Cannot add an entry to an audition where seats are published'); +})->throws(ManageEntryException::class, 'Cannot add an entry to an audition where seats are published'); it('throws an exception if advancement is published for the audition', function () { // Arrange $audition = Audition::factory()->create(); @@ -50,21 +50,21 @@ it('throws an exception if advancement is published for the audition', function $audition->addFlag('advancement_published'); // Act & Assert $this->createEntry->createEntry($student, $audition); -})->throws(CreateEntryException::class, 'Cannot add an entry to an audition where advancement is published'); +})->throws(ManageEntryException::class, 'Cannot add an entry to an audition where advancement is published'); it('throws an exception if the grade of the student exceeds the maximum grade for the audition', function () { // Arrange $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); $student = Student::factory()->create(['grade' => 11]); // Act & Assert $this->createEntry->createEntry($student, $audition); -})->throws(CreateEntryException::class, 'The grade of the student exceeds the maximum for that audition'); +})->throws(ManageEntryException::class, 'The grade of the student exceeds the maximum for that audition'); it('throws an exception if the grade of the student does not meet the minimum grade for the audition', function () { // Arrange $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); $student = Student::factory()->create(['grade' => 7]); // Act & Assert $this->createEntry->createEntry($student, $audition); -})->throws(CreateEntryException::class, 'The grade of the student does not meet the minimum for that audition'); +})->throws(ManageEntryException::class, 'The grade of the student does not meet the minimum for that audition'); it('returns an entry object', function () { // Arrange $audition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); diff --git a/tests/Feature/Actions/UpdateEntryTest.php b/tests/Feature/Actions/UpdateEntryTest.php new file mode 100644 index 0000000..c9350e2 --- /dev/null +++ b/tests/Feature/Actions/UpdateEntryTest.php @@ -0,0 +1,219 @@ +updater = App::make(UpdateEntry::class); +}); + +it('throws an error if an invalid entry is provided', function () { + $this->updater->updateEntry(2, []); +})->throws(ManageEntryException::class, 'Invalid entry provided'); + +it('throws an error if we try to remove for_seating while seating is published', function () { + // Arrange + $entry = Entry::factory()->create(); + $entry->audition->addFlag('seats_published'); + $data = ['for_seating' => 0]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot remove seating from an entry in an audition where seats are published'); +it('throws an error if we try to add for_seating while seating is published', function () { + // Arrange + $entry = Entry::factory()->advanceOnly()->create(); + $entry->audition->addFlag('seats_published'); + $data = ['for_seating' => 1]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot add seating to an entry in an audition where seats are published'); +it('allows us to remove for_seating if seating is not published', function () { + // Arrange + $entry = Entry::factory()->create(); + $data = ['for_seating' => 0]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_seating' => 0]); +}); +it('allows us to add for_seating if seating is not published', function () { + // Arrange + $entry = Entry::factory()->advanceOnly()->create(); + $data = ['for_seating' => 1]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_seating' => 1]); +}); + +it('throws an error if we try to remove for_advancement while seating is published', function () { + // Arrange + $entry = Entry::factory()->create(); + $entry->audition->addFlag('advancement_published'); + $data = ['for_advancement' => 0]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot remove advancement from an entry in an audition where advancement is published'); +it('throws an error if we try to add for_advancement while advancement is published', function () { + // Arrange + $entry = Entry::factory()->seatingOnly()->create(); + $entry->audition->addFlag('advancement_published'); + $data = ['for_advancement' => 1]; + // Act & Assert + $this->updater->updateEntry($entry, $data); +})->throws('Cannot add advancement to an entry in an audition where advancement is published'); +it('allows us to remove for_advancement if advancement is not published', function () { + // Arrange + $entry = Entry::factory()->create(); + $data = ['for_advancement' => 0]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_advancement' => 0]); +}); +it('allows us to add for_advancement if advancement is not published', function () { + // Arrange + $entry = Entry::factory()->seatingOnly()->create(); + $data = ['for_advancement' => 1]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'for_advancement' => 1]); +}); + +it('throws an exception if an attempt to change to an invalid audition is made', function () { + $entry = Entry::factory()->create(); + $data = ['audition' => 2]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Invalid audition provided'); + +it('cannot change auditions if our current audition advancement is published', function () { + $entry = Entry::factory()->create(); + $entry->audition->addFlag('advancement_published'); + $otherAudition = Audition::factory()->create(); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, + 'Cannot change the audition for an entry where advancement for that entry\'s current audition is published'); +it('cannot change auditions if our current audition seating is published', function () { + $entry = Entry::factory()->create(); + $entry->audition->addFlag('seats_published'); + $otherAudition = Audition::factory()->create(); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, + 'Cannot change the audition for an entry where seating for that entry\'s current audition is published'); + +it('cannot change auditions if our proposed audition advancement is published', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(); + $otherAudition->addFlag('advancement_published'); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Cannot change the entry to an audition with published advancement'); +it('cannot change auditions if our proposed audition seating is published', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(); + $otherAudition->addFlag('seats_published'); + $data = ['audition' => $otherAudition]; + // Act + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Cannot change the entry to an audition with published seating'); + +it('will not let us switch to an audition that is too old for the student', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $entry->student->update(['grade' => 7]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'The grade of the student does not meet the minimum for that audition'); +it('will not let us switch to an audition that is too young for the student', function () { + $entry = Entry::factory()->create(); + $otherAudition = Audition::factory()->create(['minimum_grade' => 8, 'maximum_grade' => 9]); + $entry->student->update(['grade' => 11]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'The grade of the student exceeds the maximum for that audition'); +it('will not let us change auditions for an entry with scores', function () { + $entry = Entry::factory()->create(); + $judge = User::factory()->create(); + ScoreSheet::create([ + 'entry_id' => $entry->id, + 'user_id' => $judge->id, + 'subscores' => 100, + ]); + $otherAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'Cannot change the audition for an entry with scores'); +it('will not let us change to an audition that the student is already entered in', function () { + $entry = Entry::factory()->create(); + + $otherAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + Entry::create([ + 'student_id' => $entry->student->id, + 'audition_id' => $otherAudition->id, + ]); + $data = ['audition' => $otherAudition]; + $this->updater->updateEntry($entry, $data); +})->throws(ManageEntryException::class, 'That student is already entered in that audition'); +it('allows us to switch auditions', function () { + // Arrange + $entry = Entry::factory()->create(); + $originalAudition = $entry->audition; + $otherAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $data = ['audition' => $otherAudition]; + // Act & Assert + $this->updater->updateEntry($entry, $data); + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'audition_id' => $otherAudition->id]); + $this->assertDatabaseMissing('entries', + ['student_id' => $entry->student->id, 'audition_id' => $originalAudition->id]); +}); +it('sets the draw number null if the new audition is not drawn', function () { + // Arrange + $entry = Entry::factory()->create(['draw_number' => 3]); + $newAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $data = ['audition' => $newAudition]; + // Act + $this->updater->updateEntry($entry, $data); + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'draw_number' => null]); +}); +it('sets the draw number last if the new audition is not drawn', function () { + // Arrange + $entry = Entry::factory()->create(['draw_number' => 3]); + $newAudition = Audition::factory()->create([ + 'minimum_grade' => $entry->student->grade, 'maximum_grade' => $entry->student->grade, + ]); + $newAudition->addFlag('drawn'); + foreach (range(1, 6) as $dn) { + Entry::factory()->create(['audition_id' => $newAudition->id, 'draw_number' => $dn]); + } + $data = ['audition' => $newAudition]; + // Act + $this->updater->updateEntry($entry, $data); + + // Assert + $this->assertDatabaseHas('entries', ['id' => $entry->id, 'draw_number' => 7]); +});