From 6b42f2c1fb19335bb3446bc18ee5ceda4ace2e17 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 10 Jun 2025 23:35:21 -0500 Subject: [PATCH 1/6] EntrySeeder update --- database/seeders/EntrySeeder.php | 48 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/database/seeders/EntrySeeder.php b/database/seeders/EntrySeeder.php index 9d8334a..c0acbfe 100644 --- a/database/seeders/EntrySeeder.php +++ b/database/seeders/EntrySeeder.php @@ -17,51 +17,59 @@ class EntrySeeder extends Seeder public function run(): void { $students = Student::all(); - $hs_auditions = Audition::where('maximum_grade', '=', '12'); - $freshman_auditions = Audition::where('maximum_grade', '>', '8'); - $jh_auditions = Audition::where('maximum_grade', '=', '9'); - $seventh_auditions = Audition::where('maximum_grade', '=', '7'); foreach ($students as $student) { if ($student->grade > 9) { - $audition = Audition::where('maximum_grade', '=', '12')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '=', '12') + ->inRandomOrder()->first(); } if ($student->grade == 9) { - $audition = Audition::where('maximum_grade', '>', '8')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '>', '8') + ->inRandomOrder()->first(); } if ($student->grade == 8) { - $audition = Audition::where('maximum_grade', '=', '9')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '=', '9') + ->inRandomOrder()->first(); } if ($student->grade == 7) { - $audition = Audition::where('maximum_grade', '=', '7')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '=', '7') + ->inRandomOrder()->first(); } - Entry::factory()->create([ + Entry::create([ 'student_id' => $student->id, 'audition_id' => $audition->id, + 'for_seating' => 1, + 'for_advancement' => 1, ]); if (mt_rand(1, 100) > 90) { if ($student->grade > 9) { - $audition2 = Audition::where('maximum_grade', '=', '12')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '=', '12') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } if ($student->grade == 9) { - $audition2 = Audition::where('maximum_grade', '>', '8')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '>', '8') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } if ($student->grade == 8) { - $audition2 = Audition::where('maximum_grade', '=', '9')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '=', '9') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } if ($student->grade == 7) { - $audition2 = Audition::where('maximum_grade', '=', '7')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '=', '7') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } - Entry::factory()->create([ + Entry::create([ 'student_id' => $student->id, 'audition_id' => $audition2->id, + 'for_seating' => 1, + 'for_advancement' => 1, ]); } @@ -83,9 +91,11 @@ class EntrySeeder extends Seeder $audition->id)->where('id', '!=', $audition2->id)->inRandomOrder()->first(); } - Entry::factory()->create([ + Entry::create([ 'student_id' => $student->id, 'audition_id' => $audition3->id, + 'for_seating' => 1, + 'for_advancement' => 1, ]); } } From 13ca712ce986be46e1068005290862434a6551cb Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 10 Jun 2025 23:46:49 -0500 Subject: [PATCH 2/6] Add sheet_total column to score_sheets table --- ...eet_total_column_to_score_sheets_table.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php diff --git a/database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php b/database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php new file mode 100644 index 0000000..9714f66 --- /dev/null +++ b/database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php @@ -0,0 +1,28 @@ +decimal('sheet_total', 9, 6)->after('subscores'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('score_sheets', function (Blueprint $table) { + $table->dropColumn('sheet_total'); + }); + } +}; From 78e90cbe25e5f758330342eee1dfce274858e3d8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 11 Jun 2025 07:27:48 -0500 Subject: [PATCH 3/6] Rewrite enter score action --- app/Actions/Tabulation/EnterScore.php | 159 ++++++++++++-------------- 1 file changed, 76 insertions(+), 83 deletions(-) diff --git a/app/Actions/Tabulation/EnterScore.php b/app/Actions/Tabulation/EnterScore.php index 407f4cb..ab7be4b 100644 --- a/app/Actions/Tabulation/EnterScore.php +++ b/app/Actions/Tabulation/EnterScore.php @@ -7,11 +7,9 @@ namespace App\Actions\Tabulation; use App\Exceptions\ScoreEntryException; -use App\Models\CalculatedScore; use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; class EnterScore @@ -20,92 +18,18 @@ class EnterScore * @param User $user A user acting as the judge for this sheet * @param Entry $entry An entry to which this score should be assigned * @param array $scores Scores to be entered in the form of SubscoreID => score + * @param ScoreSheet|false $scoreSheet If this is an update to an existing scoresheet, pass it here + * @return ScoreSheet The scoresheet that was created or updated * * @throws ScoreEntryException */ public function __invoke(User $user, Entry $entry, array $scores, ScoreSheet|false $scoreSheet = false): ScoreSheet { - CalculatedScore::where('entry_id', $entry->id)->delete(); + // TODO: Remove the CalculatedScore model and table when rewrite is complete, they'll be obsolete + // CalculatedScore::where('entry_id', $entry->id)->delete(); $scores = collect($scores); - $this->basicChecks($user, $entry, $scores); - $this->checkJudgeAssignment($user, $entry); - $this->checkForExistingScore($user, $entry, $scoreSheet); - $this->validateScoresSubmitted($entry, $scores); - $entry->removeFlag('no_show'); - if ($scoreSheet instanceof ScoreSheet) { - $scoreSheet->update([ - 'user_id' => $user->id, - 'entry_id' => $entry->id, - 'subscores' => $this->subscoresForStorage($entry, $scores), - ]); - } else { - $scoreSheet = ScoreSheet::create([ - 'user_id' => $user->id, - 'entry_id' => $entry->id, - 'subscores' => $this->subscoresForStorage($entry, $scores), - ]); - } - return $scoreSheet; - } - - protected function subscoresForStorage(Entry $entry, Collection $scores) - { - $subscores = []; - foreach ($entry->audition->scoringGuide->subscores as $subscore) { - $subscores[$subscore->id] = [ - 'score' => $scores[$subscore->id], - 'subscore_id' => $subscore->id, - 'subscore_name' => $subscore->name, - ]; - } - - return $subscores; - } - - protected function checkForExistingScore(User $user, Entry $entry, $existingScoreSheet) - { - if (! $existingScoreSheet) { - if (ScoreSheet::where('user_id', $user->id)->where('entry_id', $entry->id)->exists()) { - throw new ScoreEntryException('That judge has already entered scores for that entry'); - } - } else { - if ($existingScoreSheet->user_id !== $user->id) { - throw new ScoreEntryException('Existing score sheet is from a different judge'); - } - if ($existingScoreSheet->entry_id !== $entry->id) { - throw new ScoreEntryException('Existing score sheet is for a different entry'); - } - } - } - - protected function validateScoresSubmitted(Entry $entry, Collection $scores) - { - $subscoresRequired = $entry->audition->scoringGuide->subscores; - - foreach ($subscoresRequired as $subscore) { - // check that there is an element in the $scores collection with the key = $subscore->id - if (! $scores->keys()->contains($subscore->id)) { - throw new ScoreEntryException('Invalid Score Submission'); - } - if ($scores[$subscore->id] > $subscore->max_score) { - throw new ScoreEntryException('Supplied subscore exceeds maximum allowed'); - } - } - } - - protected function checkJudgeAssignment(User $user, Entry $entry) - { - $check = DB::table('room_user') - ->where('room_id', $entry->audition->room_id) - ->where('user_id', $user->id)->exists(); - if (! $check) { - throw new ScoreEntryException('This judge is not assigned to judge this entry'); - } - } - - protected function basicChecks(User $user, Entry $entry, Collection $scores) - { + // Basic Validity Checks if (! $user->exists()) { throw new ScoreEntryException('User does not exist'); } @@ -118,9 +42,78 @@ class EnterScore if ($entry->audition->hasFlag('advancement_published')) { throw new ScoreEntryException('Cannot score an entry in an audition with published advancement'); } - $requiredScores = $entry->audition->scoringGuide->subscores()->count(); - if ($scores->count() !== $requiredScores) { + + // Check that the specified user is assigned to judge this entry + $check = DB::table('room_user') + ->where('room_id', $entry->audition->room_id) + ->where('user_id', $user->id)->exists(); + if (! $check) { + throw new ScoreEntryException('This judge is not assigned to judge this entry'); + } + + // Check if a score already exists + if (! $scoreSheet) { + if (ScoreSheet::where('user_id', $user->id)->where('entry_id', $entry->id)->exists()) { + throw new ScoreEntryException('That judge has already entered scores for that entry'); + } + } else { + if ($scoreSheet->user_id !== $user->id) { + throw new ScoreEntryException('Existing score sheet is from a different judge'); + } + if ($scoreSheet->entry_id !== $entry->id) { + throw new ScoreEntryException('Existing score sheet is for a different entry'); + } + } + + // Check the validity of submitted subscores, format array for storage, and sum score + $subscoresRequired = $entry->audition->scoringGuide->subscores; + $subscoresStorageArray = []; + $scoreSheetTotal = 0; + $maxPossible = 0; + if ($scores->count() !== $subscoresRequired->count()) { throw new ScoreEntryException('Invalid number of scores'); } + + foreach ($subscoresRequired as $subscore) { + // check that there is an element in the $scores collection with the key = $subscore->id + if (! $scores->keys()->contains($subscore->id)) { + throw new ScoreEntryException('Invalid Score Submission'); + } + if ($scores[$subscore->id] > $subscore->max_score) { + throw new ScoreEntryException('Supplied subscore exceeds maximum allowed'); + } + + // Add subscore to the storage array + $subscoresStorageArray[$subscore->id] = [ + 'score' => $scores[$subscore->id], + 'subscore_id' => $subscore->id, + 'subscore_name' => $subscore->name, + ]; + + // Multiply subscore by weight and add to the total + $scoreSheetTotal += ($subscore->weight * $scores[$subscore->id]); + + // Add weight to total weights + $maxPossible += ($subscore->weight * $subscore->max_score); + } + + $entry->removeFlag('no_show'); + if ($scoreSheet instanceof ScoreSheet) { + $scoreSheet->update([ + 'user_id' => $user->id, + 'entry_id' => $entry->id, + 'subscores' => $subscoresStorageArray, + 'sheet_total' => ($scoreSheetTotal / $maxPossible) * 100, + ]); + } else { + $scoreSheet = ScoreSheet::create([ + 'user_id' => $user->id, + 'entry_id' => $entry->id, + 'subscores' => $subscoresStorageArray, + 'sheet_total' => ($scoreSheetTotal / $maxPossible) * 100, + ]); + } + + return $scoreSheet; } } From bcaab84dede55fa303f32e98776ba60837ac2448 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 10 Jun 2025 23:35:21 -0500 Subject: [PATCH 4/6] EntrySeeder update --- database/seeders/EntrySeeder.php | 48 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/database/seeders/EntrySeeder.php b/database/seeders/EntrySeeder.php index 9d8334a..c0acbfe 100644 --- a/database/seeders/EntrySeeder.php +++ b/database/seeders/EntrySeeder.php @@ -17,51 +17,59 @@ class EntrySeeder extends Seeder public function run(): void { $students = Student::all(); - $hs_auditions = Audition::where('maximum_grade', '=', '12'); - $freshman_auditions = Audition::where('maximum_grade', '>', '8'); - $jh_auditions = Audition::where('maximum_grade', '=', '9'); - $seventh_auditions = Audition::where('maximum_grade', '=', '7'); foreach ($students as $student) { if ($student->grade > 9) { - $audition = Audition::where('maximum_grade', '=', '12')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '=', '12') + ->inRandomOrder()->first(); } if ($student->grade == 9) { - $audition = Audition::where('maximum_grade', '>', '8')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '>', '8') + ->inRandomOrder()->first(); } if ($student->grade == 8) { - $audition = Audition::where('maximum_grade', '=', '9')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '=', '9') + ->inRandomOrder()->first(); } if ($student->grade == 7) { - $audition = Audition::where('maximum_grade', '=', '7')->inRandomOrder()->first(); + $audition = Audition::where('maximum_grade', '=', '7') + ->inRandomOrder()->first(); } - Entry::factory()->create([ + Entry::create([ 'student_id' => $student->id, 'audition_id' => $audition->id, + 'for_seating' => 1, + 'for_advancement' => 1, ]); if (mt_rand(1, 100) > 90) { if ($student->grade > 9) { - $audition2 = Audition::where('maximum_grade', '=', '12')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '=', '12') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } if ($student->grade == 9) { - $audition2 = Audition::where('maximum_grade', '>', '8')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '>', '8') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } if ($student->grade == 8) { - $audition2 = Audition::where('maximum_grade', '=', '9')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '=', '9') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } if ($student->grade == 7) { - $audition2 = Audition::where('maximum_grade', '=', '7')->where('id', '!=', - $audition->id)->inRandomOrder()->first(); + $audition2 = Audition::where('maximum_grade', '=', '7') + ->where('id', '!=', $audition->id) + ->inRandomOrder()->first(); } - Entry::factory()->create([ + Entry::create([ 'student_id' => $student->id, 'audition_id' => $audition2->id, + 'for_seating' => 1, + 'for_advancement' => 1, ]); } @@ -83,9 +91,11 @@ class EntrySeeder extends Seeder $audition->id)->where('id', '!=', $audition2->id)->inRandomOrder()->first(); } - Entry::factory()->create([ + Entry::create([ 'student_id' => $student->id, 'audition_id' => $audition3->id, + 'for_seating' => 1, + 'for_advancement' => 1, ]); } } From d95e8e577206943ba8758d799a78dc91761c2f3c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 10 Jun 2025 23:46:49 -0500 Subject: [PATCH 5/6] Add sheet_total column to score_sheets table --- ...eet_total_column_to_score_sheets_table.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php diff --git a/database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php b/database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php new file mode 100644 index 0000000..9714f66 --- /dev/null +++ b/database/migrations/2025_06_11_043508_add_sheet_total_column_to_score_sheets_table.php @@ -0,0 +1,28 @@ +decimal('sheet_total', 9, 6)->after('subscores'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('score_sheets', function (Blueprint $table) { + $table->dropColumn('sheet_total'); + }); + } +}; From df732c4f5a92ac4af6110da87bf2753e71dabeb6 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 11 Jun 2025 07:27:48 -0500 Subject: [PATCH 6/6] Rewrite enter score action --- app/Actions/Tabulation/EnterScore.php | 159 ++++++++++++-------------- 1 file changed, 76 insertions(+), 83 deletions(-) diff --git a/app/Actions/Tabulation/EnterScore.php b/app/Actions/Tabulation/EnterScore.php index 407f4cb..ab7be4b 100644 --- a/app/Actions/Tabulation/EnterScore.php +++ b/app/Actions/Tabulation/EnterScore.php @@ -7,11 +7,9 @@ namespace App\Actions\Tabulation; use App\Exceptions\ScoreEntryException; -use App\Models\CalculatedScore; use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; class EnterScore @@ -20,92 +18,18 @@ class EnterScore * @param User $user A user acting as the judge for this sheet * @param Entry $entry An entry to which this score should be assigned * @param array $scores Scores to be entered in the form of SubscoreID => score + * @param ScoreSheet|false $scoreSheet If this is an update to an existing scoresheet, pass it here + * @return ScoreSheet The scoresheet that was created or updated * * @throws ScoreEntryException */ public function __invoke(User $user, Entry $entry, array $scores, ScoreSheet|false $scoreSheet = false): ScoreSheet { - CalculatedScore::where('entry_id', $entry->id)->delete(); + // TODO: Remove the CalculatedScore model and table when rewrite is complete, they'll be obsolete + // CalculatedScore::where('entry_id', $entry->id)->delete(); $scores = collect($scores); - $this->basicChecks($user, $entry, $scores); - $this->checkJudgeAssignment($user, $entry); - $this->checkForExistingScore($user, $entry, $scoreSheet); - $this->validateScoresSubmitted($entry, $scores); - $entry->removeFlag('no_show'); - if ($scoreSheet instanceof ScoreSheet) { - $scoreSheet->update([ - 'user_id' => $user->id, - 'entry_id' => $entry->id, - 'subscores' => $this->subscoresForStorage($entry, $scores), - ]); - } else { - $scoreSheet = ScoreSheet::create([ - 'user_id' => $user->id, - 'entry_id' => $entry->id, - 'subscores' => $this->subscoresForStorage($entry, $scores), - ]); - } - return $scoreSheet; - } - - protected function subscoresForStorage(Entry $entry, Collection $scores) - { - $subscores = []; - foreach ($entry->audition->scoringGuide->subscores as $subscore) { - $subscores[$subscore->id] = [ - 'score' => $scores[$subscore->id], - 'subscore_id' => $subscore->id, - 'subscore_name' => $subscore->name, - ]; - } - - return $subscores; - } - - protected function checkForExistingScore(User $user, Entry $entry, $existingScoreSheet) - { - if (! $existingScoreSheet) { - if (ScoreSheet::where('user_id', $user->id)->where('entry_id', $entry->id)->exists()) { - throw new ScoreEntryException('That judge has already entered scores for that entry'); - } - } else { - if ($existingScoreSheet->user_id !== $user->id) { - throw new ScoreEntryException('Existing score sheet is from a different judge'); - } - if ($existingScoreSheet->entry_id !== $entry->id) { - throw new ScoreEntryException('Existing score sheet is for a different entry'); - } - } - } - - protected function validateScoresSubmitted(Entry $entry, Collection $scores) - { - $subscoresRequired = $entry->audition->scoringGuide->subscores; - - foreach ($subscoresRequired as $subscore) { - // check that there is an element in the $scores collection with the key = $subscore->id - if (! $scores->keys()->contains($subscore->id)) { - throw new ScoreEntryException('Invalid Score Submission'); - } - if ($scores[$subscore->id] > $subscore->max_score) { - throw new ScoreEntryException('Supplied subscore exceeds maximum allowed'); - } - } - } - - protected function checkJudgeAssignment(User $user, Entry $entry) - { - $check = DB::table('room_user') - ->where('room_id', $entry->audition->room_id) - ->where('user_id', $user->id)->exists(); - if (! $check) { - throw new ScoreEntryException('This judge is not assigned to judge this entry'); - } - } - - protected function basicChecks(User $user, Entry $entry, Collection $scores) - { + // Basic Validity Checks if (! $user->exists()) { throw new ScoreEntryException('User does not exist'); } @@ -118,9 +42,78 @@ class EnterScore if ($entry->audition->hasFlag('advancement_published')) { throw new ScoreEntryException('Cannot score an entry in an audition with published advancement'); } - $requiredScores = $entry->audition->scoringGuide->subscores()->count(); - if ($scores->count() !== $requiredScores) { + + // Check that the specified user is assigned to judge this entry + $check = DB::table('room_user') + ->where('room_id', $entry->audition->room_id) + ->where('user_id', $user->id)->exists(); + if (! $check) { + throw new ScoreEntryException('This judge is not assigned to judge this entry'); + } + + // Check if a score already exists + if (! $scoreSheet) { + if (ScoreSheet::where('user_id', $user->id)->where('entry_id', $entry->id)->exists()) { + throw new ScoreEntryException('That judge has already entered scores for that entry'); + } + } else { + if ($scoreSheet->user_id !== $user->id) { + throw new ScoreEntryException('Existing score sheet is from a different judge'); + } + if ($scoreSheet->entry_id !== $entry->id) { + throw new ScoreEntryException('Existing score sheet is for a different entry'); + } + } + + // Check the validity of submitted subscores, format array for storage, and sum score + $subscoresRequired = $entry->audition->scoringGuide->subscores; + $subscoresStorageArray = []; + $scoreSheetTotal = 0; + $maxPossible = 0; + if ($scores->count() !== $subscoresRequired->count()) { throw new ScoreEntryException('Invalid number of scores'); } + + foreach ($subscoresRequired as $subscore) { + // check that there is an element in the $scores collection with the key = $subscore->id + if (! $scores->keys()->contains($subscore->id)) { + throw new ScoreEntryException('Invalid Score Submission'); + } + if ($scores[$subscore->id] > $subscore->max_score) { + throw new ScoreEntryException('Supplied subscore exceeds maximum allowed'); + } + + // Add subscore to the storage array + $subscoresStorageArray[$subscore->id] = [ + 'score' => $scores[$subscore->id], + 'subscore_id' => $subscore->id, + 'subscore_name' => $subscore->name, + ]; + + // Multiply subscore by weight and add to the total + $scoreSheetTotal += ($subscore->weight * $scores[$subscore->id]); + + // Add weight to total weights + $maxPossible += ($subscore->weight * $subscore->max_score); + } + + $entry->removeFlag('no_show'); + if ($scoreSheet instanceof ScoreSheet) { + $scoreSheet->update([ + 'user_id' => $user->id, + 'entry_id' => $entry->id, + 'subscores' => $subscoresStorageArray, + 'sheet_total' => ($scoreSheetTotal / $maxPossible) * 100, + ]); + } else { + $scoreSheet = ScoreSheet::create([ + 'user_id' => $user->id, + 'entry_id' => $entry->id, + 'subscores' => $subscoresStorageArray, + 'sheet_total' => ($scoreSheetTotal / $maxPossible) * 100, + ]); + } + + return $scoreSheet; } }