From 6c52aa255c0ffd4bf0ec5abe989dfc99cbff2754 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 08:10:09 -0500 Subject: [PATCH 1/9] Remove depricated code from EnterBonusScore action. --- app/Actions/Tabulation/EnterBonusScore.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Actions/Tabulation/EnterBonusScore.php b/app/Actions/Tabulation/EnterBonusScore.php index b42a183..7f57d8e 100644 --- a/app/Actions/Tabulation/EnterBonusScore.php +++ b/app/Actions/Tabulation/EnterBonusScore.php @@ -6,7 +6,6 @@ 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; @@ -29,7 +28,6 @@ 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 ee45499e7a10b9177290c84ef6f8ee29a75c0aed Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 09:15:50 -0500 Subject: [PATCH 2/9] Migration to add bonus score related columns to the total scores table. --- ...dd_bonus_columns_to_entry_total_scores.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2025_06_26_131356_add_bonus_columns_to_entry_total_scores.php diff --git a/database/migrations/2025_06_26_131356_add_bonus_columns_to_entry_total_scores.php b/database/migrations/2025_06_26_131356_add_bonus_columns_to_entry_total_scores.php new file mode 100644 index 0000000..54c4c08 --- /dev/null +++ b/database/migrations/2025_06_26_131356_add_bonus_columns_to_entry_total_scores.php @@ -0,0 +1,32 @@ +decimal('bonus_total', 9, 6)->nullable()->after('advancement_subscore_totals'); + $table->decimal('seating_total_with_bonus', 9, 6) + ->storedAs('seating_total + COALESCE(bonus_total, 0)') + ->after('bonus_total'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('entry_total_scores', function (Blueprint $table) { + $table->dropColumn('bonus_total'); + $table->dropColumn('seating_total_with_bonus'); + }); + } +}; From 86ec4f4062b5cb10ea0fb7ff4aef24369dda866d Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 09:32:06 -0500 Subject: [PATCH 3/9] Deal with bonus scores when calculating total scores. --- app/Actions/Tabulation/TotalEntryScores.php | 6 +++ app/Observers/BonusScoreObserver.php | 52 +++++++++++++++++++++ app/Observers/ScoreSheetObserver.php | 6 ++- app/Providers/AppServiceProvider.php | 3 ++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 app/Observers/BonusScoreObserver.php diff --git a/app/Actions/Tabulation/TotalEntryScores.php b/app/Actions/Tabulation/TotalEntryScores.php index 448e650..196d04f 100644 --- a/app/Actions/Tabulation/TotalEntryScores.php +++ b/app/Actions/Tabulation/TotalEntryScores.php @@ -2,6 +2,7 @@ namespace App\Actions\Tabulation; +use App\Models\BonusScore; use App\Models\Entry; use App\Models\EntryTotalScore; use App\Models\ScoreSheet; @@ -75,6 +76,11 @@ class TotalEntryScores $total_advancement_subscores[] = $runningTotal / $scoreSheets->count(); } $newTotaledScore->advancement_subscore_totals = $total_advancement_subscores; + + // pull in bonus scores + $bonusScores = BonusScore::where('entry_id', $entry->id)->sum('score'); + $newTotaledScore->bonus_score = $bonusScores; + $newTotaledScore->save(); } } diff --git a/app/Observers/BonusScoreObserver.php b/app/Observers/BonusScoreObserver.php new file mode 100644 index 0000000..79878fd --- /dev/null +++ b/app/Observers/BonusScoreObserver.php @@ -0,0 +1,52 @@ +entry, true); + } + + /** + * Handle the ScoreSheet "updated" event. + */ + public function updated(BonusScore $bonusScore): void + { + $calculator = app(TotalEntryScores::class); + $calculator($bonusScore->entry, true); + } + + /** + * Handle the ScoreSheet "deleted" event. + */ + public function deleted(BonusScore $bonusScore): void + { + $calculator = app(TotalEntryScores::class); + $calculator($bonusScore->entry, true); + } + + /** + * Handle the ScoreSheet "restored" event. + */ + public function restored(BonusScore $bonusScore): void + { + // + } + + /** + * Handle the ScoreSheet "force deleted" event. + */ + public function forceDeleted(BonusScore $bonusScore): void + { + // + } +} diff --git a/app/Observers/ScoreSheetObserver.php b/app/Observers/ScoreSheetObserver.php index 0b419af..5ade31e 100644 --- a/app/Observers/ScoreSheetObserver.php +++ b/app/Observers/ScoreSheetObserver.php @@ -21,7 +21,8 @@ class ScoreSheetObserver */ public function updated(ScoreSheet $scoreSheet): void { - // + $calculator = app(TotalEntryScores::class); + $calculator($scoreSheet->entry, true); } /** @@ -29,7 +30,8 @@ class ScoreSheetObserver */ public function deleted(ScoreSheet $scoreSheet): void { - // + $calculator = app(TotalEntryScores::class); + $calculator($scoreSheet->entry, true); } /** diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 4d3a795..09e5005 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -18,6 +18,7 @@ use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleController; use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleEntryController; use App\Http\Controllers\NominationEnsembles\ScobdaNominationSeatingController; use App\Models\Audition; +use App\Models\BonusScore; use App\Models\Entry; use App\Models\EntryFlag; use App\Models\Room; @@ -30,6 +31,7 @@ use App\Models\Student; use App\Models\SubscoreDefinition; use App\Models\User; use App\Observers\AuditionObserver; +use App\Observers\BonusScoreObserver; use App\Observers\EntryFlagObserver; use App\Observers\EntryObserver; use App\Observers\RoomObserver; @@ -83,6 +85,7 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { + BonusScore::observe(BonusScoreObserver::class); Entry::observe(EntryObserver::class); Audition::observe(AuditionObserver::class); Room::observe(RoomObserver::class); From fd3855a775c7fc4e4af4134f9750d93de0fde7ec Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 10:07:51 -0500 Subject: [PATCH 4/9] Add console command to force recalculation of scores --- .../ForceRecalculateTotalScores.php | 16 +++++++++ app/Actions/Tabulation/TotalEntryScores.php | 7 ++-- app/Console/Commands/RecalculateScores.php | 35 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 app/Actions/Tabulation/ForceRecalculateTotalScores.php create mode 100644 app/Console/Commands/RecalculateScores.php diff --git a/app/Actions/Tabulation/ForceRecalculateTotalScores.php b/app/Actions/Tabulation/ForceRecalculateTotalScores.php new file mode 100644 index 0000000..d5c6bde --- /dev/null +++ b/app/Actions/Tabulation/ForceRecalculateTotalScores.php @@ -0,0 +1,16 @@ +advancement_subscore_totals = $total_advancement_subscores; // pull in bonus scores - $bonusScores = BonusScore::where('entry_id', $entry->id)->sum('score'); - $newTotaledScore->bonus_score = $bonusScores; + $bonusScores = BonusScore::where('entry_id', $entry->id) + ->selectRaw('SUM(score) as total') + ->value('total'); + + $newTotaledScore->bonus_total = $bonusScores; $newTotaledScore->save(); } diff --git a/app/Console/Commands/RecalculateScores.php b/app/Console/Commands/RecalculateScores.php new file mode 100644 index 0000000..acc43ad --- /dev/null +++ b/app/Console/Commands/RecalculateScores.php @@ -0,0 +1,35 @@ +info('Starting score recalculation...'); + + $action(); + + $this->info('Score recalculation completed successfully.'); + } +} From 04cfde353ed6e0335deeaf0fad369477f685a0ba Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 10:22:59 -0500 Subject: [PATCH 5/9] When appropriate, include bonus score in ranking entrie. Show if an entry has bonus scores when appropriate. --- app/Actions/Tabulation/RankAuditionEntries.php | 8 +++++++- .../views/tabulation/auditionSeating.blade.php | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index 77fd90d..1f64d56 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -42,13 +42,19 @@ class RankAuditionEntries private function get_seating_ranks(Audition $audition): Collection { + if ($audition->bonusScore()->count() > 0) { + $totalColumn = 'seating_total_with_bonus'; + } else { + $totalColumn = 'seating_total'; + } + $sortedEntries = $audition->entries() ->whereHas('totalScore') ->with('totalScore') ->with('student.school') ->with('audition') ->join('entry_total_scores', 'entries.id', '=', 'entry_total_scores.entry_id') - ->orderBy('entry_total_scores.seating_total', 'desc') + ->orderBy('entry_total_scores.'.$totalColumn, 'desc') ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[0]"), -999999) DESC') ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[1]"), -999999) DESC') ->orderByRaw('COALESCE(JSON_EXTRACT(entry_total_scores.seating_subscore_totals, "$[2]"), -999999) DESC') diff --git a/resources/views/tabulation/auditionSeating.blade.php b/resources/views/tabulation/auditionSeating.blade.php index 1a66854..6e1f3e8 100644 --- a/resources/views/tabulation/auditionSeating.blade.php +++ b/resources/views/tabulation/auditionSeating.blade.php @@ -17,8 +17,7 @@ @if($audition->bonusScore()->count() > 0)
- - Has Bonus + No Bonus Score
@endif @@ -65,7 +64,17 @@ - {{ $entry->totalScore->seating_total }} + + @if($audition->bonusScore()->count() > 0) + @if($entry->totalScore->bonus_total) + {{ $entry->totalScore->seating_total_with_bonus }} + @else + {{ $entry->totalScore->seating_total_with_bonus }} + @endif + @else + {{ $entry->totalScore->seating_total }} + @endif + @endforeach From abc86ba726f6413a727a2ec2ddff178e23cffd67 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 10:31:43 -0500 Subject: [PATCH 6/9] Remove depricated code from bonusscore model --- app/Models/BonusScore.php | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/app/Models/BonusScore.php b/app/Models/BonusScore.php index ece28cb..0423629 100644 --- a/app/Models/BonusScore.php +++ b/app/Models/BonusScore.php @@ -4,28 +4,11 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Support\Facades\Cache; class BonusScore extends Model { protected $guarded = []; - protected static function boot() - { - parent::boot(); - static::created(function ($bonusScore) { - $bonusScore->deleteRelatedCalculatedScores(); - }); - - static::updated(function ($bonusScore) { - $bonusScore->deleteRelatedCalculatedScores(); - }); - - static::deleted(function ($bonusScore) { - $bonusScore->deleteRelatedCalculatedScores(); - }); - } - public function entry(): BelongsTo { return $this->belongsTo(Entry::class); @@ -40,16 +23,4 @@ class BonusScore extends Model { return $this->belongsTo(Entry::class, 'originally_scored_entry'); } - - public function deleteRelatedCalculatedScores(): void - { - $entry = $this->entry; - if ($entry) { - $entry->calculatedScores()->delete(); - Cache::forget('entryScore-'.$entry->id.'-seating'); - Cache::forget('entryScore-'.$entry->id.'-advancement'); - Cache::forget('audition'.$entry->audition_id.'seating'); - Cache::forget('audition'.$entry->audition_id.'advancement'); - } - } } From 0bc80002bb58f7263dc3b9dfd538573fb2fbe418 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 10:35:01 -0500 Subject: [PATCH 7/9] rename sync-doublers console command --- app/Console/Commands/SyncDoublers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/SyncDoublers.php b/app/Console/Commands/SyncDoublers.php index d50235f..a79a93c 100644 --- a/app/Console/Commands/SyncDoublers.php +++ b/app/Console/Commands/SyncDoublers.php @@ -13,7 +13,7 @@ class SyncDoublers extends Command * * @var string */ - protected $signature = 'doublers:sync {event? : Optional event ID}'; + protected $signature = 'audition:sync-doublers {event? : Optional event ID}'; /** * The console command description. From a3e8785767fc98b7ad823d0721687725083cbb3f Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 11:00:13 -0500 Subject: [PATCH 8/9] add ability to fictionalize data --- app/Console/Commands/fictionalize.php | 52 +++++++++++++++++++ ...dit_log_entries_message_column_to_text.php | 28 ++++++++++ 2 files changed, 80 insertions(+) create mode 100644 app/Console/Commands/fictionalize.php create mode 100644 database/migrations/2025_06_26_155129_change_audit_log_entries_message_column_to_text.php diff --git a/app/Console/Commands/fictionalize.php b/app/Console/Commands/fictionalize.php new file mode 100644 index 0000000..b860470 --- /dev/null +++ b/app/Console/Commands/fictionalize.php @@ -0,0 +1,52 @@ +first_name = $faker->firstName(); + $student->last_name = $faker->lastName(); + $student->save(); + } + + foreach (School::all() as $school) { + $school->name = $faker->city().' High School'; + $school->save(); + } + + foreach (User::where('email', '!=', 'matt@mattyoung.us')->get() as $user) { + $user->email = $faker->email(); + $user->first_name = $faker->firstName(); + $user->last_name = $faker->lastName(); + $user->cell_phone = $faker->phoneNumber(); + $user->save(); + } + } +} diff --git a/database/migrations/2025_06_26_155129_change_audit_log_entries_message_column_to_text.php b/database/migrations/2025_06_26_155129_change_audit_log_entries_message_column_to_text.php new file mode 100644 index 0000000..fde2bcd --- /dev/null +++ b/database/migrations/2025_06_26_155129_change_audit_log_entries_message_column_to_text.php @@ -0,0 +1,28 @@ +text('message')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('audit_log_entries', function (Blueprint $table) { + $table->string('message')->change(); + }); + } +}; From 7670e91f43008653d21ef8f065d649a013b97cd1 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 26 Jun 2025 18:32:16 -0500 Subject: [PATCH 9/9] Allow for bluk declining seats --- .../Tabulation/SeatAuditionFormController.php | 72 +++++++++++++++---- app/Providers/AppServiceProvider.php | 2 +- .../tabulation/auditionSeating.blade.php | 9 ++- routes/tabulation.php | 1 + 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Tabulation/SeatAuditionFormController.php b/app/Http/Controllers/Tabulation/SeatAuditionFormController.php index 1a0e8a1..8eb9f0b 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionFormController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionFormController.php @@ -11,6 +11,7 @@ use App\Models\Doubler; use App\Models\Ensemble; use App\Models\Entry; use App\Models\Seat; +use Debugbar; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; @@ -116,8 +117,44 @@ class SeatAuditionFormController extends Controller $entry->student->full_name().' has declined '.$audition->name); } - public function acceptSeat(Audition $audition, Entry $entry) + public function massDecline(Audition $audition) { + $validData = request()->validate([ + 'decline-below' => ['required', 'integer', 'min:0'], + ]); + $ranker = app(RankAuditionEntries::class); + // Get scored entries in order + $scored_entries = $ranker($audition, 'seating'); + $scored_entries->load(['student.doublers', 'student.school']); + foreach ($scored_entries as $entry) { + Debugbar::info('Starting entry '.$entry->student->full_name()); + if ($entry->hasFlag('declined')) { + Debugbar::info('Skipping '.$entry->student->full_name().' because they have already been declined'); + + continue; + } + if (! $entry->student->isDoublerInEvent($audition->event_id)) { + Debugbar::info('Skipping '.$entry->student->full_name().' because they are not a doubler'); + + continue; + } + if ($entry->student->doublers->where('event_id', $audition->event_id)->first()->accepted_entry) { + Debugbar::info('Skipping '.$entry->student->full_name().' because they have already accepted a seat'); + + continue; + } + $entry->addFlag('declined'); + } + Cache::forget('rank_seating_'.$entry->audition_id); + + return redirect()->route('seating.audition', ['audition' => $audition->id]); + + } + + public function acceptSeat( + Audition $audition, + Entry $entry + ) { $doublerData = Doubler::findDoubler($entry->student_id, $audition->event_id); foreach ($doublerData->entries() as $doublerEntry) { if (! $doublerEntry->totalScore && ! $doublerEntry->hasFlag('declined') && ! $doublerEntry->hasFlag('no_show') && ! $doublerEntry->hasFlag('failed_prelim')) { @@ -136,8 +173,10 @@ class SeatAuditionFormController extends Controller $entry->student->full_name().' has accepted '.$audition->name); } - public function noshow(Audition $audition, Entry $entry) - { + public function noshow( + Audition $audition, + Entry $entry + ) { $recorder = app('App\Actions\Tabulation\EnterNoShow'); try { $msg = $recorder($entry); @@ -148,8 +187,10 @@ class SeatAuditionFormController extends Controller return redirect()->route('seating.audition', [$audition])->with('success', $msg); } - public function draftSeats(Audition $audition, Request $request) - { + public function draftSeats( + Audition $audition, + Request $request + ) { $ranker = app(RankAuditionEntries::class); $validated = $request->validate([ 'ensemble' => ['required', 'array'], @@ -192,15 +233,17 @@ class SeatAuditionFormController extends Controller return redirect()->route('seating.audition', ['audition' => $audition->id]); } - public function clearDraft(Audition $audition) - { + public function clearDraft( + Audition $audition + ) { session()->forget('proposedSeatingArray-'.$audition->id); return redirect()->route('seating.audition', ['audition' => $audition->id]); } - public function publishSeats(Audition $audition) - { + public function publishSeats( + Audition $audition + ) { $publisher = app('App\Actions\Tabulation\PublishSeats'); $seatingProposal = (session('proposedSeatingArray-'.$audition->id)); $proposal = []; @@ -223,8 +266,9 @@ class SeatAuditionFormController extends Controller return redirect()->route('seating.audition', [$audition]); } - public function unpublishSeats(Audition $audition) - { + public function unpublishSeats( + Audition $audition + ) { $unpublisher = app('App\Actions\Tabulation\UnpublishSeats'); $unpublisher($audition); session()->forget('proposedSeatingArray-'.$audition->id); @@ -232,8 +276,10 @@ class SeatAuditionFormController extends Controller return redirect()->route('seating.audition', [$audition]); } - protected function pickRightPanel(Audition $audition, array $seatable) - { + protected function pickRightPanel( + Audition $audition, + array $seatable + ) { if ($audition->hasFlag('seats_published')) { $resultsWindow = new GetAuditionSeats; $rightPanel['view'] = 'tabulation.auditionSeating-show-published-seats'; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 09e5005..744c46e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -99,6 +99,6 @@ class AppServiceProvider extends ServiceProvider SeatingLimit::observe(SeatingLimitObserver::class); EntryFlag::observe(EntryFlagObserver::class); - // Model::preventLazyLoading(! app()->isProduction()); + Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/resources/views/tabulation/auditionSeating.blade.php b/resources/views/tabulation/auditionSeating.blade.php index 6e1f3e8..78771ac 100644 --- a/resources/views/tabulation/auditionSeating.blade.php +++ b/resources/views/tabulation/auditionSeating.blade.php @@ -239,7 +239,6 @@ @endif @else
- {{-- TODO: Add in bulk decline doubler option --}} @if($unscored_entries->count() > 0) Cannot seat the audition while entries are unscored. @@ -247,8 +246,12 @@ @endif @if($auditionHasUnresolvedDoublers) - - Cannot seat the audition while there are unresolved doublers. + +

Cannot seat the audition while there are unresolved doublers.

+ + + Decline +
@endif
diff --git a/routes/tabulation.php b/routes/tabulation.php index 0accdec..b566875 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -45,6 +45,7 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () Route::post('/{audition}/draftSeats', [SeatAuditionFormController::class, 'draftSeats'])->name('seating.audition.draftSeats'); Route::post('/{audition}/clearDraft', [SeatAuditionFormController::class, 'clearDraft'])->name('seating.audition.clearDraft'); Route::post('/{audition}/{entry}/decline', [SeatAuditionFormController::class, 'declineSeat'])->name('seating.audition.decline'); + Route::post('/{audition}/mass_decline', [SeatAuditionFormController::class, 'massDecline'])->name('seating.audition.mass_decline'); Route::post('/{audition}/{entry}/accept', [SeatAuditionFormController::class, 'acceptSeat'])->name('seating.audition.accept'); Route::post('/{audition}/{entry}/noshow', [SeatAuditionFormController::class, 'noshow'])->name('seating.audition.noshow'); Route::post('/{audition}/publish',