Tabulator Score Entry checks published status

This commit is contained in:
Matt Young 2024-07-08 15:46:44 -05:00
parent a0913861fc
commit 53529fe0e9
6 changed files with 200 additions and 7 deletions

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Models\Audition; use App\Models\Audition;
use App\Models\Entry; use App\Models\Entry;
use App\Models\JudgeAdvancementVote; use App\Models\JudgeAdvancementVote;
use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
@ -39,15 +40,23 @@ class JudgingController extends Controller
public function entryScoreSheet(Request $request, Entry $entry) public function entryScoreSheet(Request $request, Entry $entry)
{ {
// Turn away users not assigned to judge this entry
if ($request->user()->cannot('judge', $entry->audition)) { if ($request->user()->cannot('judge', $entry->audition)) {
return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this entry'); return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this entry');
} }
// Turn away users if the audition is published
if ($entry->audition->hasFlag('seats_published') || $entry->audition->hasFlag('advancement_published')) { if ($entry->audition->hasFlag('seats_published') || $entry->audition->hasFlag('advancement_published')) {
return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error', 'Scores for entries in published auditions cannot be modified'); return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error',
'Scores for entries in published auditions cannot be modified');
}
// Turn away users if the entry is flagged as a no-show
if ($entry->hasFlag('no_show')) {
return redirect()->route('judging.auditionEntryList', $entry->audition)->with('error',
'The requested entry is marked as a no-show. Scores cannot be entered.');
} }
$oldSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->value('subscores') ?? null; $oldSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->value('subscores') ?? null;
$oldVote = JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->first(); $oldVote = JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->first();
$oldVote = $oldVote ? $oldVote->vote : 'novote'; $oldVote = $oldVote ? $oldVote->vote : 'noVote';
return view('judging.entry_score_sheet', compact('entry', 'oldSheet', 'oldVote')); return view('judging.entry_score_sheet', compact('entry', 'oldSheet', 'oldVote'));
} }
@ -57,6 +66,7 @@ class JudgingController extends Controller
if ($request->user()->cannot('judge', $entry->audition)) { if ($request->user()->cannot('judge', $entry->audition)) {
abort(403, 'You are not assigned to judge this entry'); abort(403, 'You are not assigned to judge this entry');
} }
// TODO extract to a service class
$scoringGuide = $entry->audition->scoringGuide()->with('subscores')->first(); $scoringGuide = $entry->audition->scoringGuide()->with('subscores')->first();
$scoreValidation = $scoringGuide->validateScores($request->input('score')); $scoreValidation = $scoringGuide->validateScores($request->input('score'));
if ($scoreValidation != 'success') { if ($scoreValidation != 'success') {
@ -79,7 +89,8 @@ class JudgingController extends Controller
$this->advancementVote($request, $entry); $this->advancementVote($request, $entry);
return redirect('/judging/audition/'.$entry->audition_id)->with('success', 'Entered scores for '.$entry->audition->name.' '.$entry->draw_number); return redirect('/judging/audition/'.$entry->audition_id)->with('success',
'Entered scores for '.$entry->audition->name.' '.$entry->draw_number);
} }
@ -114,7 +125,8 @@ class JudgingController extends Controller
$this->advancementVote($request, $entry); $this->advancementVote($request, $entry);
return redirect('/judging/audition/'.$entry->audition_id)->with('success', 'Updated scores for '.$entry->audition->name.' '.$entry->draw_number); return redirect('/judging/audition/'.$entry->audition_id)->with('success',
'Updated scores for '.$entry->audition->name.' '.$entry->draw_number);
} }
protected function advancementVote(Request $request, Entry $entry) protected function advancementVote(Request $request, Entry $entry)
@ -134,9 +146,10 @@ class JudgingController extends Controller
'entry_id' => $entry->id, 'entry_id' => $entry->id,
'vote' => $request->input('advancement-vote'), 'vote' => $request->input('advancement-vote'),
]); ]);
} catch (\Exception $e) { } catch (Exception) {
return redirect(url()->previous())->with('error', 'Error saving advancement vote'); return redirect(url()->previous())->with('error', 'Error saving advancement vote');
} }
} }
return null;
} }
} }

View File

