Implement judge pass fail advancement #4

Merged
okorpheus merged 3 commits from ipmlement-judge-pass-fail-advancement into master 2024-06-27 05:54:59 +00:00
8 changed files with 195 additions and 43 deletions
Showing only changes of commit 0203505f5b - Show all commits

View File

@ -4,12 +4,13 @@ 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\ScoreSheet; use App\Models\ScoreSheet;
use App\Models\SubscoreDefinition;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use function compact; use function compact;
use function redirect; use function redirect;
use function url; use function url;
@ -19,77 +20,107 @@ class JudgingController extends Controller
public function index() public function index()
{ {
$rooms = Auth::user()->judgingAssignments; $rooms = Auth::user()->judgingAssignments;
return view('judging.index', compact('rooms')); return view('judging.index', compact('rooms'));
} }
public function auditionEntryList(Audition $audition) public function auditionEntryList(Audition $audition)
{ {
// TODO verify user is assigned to judge this audition // TODO verify user is assigned to judge this audition
$entries = Entry::where('audition_id','=',$audition->id)->orderBy('draw_number')->with('audition')->get(); $entries = Entry::where('audition_id', '=', $audition->id)->orderBy('draw_number')->with('audition')->get();
$subscores = $audition->scoringGuide->subscores()->orderBy('display_order')->get(); $subscores = $audition->scoringGuide->subscores()->orderBy('display_order')->get();
return view('judging.audition_entry_list', compact('audition','entries','subscores'));
return view('judging.audition_entry_list', compact('audition', 'entries', 'subscores'));
} }
public function entryScoreSheet(Entry $entry) public function entryScoreSheet(Entry $entry)
{ {
// TODO verify user is assigned to judge this audition // TODO verify user is assigned to judge this audition
$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;
return view('judging.entry_score_sheet',compact('entry','oldSheet')); $oldVote = JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->first();
$oldVote = $oldVote ? $oldVote->vote : 'novote';
return view('judging.entry_score_sheet', compact('entry', 'oldSheet', 'oldVote'));
} }
public function saveScoreSheet(Request $request, Entry $entry) public function saveScoreSheet(Request $request, Entry $entry)
{ {
Gate::authorize('create',[ScoreSheet::class,$entry]); Gate::authorize('create', [ScoreSheet::class, $entry]);
// TODO verify user is assigned to judge this audition // TODO verify user is assigned to judge this audition
$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') {
return redirect(url()->previous())->with('error', $scoreValidation)->with('oldScores',$request->all()); return redirect(url()->previous())->with('error', $scoreValidation)->with('oldScores', $request->all());
} }
$scoreSheetArray = []; $scoreSheetArray = [];
foreach($scoringGuide->subscores as $subscore) { foreach ($scoringGuide->subscores as $subscore) {
$scoreSheetArray[$subscore->id] = [ $scoreSheetArray[$subscore->id] = [
'score' => $request->input('score')[$subscore->id], 'score' => $request->input('score')[$subscore->id],
'subscore_id' => $subscore->id, 'subscore_id' => $subscore->id,
'subscore_name' => $subscore->name 'subscore_name' => $subscore->name,
]; ];
} }
ScoreSheet::create([ ScoreSheet::create([
'user_id' => Auth::user()->id, 'user_id' => Auth::user()->id,
'entry_id' => $entry->id, 'entry_id' => $entry->id,
'subscores' => $scoreSheetArray 'subscores' => $scoreSheetArray,
]); ]);
return redirect('/judging/audition/' . $entry->audition_id)->with('success','Entered scores for ' . $entry->audition->name . ' ' . $entry->draw_number); $this->advancementVote($request, $entry);
return redirect('/judging/audition/'.$entry->audition_id)->with('success', 'Entered scores for '.$entry->audition->name.' '.$entry->draw_number);
} }
public function updateScoreSheet(Request $request, Entry $entry) public function updateScoreSheet(Request $request, Entry $entry)
{ {
$scoreSheet = ScoreSheet::where('user_id',Auth::id())->where('entry_id',$entry->id)->first(); $scoreSheet = ScoreSheet::where('user_id', Auth::id())->where('entry_id', $entry->id)->first();
if (!$scoreSheet) return redirect()->back()->with('error','Attempt to edit non existent entry'); if (! $scoreSheet) {
Gate::authorize('update',$scoreSheet); return redirect()->back()->with('error', 'Attempt to edit non existent entry');
}
Gate::authorize('update', $scoreSheet);
$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') {
return redirect(url()->previous())->with('error', $scoreValidation)->with('oldScores',$request->all()); return redirect(url()->previous())->with('error', $scoreValidation)->with('oldScores', $request->all());
} }
$scoreSheetArray = []; $scoreSheetArray = [];
foreach($scoringGuide->subscores as $subscore) { foreach ($scoringGuide->subscores as $subscore) {
$scoreSheetArray[$subscore->id] = [ $scoreSheetArray[$subscore->id] = [
'score' => $request->input('score')[$subscore->id], 'score' => $request->input('score')[$subscore->id],
'subscore_id' => $subscore->id, 'subscore_id' => $subscore->id,
'subscore_name' => $subscore->name 'subscore_name' => $subscore->name,
]; ];
} }
$scoreSheet->update([ $scoreSheet->update([
'subscores' => $scoreSheetArray 'subscores' => $scoreSheetArray,
]); ]);
return redirect('/judging/audition/' . $entry->audition_id)->with('success','Updated scores for ' . $entry->audition->name . ' ' . $entry->draw_number);
$this->advancementVote($request, $entry);
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)
{
if ($entry->for_advancement) {
$request->validate([
'advancement-vote' => ['required', 'in:yes,no,dq'],
]);
try {
JudgeAdvancementVote::where('user_id', Auth::id())->where('entry_id', $entry->id)->delete();
JudgeAdvancementVote::create([
'user_id' => Auth::user()->id,
'entry_id' => $entry->id,
'vote' => $request->input('advancement-vote'),
]);
} catch (\Exception $e) {
return redirect(url()->previous())->with('error', 'Error saving advancement vote');
}
}
}
} }

