Merge pull request #85 from okorpheus/auditionadmin-68

Auditionadmin 68
Closes #68
This commit is contained in:
Matt 2024-10-31 12:22:48 -05:00 committed by GitHub
commit daedc05646
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 208 additions and 6 deletions

View File

@ -0,0 +1,40 @@
<?php
namespace App\Actions\Entries;
use App\Models\Entry;
use App\Models\Seat;
class GetEntrySeatingResult
{
public function __construct()
{
}
public function __invoke(Entry $entry): string
{
return $this->getResult($entry);
}
public function getResult(Entry $entry): string
{
if ($entry->hasFlag('no_show')) {
return 'No Show';
}
if ($entry->hasFlag('declined')) {
return 'Declined';
}
if ($entry->hasFlag('failed_prelim')) {
return 'Did not pass prelim';
}
$seat = Seat::where('entry_id', $entry->id)->first();
if ($seat) {
return $seat->ensemble->name.' '.$seat->seat;
}
return 'Entry not seated';
}
}

View File

@ -6,6 +6,7 @@ namespace App\Actions\Tabulation;
use App\Exceptions\TabulationException; use App\Exceptions\TabulationException;
use App\Models\BonusScore; use App\Models\BonusScore;
use App\Models\CalculatedScore;
use App\Models\Entry; use App\Models\Entry;
use App\Services\AuditionService; use App\Services\AuditionService;
use App\Services\EntryService; use App\Services\EntryService;
@ -34,6 +35,10 @@ class AllowForOlympicScoring implements CalculateEntryScore
public function calculate(string $mode, Entry $entry): array public function calculate(string $mode, Entry $entry): array
{ {
$calculated = CalculatedScore::where('entry_id', $entry->id)->where('mode', $mode)->first();
if ($calculated) {
return $calculated->calculatedScore;
}
$cacheKey = 'entryScore-'.$entry->id.'-'.$mode; $cacheKey = 'entryScore-'.$entry->id.'-'.$mode;
@ -42,8 +47,15 @@ class AllowForOlympicScoring implements CalculateEntryScore
$this->isEntryANoShow($entry); $this->isEntryANoShow($entry);
$this->areAllJudgesIn($entry); $this->areAllJudgesIn($entry);
$this->areAllJudgesValid($entry); $this->areAllJudgesValid($entry);
$calculatedScores = $this->getJudgeTotals($mode, $entry);
CalculatedScore::create([
'entry_id' => $entry->id,
'mode' => $mode,
'calculatedScore' => $calculatedScores,
]);
return $this->getJudgeTotals($mode, $entry); return $calculatedScores;
// return $this->getJudgeTotals($mode, $entry);
}); });
} }

View File