@ -10,7 +10,7 @@ use Tests\Feature\Models\ScoreSheet;
class ScoreController extends Controller class ScoreController extends Controller
{ {
public function chooseEntry(Request $request) public function chooseEntry()
{ {
$method = 'GET'; $method = 'GET';
$formRoute = 'scores.entryScoreSheet'; $formRoute = 'scores.entryScoreSheet';
@ -29,6 +29,12 @@ class ScoreController extends Controller
{ {
$existing_sheets = []; $existing_sheets = [];
$entry = Entry::with(['student', 'audition.room.judges'])->find($request->input('entry_id')); $entry = Entry::with(['student', 'audition.room.judges'])->find($request->input('entry_id'));
$publishedCheck = $this->checkIfPublished($entry);
if ($publishedCheck) {
return $publishedCheck;
}
$judges = $entry->audition->room->judges; $judges = $entry->audition->room->judges;
foreach ($judges as $judge) { foreach ($judges as $judge) {
$scoreSheet = ScoreSheet::where('entry_id', $entry->id)->where('user_id', $judge->id)->first(); $scoreSheet = ScoreSheet::where('entry_id', $entry->id)->where('user_id', $judge->id)->first();
@ -49,6 +55,11 @@ class ScoreController extends Controller
public function saveEntryScoreSheet(Request $request, Entry $entry) public function saveEntryScoreSheet(Request $request, Entry $entry)
{ {
$publishedCheck = $this->checkIfPublished($entry);
if ($publishedCheck) {
return $publishedCheck;
}
$judges = $entry->audition->room->judges; $judges = $entry->audition->room->judges;
$subscores = $entry->audition->scoringGuide->subscores->sortBy('tiebreak_order'); $subscores = $entry->audition->scoringGuide->subscores->sortBy('tiebreak_order');
@ -84,4 +95,20 @@ class ScoreController extends Controller
return redirect()->route('scores.chooseEntry')->with('success', count($preparedScoreSheets).' Scores saved'); return redirect()->route('scores.chooseEntry')->with('success', count($preparedScoreSheets).' Scores saved');
} }
protected function checkIfPublished($entry)
{
// We're not going to enter scores if seats are published
if ($entry->audition->hasFlag('seats_published')) {
return to_route('scores.chooseEntry')->with('error',
'Cannot enter scores for entry '.$entry->id.'. '.$entry->audition->name.' seats are published');
}
// Nope, not if advancement is published either
if ($entry->audition->hasFlag('advancement_published')) {
return to_route('scores.chooseEntry')->with('error',
'Cannot enter scores for entry '.$entry->id.'. '.$entry->audition->name.' advancement is published');
}
return false;
}
} }

View File

@ -11,7 +11,7 @@
</x-slot:right_side> </x-slot:right_side>
</x-card.heading> </x-card.heading>
<x-form.form method="POST" action="{{ route('scores.saveEntryScoreSheet',[ 'entry' => $entry->id]) }}"> <x-form.form method="POST" id='scoreForm' action="{{ route('scores.saveEntryScoreSheet',[ 'entry' => $entry->id]) }}">
<x-table.table> <x-table.table>
<thead> <thead>
<tr> <tr>

View File

@ -141,3 +141,12 @@ it('redirects if advancement is published', function () {
->assertSessionHas('error', 'Scores for entries in published auditions cannot be modified'); ->assertSessionHas('error', 'Scores for entries in published auditions cannot be modified');
}); });
it('will not show a score sheet for an entry marked as no_show', function () {
// Arrange
$this->entries->first()->addFlag('no_show');
$this->actingAs($this->user);
// Act & Assert
$response = $this->get(route('judging.entryScoreSheet', $this->entries->first()));
$response->assertRedirect(route('judging.auditionEntryList', $this->entries[0]->audition))
->assertSessionHas('error', 'The requested entry is marked as a no-show. Scores cannot be entered.');
});

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Foundation\Testing\RefreshDatabase;
use Sinnbeck\DomAssertions\Asserts\AssertElement;
use Sinnbeck\DomAssertions\Asserts\AssertForm;
use function Pest\Laravel\get;
uses(RefreshDatabase::class);
it('responds to only admin and tab users', function () {
get(route('scores.chooseEntry'))
->assertRedirect(route('home'));
actAsAdmin();
get(route('scores.chooseEntry'))
->assertOk();
actAsTab();
get(route('scores.chooseEntry'))
->assertOk();
actAsNormal();
get(route('scores.chooseEntry'))
->assertRedirect(route('dashboard'))
->assertSessionHas('error', 'You are not authorized to perform this action');
});
it('has an input for entry_id', function () {
actAsAdmin();
get(route('scores.chooseEntry'))
->assertOk()
->assertElementExists('#entry_id', function (AssertElement $element) {
$element->is('input');
});
});
it('submits to entry-flags.confirmNoShow', function () {
actAsAdmin();
get(route('scores.chooseEntry'))
->assertOk()
->assertFormExists('#entry-select-form', function (AssertForm $form) {
$form->hasMethod('GET')
->hasAction(route('scores.entryScoreSheet'));
});
});

View File

@ -0,0 +1,103 @@
<?php
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Event;
use App\Models\Room;
use App\Models\ScoringGuide;
use App\Models\SubscoreDefinition;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Sinnbeck\DomAssertions\Asserts\AssertForm;
use function Pest\Laravel\get;
uses(RefreshDatabase::class);
function testData(): array
{
$data['judge'] = User::factory()->create();
$data['room'] = Room::factory()->create();
$data['room']->addJudge($data['judge']);
$data['event'] = Event::factory()->create();
$data['scoringGuide'] = ScoringGuide::factory()->create();
$data['subscores'] = SubscoreDefinition::factory()->count(3)->create([
'scoring_guide_id' => $data['scoringGuide']->id,
]);
$data['audition'] = Audition::factory()->create([
'room_id' => $data['room']->id,
'event_id' => $data['event']->id,
'scoring_guide_id' => $data['scoringGuide']->id,
]);
$data['entry'] = Entry::factory()->create([
'audition_id' => $data['audition']->id,
]);
return $data;
}
/** @noinspection PhpMissingReturnTypeInspection */
function validRequest(array $data = [])
{
if (! $data) {
$data = testData();
}
actAsTab();
return get(route('scores.entryScoreSheet', ['entry_id' => $data['entry']->id]));
}
it('does not allow guests or normal users to access the admin score form', function () {
get(route('scores.entryScoreSheet'))->assertRedirect(route('home'));
actAsNormal();
get(route('scores.entryScoreSheet'))->assertRedirect(route('dashboard'))->assertSessionHas('error',
'You are not authorized to perform this action');
});
it('grants an admin or tab user access', function () {
// Arrange
$response = validRequest();
// Act & Assert
$response->assertOk();
});
it('has a form with field for each subscore for each judge', function () {
// Arrange
$data = testData();
$response = validRequest($data);
// Act & Assert
$response->assertOk();
$fieldsToCheck = [];
foreach ($data['subscores'] as $subscore) {
$fieldsToCheck[] = 'j'.$data['judge']->id.'ss'.$subscore->id;
}
/** @noinspection PhpExpressionResultUnusedInspection */
$response->assertFormExists('#scoreForm', function (AssertForm $form) use ($fieldsToCheck) {
$form->hasCSRF();
$form->hasMethod('POST');
$form->hasAction(route('scores.saveEntryScoreSheet', ['entry' => 1]));
foreach ($fieldsToCheck as $field) {
$form->contains('#'.$field);
}
});
});
it('will not accept scores for an entry in a an audition with published seats', function () {
// Arrange
$data = testData();
$data['audition']->addFlag('seats_published');
$response = validRequest($data);
// Act & Assert
$expectedError = 'Cannot enter scores for entry '.$data['entry']->id.'. '.$data['entry']->audition->name.' seats are published';
$response
->assertRedirect(route('scores.chooseEntry'))
->assertSessionHas('error', $expectedError);
});
it('will not accept scores for an entry in an audition with published advancement', function () {
// Arrange
$data = testData();
$data['audition']->addFlag('advancement_published');
$response = validRequest($data);
// Act & Assert
$expectedError = 'Cannot enter scores for entry '.$data['entry']->id.'. '.$data['entry']->audition->name.' advancement is published';
$response
->assertRedirect(route('scores.chooseEntry'))
->assertSessionHas('error', $expectedError);
});