From 0ab7d706126c66faacf02b9dee0374e8b2e3c95b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 07:07:32 -0500 Subject: [PATCH 01/11] Add TODO issue reminder --- app/Http/Controllers/Tabulation/ScoreController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index e714a9f..dcf53be 100644 --- a/app/Http/Controllers/Tabulation/ScoreController.php +++ b/app/Http/Controllers/Tabulation/ScoreController.php @@ -29,6 +29,7 @@ class ScoreController extends Controller } $score->delete(); + // TODO clear scoring cache when a score is deleted return redirect()->back()->with('success', 'Score Deleted'); } @@ -105,6 +106,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'); } From 8f7a3338983ed3e4b4e47d19e4be285254323365 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 07:19:27 -0500 Subject: [PATCH 02/11] Cache scores after calculating --- .../Tabulation/AllowForOlympicScoring.php | 10 +++++- app/Models/CalculatedScore.php | 15 +++++++++ ..._120759_create_calculated_scores_table.php | 31 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 app/Models/CalculatedScore.php create mode 100644 database/migrations/2024_10_31_120759_create_calculated_scores_table.php diff --git a/app/Actions/Tabulation/AllowForOlympicScoring.php b/app/Actions/Tabulation/AllowForOlympicScoring.php index 258b8bb..483088b 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; @@ -42,8 +43,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/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/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'); + } +}; From 94ed41ee6f3e612efbd4595378b1599b284033a5 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 07:58:50 -0500 Subject: [PATCH 03/11] use cached scores --- app/Actions/Tabulation/AllowForOlympicScoring.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Actions/Tabulation/AllowForOlympicScoring.php b/app/Actions/Tabulation/AllowForOlympicScoring.php index 483088b..be88354 100644 --- a/app/Actions/Tabulation/AllowForOlympicScoring.php +++ b/app/Actions/Tabulation/AllowForOlympicScoring.php @@ -35,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; From 653305c9380a50fb58d85c579e1b4e00f839a5ee Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 08:17:55 -0500 Subject: [PATCH 04/11] a judge entering a score will delete cached scores for that entry --- app/Actions/Tabulation/EnterScore.php | 2 ++ 1 file changed, 2 insertions(+) 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); From 42229e487c66303ef619bc8d78a453558038a9aa Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 08:29:02 -0500 Subject: [PATCH 05/11] Admin entering a score will clear a cached score --- app/Http/Controllers/Tabulation/ScoreController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index dcf53be..41a2702 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; @@ -67,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; From 0d19b877151b4714406c9c4f4f492f6078bd46b1 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 08:42:39 -0500 Subject: [PATCH 06/11] entering a bonus score will clear cached scores --- app/Actions/Tabulation/EnterBonusScore.php | 3 +++ 1 file changed, 3 insertions(+) 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, From 45287933030bc8ba6a039edaad171886147a5219 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 09:15:08 -0500 Subject: [PATCH 07/11] deleting a score will clear cached scores --- app/Http/Controllers/Tabulation/ScoreController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index 41a2702..2080343 100644 --- a/app/Http/Controllers/Tabulation/ScoreController.php +++ b/app/Http/Controllers/Tabulation/ScoreController.php @@ -22,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'); } From db6f1b1d45ec71855363e80b719292a888cc7149 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 09:15:37 -0500 Subject: [PATCH 08/11] Remove TODO --- app/Http/Controllers/Tabulation/ScoreController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index 2080343..d9b5931 100644 --- a/app/Http/Controllers/Tabulation/ScoreController.php +++ b/app/Http/Controllers/Tabulation/ScoreController.php @@ -31,7 +31,6 @@ class ScoreController extends Controller } $score->delete(); - // TODO clear scoring cache when a score is deleted return redirect()->back()->with('success', 'Score Deleted'); } From dc6fec399c38b85682e114edab21844dfb7eeb22 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 11:48:10 -0500 Subject: [PATCH 09/11] Work on showing results on user dashboard Addresses #68 --- app/Actions/Entries/GetEntrySeatingResult.php | 40 +++++++++++++++++++ .../Tabulation/RankAuditionEntries.php | 3 ++ app/Http/Controllers/DashboardController.php | 31 ++++++++++++-- .../Tabulation/EntryFlagController.php | 5 ++- resources/views/dashboard/dashboard.blade.php | 12 +++++- .../views/dashboard/results-table.blade.php | 24 +++++++++++ 6 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 app/Actions/Entries/GetEntrySeatingResult.php create mode 100644 resources/views/dashboard/results-table.blade.php 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/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/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..8fd40f6 --- /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 + + From f8d778dbcc69c8917237e4e47b423b8a14f14503 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 12:00:49 -0500 Subject: [PATCH 10/11] Work on showing results on user dashboard Addresses #68 --- resources/views/dashboard/results-table.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/dashboard/results-table.blade.php b/resources/views/dashboard/results-table.blade.php index 8fd40f6..060f7f9 100644 --- a/resources/views/dashboard/results-table.blade.php +++ b/resources/views/dashboard/results-table.blade.php @@ -14,9 +14,9 @@ @if(! $entry->audition->hasFlag('seats_published')) Results not available @else - {{ $scores[$entry->id ][0] }} - {{ $ranks[$entry->id ] }} - {{ $results[$entry->id ] }} + {{ $scores[$entry->id ][0] ?? '--' }} + {{ $ranks[$entry->id ] ?? '--' }} + {{ $results[$entry->id ] ?? '--' }} @endif @endforeach From e93ae750e4aee7fe8906a1af44750929ab6cef6e Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 31 Oct 2024 12:07:49 -0500 Subject: [PATCH 11/11] Delete calculated scores on score sheet changes, deletions, and additions. Addresses #68 --- app/Models/Entry.php | 5 +++++ app/Models/ScoreSheet.php | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) 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(); + } + } }