@ -6,6 +6,7 @@ namespace App\Actions\Tabulation;
use App\Exceptions\ScoreEntryException; use App\Exceptions\ScoreEntryException;
use App\Models\BonusScore; use App\Models\BonusScore;
use App\Models\CalculatedScore;
use App\Models\Entry; use App\Models\Entry;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -27,6 +28,8 @@ class EnterBonusScore
// Create the score for each related entry // Create the score for each related entry
foreach ($entries as $relatedEntry) { foreach ($entries as $relatedEntry) {
// Also delete any cached scores
CalculatedScore::where('entry_id', $relatedEntry->id)->delete();
BonusScore::create([ BonusScore::create([
'entry_id' => $relatedEntry->id, 'entry_id' => $relatedEntry->id,
'user_id' => $judge->id, 'user_id' => $judge->id,

View File

@ -7,6 +7,7 @@
namespace App\Actions\Tabulation; namespace App\Actions\Tabulation;
use App\Exceptions\ScoreEntryException; use App\Exceptions\ScoreEntryException;
use App\Models\CalculatedScore;
use App\Models\Entry; use App\Models\Entry;
use App\Models\ScoreSheet; use App\Models\ScoreSheet;
use App\Models\User; use App\Models\User;
@ -24,6 +25,7 @@ class EnterScore
*/ */
public function __invoke(User $user, Entry $entry, array $scores, ScoreSheet|false $scoreSheet = false): ScoreSheet public function __invoke(User $user, Entry $entry, array $scores, ScoreSheet|false $scoreSheet = false): ScoreSheet
{ {
CalculatedScore::where('entry_id', $entry->id)->delete();
$scores = collect($scores); $scores = collect($scores);
$this->basicChecks($user, $entry, $scores); $this->basicChecks($user, $entry, $scores);
$this->checkJudgeAssignment($user, $entry); $this->checkJudgeAssignment($user, $entry);

View File

@ -66,8 +66,10 @@ class RankAuditionEntries
return 0; return 0;
}); });
$rank = 1; $rank = 1;
$rawRank = 1;
foreach ($entries as $entry) { foreach ($entries as $entry) {
$entry->rank = $rank; $entry->rank = $rank;
$entry->raw_rank = $rawRank;
// We don't really get a rank for seating if we have certain flags // We don't really get a rank for seating if we have certain flags
if ($mode === 'seating') { if ($mode === 'seating') {
if ($entry->hasFlag('declined')) { if ($entry->hasFlag('declined')) {
@ -82,6 +84,7 @@ class RankAuditionEntries
if (is_numeric($entry->rank)) { if (is_numeric($entry->rank)) {
$rank++; $rank++;
} }
$rawRank++;
} }
return $entries; return $entries;

View File

@ -2,6 +2,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Entries\GetEntrySeatingResult;
use App\Actions\Tabulation\CalculateEntryScore;
use App\Actions\Tabulation\RankAuditionEntries;
use App\Models\School; use App\Models\School;
use App\Services\Invoice\InvoiceDataService; use App\Services\Invoice\InvoiceDataService;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -22,9 +25,31 @@ class DashboardController extends Controller
return view('dashboard.profile'); return view('dashboard.profile');
} }
public function dashboard() public function dashboard(
{ CalculateEntryScore $scoreCalc,
return view('dashboard.dashboard'); GetEntrySeatingResult $resultGenerator,
RankAuditionEntries $ranker
) {
$entries = Auth::user()->entries;
$entries = $entries->filter(function ($entry) {
return $entry->audition->hasFlag('seats_published');
});
$entries = $entries->sortBy(function ($entry) {
return $entry->student->full_name(true);
});
$scores = [];
$results = [];
$ranks = [];
foreach ($entries as $entry) {
$results[$entry->id] = $resultGenerator->getResult($entry);
if (! $entry->hasFlag('no_show') && ! $entry->hasFlag('failed_prelim')) {
$scores[$entry->id] = $scoreCalc->calculate('seating', $entry);
$auditionResults = $ranker->rank('seating', $entry->audition);
$ranks[$entry->id] = $auditionResults->firstWhere('id', $entry->id)->raw_rank;
}
}
return view('dashboard.dashboard', compact('entries', 'scores', 'results', 'ranks'));
} }
public function my_school() public function my_school()

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\Tabulation; namespace App\Http\Controllers\Tabulation;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\CalculatedScore;
use App\Models\Entry; use App\Models\Entry;
use App\Models\ScoreSheet;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -79,7 +81,8 @@ class EntryFlagController extends Controller
DB::table('score_sheets')->where('entry_id', $entry->id)->delete(); DB::table('score_sheets')->where('entry_id', $entry->id)->delete();
$entry->addFlag('no_show'); $entry->addFlag('no_show');
ScoreSheet::where('entry_id', $entry->id)->delete();
CalculatedScore::where('entry_id', $entry->id)->delete();
$msg = 'No Show has been entered for '.$entry->audition->name.' #'.$entry->draw_number.' (ID: '.$entry->id.').'; $msg = 'No Show has been entered for '.$entry->audition->name.' #'.$entry->draw_number.' (ID: '.$entry->id.').';
return to_route('entry-flags.noShowSelect')->with('success', $msg); return to_route('entry-flags.noShowSelect')->with('success', $msg);

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Tabulation; namespace App\Http\Controllers\Tabulation;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\CalculatedScore;
use App\Models\Entry; use App\Models\Entry;
use App\Models\ScoreSheet; use App\Models\ScoreSheet;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -21,6 +22,7 @@ class ScoreController extends Controller
public function destroyScore(ScoreSheet $score) public function destroyScore(ScoreSheet $score)
{ {
CalculatedScore::where('entry_id', $score->entry_id)->delete();
if ($score->entry->audition->hasFlag('seats_published')) { if ($score->entry->audition->hasFlag('seats_published')) {
return redirect()->back()->with('error', 'Cannot delete scores for an entry where seats are published'); return redirect()->back()->with('error', 'Cannot delete scores for an entry where seats are published');
} }
@ -66,6 +68,7 @@ class ScoreController extends Controller
public function saveEntryScoreSheet(Request $request, Entry $entry) public function saveEntryScoreSheet(Request $request, Entry $entry)
{ {
CalculatedScore::where('entry_id', $entry->id)->delete();
$publishedCheck = $this->checkIfPublished($entry); $publishedCheck = $this->checkIfPublished($entry);
if ($publishedCheck) { if ($publishedCheck) {
return $publishedCheck; return $publishedCheck;
@ -105,6 +108,7 @@ class ScoreController extends Controller
['subscores' => $sheet['scores']] ['subscores' => $sheet['scores']]
); );
} }
// TODO rewrite to use EnterScore action or clear score cache
return redirect()->route('scores.chooseEntry')->with('success', count($preparedScoreSheets).' Scores saved'); return redirect()->route('scores.chooseEntry')->with('success', count($preparedScoreSheets).' Scores saved');
} }

View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CalculatedScore extends Model
{
use HasFactory;
protected $guarded = [];
protected $casts = ['calculatedScore' => 'json'];
}

View File

@ -121,6 +121,11 @@ class Entry extends Model
return $this->hasOne(Seat::class); return $this->hasOne(Seat::class);
} }
public function calculatedScores(): HasMany
{
return $this->hasMany(CalculatedScore::class);
}
public function scopeForSeating(Builder $query): void public function scopeForSeating(Builder $query): void
{ {
$query->where('for_seating', 1); $query->where('for_seating', 1);

View File

@ -16,6 +16,22 @@ class ScoreSheet extends Model
protected $casts = ['subscores' => 'json']; protected $casts = ['subscores' => 'json'];
protected static function boot()
{
parent::boot();
static::created(function ($scoreSheet) {
$scoreSheet->deleteRelatedCalculatedScores();
});
static::updated(function ($scoreSheet) {
$scoreSheet->deleteRelatedCalculatedScores();
});
static::deleted(function ($scoreSheet) {
$scoreSheet->deleteRelatedCalculatedScores();
});
}
public function entry(): BelongsTo public function entry(): BelongsTo
{ {
return $this->belongsTo(Entry::class); return $this->belongsTo(Entry::class);
@ -37,9 +53,18 @@ class ScoreSheet extends Model
'audition_id' // Local key on the intermediate model (Entry) 'audition_id' // Local key on the intermediate model (Entry)
); );
} }
public function getSubscore($id) public function getSubscore($id)
{ {
return $this->subscores[$id]['score'] ?? false; return $this->subscores[$id]['score'] ?? false;
// this function is used at resources/views/tabulation/entry_score_sheet.blade.php // this function is used at resources/views/tabulation/entry_score_sheet.blade.php
} }
public function deleteRelatedCalculatedScores(): void
{
$entry = $this->entry;
if ($entry) {
$entry->calculatedScores()->delete();
}
}
} }

View File

@ -0,0 +1,31 @@
<?php
use App\Models\Entry;
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('calculated_scores', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Entry::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate();
$table->string('mode');
$table->json('calculatedScore');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('calculated_scores');
}
};

View File

@ -2,7 +2,8 @@
<x-layout.app> <x-layout.app>
<x-slot:page_title>Dashboard</x-slot:page_title> <x-slot:page_title>Dashboard</x-slot:page_title>
@if(! Auth::user()->school_id) @if(! Auth::user()->school_id)
<p class="pb-5">You aren't currently associated with a school. <a href="/my_school" class="text-blue-600">Click here to choose or create one.</a></p> <p class="pb-5">You aren't currently associated with a school. <a href="/my_school" class="text-blue-600">Click
here to choose or create one.</a></p>
@endif @endif
<div class="grid sm:grid-cols-2 md:grid-cols-4"> <div class="grid sm:grid-cols-2 md:grid-cols-4">
<div>{{-- Column 1 --}} <div>{{-- Column 1 --}}
@ -29,6 +30,15 @@
</x-card.list.body> </x-card.list.body>
</x-card.card> </x-card.card>
</div> </div>
@if(Auth::user()->school_id)
<div class="md:col-span-3 pl-3">{{-- Column 2 Results --}}
<x-card.card>
<x-card.heading>My Results</x-card.heading>
@include('dashboard.results-table')
</x-card.card>
</div>
@endif
</div> </div>
</x-layout.app> </x-layout.app>

View File

@ -0,0 +1,24 @@
<x-table.table>
<thead>
<x-table.th>Student</x-table.th>
<x-table.th>Audition</x-table.th>
<x-table.th>Score</x-table.th>
<x-table.th>Rank</x-table.th>
<x-table.th>Result</x-table.th>
</thead>
<x-table.body>
@foreach($entries as $entry)
<tr>
<x-table.td>{{ $entry->student->full_name() }}</x-table.td>
<x-table.td>{{ $entry->audition->name }}</x-table.td>
@if(! $entry->audition->hasFlag('seats_published'))
<x-table.td colspan="3">Results not available</x-table.td>
@else
<x-table.td> {{ $scores[$entry->id ][0] ?? '--' }}</x-table.td>
<x-table.td> {{ $ranks[$entry->id ] ?? '--' }}</x-table.td>
<x-table.td> {{ $results[$entry->id ] ?? '--' }}</x-table.td>
@endif
</tr>
@endforeach
</x-table.body>
</x-table.table>