From 0e4b8acce6585bbed8ebe4f336fa9833c504ff89 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 8 Oct 2025 21:50:02 -0500 Subject: [PATCH] Judge prelim entry scores functioning. --- .../Judging/PrelimJudgingController.php | 50 ++++++++ .../views/judging/prelim_entry_form.blade.php | 58 +++++++++ .../views/judging/prelim_entry_list.blade.php | 2 +- routes/judging.php | 2 + .../Judging/PrelimJudgingControllerTest.php | 118 ++++++++++++++++++ 5 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 resources/views/judging/prelim_entry_form.blade.php diff --git a/app/Http/Controllers/Judging/PrelimJudgingController.php b/app/Http/Controllers/Judging/PrelimJudgingController.php index 4046279..a6518eb 100644 --- a/app/Http/Controllers/Judging/PrelimJudgingController.php +++ b/app/Http/Controllers/Judging/PrelimJudgingController.php @@ -2,8 +2,14 @@ namespace App\Http\Controllers\Judging; +use App\Actions\Tabulation\EnterPrelimScore; +use App\Exceptions\AuditionAdminException; use App\Http\Controllers\Controller; +use App\Models\Entry; use App\Models\PrelimDefinition; +use App\Models\PrelimScoreSheet; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; class PrelimJudgingController extends Controller { @@ -18,4 +24,48 @@ class PrelimJudgingController extends Controller return view('judging.prelim_entry_list', compact('prelimDefinition', 'entries', 'subscores', 'published')); } + + public function prelimScoreEntryForm(Entry $entry) + { + if (auth()->user()->cannot('judge', $entry->audition->prelimDefinition)) { + return redirect()->route('dashboard')->with('error', 'You are not assigned to judge that prelim audition.'); + } + if ($entry->audition->hasFlag('seats_published')) { + return redirect()->route('dashboard')->with('error', + 'Scores for entries in published auditions cannot be modified.'); + } + if ($entry->hasFlag('no_show')) { + return redirect()->route('judging.prelimEntryList', $entry->audition->prelimDefinition)->with('error', + 'The requested entry is marked as a no-show. Scores cannot be entered.'); + } + + $oldSheet = PrelimScoreSheet::where('user_id', Auth::id())->where('entry_id', + $entry->id)->value('subscores') ?? null; + + return view('judging.prelim_entry_form', compact('entry', 'oldSheet')); + } + + /** + * @throws AuditionAdminException + */ + public function savePrelimScoreSheet(Entry $entry, Request $request, EnterPrelimScore $scribe) + { + if (auth()->user()->cannot('judge', $entry->audition->prelimDefinition)) { + return redirect()->route('dashboard')->with('error', 'You are not assigned to judge that prelim audition.'); + } + + // Validate form data + $subscores = $entry->audition->prelimDefinition->scoringGuide->subscores; + $validationChecks = []; + foreach ($subscores as $subscore) { + $validationChecks['score'.'.'.$subscore->id] = 'required|integer|max:'.$subscore->max_score; + } + $validatedData = $request->validate($validationChecks); + + // Enter the score + $scribe(auth()->user(), $entry, $validatedData['score']); + + return redirect()->route('judging.prelimEntryList', $entry->audition->prelimDefinition)->with('success', + 'Entered prelim scores for '.$entry->audition->name.' '.$entry->draw_number); + } } diff --git a/resources/views/judging/prelim_entry_form.blade.php b/resources/views/judging/prelim_entry_form.blade.php new file mode 100644 index 0000000..58ca75b --- /dev/null +++ b/resources/views/judging/prelim_entry_form.blade.php @@ -0,0 +1,58 @@ + + @php + $oldScores = session()->get('oldScores') ?? null; + @endphp + + Prelim Score Entry + + + {{ $entry->audition->name }} {{ $entry->draw_number }} + +
    +
  • All Scores must be complete
  • +
  • You may enter zero
  • +
  • Whole numbers only
  • +
+
+
+ + @if($oldSheet) + {{-- if there are existing scores, make this a patch request --}} + @method('PATCH') + @endif + + @foreach($entry->audition->prelimDefinition->scoringGuide->subscores()->orderBy('display_order')->get() as $subscore) + @php + if($oldScores) { + $value = $oldScores['score'][$subscore->id]; + } elseif ($oldSheet) { + $value = $oldSheet[$subscore->id]['score']; + } else { + $value = ''; + } + @endphp + +
  • + + + {{ $subscore->name }} max: {{$subscore->max_score}} + + + +
  • + + @endforeach +
    + + Save Scores + +
    +
    +
    diff --git a/resources/views/judging/prelim_entry_list.blade.php b/resources/views/judging/prelim_entry_list.blade.php index 3c70d8a..c1feaa8 100644 --- a/resources/views/judging/prelim_entry_list.blade.php +++ b/resources/views/judging/prelim_entry_list.blade.php @@ -24,7 +24,7 @@ @if(! $published && ! $entry->hasFlag('no_show')) - + @endif {{ $prelimDefinition->audition->name }} {{ $entry->draw_number }} @if($entry->hasFlag('no_show')) diff --git a/routes/judging.php b/routes/judging.php index 0919583..6776fb7 100644 --- a/routes/judging.php +++ b/routes/judging.php @@ -20,6 +20,8 @@ Route::middleware(['auth', 'verified', CheckIfCanJudge::class])->prefix('judging // Prelim Audition Related Routes Route::middleware(['auth', 'verified', CheckIfCanJudge::class])->prefix('judging/prelims')->controller(PrelimJudgingController::class)->group(function () { Route::get('/{prelimDefinition}', 'prelimEntryList')->name('judging.prelimEntryList'); + route::get('/enterScore/{entry}', 'prelimScoreEntryForm')->name('judging.prelimScoreEntryForm'); + route::post('/enterScore/{entry}', 'savePrelimScoreSheet')->name('judging.savePrelimScoreSheet'); }); // Bonus score judging routes diff --git a/tests/Feature/app/Http/Controllers/Judging/PrelimJudgingControllerTest.php b/tests/Feature/app/Http/Controllers/Judging/PrelimJudgingControllerTest.php index 45b7416..7388f9b 100644 --- a/tests/Feature/app/Http/Controllers/Judging/PrelimJudgingControllerTest.php +++ b/tests/Feature/app/Http/Controllers/Judging/PrelimJudgingControllerTest.php @@ -4,7 +4,10 @@ use App\Actions\Draw\RunDraw; use App\Models\Audition; use App\Models\Entry; use App\Models\PrelimDefinition; +use App\Models\PrelimScoreSheet; use App\Models\Room; +use App\Models\ScoringGuide; +use App\Models\SubscoreDefinition; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -76,3 +79,118 @@ describe('PrelimJudgingController:prelimEntryList', function () { }); }); + +describe('PrelimJudgingController:prelimScoreEntryForm', function () { + beforeEach(function () { + $this->room = Room::factory()->create(); + $this->finalsRoom = Room::factory()->create(); + $this->scoringGuide = ScoringGuide::factory()->create(); + $this->audition = Audition::factory()->create(['room_id' => $this->finalsRoom->id]); + $this->prelimDefinition = PrelimDefinition::create([ + 'audition_id' => $this->audition->id, + 'room_id' => $this->room->id, + 'scoring_guide_id' => $this->scoringGuide->id, + 'passing_score' => 75, + ]); + $this->prelimJudge = User::factory()->create(['judging_preference' => 'Prelims']); + $this->finalsJudge = User::factory()->create(['judging_preference' => 'Finals']); + $this->room->addJudge($this->prelimJudge); + $this->finalsRoom->addJudge($this->finalsJudge); + $this->entry = Entry::factory()->create(['audition_id' => $this->audition->id]); + + }); + it('denies access to non-judges', function () { + actAsNormal(); + $entry = Entry::factory()->create(); + $response = $this->get(route('judging.prelimScoreEntryForm', $this->entry)); + $response->assertRedirect(route('dashboard')); + $response->assertSessionHas('error', 'You are not assigned to judge.'); + }); + + it('denies access if the judge is not assigned to the room', function () { + $this->actingAs($this->finalsJudge); + $response = $this->get(route('judging.prelimScoreEntryForm', $this->entry)); + $response->assertRedirect(route('dashboard')); + $response->assertSessionHas('error', 'You are not assigned to judge that prelim audition.'); + }); + + it('denies access if the audition is published', function () { + $this->actingAs($this->prelimJudge); + $this->entry->audition->addFlag('seats_published'); + $response = $this->get(route('judging.prelimScoreEntryForm', $this->entry)); + $response->assertRedirect(route('dashboard')); + $response->assertSessionHas('error', 'Scores for entries in published auditions cannot be modified.'); + }); + + it('denies access if the entry is flagged as a no-show', function () { + $this->entry->addFlag('no_show'); + $this->actingAs($this->prelimJudge); + $response = $this->get(route('judging.prelimScoreEntryForm', $this->entry)); + $response->assertRedirect(route('judging.prelimEntryList', $this->prelimDefinition)); + $response->assertSessionHas('error', 'The requested entry is marked as a no-show. Scores cannot be entered.'); + }); + + it('gives us a form to enter a score for an entry', function () { + $this->actingAs($this->prelimJudge); + $response = $this->get(route('judging.prelimScoreEntryForm', $this->entry)); + $response->assertOk(); + $response->assertDontSee($this->entry->student->last_name) + ->assertDontSee($this->entry->student->first_name); + foreach (SubscoreDefinition::all() as $subscore) { + $response->assertSee($subscore->name); + $response->assertSee('score['.$subscore->id.']'); + } + }); +}); + +describe('PrelimJudgingController:savePrelimEntryForm', function () { + beforeEach(function () { + $this->room = Room::factory()->create(); + $this->finalsRoom = Room::factory()->create(); + $this->scoringGuide = ScoringGuide::factory()->create(); + $this->audition = Audition::factory()->create(['room_id' => $this->finalsRoom->id]); + $this->prelimDefinition = PrelimDefinition::create([ + 'audition_id' => $this->audition->id, + 'room_id' => $this->room->id, + 'scoring_guide_id' => $this->scoringGuide->id, + 'passing_score' => 75, + ]); + $this->prelimJudge = User::factory()->create(['judging_preference' => 'Prelims']); + $this->finalsJudge = User::factory()->create(['judging_preference' => 'Finals']); + $this->room->addJudge($this->prelimJudge); + $this->finalsRoom->addJudge($this->finalsJudge); + $this->entry = Entry::factory()->create(['audition_id' => $this->audition->id]); + + }); + it('denies access to non-judges', function () { + actAsNormal(); + $response = $this->post(route('judging.savePrelimScoreSheet', $this->entry)); + $response->assertRedirect(route('dashboard')); + $response->assertSessionHas('error', 'You are not assigned to judge.'); + }); + + it('denies access if the judge is not assigned to the room', function () { + $this->actingAs($this->finalsJudge); + $response = $this->post(route('judging.savePrelimScoreSheet', $this->entry)); + $response->assertRedirect(route('dashboard')); + $response->assertSessionHas('error', 'You are not assigned to judge that prelim audition.'); + }); + + it('saves a score sheet', function () { + $subscoreIds = SubscoreDefinition::all()->pluck('id')->toArray(); + $submitData = [ + 'score' => [ + $subscoreIds[0] => 10, + $subscoreIds[1] => 20, + $subscoreIds[2] => 30, + $subscoreIds[3] => 40, + $subscoreIds[4] => 50, + ], + ]; + $this->actingAs($this->prelimJudge); + $response = $this->post(route('judging.savePrelimScoreSheet', $this->entry), $submitData); + $response->assertRedirect(route('judging.prelimEntryList', $this->prelimDefinition)); + $response->assertSessionHas('success'); + expect(PrelimScoreSheet::where('entry_id', $this->entry->id)->count())->toBe(1); + }); +});