View File

@ -51,6 +51,11 @@ class Entry extends Model
} }
public function advancementVotes(): HasMany
{
return $this->hasMany(JudgeAdvancementVote::class);
}
public function flags(): HasMany public function flags(): HasMany
{ {
return $this->hasMany(EntryFlag::class); return $this->hasMany(EntryFlag::class);

View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class JudgeAdvancementVote extends Model
{
use HasFactory;
protected $guarded = [];
public function entry(): BelongsTo
{
return $this->belongsTo(Entry::class);
}
public function judge(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@ -2,7 +2,6 @@
namespace App\Models; namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -11,7 +10,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use phpDocumentor\Reflection\Types\Boolean;
class User extends Authenticatable implements MustVerifyEmail class User extends Authenticatable implements MustVerifyEmail
{ {
@ -30,7 +28,7 @@ class User extends Authenticatable implements MustVerifyEmail
'email', 'email',
'password', 'password',
'profile_image_url', 'profile_image_url',
'school_id' 'school_id',
]; ];
/** /**
@ -56,16 +54,19 @@ class User extends Authenticatable implements MustVerifyEmail
]; ];
} }
public function full_name(Bool $last_name_first = false): String public function full_name(bool $last_name_first = false): string
{ {
if ($last_name_first) return $this->last_name . ', ' . $this->first_name; if ($last_name_first) {
return $this->first_name . ' ' . $this->last_name; return $this->last_name.', '.$this->first_name;
} }
public function short_name(): String return $this->first_name.' '.$this->last_name;
}
public function short_name(): string
{ {
// return the first letter of $this->first_name and the full $this->last_name // return the first letter of $this->first_name and the full $this->last_name
return $this->first_name[0] . '. ' . $this->last_name; return $this->first_name[0].'. '.$this->last_name;
} }
public function has_school(): bool public function has_school(): bool
@ -76,7 +77,8 @@ class User extends Authenticatable implements MustVerifyEmail
public function emailDomain(): string public function emailDomain(): string
{ {
$pos = strpos($this->email, '@'); $pos = strpos($this->email, '@');
return substr($this->email, $pos+1);
return substr($this->email, $pos + 1);
} }
public function school(): BelongsTo public function school(): BelongsTo
@ -87,7 +89,7 @@ class User extends Authenticatable implements MustVerifyEmail
public function students(): HasManyThrough public function students(): HasManyThrough
{ {
return $this return $this
->hasManyThrough(Student::class, School::class, 'id','school_id','school_id','id') ->hasManyThrough(Student::class, School::class, 'id', 'school_id', 'school_id', 'id')
->orderBy('last_name') ->orderBy('last_name')
->orderBy('first_name'); ->orderBy('first_name');
} }
@ -106,7 +108,7 @@ class User extends Authenticatable implements MustVerifyEmail
public function rooms(): BelongsToMany public function rooms(): BelongsToMany
{ {
return $this->belongsToMany(Room::class,'room_user'); return $this->belongsToMany(Room::class, 'room_user');
} }
public function judgingAssignments(): BelongsToMany public function judgingAssignments(): BelongsToMany
@ -114,26 +116,38 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->rooms(); return $this->rooms();
} }
public function isJudge(): Bool public function advancementVotes(): HasMany
{
return $this->hasMany(JudgeAdvancementVote::class);
}
public function isJudge(): bool
{ {
return $this->judgingAssignments->count() > 0; return $this->judgingAssignments->count() > 0;
} }
/** /**
* Return an array of schools using the users email domain * Return an array of schools using the users email domain
*
* @return SchoolEmailDomain[] * @return SchoolEmailDomain[]
*/ */
public function possibleSchools() public function possibleSchools()
{ {
if ($this->school_id) { if ($this->school_id) {
$return[] = $this->school; $return[] = $this->school;
return $return; return $return;
} }
return SchoolEmailDomain::with('school')->where('domain','=',$this->emailDomain())->get();
return SchoolEmailDomain::with('school')->where('domain', '=', $this->emailDomain())->get();
}
public function canTab()
{
if ($this->is_admin) {
return true;
} }
public function canTab() {
if ($this->is_admin) return true;
return $this->is_tab; return $this->is_tab;
} }
@ -145,12 +159,12 @@ class User extends Authenticatable implements MustVerifyEmail
public function scoresForEntry($entry) public function scoresForEntry($entry)
{ {
// TODO Again, why is this here? Needs to go somewhere else. Maybe a Judging service // TODO Again, why is this here? Needs to go somewhere else. Maybe a Judging service
return $this->scoreSheets->where('entry_id','=',$entry)->first()?->subscores; return $this->scoreSheets->where('entry_id', '=', $entry)->first()?->subscores;
} }
public function timeForEntryScores($entry) public function timeForEntryScores($entry)
{ {
// TODO Why is this in the User mode? Move it somewhere else // TODO Why is this in the User mode? Move it somewhere else
return $this->scoreSheets->where('entry_id','=',$entry)->first()?->created_at; return $this->scoreSheets->where('entry_id', '=', $entry)->first()?->created_at;
} }
} }

View File

@ -0,0 +1,33 @@
<?php
use App\Models\Entry;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('judge_advancement_votes', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignIdFor(Entry::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->string('vote');
$table->unique(['user_id', 'entry_id']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('judge_advancement_votes');
}
};

View File

@ -0,0 +1,37 @@
<fieldset>
<input type="hidden" name="require-vote" value="true">
<legend class="text-sm font-semibold leading-6 text-gray-900">{{ auditionSetting('advanceTo') }} Advancement </legend>
<p class="mt-1 text-sm leading-6 text-gray-600">Only choose DQ if a rule of some kinds was broken</p>
<div class="mt-6 space-y-6 sm:flex sm:items-center sm:space-x-10 sm:space-y-0">
<div class="flex items-center">
<input id="advance-yes"
name="advancement-vote"
type="radio"
value="yes"
@if($oldVote === 'yes') checked @endif
class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600">
<label for="advance-yes" class="ml-3 block text-sm font-medium leading-6 text-gray-900">Yes</label>
</div>
<div class="flex items-center">
<input id="advance-no"
name="advancement-vote"
type="radio"
value="no"
@if($oldVote === 'no') checked @endif
class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600">
<label for="advance-no" class="ml-3 block text-sm font-medium leading-6 text-gray-900">No</label>
</div>
<div class="flex items-center">
<input id="advance-dq"
name="advancement-vote"
type="radio"
value="dq"
@if($oldVote === 'dq') checked @endif
class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600">
<label for="advance-dq" class="ml-3 block text-sm font-medium leading-6 text-gray-900">DQ</label>
</div>
</div>
@error('advancement-vote')
<p class="text-xs text-red-500 font-semibold mt-1 ml-3">{{ $message }}</p>
@enderror
</fieldset>

View File

@ -1,6 +1,9 @@
<x-layout.app> <x-layout.app>
{{-- TODO A user should only be able to get this form for an entry they're actually assigned to judge--}}
@php @php
$oldScores = session()->get('oldScores') ?? null; $oldScores = session()->get('oldScores') ?? null;
// TODO get old vote
@endphp @endphp
<x-slot:page_title>Entry Dashboard</x-slot:page_title> <x-slot:page_title>Entry Dashboard</x-slot:page_title>
@ -15,7 +18,7 @@
</ul> </ul>
</x-slot:subheading> </x-slot:subheading>
</x-card.heading> </x-card.heading>
<x-form.form metohd="POST" action="/judging/entry/{{$entry->id}}"> <x-form.form metohd="POST" action="{{ route('judging.saveScoreSheet',['entry' => $entry->id]) }}">
@if($oldSheet) {{-- if there are existing sores, make this a patch request --}} @if($oldSheet) {{-- if there are existing sores, make this a patch request --}}
@method('PATCH') @method('PATCH')
@endif @endif
@ -46,6 +49,10 @@
</li> </li>
@endforeach @endforeach
@if($entry->for_advancement)
@include('judging.advancement-vote-form')
@endif
</x-card.list.body> </x-card.list.body>
<x-form.footer><x-form.button class="mb-5">Save Scores</x-form.button></x-form.footer> <x-form.footer><x-form.button class="mb-5">Save Scores</x-form.button></x-form.footer>

View File

@ -5,9 +5,9 @@ use App\Http\Middleware\CheckIfCanJudge;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'verified', CheckIfCanJudge::class])->prefix('judging')->controller(JudgingController::class)->group(function () { Route::middleware(['auth', 'verified', CheckIfCanJudge::class])->prefix('judging')->controller(JudgingController::class)->group(function () {
Route::get('/', 'index'); Route::get('/', 'index')->name('judging.index');
Route::get('/audition/{audition}', 'auditionEntryList'); Route::get('/audition/{audition}', 'auditionEntryList')->name('judging.auditionEntryList');
Route::get('/entry/{entry}', 'entryScoreSheet'); Route::get('/entry/{entry}', 'entryScoreSheet')->name('judging.entryScoreSheet');
Route::post('/entry/{entry}', 'saveScoreSheet'); Route::post('/entry/{entry}', 'saveScoreSheet')->name('judging.saveScoreSheet');
Route::patch('/entry/{entry}', 'updateScoreSheet'); Route::patch('/entry/{entry}', 'updateScoreSheet')->name('judging.updateScoreSheet');
}); });