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,
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 @@
+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/app/Actions/Tabulation/TotalEntryScores.php b/app/Actions/Tabulation/TotalEntryScores.php
index 448e650..8da321f 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,14 @@ 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)
+ ->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.');
+ }
+}
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.
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/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/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');
- }
- }
}
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..744c46e 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);
@@ -96,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/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');
+ });
+ }
+};
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();
+ });
+ }
+};
diff --git a/resources/views/tabulation/auditionSeating.blade.php b/resources/views/tabulation/auditionSeating.blade.php
index 1a66854..78771ac 100644
--- a/resources/views/tabulation/auditionSeating.blade.php
+++ b/resources/views/tabulation/auditionSeating.blade.php
@@ -17,8 +17,7 @@
@if($audition->bonusScore()->count() > 0)
Cannot seat the audition while there are unresolved doublers.
+