diff --git a/app/Actions/Entries/GetEntrySeatingResult.php b/app/Actions/Entries/GetEntrySeatingResult.php new file mode 100644 index 0000000..054c06f --- /dev/null +++ b/app/Actions/Entries/GetEntrySeatingResult.php @@ -0,0 +1,40 @@ +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'; + } +} diff --git a/app/Actions/Tabulation/AllowForOlympicScoring.php b/app/Actions/Tabulation/AllowForOlympicScoring.php index 258b8bb..be88354 100644 --- a/app/Actions/Tabulation/AllowForOlympicScoring.php +++ b/app/Actions/Tabulation/AllowForOlympicScoring.php @@ -6,6 +6,7 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\BonusScore; +use App\Models\CalculatedScore; use App\Models\Entry; use App\Services\AuditionService; use App\Services\EntryService; @@ -34,6 +35,10 @@ class AllowForOlympicScoring implements CalculateEntryScore 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; @@ -42,8 +47,15 @@ class AllowForOlympicScoring implements CalculateEntryScore $this->isEntryANoShow($entry); $this->areAllJudgesIn($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); }); } diff --git a/app/Actions/Tabulation/EnterBonusScore.php b/app/Actions/Tabulation/EnterBonusScore.php index 2706f34..b42a183 100644 --- a/app/Actions/Tabulation/EnterBonusScore.php +++ b/app/Actions/Tabulation/EnterBonusScore.php @@ -6,6 +6,7 @@ namespace App\Actions\Tabulation; use App\Exceptions\ScoreEntryException; use App\Models\BonusScore; +use App\Models\CalculatedScore; use App\Models\Entry; use App\Models\User; use Illuminate\Database\Eloquent\Collection; @@ -27,6 +28,8 @@ class EnterBonusScore // Create the score for each related entry foreach ($entries as $relatedEntry) { + // Also delete any cached scores + CalculatedScore::where('entry_id', $relatedEntry->id)->delete(); BonusScore::create([ 'entry_id' => $relatedEntry->id, 'user_id' => $judge->id, diff --git a/app/Actions/Tabulation/EnterScore.php b/app/Actions/Tabulation/EnterScore.php index 4354ac3..407f4cb 100644 --- a/app/Actions/Tabulation/EnterScore.php +++ b/app/Actions/Tabulation/EnterScore.php @@ -7,6 +7,7 @@ namespace App\Actions\Tabulation; use App\Exceptions\ScoreEntryException; +use App\Models\CalculatedScore; use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; @@ -24,6 +25,7 @@ class EnterScore */ public function __invoke(User $user, Entry $entry, array $scores, ScoreSheet|false $scoreSheet = false): ScoreSheet { + CalculatedScore::where('entry_id', $entry->id)->delete(); $scores = collect($scores); $this->basicChecks($user, $entry, $scores); $this->checkJudgeAssignment($user, $entry); diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index f5619dd..7071ff2 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -66,8 +66,10 @@ class RankAuditionEntries return 0; }); $rank = 1; + $rawRank = 1; foreach ($entries as $entry) { $entry->rank = $rank; + $entry->raw_rank = $rawRank; // We don't really get a rank for seating if we have certain flags if ($mode === 'seating') { if ($entry->hasFlag('declined')) { @@ -82,6 +84,7 @@ class RankAuditionEntries if (is_numeric($entry->rank)) { $rank++; } + $rawRank++; } return $entries; diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index b9f93c6..39233ae 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,6 +2,9 @@ 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\Services\Invoice\InvoiceDataService; use Illuminate\Support\Facades\Auth; @@ -22,9 +25,31 @@ class DashboardController extends Controller return view('dashboard.profile'); } - public function dashboard() - { - return view('dashboard.dashboard'); + public function dashboard( + CalculateEntryScore $scoreCalc, + 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() diff --git a/app/Http/Controllers/Tabulation/EntryFlagController.php b/app/Http/Controllers/Tabulation/EntryFlagController.php index bd0aed0..fdbe9fe 100644 --- a/app/Http/Controllers/Tabulation/EntryFlagController.php +++ b/app/Http/Controllers/Tabulation/EntryFlagController.php @@ -3,7 +3,9 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; +use App\Models\CalculatedScore; use App\Models\Entry; +use App\Models\ScoreSheet; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -79,7 +81,8 @@ class EntryFlagController extends Controller DB::table('score_sheets')->where('entry_id', $entry->id)->delete(); $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.').'; return to_route('entry-flags.noShowSelect')->with('success', $msg); diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index e714a9f..d9b5931 100644 --- a/app/Http/Controllers/Tabulation/ScoreController.php +++ b/app/Http/Controllers/Tabulation/ScoreController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; +use App\Models\CalculatedScore; use App\Models\Entry; use App\Models\ScoreSheet; use Illuminate\Http\Request; @@ -21,6 +22,7 @@ class ScoreController extends Controller public function destroyScore(ScoreSheet $score) { + CalculatedScore::where('entry_id', $score->entry_id)->delete(); if ($score->entry->audition->hasFlag('seats_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) { + CalculatedScore::where('entry_id', $entry->id)->delete(); $publishedCheck = $this->checkIfPublished($entry); if ($publishedCheck) { return $publishedCheck; @@ -105,6 +108,7 @@ class ScoreController extends Controller ['subscores' => $sheet['scores']] ); } + // TODO rewrite to use EnterScore action or clear score cache return redirect()->route('scores.chooseEntry')->with('success', count($preparedScoreSheets).' Scores saved'); } diff --git a/app/Models/CalculatedScore.php b/app/Models/CalculatedScore.php new file mode 100644 index 0000000..f514d8d --- /dev/null +++ b/app/Models/CalculatedScore.php @@ -0,0 +1,15 @@ + 'json']; +} diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 1670c02..a758368 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -121,6 +121,11 @@ class Entry extends Model return $this->hasOne(Seat::class); } + public function calculatedScores(): HasMany + { + return $this->hasMany(CalculatedScore::class); + } + public function scopeForSeating(Builder $query): void { $query->where('for_seating', 1); diff --git a/app/Models/ScoreSheet.php b/app/Models/ScoreSheet.php index 01c310f..d3a1e47 100644 --- a/app/Models/ScoreSheet.php +++ b/app/Models/ScoreSheet.php @@ -16,6 +16,22 @@ class ScoreSheet extends Model 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 { return $this->belongsTo(Entry::class); @@ -37,9 +53,18 @@ class ScoreSheet extends Model 'audition_id' // Local key on the intermediate model (Entry) ); } + public function getSubscore($id) { return $this->subscores[$id]['score'] ?? false; // 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(); + } + } } diff --git a/database/migrations/2024_10_31_120759_create_calculated_scores_table.php b/database/migrations/2024_10_31_120759_create_calculated_scores_table.php new file mode 100644 index 0000000..63d0be7 --- /dev/null +++ b/database/migrations/2024_10_31_120759_create_calculated_scores_table.php @@ -0,0 +1,31 @@ +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'); + } +}; diff --git a/resources/views/dashboard/dashboard.blade.php b/resources/views/dashboard/dashboard.blade.php index a75d84f..0b0905a 100644 --- a/resources/views/dashboard/dashboard.blade.php +++ b/resources/views/dashboard/dashboard.blade.php @@ -2,7 +2,8 @@ Dashboard @if(! Auth::user()->school_id) - You aren't currently associated with a school. Click here to choose or create one. + You aren't currently associated with a school. Click + here to choose or create one. @endif {{-- Column 1 --}} @@ -29,6 +30,15 @@ + @if(Auth::user()->school_id) + {{-- Column 2 Results --}} + + My Results + @include('dashboard.results-table') + + + @endif + diff --git a/resources/views/dashboard/results-table.blade.php b/resources/views/dashboard/results-table.blade.php new file mode 100644 index 0000000..060f7f9 --- /dev/null +++ b/resources/views/dashboard/results-table.blade.php @@ -0,0 +1,24 @@ + + + Student + Audition + Score + Rank + Result + + + @foreach($entries as $entry) + + {{ $entry->student->full_name() }} + {{ $entry->audition->name }} + @if(! $entry->audition->hasFlag('seats_published')) + Results not available + @else + {{ $scores[$entry->id ][0] ?? '--' }} + {{ $ranks[$entry->id ] ?? '--' }} + {{ $results[$entry->id ] ?? '--' }} + @endif + + @endforeach + +
You aren't currently associated with a school. Click here to choose or create one.
You aren't currently associated with a school. Click + here to choose or create one.