From a1a9744305af9fd51e2d638d1a75493f9851a5c8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 9 Jul 2024 10:42:24 -0500 Subject: [PATCH 01/43] Remove events and listeners --- app/Events/AuditionChange.php | 36 ------------------- app/Events/EntryChange.php | 36 ------------------- app/Events/ScoreSheetChange.php | 36 ------------------- app/Events/ScoringGuideChange.php | 36 ------------------- app/Events/SeatingLimitChange.php | 36 ------------------- .../Tabulation/TabulationController.php | 1 + app/Listeners/RefreshAuditionCache.php | 31 ---------------- app/Listeners/RefreshEntryCache.php | 34 ------------------ app/Listeners/RefreshScoreSheetCache.php | 35 ------------------ app/Listeners/RefreshScoringGuideCache.php | 29 --------------- app/Listeners/RefreshSeatingLimitCache.php | 28 --------------- app/Observers/AuditionObserver.php | 15 +++----- app/Observers/RoomObserver.php | 8 ++--- app/Observers/RoomUserObserver.php | 10 +++--- app/Observers/ScoreSheetObserver.php | 10 +++--- app/Observers/ScoringGuideObserver.php | 14 +++----- app/Observers/SeatingLimitObserver.php | 10 +++--- app/Observers/SubscoreDefinitionObserver.php | 15 +++----- app/Observers/UserObserver.php | 6 ++-- app/Providers/AppServiceProvider.php | 25 +------------ app/Services/AuditionService.php | 28 ++++++++------- 21 files changed, 54 insertions(+), 425 deletions(-) delete mode 100644 app/Events/AuditionChange.php delete mode 100644 app/Events/EntryChange.php delete mode 100644 app/Events/ScoreSheetChange.php delete mode 100644 app/Events/ScoringGuideChange.php delete mode 100644 app/Events/SeatingLimitChange.php delete mode 100644 app/Listeners/RefreshAuditionCache.php delete mode 100644 app/Listeners/RefreshEntryCache.php delete mode 100644 app/Listeners/RefreshScoreSheetCache.php delete mode 100644 app/Listeners/RefreshScoringGuideCache.php delete mode 100644 app/Listeners/RefreshSeatingLimitCache.php diff --git a/app/Events/AuditionChange.php b/app/Events/AuditionChange.php deleted file mode 100644 index b8aa81a..0000000 --- a/app/Events/AuditionChange.php +++ /dev/null @@ -1,36 +0,0 @@ -refreshCache = $refreshCache; - } - - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } -} diff --git a/app/Events/EntryChange.php b/app/Events/EntryChange.php deleted file mode 100644 index ae1890c..0000000 --- a/app/Events/EntryChange.php +++ /dev/null @@ -1,36 +0,0 @@ -auditionId = $auditionId; - } - - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } -} diff --git a/app/Events/ScoreSheetChange.php b/app/Events/ScoreSheetChange.php deleted file mode 100644 index 7178c49..0000000 --- a/app/Events/ScoreSheetChange.php +++ /dev/null @@ -1,36 +0,0 @@ -entryId = $entryId; - } - - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } -} diff --git a/app/Events/ScoringGuideChange.php b/app/Events/ScoringGuideChange.php deleted file mode 100644 index b265ad9..0000000 --- a/app/Events/ScoringGuideChange.php +++ /dev/null @@ -1,36 +0,0 @@ - - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } -} diff --git a/app/Events/SeatingLimitChange.php b/app/Events/SeatingLimitChange.php deleted file mode 100644 index 5217dfe..0000000 --- a/app/Events/SeatingLimitChange.php +++ /dev/null @@ -1,36 +0,0 @@ - - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } -} diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php index 78b5e7f..d11c3b2 100644 --- a/app/Http/Controllers/Tabulation/TabulationController.php +++ b/app/Http/Controllers/Tabulation/TabulationController.php @@ -37,6 +37,7 @@ class TabulationController extends Controller public function status() { + // Needs to provide a collection of auditions $auditions = $this->tabulationService->getAuditionsWithStatus('seating'); return view('tabulation.status', compact('auditions')); diff --git a/app/Listeners/RefreshAuditionCache.php b/app/Listeners/RefreshAuditionCache.php deleted file mode 100644 index 0728bad..0000000 --- a/app/Listeners/RefreshAuditionCache.php +++ /dev/null @@ -1,31 +0,0 @@ -auditionService = $cacheService; - } - - /** - * Handle the event. - */ - public function handle(AuditionChange $event): void - { - if ($event->refreshCache) { - $this->auditionService->refreshCache(); - } else { - $this->auditionService->clearCache(); - } - } -} diff --git a/app/Listeners/RefreshEntryCache.php b/app/Listeners/RefreshEntryCache.php deleted file mode 100644 index cac64e6..0000000 --- a/app/Listeners/RefreshEntryCache.php +++ /dev/null @@ -1,34 +0,0 @@ -entryService = $cacheService; - } - - /** - * Handle the event. - */ - public function handle(EntryChange $event): void - { - if ($event->auditionId) { - $this->entryService->clearEntryCacheForAudition($event->auditionId); - } else { - $this->entryService->clearEntryCaches(); - } - } -} diff --git a/app/Listeners/RefreshScoreSheetCache.php b/app/Listeners/RefreshScoreSheetCache.php deleted file mode 100644 index 1604550..0000000 --- a/app/Listeners/RefreshScoreSheetCache.php +++ /dev/null @@ -1,35 +0,0 @@ -scoreService = $scoreService; - } - - /** - * Handle the event. - */ - public function handle(ScoreSheetChange $event): void - { - $this->scoreService->clearScoreSheetCountCache(); - if ($event->entryId) { - $this->scoreService->clearEntryTotalScoresCache($event->entryId); - } - // If we are in local environment, send a success flash message - if (config('app.env') === 'local') { - session()->flash('success','Cleared cache for entry ID ' . $event->entryId); - } - } -} diff --git a/app/Listeners/RefreshScoringGuideCache.php b/app/Listeners/RefreshScoringGuideCache.php deleted file mode 100644 index 3a38cc4..0000000 --- a/app/Listeners/RefreshScoringGuideCache.php +++ /dev/null @@ -1,29 +0,0 @@ -scoreService = $scoreService; - } - - /** - * Handle the event. - */ - public function handle(ScoringGuideChange $event): void - { - $this->scoreService->clearScoringGuideCache(); - $this->scoreService->clearAllCachedTotalScores(); - } -} diff --git a/app/Listeners/RefreshSeatingLimitCache.php b/app/Listeners/RefreshSeatingLimitCache.php deleted file mode 100644 index d309d15..0000000 --- a/app/Listeners/RefreshSeatingLimitCache.php +++ /dev/null @@ -1,28 +0,0 @@ -seatingService = $seatingService; - } - - /** - * Handle the event. - */ - public function handle(SeatingLimitChange $event): void - { - $this->seatingService->refreshLimits(); - } -} diff --git a/app/Observers/AuditionObserver.php b/app/Observers/AuditionObserver.php index 27d139f..aa2bc4c 100644 --- a/app/Observers/AuditionObserver.php +++ b/app/Observers/AuditionObserver.php @@ -13,8 +13,7 @@ class AuditionObserver */ public function created(Audition $audition): void { - AuditionChange::dispatch(); - EntryChange::dispatch($audition->id); + // } /** @@ -22,8 +21,7 @@ class AuditionObserver */ public function updated(Audition $audition): void { - AuditionChange::dispatch(); - EntryChange::dispatch($audition->id); + // } /** @@ -31,8 +29,7 @@ class AuditionObserver */ public function deleted(Audition $audition): void { - AuditionChange::dispatch(); - EntryChange::dispatch($audition->id); + // } /** @@ -40,8 +37,7 @@ class AuditionObserver */ public function restored(Audition $audition): void { - AuditionChange::dispatch(); - EntryChange::dispatch($audition->id); + // } /** @@ -49,7 +45,6 @@ class AuditionObserver */ public function forceDeleted(Audition $audition): void { - AuditionChange::dispatch(); - EntryChange::dispatch($audition->id); + // } } diff --git a/app/Observers/RoomObserver.php b/app/Observers/RoomObserver.php index 984b373..ce2c790 100644 --- a/app/Observers/RoomObserver.php +++ b/app/Observers/RoomObserver.php @@ -20,7 +20,7 @@ class RoomObserver */ public function updated(Room $room): void { - AuditionChange::dispatch(); + // } /** @@ -28,7 +28,7 @@ class RoomObserver */ public function deleted(Room $room): void { - AuditionChange::dispatch(); + // } /** @@ -36,7 +36,7 @@ class RoomObserver */ public function restored(Room $room): void { - AuditionChange::dispatch(); + // } /** @@ -44,6 +44,6 @@ class RoomObserver */ public function forceDeleted(Room $room): void { - AuditionChange::dispatch(); + // } } diff --git a/app/Observers/RoomUserObserver.php b/app/Observers/RoomUserObserver.php index f089319..024f3ba 100644 --- a/app/Observers/RoomUserObserver.php +++ b/app/Observers/RoomUserObserver.php @@ -12,7 +12,7 @@ class RoomUserObserver */ public function created(RoomUser $roomUser): void { - AuditionChange::dispatch(); + // } /** @@ -20,7 +20,7 @@ class RoomUserObserver */ public function updated(RoomUser $roomUser): void { - AuditionChange::dispatch(); + // } /** @@ -28,7 +28,7 @@ class RoomUserObserver */ public function deleted(RoomUser $roomUser): void { - AuditionChange::dispatch(); + // } /** @@ -36,7 +36,7 @@ class RoomUserObserver */ public function restored(RoomUser $roomUser): void { - AuditionChange::dispatch(); + // } /** @@ -44,6 +44,6 @@ class RoomUserObserver */ public function forceDeleted(RoomUser $roomUser): void { - AuditionChange::dispatch(); + // } } diff --git a/app/Observers/ScoreSheetObserver.php b/app/Observers/ScoreSheetObserver.php index 533763e..ceb8711 100644 --- a/app/Observers/ScoreSheetObserver.php +++ b/app/Observers/ScoreSheetObserver.php @@ -12,7 +12,7 @@ class ScoreSheetObserver */ public function created(ScoreSheet $scoreSheet): void { - ScoreSheetChange::dispatch($scoreSheet->entry_id); + // } /** @@ -20,7 +20,7 @@ class ScoreSheetObserver */ public function updated(ScoreSheet $scoreSheet): void { - ScoreSheetChange::dispatch($scoreSheet->entry_id); + // } /** @@ -28,7 +28,7 @@ class ScoreSheetObserver */ public function deleted(ScoreSheet $scoreSheet): void { - ScoreSheetChange::dispatch($scoreSheet->entry_id); + // } /** @@ -36,7 +36,7 @@ class ScoreSheetObserver */ public function restored(ScoreSheet $scoreSheet): void { - ScoreSheetChange::dispatch($scoreSheet->entry_id); + // } /** @@ -44,6 +44,6 @@ class ScoreSheetObserver */ public function forceDeleted(ScoreSheet $scoreSheet): void { - ScoreSheetChange::dispatch($scoreSheet->entry_id); + // } } diff --git a/app/Observers/ScoringGuideObserver.php b/app/Observers/ScoringGuideObserver.php index 00a1dc7..bf35243 100644 --- a/app/Observers/ScoringGuideObserver.php +++ b/app/Observers/ScoringGuideObserver.php @@ -13,7 +13,7 @@ class ScoringGuideObserver */ public function created(ScoringGuide $scoringGuide): void { - ScoringGuideChange::dispatch(); + // } /** @@ -21,8 +21,7 @@ class ScoringGuideObserver */ public function updated(ScoringGuide $scoringGuide): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -30,8 +29,7 @@ class ScoringGuideObserver */ public function deleted(ScoringGuide $scoringGuide): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -39,8 +37,7 @@ class ScoringGuideObserver */ public function restored(ScoringGuide $scoringGuide): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -48,7 +45,6 @@ class ScoringGuideObserver */ public function forceDeleted(ScoringGuide $scoringGuide): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } } diff --git a/app/Observers/SeatingLimitObserver.php b/app/Observers/SeatingLimitObserver.php index 5b36ca7..4f4602f 100644 --- a/app/Observers/SeatingLimitObserver.php +++ b/app/Observers/SeatingLimitObserver.php @@ -12,7 +12,7 @@ class SeatingLimitObserver */ public function created(SeatingLimit $seatingLimit): void { - ScoringGuideChange::dispatch(); + // } /** @@ -20,7 +20,7 @@ class SeatingLimitObserver */ public function updated(SeatingLimit $seatingLimit): void { - ScoringGuideChange::dispatch(); + // } /** @@ -28,7 +28,7 @@ class SeatingLimitObserver */ public function deleted(SeatingLimit $seatingLimit): void { - ScoringGuideChange::dispatch(); + // } /** @@ -36,7 +36,7 @@ class SeatingLimitObserver */ public function restored(SeatingLimit $seatingLimit): void { - ScoringGuideChange::dispatch(); + // } /** @@ -44,6 +44,6 @@ class SeatingLimitObserver */ public function forceDeleted(SeatingLimit $seatingLimit): void { - ScoringGuideChange::dispatch(); + // } } diff --git a/app/Observers/SubscoreDefinitionObserver.php b/app/Observers/SubscoreDefinitionObserver.php index ae54dec..afbeccb 100644 --- a/app/Observers/SubscoreDefinitionObserver.php +++ b/app/Observers/SubscoreDefinitionObserver.php @@ -13,8 +13,7 @@ class SubscoreDefinitionObserver */ public function created(SubscoreDefinition $subscoreDefinition): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -22,8 +21,7 @@ class SubscoreDefinitionObserver */ public function updated(SubscoreDefinition $subscoreDefinition): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -31,8 +29,7 @@ class SubscoreDefinitionObserver */ public function deleted(SubscoreDefinition $subscoreDefinition): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -40,8 +37,7 @@ class SubscoreDefinitionObserver */ public function restored(SubscoreDefinition $subscoreDefinition): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } /** @@ -49,7 +45,6 @@ class SubscoreDefinitionObserver */ public function forceDeleted(SubscoreDefinition $subscoreDefinition): void { - AuditionChange::dispatch(); - ScoringGuideChange::dispatch(); + // } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index e1dc7d3..81c623f 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -20,7 +20,7 @@ class UserObserver */ public function updated(User $user): void { - AuditionChange::dispatch(); + // } /** @@ -28,7 +28,7 @@ class UserObserver */ public function deleted(User $user): void { - AuditionChange::dispatch(); + // } /** @@ -44,6 +44,6 @@ class UserObserver */ public function forceDeleted(User $user): void { - AuditionChange::dispatch(); + // } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3107614..b804a12 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -102,29 +102,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - Event::listen( - AuditionChange::class, - RefreshAuditionCache::class - ); - - Event::listen( - EntryChange::class, - RefreshEntryCache::class - ); - - Event::listen( - ScoringGuideChange::class, - RefreshScoringGuideCache::class - ); - - Event::listen( - ScoreSheetChange::class, - RefreshScoreSheetCache::class - ); - - Event::listen( - SeatingLimitChange::class, - RefreshSeatingLimitCache::class - ); + } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index 6e2d9a9..a9dc674 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -5,9 +5,7 @@ namespace App\Services; use App\Models\Audition; use App\Models\ScoringGuide; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Session; class AuditionService { @@ -28,19 +26,20 @@ class AuditionService public function getAuditions($mode = 'seating'): \Illuminate\Database\Eloquent\Collection { $auditions = Cache::remember($this->cacheKey, 3600, function () { - if (App::environment('local')) { - Session::flash('success', 'Audition Cache Updated'); - } return Audition::with(['scoringGuide.subscores', 'judges']) ->withCount('judges') ->withCount('entries') - ->withCount(['entries as seating_entries_count' => function (Builder $query) { - $query->where('for_seating', true); - }]) - ->withCount(['entries as advancement_entries_count' => function (Builder $query) { - $query->where('for_advancement', true); - }]) + ->withCount([ + 'entries as seating_entries_count' => function (Builder $query) { + $query->where('for_seating', true); + }, + ]) + ->withCount([ + 'entries as advancement_entries_count' => function (Builder $query) { + $query->where('for_advancement', true); + }, + ]) ->orderBy('score_order') ->get() ->keyBy('id'); @@ -80,18 +79,21 @@ class AuditionService $cacheKey, now()->addHour(), function () { - return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition) => $audition->hasFlag('seats_published')); + return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition + ) => $audition->hasFlag('seats_published')); }); } public function getPublishedAdvancementAuditions() { $cacheKey = 'publishedAdvancementAuditions'; + return Cache::remember( $cacheKey, now()->addHour(), function () { - return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition) => $audition->hasFlag('advancement_published')); + return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition + ) => $audition->hasFlag('advancement_published')); }); } -- 2.39.5 From 9058e8f06d193f5bf2a17e6b23b079dad9ff7a2b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 9 Jul 2024 12:31:06 -0500 Subject: [PATCH 02/43] Cleanup --- .../Controllers/Admin/SchoolController.php | 4 +- .../Tabulation/TabulationController.php | 5 - app/Http/Controllers/UserController.php | 1 - app/Models/Room.php | 2 - app/Services/AuditionService.php | 83 -------- app/Services/DoublerService.php | 103 +--------- app/Services/EntryService.php | 79 +------ .../Invoice/InvoiceOneFeePerEntry.php | 2 +- app/Services/ScoreService.php | 193 +----------------- app/Services/SeatingService.php | 66 ------ app/Services/TabulationService.php | 149 +------------- app/Settings.php | 1 + 12 files changed, 16 insertions(+), 672 deletions(-) diff --git a/app/Http/Controllers/Admin/SchoolController.php b/app/Http/Controllers/Admin/SchoolController.php index da73483..3d335a6 100644 --- a/app/Http/Controllers/Admin/SchoolController.php +++ b/app/Http/Controllers/Admin/SchoolController.php @@ -23,11 +23,9 @@ class SchoolController extends Controller public function index() { - if (! Auth::user()->is_admin) { - abort(403); - } $schools = School::with(['users', 'students', 'entries'])->orderBy('name')->get(); $schoolTotalFees = []; + foreach ($schools as $school) { $schoolTotalFees[$school->id] = $this->invoiceService->getGrandTotal($school->id); } diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php index d11c3b2..e49c324 100644 --- a/app/Http/Controllers/Tabulation/TabulationController.php +++ b/app/Http/Controllers/Tabulation/TabulationController.php @@ -103,8 +103,6 @@ class TabulationController extends Controller $audition->addFlag('seats_published'); $request->session()->forget($sessionKey); Cache::forget('resultsSeatList'); - Cache::forget('publishedAuditions'); - Cache::forget('audition'.$audition->id.'seats'); // TODO move the previous Cache functions here and in unplublish to the services, need to add an event for publishing an audition as well return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]); @@ -115,9 +113,6 @@ class TabulationController extends Controller // TODO move this to SeatingService $audition->removeFlag('seats_published'); Cache::forget('resultsSeatList'); - Cache::forget('publishedAuditions'); - Cache::forget('audition'.$audition->id.'seats'); - $this->seatingService->forgetSeatsForAudition($audition->id); Seat::where('audition_id', $audition->id)->delete(); return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 9d60447..d109f38 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -83,4 +83,3 @@ class UserController extends Controller } -//TODO allow users to modify their profile information. RoomJudgeChange::dispatch(); when they do diff --git a/app/Models/Room.php b/app/Models/Room.php index d92b944..0a8d2e9 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -46,13 +46,11 @@ class Room extends Model { $this->judges()->attach($userId); $this->load('judges'); - AuditionChange::dispatch(); } public function removeJudge($userId): void { $this->judges()->detach($userId); $this->load('judges'); - AuditionChange::dispatch(); } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index a9dc674..ba0db24 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -9,7 +9,6 @@ use Illuminate\Support\Facades\Cache; class AuditionService { - protected $cacheKey = 'auditions'; /** * Create a new class instance. @@ -19,87 +18,5 @@ class AuditionService // } - /** - * Return or fill cache of auditions including the audition, - * scoringGuide.subscores, judges, judges_count, and entries_count - */ - public function getAuditions($mode = 'seating'): \Illuminate\Database\Eloquent\Collection - { - $auditions = Cache::remember($this->cacheKey, 3600, function () { - return Audition::with(['scoringGuide.subscores', 'judges']) - ->withCount('judges') - ->withCount('entries') - ->withCount([ - 'entries as seating_entries_count' => function (Builder $query) { - $query->where('for_seating', true); - }, - ]) - ->withCount([ - 'entries as advancement_entries_count' => function (Builder $query) { - $query->where('for_advancement', true); - }, - ]) - ->orderBy('score_order') - ->get() - ->keyBy('id'); - }); - - switch ($mode) { - case 'seating': - return $auditions->filter(fn ($audition) => $audition->for_seating); - case 'advancement': - return $auditions->filter(fn ($audition) => $audition->for_advancement); - default: - return $auditions; - } - } - - public function getAudition($id): Audition - { - return $this->getAuditions()->firstWhere('id', $id); - } - - public function refreshCache(): void - { - Cache::forget($this->cacheKey); - $this->getAuditions(); - } - - public function clearCache(): void - { - Cache::forget($this->cacheKey); - } - - public function getPublishedAuditions() - { - $cacheKey = 'publishedAuditions'; - - return Cache::remember( - $cacheKey, - now()->addHour(), - function () { - return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition - ) => $audition->hasFlag('seats_published')); - }); - } - - public function getPublishedAdvancementAuditions() - { - $cacheKey = 'publishedAdvancementAuditions'; - - return Cache::remember( - $cacheKey, - now()->addHour(), - function () { - return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition - ) => $audition->hasFlag('advancement_published')); - }); - - } - - public function clearPublishedAuditionsCache(): void - { - Cache::forget('publishedAuditions'); - } } diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index a7e3c55..2f7fe3f 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -20,106 +20,15 @@ class DoublerService /** * Create a new class instance. */ - public function __construct(AuditionService $auditionService, TabulationService $tabulationService, SeatingService $seatingService) - { - $this->auditionService = $auditionService; + public function __construct( + AuditionService $auditionService, + TabulationService $tabulationService, + SeatingService $seatingService + ) { + $this->auditionService = $auditionService; $this->tabulationService = $tabulationService; $this->seatingService = $seatingService; } - /** - * Returns a collection of students that have more than one entry - */ - public function getDoublers(): \Illuminate\Database\Eloquent\Collection - { - // TODO creating or destroying an entry should refresh the doubler cache - // TODO needs to split by event so that a doubler may enter jazz and concert events for example - $doublers = Cache::remember($this->doublersCacheKey, 60, function () { - return Student::withCount(['entries' => function (Builder $query) { - $query->where('for_seating', true); - }]) - ->with(['entries' => function (Builder $query) { - $query->where('for_seating', true); - }]) - ->havingRaw('entries_count > ?', [1]) - ->get(); - }); - return $doublers; - } - - public function refreshDoublerCache() - { - Cache::forget($this->doublersCacheKey); - $this->getDoublers(); - } - - /** - * Returns an array of information about each entry for a specific doubler. Info for each entry includes - * entryID - * auditionID - * auditionName - * rank => This student's rank in the given audition - * unscored => How many entries remain to be scored in this audition - * limits => acceptance limits for this audition - * status => accepted, declined, or undecided - * - * @param int $studentId The ID of the doubler - */ - public function getDoublerInfo($studentId): array - { - $doubler = $this->getDoublers()->firstWhere('id', $studentId); - - // Split $doubler->entries into two arrays based on the result of hasFlag('declined') - $undecidedEntries = $doubler->entries->filter(function ($entry) { - return ! $entry->hasFlag('declined'); - }); - $acceptedEntry = null; - if ($undecidedEntries->count() == 1) { - $acceptedEntry = $undecidedEntries->first(); - } - // TODO can I rewrite this? - - // When getting a doubler we need to know - // 1) What their entries are - // 2) For each audition they're entered in, what is their rank - // 3) For each audition they're entered in, how many entries are unscored - // 4) How many are accepted on that instrument - // 5) Status - accepted, declined or undecided - - $info = []; - - foreach ($doubler->entries as $entry) { - if ($entry->hasFlag('declined')) { - $status = 'declined'; - } elseif ($entry === $acceptedEntry) { - $status = 'accepted'; - } else { - $status = 'undecided'; - } - $info[$entry->id] = [ - 'entryID' => $entry->id, - 'auditionID' => $entry->audition_id, - 'auditionName' => $this->auditionService->getAudition($entry->audition_id)->name, - 'rank' => $this->tabulationService->entryRank($entry), - 'unscored' => $this->tabulationService->remainingEntriesForAudition($entry->audition_id), - 'limits' => $this->seatingService->getLimitForAudition($entry->audition_id), - 'status' => $status, - ]; - $entry->audition = $this->auditionService->getAudition($entry->audition_id); - } - - return $info; - } - - /** - * Checks if a student is a doubler based on the given student ID - * - * @param int $studentId The ID of the student to check - * @return bool Returns true if the student is a doubler, false otherwise - */ - public function studentIsDoubler($studentId): bool - { - return $this->getDoublers()->contains('id', $studentId); - } } diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index f2071d6..0954889 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -8,89 +8,16 @@ use Illuminate\Support\Facades\Cache; class EntryService { - protected $auditionCache; /** * Create a new class instance. */ - public function __construct(AuditionService $auditionCache) + public function __construct() { - $this->auditionCache = $auditionCache; + // } - /** - * Return a collection of all entries for the provided auditionId along with the - * student.school for each entry. - * - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getEntriesForAudition($auditionId, $mode = 'seating') - { - // TODO this invokes a lot of lazy loading. Perhaps cache the data for all entries then draw from that for each audition - $cacheKey = 'audition'.$auditionId.'entries'; - - $entries = Cache::remember($cacheKey, 3600, function () use ($auditionId) { - return Entry::where('audition_id', $auditionId) - ->with('student.school') - ->get() - ->keyBy('id'); - }); - - switch ($mode) { - case 'seating': - return $entries->filter(function ($entry) { - return $entry->for_seating; - }); - case 'advancement': - return $entries->filter(function ($entry) { - return $entry->for_advancement; - }); - default: - return $entries; - } - } - - /** - * Returns a collection of collections of entries, one collection for each audition. - * The outer collection is keyed by the audition ID. The included entries are - * with their student.school. - */ - public function getAllEntriesByAudition(): Collection - { - $auditions = $this->auditionCache->getAuditions(); - $allEntries = []; - foreach ($auditions as $audition) { - $allEntries[$audition->id] = $this->getEntriesForAudition($audition->id); - } - - return collect($allEntries); - } - - public function getAllEntries() - { - $cacheKey = 'allEntries'; - - return Cache::remember($cacheKey, 5, function () { - return Entry::all(); - }); - } - - public function clearEntryCacheForAudition($auditionId): void - { - $cacheKey = 'audition'.$auditionId.'entries'; - Cache::forget($cacheKey); - Cache::forget('allEntries'); - } - - public function clearEntryCaches(): void - { - $auditions = $this->auditionCache->getAuditions(); - foreach ($auditions as $audition) { - $this->clearEntryCacheForAudition($audition->id); - } - } - - public function entryIsLate(Entry $entry): bool + public function isEntryLate(Entry $entry): bool { if ($entry->hasFlag('wave_late_fee')) { return false; diff --git a/app/Services/Invoice/InvoiceOneFeePerEntry.php b/app/Services/Invoice/InvoiceOneFeePerEntry.php index b08b27d..1ca5b34 100644 --- a/app/Services/Invoice/InvoiceOneFeePerEntry.php +++ b/app/Services/Invoice/InvoiceOneFeePerEntry.php @@ -39,7 +39,7 @@ class InvoiceOneFeePerEntry implements InvoiceDataService foreach ($school->students as $student) { foreach ($entries[$student->id] ?? [] as $entry) { $entryFee = $entry->audition->entry_fee / 100; - $lateFee = $this->entryService->entryIsLate($entry) ? auditionSetting('late_fee') / 100 : 0; + $lateFee = $this->entryService->isEntryLate($entry) ? auditionSetting('late_fee') / 100 : 0; $invoiceData['lines'][] = [ 'student_name' => $student->full_name(true), diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index e15d9da..03fe2b1 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -3,11 +3,12 @@ namespace App\Services; use App\Models\Entry; +use App\Models\ScoreSheet; use App\Models\ScoringGuide; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; -use App\Models\ScoreSheet; + use function array_unshift; class ScoreService @@ -25,194 +26,4 @@ class ScoreService $this->entryCache = $entryCache; } - /** - * Cache all scoring guides - */ - public function getScoringGuides(): \Illuminate\Database\Eloquent\Collection - { - $cacheKey = 'scoringGuides'; - - return Cache::remember($cacheKey, 3600, fn () => ScoringGuide::with('subscores')->withCount('subscores')->get()); - } - - /** - * Retrieve a single scoring guide from the cache - */ - public function getScoringGuide($id): ScoringGuide - { - return $this->getScoringGuides()->find($id); - } - - /** - * Clear the scoring guide cache - */ - public function clearScoringGuideCache(): void - { - Cache::forget('scoringGuides'); - } - - /** - * Returns an array where each key is an entry id and the value is the number - * of score sheets assigned to that entry. - * - * @return Collection - */ - public function entryScoreSheetCounts() - { - $cacheKey = 'entryScoreSheetCounts'; - - return Cache::remember($cacheKey, 10, function () { - // For each Entry get the number of ScoreSheets associated with it - $scoreSheetCountsByEntry = ScoreSheet::select('entry_id', DB::raw('count(*) as count')) - ->groupBy('entry_id') - ->get() - ->pluck('count', 'entry_id'); - - $entryScoreSheetCounts = []; - $entries = $this->entryCache->getAllEntries(); - foreach ($entries as $entry) { - $entryScoreSheetCounts[$entry->id] = $scoreSheetCountsByEntry[$entry->id] ?? 0; - } - - return $entryScoreSheetCounts; - - }); - - } - - /** - * Get final scores array for the requested entry. The first element is the total score. The following elements are sums - * of each subscore in tiebreaker order - * - * @return array - */ - public function entryTotalScores(Entry $entry) - { - $cacheKey = 'entry'.$entry->id.'totalScores'; - - return Cache::remember($cacheKey, 3600, function () use ($entry) { - return $this->calculateFinalScoreArray($entry->audition->scoring_guide_id, $entry->scoreSheets); - }); - } - - /** - * Calculate and cache scores for all entries for the provided audition ID - * - * @return void - */ - public function calculateScoresForAudition($auditionId, $mode= 'seating') - { - static $alreadyChecked = []; - // if $auditionId is in the array $alreadyChecked return - if (in_array($auditionId, $alreadyChecked)) { - return; - } - $alreadyChecked[] = $auditionId; - $audition = $this->auditionCache->getAudition($auditionId); - $scoringGuideId = $audition->scoring_guide_id; - $entries = $this->entryCache->getEntriesForAudition($auditionId, $mode); - $entries->load('scoreSheets'); // TODO Cache this somehow, it's expensive and repetitive on the seating page - - foreach ($entries as $entry) { - $cacheKey = 'entry'.$entry->id.'totalScores'; - if (Cache::has($cacheKey)) { - continue; - } - $thisTotalScore = $this->calculateFinalScoreArray($scoringGuideId, $entry->scoreSheets); - Cache::put($cacheKey, $thisTotalScore, 3600); - } - } - - public function clearScoreSheetCountCache() - { - $cacheKey = 'entryScoreSheetCounts'; - Cache::forget($cacheKey); - } - - public function clearEntryTotalScoresCache($entryId) - { - $cacheKey = 'entry'.$entryId.'totalScores'; - Cache::forget($cacheKey); - } - - public function clearAllCachedTotalScores() - { - foreach ($this->entryCache->getAllEntries() as $entry) { - $cacheKey = 'entry'.$entry->id.'totalScores'; - Cache::forget($cacheKey); - } - } - - /** - * Calculate final score using the provided scoring guide and score sheets. Returns an array of scores - * The first element is the total score. The following elements are the sum of each subscore - * in tiebreaker order. - */ - public function calculateFinalScoreArray($scoringGuideId, array|Collection $scoreSheets): array - { - - $sg = $this->getScoringGuide($scoringGuideId); - - // TODO cache the scoring guides with their subscores - $subscores = $sg->subscores->sortBy('tiebreak_order'); - - $ignoredSubscores = []; // This will be subscores not used for seating - - // Init final scores array - $finalScoresArray = []; - foreach ($subscores as $subscore) { - if (! $subscore->for_seating) { // Ignore scores that are not for seating - $ignoredSubscores[] = $subscore->id; - - continue; - } - $finalScoresArray[$subscore->id] = 0; - } - - foreach ($scoreSheets as $sheet) { - foreach ($sheet->subscores as $ss) { - if (in_array($ss['subscore_id'], $ignoredSubscores)) { // Ignore scores that are not for seating - continue; - } - $finalScoresArray[$ss['subscore_id']] += $ss['score']; - } - } - - // calculate weighted final score - $totalScore = 0; - $totalWeight = 0; - foreach ($subscores as $subscore) { - if (in_array($subscore->id, $ignoredSubscores)) { // Ignore scores that are not for seating - continue; - } - $totalScore += ($finalScoresArray[$subscore->id] * $subscore->weight); - $totalWeight += $subscore->weight; - } - $totalScore = ($totalScore / $totalWeight); - array_unshift($finalScoresArray, $totalScore); - - return $finalScoresArray; - } - - /** - * Validate that the judge on the score sheet is actually assigned to judge - * then entry - * - * @return bool - */ - public function validateScoreSheet(ScoreSheet $sheet) - { - // TODO use this when calculating scores - $entry = $this->entryCache->getAllEntries()->find($sheet->entry_id); - $audition = $this->auditionCache->getAudition($entry->audition_id); - $validJudges = $audition->judges; - // send a laravel flash message with an error if the $sheet->user_id is not in the collection $validJudges - if (! $validJudges->contains('id', $sheet->user_id)) { - session()->flash('error', 'Entry ID '.$sheet->entry_id.' has an invalid score entered by '.$sheet->judge->full_name()); - } - - // check if $sheet->user_id is in the collection $validJudges, return false if not, true if it is - return $validJudges->contains('id', $sheet->user_id); - - } } diff --git a/app/Services/SeatingService.php b/app/Services/SeatingService.php index 1b39a30..993adeb 100644 --- a/app/Services/SeatingService.php +++ b/app/Services/SeatingService.php @@ -21,70 +21,4 @@ class SeatingService $this->tabulationService = $tabulationService; } - public function getAcceptanceLimits() - { - return Cache::remember($this->limitsCacheKey, now()->addDay(), function () { - $limits = SeatingLimit::with('ensemble')->get(); - // Sort limits by ensemble->rank - $limits = $limits->sortBy(function ($limit) { - return $limit->ensemble->rank; - }); - - return $limits->groupBy('audition_id'); - }); - } - - public function getLimitForAudition($auditionId) - { - if (! $this->getAcceptanceLimits()->has($auditionId)) { - return new \Illuminate\Database\Eloquent\Collection(); - } - return $this->getAcceptanceLimits()[$auditionId]; - } - - public function refreshLimits(): void - { - Cache::forget($this->limitsCacheKey); - } - - public function getSeatableEntries($auditionId) - { - $entries = $this->tabulationService->auditionEntries($auditionId); - - return $entries->reject(function ($entry) { - return $entry->hasFlag('declined'); - }); - } - - public function getSeatsForAudition($auditionId) - { - $cacheKey = 'audition'.$auditionId.'seats'; - // TODO rework to pull entry info from cache - return Cache::remember($cacheKey, now()->addHour(), function () use ($auditionId) { - return Seat::with('entry.student.school') - ->where('audition_id', $auditionId) - ->orderBy('seat') - ->get() - ->groupBy('ensemble_id'); - }); - } - - public function forgetSeatsForAudition($auditionId) - { - $cacheKey = 'audition'.$auditionId.'seats'; - Cache::forget($cacheKey); - } - - public function getEnsemblesForEvent($eventId) - { - static $eventEnsembles = []; - - if (array_key_exists($eventId, $eventEnsembles)) { - return $eventEnsembles[$eventId]; - } - $event = Event::find($eventId); - $eventEnsembles[$eventId] = $event->ensembles; - - return $eventEnsembles[$eventId]; - } } diff --git a/app/Services/TabulationService.php b/app/Services/TabulationService.php index 3957432..287c741 100644 --- a/app/Services/TabulationService.php +++ b/app/Services/TabulationService.php @@ -21,156 +21,11 @@ class TabulationService public function __construct( AuditionService $auditionService, ScoreService $scoreService, - EntryService $entryService) - { + EntryService $entryService + ) { $this->auditionService = $auditionService; $this->scoreService = $scoreService; $this->entryService = $entryService; } - /** - * Returns the rank of the entry in its audition - * - * @return mixed - */ - public function entryRank(Entry $entry) - { - return $this->auditionEntries($entry->audition_id)[$entry->id]->rank; - } - - /** - * Returns a collection of entries including their calculated final_score_array and ranked - * based upon their scores. - * - * @return \Illuminate\Support\Collection|mixed - */ - public function auditionEntries(int $auditionId, $mode = 'seating') - { - static $cache = []; - if (isset($cache[$auditionId])) { - return $cache[$auditionId]; - } - - $audition = $this->auditionService->getAudition($auditionId); - $entries = $this->entryService->getEntriesForAudition($auditionId, $mode); - $this->scoreService->calculateScoresForAudition($auditionId); - // TODO will need to pass a mode to the above function to only use subscores for hte appropriate mode - foreach ($entries as $entry) { - $entry->final_score_array = $this->scoreService->entryTotalScores($entry); - $entry->scoring_complete = ($this->scoreService->entryScoreSheetCounts()[$entry->id] == $audition->judges_count); - } - // Sort the array $entries by the first element in the final_score_array on each entry, then by the second element in that array continuing through each element in the final_score_array for each entry - $entries = $entries->sort(function ($a, $b) { - for ($i = 0; $i < count($a->final_score_array); $i++) { - if ($a->final_score_array[$i] != $b->final_score_array[$i]) { - return $b->final_score_array[$i] > $a->final_score_array[$i] ? 1 : -1; - } - } - - return 0; - }); - //TODO verify this actually sorts by subscores correctly - - // Assign a rank to each entry. In the case of a declined seat by a doubler, indicate as so and do not increment rank - $n = 1; - /** @var Entry $entry */ - foreach ($entries as $entry) { - if (! $entry->hasFlag('declined') or $mode != 'seating') { - $entry->rank = $n; - $n++; - } else { - $entry->rank = $n.' - declined'; - } - } - - $cache[$auditionId] = $entries->keyBy('id'); - - return $entries->keyBy('id'); - } - - public function entryScoreSheetsAreValid(Entry $entry): bool - { - //TODO consider making this move the invalid score to another database for further investigation - $validJudges = $this->auditionService->getAudition($entry->audition_id)->judges; - foreach ($entry->scoreSheets as $sheet) { - if (! $validJudges->contains($sheet->user_id)) { - $invalidJudge = User::find($sheet->user_id); - Session::flash('error', 'Invalid scores for entry '.$entry->id.' exist from '.$invalidJudge->full_name()); - - return false; - } - } - - return true; - } - - /** - * Returns the number of un-scored entries for the audition with the given ID. - * - * @return mixed - */ - public function remainingEntriesForAudition($auditionId, $mode = 'seating') - { - $audition = $this->getAuditionsWithStatus($mode)[$auditionId]; - - switch ($mode) { - case 'seating': - return $audition->seating_entries_count - $audition->scored_entries_count; - case 'advancement': - return $audition->advancement_entries_count - $audition->scored_entries_count; - } - - return $audition->entries_count - $audition->scored_entries_count; - } - - /** - * Get the array of all auditions from the cache. For each one, set a property - * scored_entries_count that indicates the number of entries for that audition that - * have a number of score sheets equal to the number of judges for that audition. - * - * @return mixed - */ - public function getAuditionsWithStatus($mode = 'seating') - { - return Cache::remember('auditionsWithStatus', 30, function () use ($mode) { - - // Retrieve auditions from the cache and load entry IDs - $auditions = $this->auditionService->getAuditions($mode); - // Iterate over the auditions and calculate the scored_entries_count - foreach ($auditions as $audition) { - $scored_entries_count = 0; - $entries_to_check = $this->entryService->getEntriesForAudition($audition->id); - - switch ($mode) { - case 'seating': - $entries_to_check = $entries_to_check->filter(function ($entry) { - return $entry->for_seating; - }); - $auditions = $auditions->filter(function ($audition) { - return $audition->for_seating; - }); - break; - case 'advancement': - $entries_to_check = $entries_to_check->filter(function ($entry) { - return $entry->for_advancement; - }); - $auditions = $auditions->filter(function ($audition) { - return $audition->for_advancement; - }); - break; - } - - foreach ($entries_to_check as $entry) { - if ($this->scoreService->entryScoreSheetCounts()[$entry->id] - $audition->judges_count == 0) { - $scored_entries_count++; - } - } - - $audition->scored_entries_count = $scored_entries_count; - } - - return $auditions; - - }); - } } diff --git a/app/Settings.php b/app/Settings.php index c31f260..d26e314 100644 --- a/app/Settings.php +++ b/app/Settings.php @@ -25,6 +25,7 @@ class Settings public static function get($key, $default = null) { $settings = Cache::get(self::$cacheKey, []); + return $settings[$key] ?? $default; } -- 2.39.5 From 4ff5e1dbcb3e1f51e016f69bc59728571b568dec Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 9 Jul 2024 12:32:05 -0500 Subject: [PATCH 03/43] Seating Status Screen --- .../Tabulation/SeatingStatusController.php | 32 +++++++++++++++++++ app/Models/Audition.php | 10 ++++-- resources/views/tabulation/status.blade.php | 21 ++++-------- routes/tabulation.php | 20 +++++++++--- 4 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 app/Http/Controllers/Tabulation/SeatingStatusController.php diff --git a/app/Http/Controllers/Tabulation/SeatingStatusController.php b/app/Http/Controllers/Tabulation/SeatingStatusController.php new file mode 100644 index 0000000..48b0757 --- /dev/null +++ b/app/Http/Controllers/Tabulation/SeatingStatusController.php @@ -0,0 +1,32 @@ +withCount(['entries', 'unscoredEntries'])->with('flags')->get(); + $auditionData = []; + foreach ($auditions as $audition) { + $auditionData[] = [ + 'id' => $audition->id, + 'name' => $audition->name, + 'scoredEntriesCount' => $audition->entries_count - $audition->unscored_entries_count, + 'totalEntriesCount' => $audition->entries_count, + 'scoredPercentage' => $audition->entries_count > 0 ? ($audition->entries_count - $audition->unscored_entries_count) / $audition->entries_count * 100 : 100, + 'scoringComplete' => $audition->unscored_entries_count === 0, + 'seatsPublished' => $audition->hasFlag('seats_published'), + ]; + } + $auditionData = collect($auditionData); + return view('tabulation.status', compact('auditionData')); + } +} diff --git a/app/Models/Audition.php b/app/Models/Audition.php index 21d6c78..9553d52 100644 --- a/app/Models/Audition.php +++ b/app/Models/Audition.php @@ -29,6 +29,12 @@ class Audition extends Model return $this->hasMany(Entry::class); } + public function unscoredEntries() + { + return $this->hasMany(Entry::class) + ->whereDoesntHave('scoreSheets'); + } + public function room(): BelongsTo { return $this->belongsTo(Room::class); @@ -114,12 +120,12 @@ class Audition extends Model public function scopeForSeating(Builder $query): void { - $query->where('for_seating', 1); + $query->where('for_seating', 1)->orderBy('score_order'); } public function scopeForAdvancement(Builder $query): void { - $query->where('for_advancement', 1); + $query->where('for_advancement', 1)->orderBy('score_order'); } public function scopeSeatsPublished(Builder $query): Builder diff --git a/resources/views/tabulation/status.blade.php b/resources/views/tabulation/status.blade.php index bf594b1..af469ad 100644 --- a/resources/views/tabulation/status.blade.php +++ b/resources/views/tabulation/status.blade.php @@ -13,33 +13,26 @@ - @foreach($auditions as $audition) - @php - $percent = 100; - if($audition->seating_entries_count > 0) { - $percent = round(($audition->scored_entries_count / $audition->seating_entries_count) * 100); - } - @endphp + @foreach($auditionData as $audition) - - +
- {{ $audition->name }} - {{ $audition->scored_entries_count }} / {{ $audition->seating_entries_count }} Scored + {{ $audition['name'] }} + {{ $audition['scoredEntriesCount'] }} / {{ $audition['totalEntriesCount'] }} Scored
-
+
- @if( $audition->scored_entries_count == $audition->seating_entries_count) + @if( $audition['scoringComplete']) @endif - @if( $audition->hasFlag('seats_published')) + @if( $audition['seatsPublished']) @endif diff --git a/routes/tabulation.php b/routes/tabulation.php index 956ddde..a7118dd 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -1,4 +1,5 @@ group(function () Route::get('/choose_entry', 'chooseEntry')->name('scores.chooseEntry'); Route::get('/entry', 'entryScoreSheet')->name('scores.entryScoreSheet'); Route::post('/entry/{entry}', 'saveEntryScoreSheet')->name('scores.saveEntryScoreSheet'); - Route::delete('/{score}', [\App\Http\Controllers\Tabulation\ScoreController::class, 'destroyScore'])->name('scores.destroy'); + Route::delete('/{score}', + [\App\Http\Controllers\Tabulation\ScoreController::class, 'destroyScore'])->name('scores.destroy'); }); // Entry Flagging - Route::prefix('entry-flags/')->controller(\App\Http\Controllers\Tabulation\EntryFlagController::class)->group(function () { + Route::prefix('entry-flags/')->controller(\App\Http\Controllers\Tabulation\EntryFlagController::class)->group(function ( + ) { Route::get('/choose_no_show', 'noShowSelect')->name('entry-flags.noShowSelect'); Route::get('/propose-no-show', 'noShowConfirm')->name('entry-flags.confirmNoShow'); Route::post('/no-show/{entry}', 'enterNoShow')->name('entry-flags.enterNoShow'); Route::delete('/no-show/{entry}', 'undoNoShow')->name('entry-flags.undoNoShow'); }); - // Generic Tabulation Routes - Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function () { + // Seating Routes + Route::prefix('seating/')->group(function () { + Route::get('/', App\Http\Controllers\Tabulation\SeatingStatusController::class)->name('seating.status'); + }); + + // Generic Tabulation Routes (TO BE REPLACED) + Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function ( + ) { Route::get('/status', 'status')->name('tabulation.status'); Route::match(['get', 'post'], '/auditions/{audition}', 'auditionSeating')->name('tabulation.audition.seat'); Route::post('/auditions/{audition}/publish-seats', 'publishSeats')->name('tabulation.seat.publish'); @@ -31,7 +40,8 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () }); // Advancement Routes - Route::prefix('advancement/')->controller(\App\Http\Controllers\Tabulation\AdvancementController::class)->group(function () { + Route::prefix('advancement/')->controller(\App\Http\Controllers\Tabulation\AdvancementController::class)->group(function ( + ) { Route::get('/status', 'status')->name('advancement.status'); Route::get('/{audition}', 'ranking')->name('advancement.ranking'); Route::post('/{audition}', 'setAuditionPassers')->name('advancement.setAuditionPassers'); -- 2.39.5 From d45ebf4eece34c5a1634be3a76ebb4a88caa5f7d Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 9 Jul 2024 14:36:04 -0500 Subject: [PATCH 04/43] Cleanup and add test for seating status screen --- .../Tabulation/SeatAuditionController.php | 17 ++ .../Tabulation/SeatingStatusController.php | 2 +- app/Models/Audition.php | 2 +- routes/tabulation.php | 20 ++- tests/Feature/Seating/indexTest.php | 149 ++++++++++++++++++ 5 files changed, 182 insertions(+), 8 deletions(-) create mode 100644 app/Http/Controllers/Tabulation/SeatAuditionController.php create mode 100644 tests/Feature/Seating/indexTest.php diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php new file mode 100644 index 0000000..8eb243e --- /dev/null +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -0,0 +1,17 @@ +withCount(['entries', 'unscoredEntries'])->with('flags')->get(); $auditionData = []; foreach ($auditions as $audition) { - $auditionData[] = [ + $auditionData[$audition->id] = [ 'id' => $audition->id, 'name' => $audition->name, 'scoredEntriesCount' => $audition->entries_count - $audition->unscored_entries_count, diff --git a/app/Models/Audition.php b/app/Models/Audition.php index 9553d52..9376bc4 100644 --- a/app/Models/Audition.php +++ b/app/Models/Audition.php @@ -29,7 +29,7 @@ class Audition extends Model return $this->hasMany(Entry::class); } - public function unscoredEntries() + public function unscoredEntries(): HasMany { return $this->hasMany(Entry::class) ->whereDoesntHave('scoreSheets'); diff --git a/routes/tabulation.php b/routes/tabulation.php index a7118dd..85a39f3 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -1,23 +1,30 @@ group(function () { // Score Management - Route::prefix('scores/')->controller(\App\Http\Controllers\Tabulation\ScoreController::class)->group(function () { + Route::prefix('scores/')->controller(ScoreController::class)->group(function () { Route::get('/choose_entry', 'chooseEntry')->name('scores.chooseEntry'); Route::get('/entry', 'entryScoreSheet')->name('scores.entryScoreSheet'); Route::post('/entry/{entry}', 'saveEntryScoreSheet')->name('scores.saveEntryScoreSheet'); Route::delete('/{score}', - [\App\Http\Controllers\Tabulation\ScoreController::class, 'destroyScore'])->name('scores.destroy'); + [ScoreController::class, 'destroyScore'])->name('scores.destroy'); }); // Entry Flagging - Route::prefix('entry-flags/')->controller(\App\Http\Controllers\Tabulation\EntryFlagController::class)->group(function ( + Route::prefix('entry-flags/')->controller(EntryFlagController::class)->group(function ( ) { Route::get('/choose_no_show', 'noShowSelect')->name('entry-flags.noShowSelect'); Route::get('/propose-no-show', 'noShowConfirm')->name('entry-flags.confirmNoShow'); @@ -27,11 +34,12 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () // Seating Routes Route::prefix('seating/')->group(function () { - Route::get('/', App\Http\Controllers\Tabulation\SeatingStatusController::class)->name('seating.status'); + Route::get('/', SeatingStatusController::class)->name('seating.status'); + Route::get('/{audition}', SeatAuditionController::class)->name('seating.audition'); }); // Generic Tabulation Routes (TO BE REPLACED) - Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function ( + Route::prefix('tabulation/')->controller(TabulationController::class)->group(function ( ) { Route::get('/status', 'status')->name('tabulation.status'); Route::match(['get', 'post'], '/auditions/{audition}', 'auditionSeating')->name('tabulation.audition.seat'); @@ -40,7 +48,7 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () }); // Advancement Routes - Route::prefix('advancement/')->controller(\App\Http\Controllers\Tabulation\AdvancementController::class)->group(function ( + Route::prefix('advancement/')->controller(AdvancementController::class)->group(function ( ) { Route::get('/status', 'status')->name('advancement.status'); Route::get('/{audition}', 'ranking')->name('advancement.ranking'); diff --git a/tests/Feature/Seating/indexTest.php b/tests/Feature/Seating/indexTest.php new file mode 100644 index 0000000..a05a09a --- /dev/null +++ b/tests/Feature/Seating/indexTest.php @@ -0,0 +1,149 @@ +assertRedirect(route('home')); + actAsNormal(); + get(route('seating.status')) + ->assertRedirect(route('dashboard')) + ->assertSessionHas('error', 'You are not authorized to perform this action'); +}); +it('responds to an admin', function () { + // Arrange + actAsAdmin(); + // Act & Assert + get(route('seating.status')) + ->assertOk(); +}); +it('responds to a tabulator', function () { + // Arrange + actAsTab(); + // Act & Assert + get(route('seating.status')) + ->assertOk(); +}); +it('sends the view a collection of audition data that includes data needed by the view', function () { + // Arrange + $auditions = Audition::factory()->count(5)->create(); + actAsAdmin(); + // Act + $response = get(route('seating.status')); + // Assert + foreach ($auditions as $audition) { + $response->assertOk() + ->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['id'] === $audition->id; + }); + $response->assertOk() + ->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['name'] === $audition->name; + }); + } +}); +it('has correct count info for an audition with 5 entries none scored', function () { + $audition = Audition::factory()->create(); + Entry::factory()->count(5)->create(['audition_id' => $audition->id]); + + actAsAdmin(); + $response = get(route('seating.status')); + $response->assertOk(); + + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoredEntriesCount'] === 0; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['totalEntriesCount'] === 5; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoredPercentage'] === 0; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoringComplete'] === false; + }); +}); +it('has correct count info for an audition with 8 entries 2 scored', function () { + $judge = User::factory()->create(); + + $audition = Audition::factory()->create(); + $entries = Entry::factory()->count(2)->create(['audition_id' => $audition->id]); + $entries->each(fn ($entry) => ScoreSheet::create([ + 'user_id' => $judge->id, + 'entry_id' => $entry->id, + 'subscores' => 7, + ])); + Entry::factory()->count(6)->create(['audition_id' => $audition->id]); + + actAsAdmin(); + $response = get(route('seating.status')); + $response->assertOk(); + + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoredEntriesCount'] === 2; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['totalEntriesCount'] === 8; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoredPercentage'] == 25; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoringComplete'] === false; + }); +}); + +it('has correct count info for an audition with 1 entries 1 scored', function () { + $judge = User::factory()->create(); + + $audition = Audition::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => $audition->id]); + ScoreSheet::create([ + 'user_id' => $judge->id, + 'entry_id' => $entry->id, + 'subscores' => 3, + ]); + + actAsAdmin(); + $response = get(route('seating.status')); + $response->assertOk(); + + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoredEntriesCount'] === 1; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['totalEntriesCount'] === 1; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoredPercentage'] == 100; + }); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['scoringComplete'] === true; + }); +}); + +it('correctly shows a flag when the audition is flagged as seated', function () { + $audition = Audition::factory()->create(); + + actAsAdmin(); + $response = get(route('seating.status')); + $response->assertOk(); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['seatsPublished'] === false; + }); + $audition->addFlag('seats_published'); + $response = get(route('seating.status')); + $response->assertOk(); + $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { + return $viewAuditionData[$audition->id]['seatsPublished'] === true; + }); + +}); -- 2.39.5 From 0eda3ab32e5b2cd6e9661d8afc61b22760f13104 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Tue, 9 Jul 2024 22:23:23 -0500 Subject: [PATCH 05/43] Implement EnterScore action --- app/Actions/EnterScore.php | 101 ++++++++++ app/Exceptions/ScoreEntryException.php | 10 + app/Services/ScoreService.php | 24 +-- app/helpers.php | 17 +- .../AuditionWithScoringGuideAndRoom.php | 84 ++++++++ database/seeders/RoomSeeder.php | 17 -- database/seeders/SchoolSeeder.php | 17 -- database/seeders/ScoreSheetSeeder.php | 17 -- database/seeders/ScoringGuideSeeder.php | 17 -- database/seeders/StudentSeeder.php | 17 -- database/seeders/SubscoreDefinitionSeeder.php | 17 -- .../layout/navbar/menus/tabulation.blade.php | 2 +- resources/views/test.blade.php | 15 +- tests/Feature/Actions/EnterScoreTest.php | 185 ++++++++++++++++++ .../Seating/statusTest.php} | 0 tests/Feature/Services/ScoreServiceTest.php | 44 +++++ tests/Pest.php | 5 + 17 files changed, 467 insertions(+), 122 deletions(-) create mode 100644 app/Actions/EnterScore.php create mode 100644 app/Exceptions/ScoreEntryException.php create mode 100644 database/seeders/AuditionWithScoringGuideAndRoom.php delete mode 100644 database/seeders/RoomSeeder.php delete mode 100644 database/seeders/SchoolSeeder.php delete mode 100644 database/seeders/ScoreSheetSeeder.php delete mode 100644 database/seeders/ScoringGuideSeeder.php delete mode 100644 database/seeders/StudentSeeder.php delete mode 100644 database/seeders/SubscoreDefinitionSeeder.php create mode 100644 tests/Feature/Actions/EnterScoreTest.php rename tests/Feature/{Seating/indexTest.php => Pages/Seating/statusTest.php} (100%) create mode 100644 tests/Feature/Services/ScoreServiceTest.php diff --git a/app/Actions/EnterScore.php b/app/Actions/EnterScore.php new file mode 100644 index 0000000..25c357b --- /dev/null +++ b/app/Actions/EnterScore.php @@ -0,0 +1,101 @@ +basicChecks($user, $entry, $scores); + $this->checkJudgeAssignment($user, $entry); + $this->checkForExistingScore($user, $entry); + $this->validateScoresSubmitted($entry, $scores); + $entry->removeFlag('no_show'); + $newScoreSheet = ScoreSheet::create([ + 'user_id' => $user->id, + 'entry_id' => $entry->id, + 'subscores' => $this->subscoresForStorage($entry, $scores), + ]); + + return $newScoreSheet; + } + + 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) + { + 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'); + } + } + + 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) + { + if (! $user->exists()) { + throw new ScoreEntryException('User does not exist'); + } + if (! $entry->exists()) { + throw new ScoreEntryException('Entry does not exist'); + } + if ($entry->audition->hasFlag('seats_published')) { + throw new ScoreEntryException('Cannot score an entry in an audition with published seats'); + } + 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) { + throw new ScoreEntryException('Invalid number of scores'); + } + } +} diff --git a/app/Exceptions/ScoreEntryException.php b/app/Exceptions/ScoreEntryException.php new file mode 100644 index 0000000..9086c34 --- /dev/null +++ b/app/Exceptions/ScoreEntryException.php @@ -0,0 +1,10 @@ +auditionCache = $auditionCache; - $this->entryCache = $entryCache; + + } + public function isEntryFullyScored(Entry $entry): bool + { + $requiredJudges = $entry->audition->judges()->count(); + $scoreSheets = $entry->scoreSheets()->count(); + + return $requiredJudges === $scoreSheets; } + } diff --git a/app/helpers.php b/app/helpers.php index ef50236..22f435f 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,5 +1,9 @@ create(['id' => 1000]); + $sg = ScoringGuide::factory()->create(['id' => 1000]); + SubscoreDefinition::create([ + 'id' => 1001, + 'scoring_guide_id' => $sg->id, + 'name' => 'Scale', + 'max_score' => 100, + 'weight' => 1, + 'display_order' => 1, + 'tiebreak_order' => 5, + 'for_seating' => 1, + 'for_advance' => 0, + ]); + SubscoreDefinition::create([ + 'id' => 1002, + 'scoring_guide_id' => $sg->id, + 'name' => 'Etude 1', + 'max_score' => 100, + 'weight' => 2, + 'display_order' => 2, + 'tiebreak_order' => 3, + 'for_seating' => 1, + 'for_advance' => 1, + ]); + SubscoreDefinition::create([ + 'id' => 1003, + 'scoring_guide_id' => $sg->id, + 'name' => 'Etude 2', + 'max_score' => 100, + 'weight' => 2, + 'display_order' => 3, + 'tiebreak_order' => 4, + 'for_seating' => 1, + 'for_advance' => 1, + ]); + SubscoreDefinition::create([ + 'id' => 1004, + 'scoring_guide_id' => $sg->id, + 'name' => 'Sight Reading', + 'max_score' => 100, + 'weight' => 3, + 'display_order' => 4, + 'tiebreak_order' => 2, + 'for_seating' => 1, + 'for_advance' => 1, + ]); + SubscoreDefinition::create([ + 'id' => 1005, + 'scoring_guide_id' => $sg->id, + 'name' => 'Tone', + 'max_score' => 100, + 'weight' => 1, + 'display_order' => 5, + 'tiebreak_order' => 1, + 'for_seating' => 0, + 'for_advance' => 1, + ]); + Audition::factory()->create([ + 'id' => 1000, + 'room_id' => $room->id, + 'scoring_guide_id' => $sg->id, + 'name' => 'Test Audition', + ]); + } +} diff --git a/database/seeders/RoomSeeder.php b/database/seeders/RoomSeeder.php deleted file mode 100644 index bdf439e..0000000 --- a/database/seeders/RoomSeeder.php +++ /dev/null @@ -1,17 +0,0 @@ - Enter Scores Enter No-Shows - Audition Status + Audition Status {{ auditionSetting('advanceTo') }} Status diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index 44f7d68..bd114cc 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -1,4 +1,4 @@ -@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag; @endphp +@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\User; @endphp @php @endphp @inject('scoreservice','App\Services\ScoreService'); @inject('auditionService','App\Services\AuditionService'); @@ -8,8 +8,17 @@ Test Page @php - $audition = Audition::first(); - $audition->addFlag('drawn'); + $entry = Entry::find(1127); + $judge = User::find(65); + $scoreArray = [ + 1 => 50, + 2 => 60, + 3 => 70, + 4 => 80, + 5 => 90, + ]; + enterScore($judge, $entry, $scoreArray); + dump($entry->audition->name); @endphp diff --git a/tests/Feature/Actions/EnterScoreTest.php b/tests/Feature/Actions/EnterScoreTest.php new file mode 100644 index 0000000..43cf3af --- /dev/null +++ b/tests/Feature/Actions/EnterScoreTest.php @@ -0,0 +1,185 @@ +scoreEntry = new EnterScore(); +}); + +test('throws an exception if the user does not exist', function () { + $user = User::factory()->make(); + $entry = Entry::factory()->create(); + enterScore($user, $entry, []); +})->throws(ScoreEntryException::class, 'User does not exist'); +test('throws an exception if the entry does not exist', function () { + $user = User::factory()->create(); + $entry = Entry::factory()->make(); + enterScore($user, $entry, []); +})->throws(ScoreEntryException::class, 'Entry does not exist'); +it('throws an exception if the seats for the entries audition are published', function () { + // Arrange + $user = User::factory()->create(); + $entry = Entry::factory()->create(); + $entry->audition->addFlag('seats_published'); + // Act & Assert + enterScore($user, $entry, []); +})->throws(ScoreEntryException::class, 'Cannot score an entry in an audition with published seats'); +it('throws an exception if the advancement for the entries audition is published', function () { + // Arrange + $user = User::factory()->create(); + $entry = Entry::factory()->create(); + $entry->audition->addFlag('advancement_published'); + // Act & Assert + enterScore($user, $entry, []); +})->throws(ScoreEntryException::class, 'Cannot score an entry in an audition with published advancement'); +it('throws an exception if too many scores are submitted', function () { + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + Room::find(1000)->addJudge($judge); + // Act & Assert + enterScore($judge, $entry, [1, 2, 5, 3, 2, 1]); +})->throws(ScoreEntryException::class, 'Invalid number of scores'); +it('throws an exception if too few scores are submitted', function () { + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + Room::find(1000)->addJudge($judge); + // Act & Assert + enterScore($judge, $entry, [1, 2, 5, 3]); +})->throws(ScoreEntryException::class, 'Invalid number of scores'); +it('throws an exception if the user is not assigned to judge the entry', function () { + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + // Act & Assert + enterScore($judge, $entry, [1, 2, 5, 3, 7]); +})->throws(ScoreEntryException::class, 'This judge is not assigned to judge this entry'); +it('throws an exception if an invalid subscore is provided', function () { + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + Room::find(1000)->addJudge($judge); + $scores = [ + 1001 => 98, + 1002 => 90, + 1003 => 87, + 1004 => 78, + 1006 => 88, + ]; + enterScore($judge, $entry, $scores); +})->throws(ScoreEntryException::class, 'Invalid Score Submission'); +it('throws an exception if a submitted subscore exceeds the maximum allowed', function () { + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + Room::find(1000)->addJudge($judge); + $scores = [ + 1001 => 98, + 1002 => 90, + 1003 => 87, + 1004 => 78, + 1005 => 101, + ]; + enterScore($judge, $entry, $scores); +})->throws(ScoreEntryException::class, 'Supplied subscore exceeds maximum allowed'); +it('removes an existing no_show flag from the entry if one exists', function () { + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $entry->addFlag('no_show'); + Room::find(1000)->addJudge($judge); + $scores = [ + 1001 => 98, + 1002 => 90, + 1003 => 87, + 1004 => 78, + 1005 => 98, + ]; + // Act + enterScore($judge, $entry, $scores); + // Assert + expect($entry->hasFlag('no_show'))->toBeFalse(); +}); +it('saves the score with a properly formatted subscore object', function () { + // Arrange + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $entry->addFlag('no_show'); + Room::find(1000)->addJudge($judge); + $scores = [ + 1001 => 98, + 1002 => 90, + 1003 => 87, + 1004 => 78, + 1005 => 98, + ]; + $desiredReturn = [ + 1001 => [ + 'score' => 98, + 'subscore_id' => 1001, + 'subscore_name' => 'Scale', + ], + 1002 => [ + 'score' => 90, + 'subscore_id' => 1002, + 'subscore_name' => 'Etude 1', + ], + 1003 => [ + 'score' => 87, + 'subscore_id' => 1003, + 'subscore_name' => 'Etude 2', + ], + 1004 => [ + 'score' => 78, + 'subscore_id' => 1004, + 'subscore_name' => 'Sight Reading', + ], + 1005 => [ + 'score' => 98, + 'subscore_id' => 1005, + 'subscore_name' => 'Tone', + ], + ]; + // Act + $newScore = enterScore($judge, $entry, $scores); + // Assert + $checkScoreSheet = ScoreSheet::find($newScore->id); + expect($checkScoreSheet->exists())->toBeTrue() + ->and($checkScoreSheet->subscores)->toBe($desiredReturn); +}); +it('throws an exception of the entry already has a score by the judge', function() { + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $entry->addFlag('no_show'); + Room::find(1000)->addJudge($judge); + $scores = [ + 1001 => 98, + 1002 => 90, + 1003 => 87, + 1004 => 78, + 1005 => 98, + ]; + // Act + enterScore($judge, $entry, $scores); + // Assert + enterScore($judge, $entry, $scores); +})->throws(ScoreEntryException::class, 'That judge has already entered scores for that entry'); diff --git a/tests/Feature/Seating/indexTest.php b/tests/Feature/Pages/Seating/statusTest.php similarity index 100% rename from tests/Feature/Seating/indexTest.php rename to tests/Feature/Pages/Seating/statusTest.php diff --git a/tests/Feature/Services/ScoreServiceTest.php b/tests/Feature/Services/ScoreServiceTest.php new file mode 100644 index 0000000..97728f6 --- /dev/null +++ b/tests/Feature/Services/ScoreServiceTest.php @@ -0,0 +1,44 @@ +scoreService = new ScoreService(); +}); +it('can record a score', function () { + // Arrange + // run the seeder AuditionWithScoringGuideAndRoom + artisan('db:seed', ['--class' => 'AuditionWithScoringGuideAndRoom']); + // Act & Assert + expect(Audition::find(1000)->name)->toBe('Test Audition'); +}); + +it('can check if an entry is fully scored', function () { + $room = Room::factory()->create(); + $judges = User::factory()->count(2)->create(); + $judges->each(fn ($judge) => $room->addJudge($judge)); + $audition = Audition::factory()->create(['room_id' => $room->id]); + $entry = Entry::factory()->create(['audition_id' => $audition->id]); + expect($this->scoreService->isEntryFullyScored($entry))->toBeFalse(); + ScoreSheet::create([ + 'user_id' => $judges->first()->id, + 'entry_id' => $entry->id, + 'subscores' => 7, + ]); + expect($this->scoreService->isEntryFullyScored($entry))->toBeFalse(); + ScoreSheet::create([ + 'user_id' => $judges->last()->id, + 'entry_id' => $entry->id, + 'subscores' => 7, + ]); + expect($this->scoreService->isEntryFullyScored($entry))->toBeTrue(); +}); diff --git a/tests/Pest.php b/tests/Pest.php index 6cd9f4c..a07cc90 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -15,6 +15,7 @@ use App\Models\User; use App\Settings; use Illuminate\Foundation\Testing\TestCase; use function Pest\Laravel\actingAs; +use function Pest\Laravel\artisan; uses( Tests\TestCase::class, @@ -59,6 +60,10 @@ function actAsNormal() { actingAs(User::factory()->create()); } +function loadSampleAudition() +{ + artisan('db:seed', ['--class' => 'AuditionWithScoringGuideAndRoom']); +} uses()->beforeEach(function () { Settings::set('auditionName', 'Somewhere Band Directors Association'); -- 2.39.5 From 49ebfda9a82c2b263a5a849d996839798463f1dc Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 00:10:56 -0500 Subject: [PATCH 06/43] CalculateScoreSheetTotal working --- app/Actions/CalculateScoreSheetTotal.php | 57 +++++++++++++++ app/Providers/AppServiceProvider.php | 69 ++++++++---------- app/Services/ScoreService.php | 6 ++ .../Actions/CalculateScoreSheetTotalTest.php | 72 +++++++++++++++++++ .../Pages/Seating/auditionSeatingTest.php | 37 ++++++++++ tests/Feature/Services/ScoreServiceTest.php | 7 -- 6 files changed, 200 insertions(+), 48 deletions(-) create mode 100644 app/Actions/CalculateScoreSheetTotal.php create mode 100644 tests/Feature/Actions/CalculateScoreSheetTotalTest.php create mode 100644 tests/Feature/Pages/Seating/auditionSeatingTest.php diff --git a/app/Actions/CalculateScoreSheetTotal.php b/app/Actions/CalculateScoreSheetTotal.php new file mode 100644 index 0000000..7a72c90 --- /dev/null +++ b/app/Actions/CalculateScoreSheetTotal.php @@ -0,0 +1,57 @@ +basicValidations($mode, $entry, $judge); + $scoreSheet = ScoreSheet::where('entry_id', $entry->id)->where('user_id', $judge->id)->first(); + if (! $scoreSheet) { + throw new TabulationException('No score sheet by that judge for that entry'); + } + $subscores = match ($mode) { + 'seating' => $entry->audition->scoringGuide->subscores->where('for_seating', true)->sortBy('tiebreak_order'), + 'advancement' => $entry->audition->scoringGuide->subscores->where('for_advance', true)->sortBy('tiebreak_order'), + }; + $scoreTotal = 0; + $weightsTotal = 0; + $scoreArray = []; + foreach ($subscores as $subscore) { + $weight = $subscore['weight']; + $score = $scoreSheet->subscores[$subscore->id]['score']; + $scoreArray[] = $score; + $scoreTotal += ($score * $weight); + $weightsTotal += $weight; + } + $finalScore = $scoreTotal / $weightsTotal; + // put $final score at the beginning of the $ScoreArray + array_unshift($scoreArray, $finalScore); + return $scoreArray; + } + + protected function basicValidations($mode, $entry, $judge): void + { + if ($mode !== 'seating' and $mode !== 'advancement') { + throw new TabulationException('Invalid mode requested. Mode must be seating or advancement'); + } + if (! $entry->exists()) { + throw new TabulationException('Invalid entry provided'); + } + if (! $judge->exists()) { + throw new TabulationException('Invalid judge provided'); + } + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b804a12..bc0e89d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,21 +2,12 @@ namespace App\Providers; -use App\Events\AuditionChange; -use App\Events\EntryChange; -use App\Events\ScoreSheetChange; -use App\Events\ScoringGuideChange; -use App\Events\SeatingLimitChange; -use App\Listeners\RefreshAuditionCache; -use App\Listeners\RefreshEntryCache; -use App\Listeners\RefreshScoreSheetCache; -use App\Listeners\RefreshScoringGuideCache; -use App\Listeners\RefreshSeatingLimitCache; use App\Models\Audition; use App\Models\Entry; use App\Models\Room; use App\Models\RoomUser; use App\Models\School; +use App\Models\ScoreSheet; use App\Models\ScoringGuide; use App\Models\SeatingLimit; use App\Models\Student; @@ -40,10 +31,7 @@ use App\Services\EntryService; use App\Services\ScoreService; use App\Services\SeatingService; use App\Services\TabulationService; -use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; -use App\Models\ScoreSheet; - class AppServiceProvider extends ServiceProvider { @@ -52,36 +40,36 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - $this->app->singleton(DrawService::class, function () { - return new DrawService(); + // $this->app->singleton(DrawService::class, function () { + // return new DrawService(); + // }); + // + // $this->app->singleton(AuditionService::class, function () { + // return new AuditionService(); + // }); + // + // $this->app->singleton(SeatingService::class, function ($app) { + // return new SeatingService($app->make(TabulationService::class)); + // }); + // + $this->app->singleton(EntryService::class, function () { + return new EntryService(); }); - $this->app->singleton(AuditionService::class, function () { - return new AuditionService(); - }); - - $this->app->singleton(SeatingService::class, function ($app) { - return new SeatingService($app->make(TabulationService::class)); - }); - - $this->app->singleton(EntryService::class, function ($app) { - return new EntryService($app->make(AuditionService::class)); - }); - - $this->app->singleton(ScoreService::class, function ($app) { - return new ScoreService($app->make(AuditionService::class), $app->make(EntryService::class)); - }); - - $this->app->singleton(TabulationService::class, function ($app) { - return new TabulationService( - $app->make(AuditionService::class), - $app->make(ScoreService::class), - $app->make(EntryService::class)); - }); - - $this->app->singleton(DoublerService::class, function ($app) { - return new DoublerService($app->make(AuditionService::class), $app->make(TabulationService::class), $app->make(SeatingService::class)); + $this->app->singleton(ScoreService::class, function () { + return new ScoreService(); }); + // + // $this->app->singleton(TabulationService::class, function ($app) { + // return new TabulationService( + // $app->make(AuditionService::class), + // $app->make(ScoreService::class), + // $app->make(EntryService::class)); + // }); + // + // $this->app->singleton(DoublerService::class, function ($app) { + // return new DoublerService($app->make(AuditionService::class), $app->make(TabulationService::class), $app->make(SeatingService::class)); + // }); } /** @@ -102,6 +90,5 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - } } diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index 7728591..25d7dc7 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Models\Entry; +use App\Models\User; class ScoreService { @@ -21,5 +22,10 @@ class ScoreService return $requiredJudges === $scoreSheets; } + public function scoreSheetTotal(string $mode, User $judge, Entry $entry): float + { + + } + } diff --git a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php new file mode 100644 index 0000000..ec18e55 --- /dev/null +++ b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php @@ -0,0 +1,72 @@ +calculator = new CalculateScoreSheetTotal(); +}); +it('throws an exception if an invalid mode is called for', function () { + $calculator = new CalculateScoreSheetTotal(); + $calculator('anything', Entry::factory()->create(), User::factory()->create()); +})->throws(TabulationException::class, 'Invalid mode requested. Mode must be seating or advancement'); +it('throws an exception if an invalid judge is provided', function () { + $calculator = new CalculateScoreSheetTotal(); + $calculator('seating', Entry::factory()->create(), User::factory()->make()); +})->throws(TabulationException::class, 'Invalid judge provided'); +it('throws an exception if an invalid entry is provided', function () { + $calculator = new CalculateScoreSheetTotal(); + $calculator('advancement', Entry::factory()->make(), User::factory()->create()); +})->throws(TabulationException::class, 'Invalid entry provided'); +it('throws an exception if the specified judge has not scored the entry', function () { + // Arrange + $calculator = new CalculateScoreSheetTotal(); + // Act + $calculator('seating', Entry::factory()->create(), User::factory()->create()); + //Assert +})->throws(TabulationException::class, 'No score sheet by that judge for that entry'); +it('correctly calculates final score for seating', function () { + loadSampleAudition(); + $judge = User::factory()->create(); + Room::find(1000)->addJudge($judge); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $scores = [ + 1001 => 50, + 1002 => 60, + 1003 => 70, + 1004 => 80, + 1005 => 90, + ]; + enterScore($judge, $entry, $scores); + $calculator = new CalculateScoreSheetTotal(); + $total = $calculator('seating', $entry, $judge); + expect($total[0])->toBe(68.75); + $expectedArray = [68.75, 80, 60, 70, 50]; + expect($total)->toBe($expectedArray); +}); +it('correctly calculates final score for advancement', function () { + loadSampleAudition(); + $judge = User::factory()->create(); + Room::find(1000)->addJudge($judge); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $scores = [ + 1001 => 50, + 1002 => 60, + 1003 => 70, + 1004 => 80, + 1005 => 90, + ]; + enterScore($judge, $entry, $scores); + $calculator = new CalculateScoreSheetTotal(); + $total = $calculator('advancement', $entry, $judge); + expect($total[0])->toBe(73.75); + $expectedArray = [73.75, 90, 80, 60, 70]; + expect($total)->toBe($expectedArray); +}); diff --git a/tests/Feature/Pages/Seating/auditionSeatingTest.php b/tests/Feature/Pages/Seating/auditionSeatingTest.php new file mode 100644 index 0000000..d46e0ed --- /dev/null +++ b/tests/Feature/Pages/Seating/auditionSeatingTest.php @@ -0,0 +1,37 @@ +audition = Audition::factory()->create(); + $this->r = route('seating.audition', $this->audition); +}); + +it('denies access to a guest', function () { + get($this->r) + ->assertRedirect(route('home')); +}); + +it('denies access to a normal user', function () { + actAsNormal(); + get($this->r) + ->assertRedirect(route('dashboard')) + ->assertSessionHas('error', 'You are not authorized to perform this action'); +}); +it('grants access to admin', function () { + // Arrange + actAsAdmin(); + // Act & Assert + get($this->r)->assertOk(); +}); +it('grants access to tabulators', function () { + // Arrange + actAsTab(); + // Act & Assert + get($this->r)->assertOk(); +}); diff --git a/tests/Feature/Services/ScoreServiceTest.php b/tests/Feature/Services/ScoreServiceTest.php index 97728f6..3c19f15 100644 --- a/tests/Feature/Services/ScoreServiceTest.php +++ b/tests/Feature/Services/ScoreServiceTest.php @@ -14,13 +14,6 @@ uses(RefreshDatabase::class); beforeEach(function () { $this->scoreService = new ScoreService(); }); -it('can record a score', function () { - // Arrange - // run the seeder AuditionWithScoringGuideAndRoom - artisan('db:seed', ['--class' => 'AuditionWithScoringGuideAndRoom']); - // Act & Assert - expect(Audition::find(1000)->name)->toBe('Test Audition'); -}); it('can check if an entry is fully scored', function () { $room = Room::factory()->create(); -- 2.39.5 From 120eaedeb594a8be2eb2c46894db6b07f09269ab Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 00:17:17 -0500 Subject: [PATCH 07/43] Cleanup --- app/Actions/{ => Tabulation}/CalculateScoreSheetTotal.php | 2 +- app/Actions/{ => Tabulation}/EnterScore.php | 2 +- app/Services/ScoreService.php | 7 ------- app/helpers.php | 2 +- tests/Feature/Actions/CalculateScoreSheetTotalTest.php | 2 +- tests/Feature/Actions/EnterScoreTest.php | 2 +- tests/Feature/Pages/EntriesIndexTest.php | 2 +- tests/Feature/Services/ScoreServiceTest.php | 1 - 8 files changed, 6 insertions(+), 14 deletions(-) rename app/Actions/{ => Tabulation}/CalculateScoreSheetTotal.php (98%) rename app/Actions/{ => Tabulation}/EnterScore.php (99%) diff --git a/app/Actions/CalculateScoreSheetTotal.php b/app/Actions/Tabulation/CalculateScoreSheetTotal.php similarity index 98% rename from app/Actions/CalculateScoreSheetTotal.php rename to app/Actions/Tabulation/CalculateScoreSheetTotal.php index 7a72c90..1f814d5 100644 --- a/app/Actions/CalculateScoreSheetTotal.php +++ b/app/Actions/Tabulation/CalculateScoreSheetTotal.php @@ -2,7 +2,7 @@ /** @noinspection PhpUnhandledExceptionInspection */ -namespace App\Actions; +namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Entry; diff --git a/app/Actions/EnterScore.php b/app/Actions/Tabulation/EnterScore.php similarity index 99% rename from app/Actions/EnterScore.php rename to app/Actions/Tabulation/EnterScore.php index 25c357b..b8c01ad 100644 --- a/app/Actions/EnterScore.php +++ b/app/Actions/Tabulation/EnterScore.php @@ -4,7 +4,7 @@ /** @noinspection PhpMissingReturnTypeInspection */ -namespace App\Actions; +namespace App\Actions\Tabulation; use App\Exceptions\ScoreEntryException; use App\Models\Entry; diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index 25d7dc7..afd29c2 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -21,11 +21,4 @@ class ScoreService return $requiredJudges === $scoreSheets; } - - public function scoreSheetTotal(string $mode, User $judge, Entry $entry): float - { - - } - - } diff --git a/app/helpers.php b/app/helpers.php index 22f435f..abe2a72 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,6 +1,6 @@ id, 'name: ', - $student->full_name(true), + e($student->full_name(true)), ], false); // The false parameter makes the assertion case-sensitive and allows for HTML tags }); diff --git a/tests/Feature/Services/ScoreServiceTest.php b/tests/Feature/Services/ScoreServiceTest.php index 3c19f15..64f544f 100644 --- a/tests/Feature/Services/ScoreServiceTest.php +++ b/tests/Feature/Services/ScoreServiceTest.php @@ -7,7 +7,6 @@ use App\Models\ScoreSheet; use App\Models\User; use App\Services\ScoreService; use Illuminate\Foundation\Testing\RefreshDatabase; -use function Pest\Laravel\artisan; uses(RefreshDatabase::class); -- 2.39.5 From 126032ae180773501ab1e232892285b4901fddbd Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 00:31:19 -0500 Subject: [PATCH 08/43] Add CalculateEntryScore Interface --- app/Actions/Tabulation/CalculateEntryScore.php | 8 ++++++++ .../Tabulation/SeatAuditionController.php | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 app/Actions/Tabulation/CalculateEntryScore.php diff --git a/app/Actions/Tabulation/CalculateEntryScore.php b/app/Actions/Tabulation/CalculateEntryScore.php new file mode 100644 index 0000000..90b8842 --- /dev/null +++ b/app/Actions/Tabulation/CalculateEntryScore.php @@ -0,0 +1,8 @@ +with('student.school')->get(); + foreach ($entries as $entry) { + $entryData[] = [ + 'rank' => 'xx', + 'id' => $entry->id, + 'studentName' => $entry->student->full_name(), + 'schoolName' => $entry->student->school->name, + ]; + } } } -- 2.39.5 From 529542d1bab1c355063cf4f8a9458ed55d6447a7 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 01:03:09 -0500 Subject: [PATCH 09/43] Establish CalculateEntryScore interface and default implementation --- app/Actions/Tabulation/AllJudgesCount.php | 13 ++++++++ .../Tabulation/CalculateEntryScore.php | 4 ++- app/Http/Controllers/TestController.php | 31 ++++++------------- app/Providers/CalculateEntryScoreProvider.php | 26 ++++++++++++++++ bootstrap/providers.php | 1 + resources/views/test.blade.php | 14 ++------- 6 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 app/Actions/Tabulation/AllJudgesCount.php create mode 100644 app/Providers/CalculateEntryScoreProvider.php diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php new file mode 100644 index 0000000..6874d77 --- /dev/null +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -0,0 +1,13 @@ +'hornets']; + } +} diff --git a/app/Actions/Tabulation/CalculateEntryScore.php b/app/Actions/Tabulation/CalculateEntryScore.php index 90b8842..8a719a1 100644 --- a/app/Actions/Tabulation/CalculateEntryScore.php +++ b/app/Actions/Tabulation/CalculateEntryScore.php @@ -2,7 +2,9 @@ namespace App\Actions\Tabulation; +use App\Models\Entry; + interface CalculateEntryScore { - public function __invoke($mode, $entry): array; + public function calculate(string $mode, Entry $entry): array; } diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 99fc429..2c336c3 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -2,32 +2,21 @@ namespace App\Http\Controllers; -use App\Services\AuditionService; -use App\Services\Invoice\InvoiceDataService; -use App\Services\TabulationService; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Session; +use App\Actions\Tabulation\CalculateEntryScore; +use App\Models\Entry; class TestController extends Controller { - protected $scoringGuideCacheService; - protected $tabulationService; - protected $invoiceService; - - public function __construct( - AuditionService $scoringGuideCacheService, - TabulationService $tabulationService, - InvoiceDataService $invoiceService - ) { - $this->scoringGuideCacheService = $scoringGuideCacheService; - $this->tabulationService = $tabulationService; - $this->invoiceService = $invoiceService; + protected CalculateEntryScore $bigCalc; + public function __construct(CalculateEntryScore $bigCalc) + { + $this->bigCalc = $bigCalc; } - public function flashTest(Request $request) + public function flashTest() { - $lines = $this->invoiceService->getLines(12); - $totalFees = $this->invoiceService->getGrandTotal(12); - return view('test', compact('lines','totalFees')); + $test = $this->bigCalc->calculate('seating', Entry::find(1127))['vinita']; + + return view('test', compact('test')); } } diff --git a/app/Providers/CalculateEntryScoreProvider.php b/app/Providers/CalculateEntryScoreProvider.php new file mode 100644 index 0000000..39bffcc --- /dev/null +++ b/app/Providers/CalculateEntryScoreProvider.php @@ -0,0 +1,26 @@ +app->singleton(CalculateEntryScore::class, AllJudgesCount::class); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 9edefb0..fd4c1d5 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -2,6 +2,7 @@ return [ App\Providers\AppServiceProvider::class, + App\Providers\CalculateEntryScoreProvider::class, App\Providers\FortifyServiceProvider::class, App\Providers\InvoiceDataServiceProvider::class, ]; diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index bd114cc..2a17e73 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -8,18 +8,8 @@ Test Page @php - $entry = Entry::find(1127); - $judge = User::find(65); - $scoreArray = [ - 1 => 50, - 2 => 60, - 3 => 70, - 4 => 80, - 5 => 90, - ]; - enterScore($judge, $entry, $scoreArray); - dump($entry->audition->name); - @endphp + @endphp + Test value: {{ $test }} -- 2.39.5 From 98b466beb68d877c22258833b41b09c9fc8eb401 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 02:43:21 -0500 Subject: [PATCH 10/43] CalculateEntryScore is functional --- app/Actions/Tabulation/AllJudgesCount.php | 56 +++++++- app/Http/Controllers/TestController.php | 26 +++- app/Providers/CalculateEntryScoreProvider.php | 2 + resources/views/test.blade.php | 7 +- .../AllJudgesCountTest.php | 135 ++++++++++++++++++ 5 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 6874d77..2e2655a 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -1,13 +1,67 @@ calculator = new CalculateScoreSheetTotal(); + } + public function calculate(string $mode, Entry $entry): array { - return ['vinita'=>'hornets']; + $this->basicValidation($mode, $entry); + $this->areAllJudgesIn($entry); + $this->areAllJudgesValid($entry); + return $this->getJudgeTotals($mode, $entry); + } + + protected function getJudgeTotals($mode, Entry $entry) + { + $scores = []; + foreach ($entry->audition->judges as $judge) { + $scores[] = $this->calculator->__invoke($mode, $entry, $judge); + } + for ($i = 0; $i < count($scores[0]); $i++) { + $sums[] = $scores[0][$i] + $scores[1][$i]; + } + + return $sums; + } + + protected function basicValidation($mode, $entry): void + { + if ($mode !== 'seating' && $mode !== 'advancement') { + throw new TabulationException('Mode must be seating or advancement'); + } + + if (! $entry->exists()) { + throw new TabulationException('Invalid entry specified'); + } + } + + protected function areAllJudgesIn(Entry $entry): void + { + $assignedJudgeCount = $entry->audition->judges->count(); + if ($entry->scoreSheets->count() !== $assignedJudgeCount) { + throw new TabulationException('Not all score sheets are in'); + } + } + + protected function areAllJudgesValid(Entry $entry): void + { + $validJudgeIds = $entry->audition->judges->pluck('id')->sort()->toArray(); + $existingJudgeIds = $entry->scoreSheets->pluck('user_id')->sort()->toArray(); + if ($validJudgeIds !== $existingJudgeIds) { + throw new TabulationException('Score exists from a judge not assigned to this audition'); + } } } diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 2c336c3..21b2b16 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Actions\Tabulation\CalculateEntryScore; +use App\Exceptions\TabulationException; use App\Models\Entry; class TestController extends Controller @@ -15,8 +16,29 @@ class TestController extends Controller public function flashTest() { - $test = $this->bigCalc->calculate('seating', Entry::find(1127))['vinita']; + $entries = Entry::forSeating()->with('student')->where('audition_id', 19)->get(); + $rows = []; + foreach ($entries as $entry) { + try { + $totalScore = $this->bigCalc->calculate('seating', $entry)[0]; + } catch (TabulationException $ex){ + $totalScore = '--'; + } + $rows[] = [ + 'name' => $entry->student->full_name(), + 'totalScore' => $totalScore, + ]; + } - return view('test', compact('test')); +// try { +// $test = $this->bigCalc->calculate('seating', Entry::find(1061))[0]; +// } catch (TabulationException $ex) { +// dd($ex); +// } + + + + + return view('test', compact('rows')); } } diff --git a/app/Providers/CalculateEntryScoreProvider.php b/app/Providers/CalculateEntryScoreProvider.php index 39bffcc..8d0a2d1 100644 --- a/app/Providers/CalculateEntryScoreProvider.php +++ b/app/Providers/CalculateEntryScoreProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; use App\Actions\Tabulation\AllJudgesCount; use App\Actions\Tabulation\CalculateEntryScore; +use App\Actions\Tabulation\CalculateScoreSheetTotal; use Illuminate\Support\ServiceProvider; class CalculateEntryScoreProvider extends ServiceProvider @@ -13,6 +14,7 @@ class CalculateEntryScoreProvider extends ServiceProvider */ public function register(): void { + $this->app->singleton(CalculateScoreSheetTotal::class, CalculateScoreSheetTotal::class); $this->app->singleton(CalculateEntryScore::class, AllJudgesCount::class); } diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index 2a17e73..f891ab6 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -7,9 +7,8 @@ @inject('drawService', 'App\Services\DrawService') Test Page - @php - - @endphp - Test value: {{ $test }} + @foreach($rows as $row) + {{ $row['name'] }} - {{ $row['totalScore'] }}
+ @endforeach
diff --git a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php new file mode 100644 index 0000000..f916a38 --- /dev/null +++ b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php @@ -0,0 +1,135 @@ +calculate('WRONG', Entry::factory()->create()); +})->throws(TabulationException::class, 'Mode must be seating or advancement'); + +it('throws an exception if entry is not valid', function () { + // Arrange + $calculator = new AllJudgesCount(); + // Act + $calculator->calculate('seating', Entry::factory()->make()); + // Assert +})->throws(TabulationException::class, 'Invalid entry specified'); +it('throws an exception if entry is missing judge scores', function () { + // Arrange + loadSampleAudition(); + $judge1 = User::factory()->create(); + $judge2 = User::factory()->create(); + Room::find(1000)->addJudge($judge1); + Room::find(1000)->addJudge($judge2); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $scores = [ + 1001 => 50, + 1002 => 60, + 1003 => 70, + 1004 => 80, + 1005 => 90, + ]; + $calculator = new AllJudgesCount(); + enterScore($judge1, $entry, $scores); + // Act + $calculator->calculate('seating', $entry); + // Assert +})->throws(TabulationException::class, 'Not all score sheets are in'); + +it('throws an exception if a score exists from an invalid judge', function () { + // Arrange + loadSampleAudition(); + $judge1 = User::factory()->create(); + $judge2 = User::factory()->create(); + $judge3 = User::factory()->create(); + Room::find(1000)->addJudge($judge1); + Room::find(1000)->addJudge($judge2); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $scores = [ + 1001 => 50, + 1002 => 60, + 1003 => 70, + 1004 => 80, + 1005 => 90, + ]; + $calculator = new AllJudgesCount(); + enterScore($judge1, $entry, $scores); + $scoreSheetToSpoof = enterScore($judge2, $entry, $scores); + $scoreSheetToSpoof->update(['user_id' => $judge3->id]); + // Act + $calculator->calculate('seating', $entry); + // Assert +})->throws(TabulationException::class, 'Score exists from a judge not assigned to this audition'); + +it('correctly calculates scores for seating', function () { + // Arrange + loadSampleAudition(); + $judge1 = User::factory()->create(); + $judge2 = User::factory()->create(); + Room::find(1000)->addJudge($judge1); + Room::find(1000)->addJudge($judge2); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $scores = [ + 1001 => 50, + 1002 => 60, + 1003 => 70, + 1004 => 80, + 1005 => 90, + ]; + $scores2 = [ + 1001 => 55, + 1002 => 65, + 1003 => 75, + 1004 => 85, + 1005 => 95, + ]; + $calculator = new AllJudgesCount(); + enterScore($judge1, $entry, $scores); + enterScore($judge2, $entry, $scores2); + // Act + $finalScores = $calculator->calculate('seating', $entry); + // Assert + $expectedScores = [142.5, 165, 125, 145, 105]; + expect($finalScores)->toBe($expectedScores); +}); + +it('correctly calculates scores for advancement', function () { + // Arrange + loadSampleAudition(); + $judge1 = User::factory()->create(); + $judge2 = User::factory()->create(); + Room::find(1000)->addJudge($judge1); + Room::find(1000)->addJudge($judge2); + $entry = Entry::factory()->create(['audition_id' => 1000]); + $scores = [ + 1001 => 50, + 1002 => 60, + 1003 => 70, + 1004 => 80, + 1005 => 90, + ]; + $scores2 = [ + 1001 => 55, + 1002 => 65, + 1003 => 75, + 1004 => 85, + 1005 => 95, + ]; + $calculator = new AllJudgesCount(); + enterScore($judge1, $entry, $scores); + enterScore($judge2, $entry, $scores2); + // Act + $finalScores = $calculator->calculate('advancement', $entry); + // Assert + $expectedScores = [152.5, 185, 165, 125, 145]; + expect($finalScores)->toBe($expectedScores); +}); -- 2.39.5 From a59a4c1d2bbaa9b9cca2c15a2f62721e12045438 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 03:26:49 -0500 Subject: [PATCH 11/43] Seating page implemented other than doublers --- app/Actions/Tabulation/AllJudgesCount.php | 4 +-- .../Tabulation/SeatAuditionController.php | 29 +++++++++++++++---- app/Http/Controllers/TestController.php | 9 ++++-- .../auditionSeating-results-table.blade.php | 22 +++++++------- .../tabulation/auditionSeating.blade.php | 22 +++++++------- resources/views/test.blade.php | 2 +- 6 files changed, 53 insertions(+), 35 deletions(-) diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 2e2655a..15ba4c8 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -58,8 +58,8 @@ class AllJudgesCount implements CalculateEntryScore protected function areAllJudgesValid(Entry $entry): void { - $validJudgeIds = $entry->audition->judges->pluck('id')->sort()->toArray(); - $existingJudgeIds = $entry->scoreSheets->pluck('user_id')->sort()->toArray(); + $validJudgeIds = $entry->audition->judges->sort()->pluck('id')->toArray(); + $existingJudgeIds = $entry->scoreSheets->sort()->pluck('user_id')->toArray(); if ($validJudgeIds !== $existingJudgeIds) { throw new TabulationException('Score exists from a judge not assigned to this audition'); } diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index 5b0a95f..3f13d7c 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -2,26 +2,43 @@ namespace App\Http\Controllers\Tabulation; +use App\Actions\Tabulation\CalculateEntryScore; +use App\Exceptions\TabulationException; use App\Http\Controllers\Controller; +use App\Models\Audition; use App\Models\Entry; use Illuminate\Http\Request; class SeatAuditionController extends Controller { - /** - * Handle the incoming request. - */ - public function __invoke(Request $request) + protected CalculateEntryScore $calc; + + public function __construct(CalculateEntryScore $calc) + { + $this->calc = $calc; + } + + public function __invoke(Request $request, Audition $audition) { $entryData = []; - $entries = Entry::forSeating()->with('student.school')->get(); + $entries = Entry::forSeating()->with('student.school')->where('audition_id', $audition->id)->get(); foreach ($entries as $entry) { + try { + $totalScore = $this->calc->calculate('seating', $entry); + } catch (TabulationException $ex) { + $totalScore[0] = $ex->getMessage(); + } $entryData[] = [ - 'rank' => 'xx', + 'rank' => 'not implemented', 'id' => $entry->id, 'studentName' => $entry->student->full_name(), 'schoolName' => $entry->student->school->name, + 'drawNumber' => $entry->draw_number, + 'totalScore' => $totalScore[0], ]; } + + //dd($entryData); + return view('tabulation.auditionSeating', ['entryData' => $entryData, 'audition' => $audition]); } } diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 21b2b16..0d68144 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -3,8 +3,10 @@ namespace App\Http\Controllers; use App\Actions\Tabulation\CalculateEntryScore; +use App\Actions\Tabulation\CalculateScoreSheetTotal; use App\Exceptions\TabulationException; use App\Models\Entry; +use App\Models\User; class TestController extends Controller { @@ -16,7 +18,7 @@ class TestController extends Controller public function flashTest() { - $entries = Entry::forSeating()->with('student')->where('audition_id', 19)->get(); + $entries = Entry::forSeating()->with('student')->where('audition_id', 17)->get(); $rows = []; foreach ($entries as $entry) { try { @@ -29,7 +31,8 @@ class TestController extends Controller 'totalScore' => $totalScore, ]; } - + $scoreCalc = new CalculateScoreSheetTotal; + $bam = $scoreCalc('seating', Entry::find(916), User::find(65))[0]; // try { // $test = $this->bigCalc->calculate('seating', Entry::find(1061))[0]; // } catch (TabulationException $ex) { @@ -39,6 +42,6 @@ class TestController extends Controller - return view('test', compact('rows')); + return view('test', compact('rows', 'bam')); } } diff --git a/resources/views/tabulation/auditionSeating-results-table.blade.php b/resources/views/tabulation/auditionSeating-results-table.blade.php index 2877e03..4b47689 100644 --- a/resources/views/tabulation/auditionSeating-results-table.blade.php +++ b/resources/views/tabulation/auditionSeating-results-table.blade.php @@ -16,23 +16,23 @@ - @foreach($entries as $entry) + @foreach($entryData as $entry) - {{ $entry->rank }} - {{ $entry->id }} - {{ $entry->draw_number }} + {{ $entry['rank'] }} + {{ $entry['id'] }} + {{ $entry['drawNumber'] }} - {{ $entry->student->full_name() }} - {{ $entry->student->school->name }} + {{ $entry['studentName'] }} + {{ $entry['schoolName'] }} - @if($doublerService->studentIsDoubler($entry->student_id)) - @include('tabulation.auditionSeating-doubler-block') - @endif +{{-- @if($doublerService->studentIsDoubler($entry->student_id))--}} +{{-- @include('tabulation.auditionSeating-doubler-block')--}} +{{-- @endif--}} - {{ number_format($entry->final_score_array[0] ?? 0,4) }} + {{ $entry['totalScore'] }} - @if($entry->scoring_complete) + @if(is_numeric($entry['totalScore'])) @endif diff --git a/resources/views/tabulation/auditionSeating.blade.php b/resources/views/tabulation/auditionSeating.blade.php index c916ff8..17292b9 100644 --- a/resources/views/tabulation/auditionSeating.blade.php +++ b/resources/views/tabulation/auditionSeating.blade.php @@ -9,21 +9,19 @@
@include('tabulation.auditionSeating-results-table')
-
- @if($audition->hasFlag('seats_published')) - @include('tabulation.auditionSeating-show-published-seats') - @elseif(! $auditionComplete) - @include('tabulation.auditionSeating-unable-to-seat-card') - @else - @include('tabulation.auditionSeating-fill-seats-form') - @include('tabulation.auditionSeating-show-proposed-seats') - @endif +{{--
--}} +{{-- @if($audition->hasFlag('seats_published'))--}} +{{-- @include('tabulation.auditionSeating-show-published-seats')--}} +{{-- @elseif(! $auditionComplete)--}} +{{-- @include('tabulation.auditionSeating-unable-to-seat-card')--}} +{{-- @else--}} +{{-- @include('tabulation.auditionSeating-fill-seats-form')--}} +{{-- @include('tabulation.auditionSeating-show-proposed-seats')--}} +{{-- @endif--}} -
+{{--
--}}
- -{{--TODO deal with unlikely scenario of a doubler is entered for seating on some auditions but not others--}} diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index f891ab6..ff56041 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -6,7 +6,7 @@ @inject('seatingService','App\Services\SeatingService') @inject('drawService', 'App\Services\DrawService') - Test Page + Test Page - {{ $bam ?? '' }} @foreach($rows as $row) {{ $row['name'] }} - {{ $row['totalScore'] }}
@endforeach -- 2.39.5 From 6939a09eefd635cf52b501808b56385acdf90286 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 03:36:35 -0500 Subject: [PATCH 12/43] Tweaks --- app/Http/Controllers/Tabulation/SeatAuditionController.php | 1 + .../views/tabulation/auditionSeating-results-table.blade.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index 3f13d7c..980ec52 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -35,6 +35,7 @@ class SeatAuditionController extends Controller 'schoolName' => $entry->student->school->name, 'drawNumber' => $entry->draw_number, 'totalScore' => $totalScore[0], + 'fullyScored' => is_numeric($totalScore[0]), ]; } diff --git a/resources/views/tabulation/auditionSeating-results-table.blade.php b/resources/views/tabulation/auditionSeating-results-table.blade.php index 4b47689..6845dc9 100644 --- a/resources/views/tabulation/auditionSeating-results-table.blade.php +++ b/resources/views/tabulation/auditionSeating-results-table.blade.php @@ -32,7 +32,7 @@ {{ $entry['totalScore'] }} - @if(is_numeric($entry['totalScore'])) + @if($entry['fullyScored']) @endif -- 2.39.5 From 6f0a4ac9bc3691fa40a1eee02000fec6059ead98 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 10 Jul 2024 23:22:37 -0500 Subject: [PATCH 13/43] Seating page lists entries in score order --- app/Actions/Tabulation/AllJudgesCount.php | 11 ++- .../Tabulation/CalculateEntryScore.php | 1 + .../Tabulation/RankAuditionEntries.php | 71 +++++++++++++++++++ .../Tabulation/SeatAuditionController.php | 22 +++--- app/Http/Controllers/TestController.php | 39 ++-------- app/Models/Entry.php | 2 + app/Models/Event.php | 5 ++ app/Providers/AppServiceProvider.php | 9 ++- composer.json | 1 + composer.lock | 67 ++++++++++++++++- resources/views/test.blade.php | 9 +-- .../Actions/RankAuditionEntriesTest.php | 61 ++++++++++++++++ 12 files changed, 246 insertions(+), 52 deletions(-) create mode 100644 app/Actions/Tabulation/RankAuditionEntries.php create mode 100644 tests/Feature/Actions/RankAuditionEntriesTest.php diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 15ba4c8..4cdb648 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -21,6 +21,7 @@ class AllJudgesCount implements CalculateEntryScore $this->basicValidation($mode, $entry); $this->areAllJudgesIn($entry); $this->areAllJudgesValid($entry); + return $this->getJudgeTotals($mode, $entry); } @@ -30,8 +31,14 @@ class AllJudgesCount implements CalculateEntryScore foreach ($entry->audition->judges as $judge) { $scores[] = $this->calculator->__invoke($mode, $entry, $judge); } - for ($i = 0; $i < count($scores[0]); $i++) { - $sums[] = $scores[0][$i] + $scores[1][$i]; + // Sum each subscore from the judges + foreach ($scores as $score) { + $index = 0; + foreach ($score as $value) { + $sums[$index] = $sums[$index] ?? 0; + $sums[$index] += $value; + $index++; + } } return $sums; diff --git a/app/Actions/Tabulation/CalculateEntryScore.php b/app/Actions/Tabulation/CalculateEntryScore.php index 8a719a1..8cb48a8 100644 --- a/app/Actions/Tabulation/CalculateEntryScore.php +++ b/app/Actions/Tabulation/CalculateEntryScore.php @@ -6,5 +6,6 @@ use App\Models\Entry; interface CalculateEntryScore { + public function calculate(string $mode, Entry $entry): array; } diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php new file mode 100644 index 0000000..aee334c --- /dev/null +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -0,0 +1,71 @@ +calculator = $calculator; + } + + public function booo() + { + return 'blah'; + } + + public function rank(string $mode, Audition $audition): Collection + { + $this->basicValidation($mode, $audition); + $entries = match ($mode) { + 'seating' => $audition->entries()->forSeating()->get(), + 'advancement' => $audition->entries()->forAdvancement()->get(), + }; + + foreach ($entries as $entry) { + try { + $entry->score_totals = $this->calculator->calculate($mode, $entry); + } catch (TabulationException $ex) { + $entry->score_totals = [-1]; + $entry->score_message = $ex->getMessage(); + } + } + // Sort entries based on their total score, then by subscores in tiebreak order + $entries = $entries->sort(function ($a, $b) { + for ($i = 0; $i < count($a->score_totals); $i++) { + if ($a->score_totals[$i] > $b->score_totals[$i]) { + return -1; + } elseif ($a->score_totals[$i] < $b->score_totals[$i]) { + return 1; + } + } + + return 0; + }); + $rank = 1; + foreach ($entries as $entry) { + $entry->rank = $rank; + $rank++; + } + + return $entries; + } + + protected function basicValidation($mode, Audition $audition): void + { + if ($mode !== 'seating' && $mode !== 'advancement') { + throw new TabulationException('Mode must be seating or advancement'); + } + if (! $audition->exists()) { + throw new TabulationException('Invalid audition provided'); + } + } +} diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index 980ec52..91dec57 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Tabulation; use App\Actions\Tabulation\CalculateEntryScore; +use App\Actions\Tabulation\RankAuditionEntries; use App\Exceptions\TabulationException; use App\Http\Controllers\Controller; use App\Models\Audition; @@ -12,30 +13,31 @@ use Illuminate\Http\Request; class SeatAuditionController extends Controller { protected CalculateEntryScore $calc; + protected RankAuditionEntries $ranker; - public function __construct(CalculateEntryScore $calc) + public function __construct(CalculateEntryScore $calc, RankAuditionEntries $ranker) { $this->calc = $calc; + $this->ranker = $ranker; } public function __invoke(Request $request, Audition $audition) { $entryData = []; - $entries = Entry::forSeating()->with('student.school')->where('audition_id', $audition->id)->get(); + #$entries = Entry::forSeating()->with('student.school')->where('audition_id', $audition->id)->get(); + $entries = $this->ranker->rank('seating', $audition); + $entries->load('student.school'); foreach ($entries as $entry) { - try { - $totalScore = $this->calc->calculate('seating', $entry); - } catch (TabulationException $ex) { - $totalScore[0] = $ex->getMessage(); - } + $totalScoreColumn = $entry->score_totals[0] >= 0 ? + $entry->score_totals[0] : $entry->score_message; $entryData[] = [ - 'rank' => 'not implemented', + 'rank' => $entry->rank, 'id' => $entry->id, 'studentName' => $entry->student->full_name(), 'schoolName' => $entry->student->school->name, 'drawNumber' => $entry->draw_number, - 'totalScore' => $totalScore[0], - 'fullyScored' => is_numeric($totalScore[0]), + 'totalScore' => $totalScoreColumn, + 'fullyScored' => $entry->score_totals[0] >= 0, ]; } diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 0d68144..9a39421 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -3,45 +3,20 @@ namespace App\Http\Controllers; use App\Actions\Tabulation\CalculateEntryScore; -use App\Actions\Tabulation\CalculateScoreSheetTotal; -use App\Exceptions\TabulationException; -use App\Models\Entry; -use App\Models\User; +use App\Actions\Tabulation\RankAuditionEntries; class TestController extends Controller { - protected CalculateEntryScore $bigCalc; - public function __construct(CalculateEntryScore $bigCalc) + protected RankAuditionEntries $rankomatic; + + public function __construct(RankAuditionEntries $rankomatic) { - $this->bigCalc = $bigCalc; + $this->rankomatic = $rankomatic; } public function flashTest() { - $entries = Entry::forSeating()->with('student')->where('audition_id', 17)->get(); - $rows = []; - foreach ($entries as $entry) { - try { - $totalScore = $this->bigCalc->calculate('seating', $entry)[0]; - } catch (TabulationException $ex){ - $totalScore = '--'; - } - $rows[] = [ - 'name' => $entry->student->full_name(), - 'totalScore' => $totalScore, - ]; - } - $scoreCalc = new CalculateScoreSheetTotal; - $bam = $scoreCalc('seating', Entry::find(916), User::find(65))[0]; -// try { -// $test = $this->bigCalc->calculate('seating', Entry::find(1061))[0]; -// } catch (TabulationException $ex) { -// dd($ex); -// } - - - - - return view('test', compact('rows', 'bam')); + dd($this->rankomatic->booo()); + return view('test'); } } diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 3d59f15..0acd0cd 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOneThrough; +use Staudenmeir\BelongsToThrough; use App\Models\ScoreSheet; class Entry extends Model @@ -38,6 +39,7 @@ class Entry extends Model return $this->belongsTo(Audition::class); } + public function school(): HasOneThrough { return $this->hasOneThrough( diff --git a/app/Models/Event.php b/app/Models/Event.php index b8b9726..b391c21 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -21,4 +21,9 @@ class Event extends Model return $this->hasMany(Ensemble::class) ->orderBy('rank'); } + + public function entries() + { + return $this->hasManyThrough(Entry::class, Audition::class); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index bc0e89d..0580e45 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,9 @@ namespace App\Providers; +use App\Actions\Tabulation\AllJudgesCount; +use App\Actions\Tabulation\CalculateEntryScore; +use App\Actions\Tabulation\CalculateScoreSheetTotal; use App\Models\Audition; use App\Models\Entry; use App\Models\Room; @@ -40,9 +43,9 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // $this->app->singleton(DrawService::class, function () { - // return new DrawService(); - // }); + $this->app->singleton(DrawService::class, function () { + return new DrawService(); + }); // // $this->app->singleton(AuditionService::class, function () { // return new AuditionService(); diff --git a/composer.json b/composer.json index 853e072..c723ebb 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "laravel/pail": "^1.1", "laravel/tinker": "^2.9", "predis/predis": "^2.2", + "staudenmeir/belongs-to-through": "^2.5", "symfony/http-client": "^7.1", "symfony/mailgun-mailer": "^7.1" }, diff --git a/composer.lock b/composer.lock index 1a7527c..a9f9731 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7aab57ef52f0152526434decd76ef1e1", + "content-hash": "cd8959ab9db27e12c6fce8cf87c52d90", "packages": [ { "name": "bacon/bacon-qr-code", @@ -3601,6 +3601,71 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "staudenmeir/belongs-to-through", + "version": "v2.16", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/belongs-to-through.git", + "reference": "79667db6660fa0065b24415bab29a5f85a0128c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/79667db6660fa0065b24415bab29a5f85a0128c7", + "reference": "79667db6660fa0065b24415bab29a5f85a0128c7", + "shasum": "" + }, + "require": { + "illuminate/database": "^11.0", + "php": "^8.2" + }, + "require-dev": { + "barryvdh/laravel-ide-helper": "^3.0", + "orchestra/testbench": "^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Staudenmeir\\BelongsToThrough\\IdeHelperServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Znck\\Eloquent\\": "src/", + "Staudenmeir\\BelongsToThrough\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rahul Kadyan", + "email": "hi@znck.me" + }, + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Laravel Eloquent BelongsToThrough relationships", + "support": { + "issues": "https://github.com/staudenmeir/belongs-to-through/issues", + "source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16" + }, + "funding": [ + { + "url": "https://paypal.me/JonasStaudenmeir", + "type": "custom" + } + ], + "time": "2024-03-09T09:53:11+00:00" + }, { "name": "symfony/clock", "version": "v7.0.7", diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index ff56041..0eb3464 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -1,4 +1,4 @@ -@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\User; @endphp +@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\Event;use App\Models\User; @endphp @php @endphp @inject('scoreservice','App\Services\ScoreService'); @inject('auditionService','App\Services\AuditionService'); @@ -7,8 +7,9 @@ @inject('drawService', 'App\Services\DrawService') Test Page - {{ $bam ?? '' }} - @foreach($rows as $row) - {{ $row['name'] }} - {{ $row['totalScore'] }}
- @endforeach + @foreach(Event::first()->entries->groupBy('student_id') as $student) + Name: {{ $student[0]->student->full_name() }}
+ Entry Count: {{ $student->count() }}
+ @endforeach
diff --git a/tests/Feature/Actions/RankAuditionEntriesTest.php b/tests/Feature/Actions/RankAuditionEntriesTest.php new file mode 100644 index 0000000..5e1a677 --- /dev/null +++ b/tests/Feature/Actions/RankAuditionEntriesTest.php @@ -0,0 +1,61 @@ +rank('wrong', Audition::factory()->create()); +})->throws(TabulationException::class, 'Mode must be seating or advancement'); +it('throws an exception if an invalid audition is provided', function () { + // Arrange + $ranker = new RankAuditionEntries(new AllJudgesCount()); + // Act & Assert + $ranker->rank('seating', Audition::factory()->make()); +})->throws(TabulationException::class, 'Invalid audition provided'); +it('includes all entries of the given mode in the return', function () { + $audition = Audition::factory()->create(); + $entries = Entry::factory()->seatingOnly()->count(10)->create(['audition_id' => $audition->id]); + $otherEntries = Entry::factory()->advanceOnly()->count(10)->create(['audition_id' => $audition->id]); + $ranker = new RankAuditionEntries(new AllJudgesCount()); + // Act + $return = $ranker->rank('seating', $audition); + // Assert + foreach ($entries as $entry) { + expect($return->pluck('id')->toArray())->toContain($entry->id); + } + foreach ($otherEntries as $entry) { + expect($return->pluck('id')->toArray())->not()->toContain($entry->id); + } +}); +it('places entries in the proper order', function () { + // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + Room::find(1000)->addJudge($judge); + $entries = Entry::factory()->count(5)->create(['audition_id' => 1000]); + $scoreArray1 = [1001 => 90, 1002 => 90, 1003 => 90, 1004 => 90, 1005 => 90]; + $scoreArray2 = [1001 => 60, 1002 => 60, 1003 => 60, 1004 => 60, 1005 => 60]; + $scoreArray3 = [1001 => 80, 1002 => 80, 1003 => 80, 1004 => 80, 1005 => 80]; + $scoreArray4 = [1001 => 100, 1002 => 100, 1003 => 100, 1004 => 100, 1005 => 100]; + $scoreArray5 = [1001 => 70, 1002 => 70, 1003 => 70, 1004 => 70, 1005 => 70]; + enterScore($judge, $entries[0], $scoreArray1); + enterScore($judge, $entries[1], $scoreArray2); + enterScore($judge, $entries[2], $scoreArray3); + enterScore($judge, $entries[3], $scoreArray4); + enterScore($judge, $entries[4], $scoreArray5); + $ranker = new RankAuditionEntries(new AllJudgesCount()); + $expectedOrder = [4, 1, 3, 5, 2]; + // Act + $return = $ranker->rank('seating', Audition::find(1000)); + // Assert + expect($return->pluck('id')->toArray())->toBe($expectedOrder); +}); -- 2.39.5 From e11741a0a1d09b9135b22bb33eaed5b5c3561ac6 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 11 Jul 2024 00:32:53 -0500 Subject: [PATCH 14/43] Work on audition service --- app/Actions/Tabulation/AllJudgesCount.php | 4 +- .../Tabulation/CalculateScoreSheetTotal.php | 38 ++++++++++++-- app/Exceptions/AuditionServiceException.php | 10 ++++ app/Providers/AppServiceProvider.php | 15 +++--- app/Services/AuditionService.php | 52 +++++++++++++++++-- .../Actions/CalculateScoreSheetTotalTest.php | 14 ++--- .../Feature/Services/AuditionServiceTest.php | 38 ++++++++++++++ 7 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 app/Exceptions/AuditionServiceException.php create mode 100644 tests/Feature/Services/AuditionServiceTest.php diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 4cdb648..d0b9978 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -13,7 +13,8 @@ class AllJudgesCount implements CalculateEntryScore public function __construct() { - $this->calculator = new CalculateScoreSheetTotal(); + #$this->calculator = new CalculateScoreSheetTotal(); + $this->calculator = app(CalculateScoreSheetTotal::class); } public function calculate(string $mode, Entry $entry): array @@ -31,6 +32,7 @@ class AllJudgesCount implements CalculateEntryScore foreach ($entry->audition->judges as $judge) { $scores[] = $this->calculator->__invoke($mode, $entry, $judge); } + $sums = []; // Sum each subscore from the judges foreach ($scores as $score) { $index = 0; diff --git a/app/Actions/Tabulation/CalculateScoreSheetTotal.php b/app/Actions/Tabulation/CalculateScoreSheetTotal.php index 1f814d5..e0c08ac 100644 --- a/app/Actions/Tabulation/CalculateScoreSheetTotal.php +++ b/app/Actions/Tabulation/CalculateScoreSheetTotal.php @@ -5,14 +5,18 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; +use App\Models\Audition; use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; +use App\Services\AuditionService; class CalculateScoreSheetTotal { - public function __construct() + protected AuditionService $auditionService; + public function __construct(AuditionService $auditionService) { + $this->auditionService = $auditionService; } public function __invoke(string $mode, Entry $entry, User $judge): array @@ -22,10 +26,8 @@ class CalculateScoreSheetTotal if (! $scoreSheet) { throw new TabulationException('No score sheet by that judge for that entry'); } - $subscores = match ($mode) { - 'seating' => $entry->audition->scoringGuide->subscores->where('for_seating', true)->sortBy('tiebreak_order'), - 'advancement' => $entry->audition->scoringGuide->subscores->where('for_advance', true)->sortBy('tiebreak_order'), - }; + #$subscores = $this->getSubscores($mode, $entry->audition); + $subscores = $this->auditionService->getSubscores($entry->audition, $mode); $scoreTotal = 0; $weightsTotal = 0; $scoreArray = []; @@ -39,6 +41,7 @@ class CalculateScoreSheetTotal $finalScore = $scoreTotal / $weightsTotal; // put $final score at the beginning of the $ScoreArray array_unshift($scoreArray, $finalScore); + return $scoreArray; } @@ -54,4 +57,29 @@ class CalculateScoreSheetTotal throw new TabulationException('Invalid judge provided'); } } + + protected function getSubscores($mode, Audition $audition) + { + static $seatingSubscores = []; + static $advancementSubscores = []; + + if ($mode === 'seating') { + if (! isset($seatingSubscores[$audition->id])) { + $seatingSubscores[$audition->id] = $audition->scoringGuide->subscores->where('for_seating', + true)->sortBy('tiebreak_order'); + } + + return $seatingSubscores[$audition->id]; + } + if ($mode === 'advancement') { + if (! isset($advancementSubscores[$audition->id])) { + $advancementSubscores[$audition->id] = $audition->scoringGuide->subscores->where('for_advance', + true)->sortBy('tiebreak_order'); + } + + return $advancementSubscores[$audition->id]; + } + + throw new TabulationException('Invalid mode requested. Mode must be seating or advancement'); + } } diff --git a/app/Exceptions/AuditionServiceException.php b/app/Exceptions/AuditionServiceException.php new file mode 100644 index 0000000..ef02c52 --- /dev/null +++ b/app/Exceptions/AuditionServiceException.php @@ -0,0 +1,10 @@ +app->singleton(DrawService::class, function () { - return new DrawService(); - }); + $this->app->singleton(DrawService::class, function () { + return new DrawService(); + }); // - // $this->app->singleton(AuditionService::class, function () { - // return new AuditionService(); - // }); + $this->app->singleton(AuditionService::class, function () { + return new AuditionService(); + }); // // $this->app->singleton(SeatingService::class, function ($app) { // return new SeatingService($app->make(TabulationService::class)); diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index ba0db24..fbed6f1 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -2,14 +2,12 @@ namespace App\Services; +use App\Exceptions\AuditionServiceException; use App\Models\Audition; -use App\Models\ScoringGuide; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Log; class AuditionService { - /** * Create a new class instance. */ @@ -18,5 +16,51 @@ class AuditionService // } + /** + * @throws AuditionServiceException + */ + public function getSubscores(Audition $audition, $mode = 'seating', $sort = 'tiebreak') + { + static $auditionSubscores = []; + $this->validateAudition($audition); + $this->validateMode($mode); + $this->validateSort($sort); + $sortColumn = match ($sort) { + 'tiebreak' => 'tiebreak_order', + 'display' => 'display_order', + }; + $modeColumn = match ($mode) { + 'seating' => 'for_seating', + 'advancement' => 'for_advance', + }; + if (! isset($auditionSubscores[$mode][$sort])) { + $auditionSubscores[$mode][$sort] = $audition->scoringGuide->subscores->where($modeColumn, true)->sortBy($sortColumn); + } else { + Log::debug('Using cached subscores'); + } + + return $auditionSubscores[$mode][$sort]; + } + + protected function validateAudition($audition) + { + if (! $audition->exists()) { + throw new AuditionServiceException('Invalid audition provided'); + } + } + + protected function validateMode($mode) + { + if ($mode !== 'seating' && $mode !== 'advancement') { + throw new AuditionServiceException('Invalid mode requested. Mode must be seating or advancement'); + } + } + + protected function validateSort($sort) + { + if ($sort !== 'tiebreak' && $sort !== 'display') { + throw new AuditionServiceException('Invalid sort requested. Sort must be tiebreak or weight'); + } + } } diff --git a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php index 5953ab1..870d729 100644 --- a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php +++ b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php @@ -11,23 +11,23 @@ use Illuminate\Foundation\Testing\RefreshDatabase; uses(RefreshDatabase::class); beforeEach(function () { - $this->calculator = new CalculateScoreSheetTotal(); + $this->calculator = app(CalculateScoreSheetTotal::class); }); it('throws an exception if an invalid mode is called for', function () { - $calculator = new CalculateScoreSheetTotal(); + $calculator = app(CalculateScoreSheetTotal::class); $calculator('anything', Entry::factory()->create(), User::factory()->create()); })->throws(TabulationException::class, 'Invalid mode requested. Mode must be seating or advancement'); it('throws an exception if an invalid judge is provided', function () { - $calculator = new CalculateScoreSheetTotal(); + $calculator = app(CalculateScoreSheetTotal::class); $calculator('seating', Entry::factory()->create(), User::factory()->make()); })->throws(TabulationException::class, 'Invalid judge provided'); it('throws an exception if an invalid entry is provided', function () { - $calculator = new CalculateScoreSheetTotal(); + $calculator = app(CalculateScoreSheetTotal::class); $calculator('advancement', Entry::factory()->make(), User::factory()->create()); })->throws(TabulationException::class, 'Invalid entry provided'); it('throws an exception if the specified judge has not scored the entry', function () { // Arrange - $calculator = new CalculateScoreSheetTotal(); + $calculator = app(CalculateScoreSheetTotal::class); // Act $calculator('seating', Entry::factory()->create(), User::factory()->create()); //Assert @@ -45,7 +45,7 @@ it('correctly calculates final score for seating', function () { 1005 => 90, ]; enterScore($judge, $entry, $scores); - $calculator = new CalculateScoreSheetTotal(); + $calculator = app(CalculateScoreSheetTotal::class); $total = $calculator('seating', $entry, $judge); expect($total[0])->toBe(68.75); $expectedArray = [68.75, 80, 60, 70, 50]; @@ -64,7 +64,7 @@ it('correctly calculates final score for advancement', function () { 1005 => 90, ]; enterScore($judge, $entry, $scores); - $calculator = new CalculateScoreSheetTotal(); + $calculator = app(CalculateScoreSheetTotal::class); $total = $calculator('advancement', $entry, $judge); expect($total[0])->toBe(73.75); $expectedArray = [73.75, 90, 80, 60, 70]; diff --git a/tests/Feature/Services/AuditionServiceTest.php b/tests/Feature/Services/AuditionServiceTest.php new file mode 100644 index 0000000..8804a14 --- /dev/null +++ b/tests/Feature/Services/AuditionServiceTest.php @@ -0,0 +1,38 @@ +expectException(\App\Exceptions\AuditionServiceException::class); + $auditionService->getSubscores(new Audition(), 'invalid_mode'); +}); +it('throws an exception when an invalid sort is requested', function () { + // Arrange + $auditionService = new \App\Services\AuditionService(); + $this->expectException(\App\Exceptions\AuditionServiceException::class); + // Act + $auditionService->getSubscores(new Audition(), 'seating', 'invalid_sort'); +}); +it('throws an exception when an invalid audition is provided', function () { + // Arrange + $auditionService = new \App\Services\AuditionService(); + $this->expectException(\App\Exceptions\AuditionServiceException::class); + $auditionService->getSubscores(new Audition(), 'seating', 'tiebreak'); + // Act & Assert + +}); +it('gets subscores for an audition', function () { + // Arrange + loadSampleAudition(); + $auditionService = new \App\Services\AuditionService(); + // Act + $subscores = $auditionService->getSubscores(Audition::find(1000), 'seating', 'tiebreak'); + // Assert + expect($subscores->toArray())->toBe(Audition::find(1000)->scoringGuide->subscores->where('for_seating', true)->sortBy('tiebreak_order')->toArray()); +}); -- 2.39.5 From ee8003fd4545f41f2f576bf1a569d4cfe6b276ff Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 11 Jul 2024 00:46:39 -0500 Subject: [PATCH 15/43] Audition Service Updates --- app/Actions/Tabulation/AllJudgesCount.php | 10 ++-- .../Tabulation/CalculateScoreSheetTotal.php | 28 +---------- app/Services/AuditionService.php | 46 +++++++++++-------- 3 files changed, 34 insertions(+), 50 deletions(-) diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index d0b9978..619d817 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -6,15 +6,17 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Entry; +use App\Services\AuditionService; class AllJudgesCount implements CalculateEntryScore { protected CalculateScoreSheetTotal $calculator; + protected AuditionService $auditionService; public function __construct() { - #$this->calculator = new CalculateScoreSheetTotal(); $this->calculator = app(CalculateScoreSheetTotal::class); + $this->auditionService = app(AuditionService::class); } public function calculate(string $mode, Entry $entry): array @@ -29,7 +31,7 @@ class AllJudgesCount implements CalculateEntryScore protected function getJudgeTotals($mode, Entry $entry) { $scores = []; - foreach ($entry->audition->judges as $judge) { + foreach ($this->auditionService->getJudges($entry->audition) as $judge) { $scores[] = $this->calculator->__invoke($mode, $entry, $judge); } $sums = []; @@ -59,7 +61,7 @@ class AllJudgesCount implements CalculateEntryScore protected function areAllJudgesIn(Entry $entry): void { - $assignedJudgeCount = $entry->audition->judges->count(); + $assignedJudgeCount = $this->auditionService->getJudges($entry->audition)->count(); if ($entry->scoreSheets->count() !== $assignedJudgeCount) { throw new TabulationException('Not all score sheets are in'); } @@ -67,7 +69,7 @@ class AllJudgesCount implements CalculateEntryScore protected function areAllJudgesValid(Entry $entry): void { - $validJudgeIds = $entry->audition->judges->sort()->pluck('id')->toArray(); + $validJudgeIds = $this->auditionService->getJudges($entry->audition)->sort()->pluck('id')->toArray(); $existingJudgeIds = $entry->scoreSheets->sort()->pluck('user_id')->toArray(); if ($validJudgeIds !== $existingJudgeIds) { throw new TabulationException('Score exists from a judge not assigned to this audition'); diff --git a/app/Actions/Tabulation/CalculateScoreSheetTotal.php b/app/Actions/Tabulation/CalculateScoreSheetTotal.php index e0c08ac..ce626e2 100644 --- a/app/Actions/Tabulation/CalculateScoreSheetTotal.php +++ b/app/Actions/Tabulation/CalculateScoreSheetTotal.php @@ -5,7 +5,6 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; -use App\Models\Audition; use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; @@ -14,6 +13,7 @@ use App\Services\AuditionService; class CalculateScoreSheetTotal { protected AuditionService $auditionService; + public function __construct(AuditionService $auditionService) { $this->auditionService = $auditionService; @@ -26,7 +26,6 @@ class CalculateScoreSheetTotal if (! $scoreSheet) { throw new TabulationException('No score sheet by that judge for that entry'); } - #$subscores = $this->getSubscores($mode, $entry->audition); $subscores = $this->auditionService->getSubscores($entry->audition, $mode); $scoreTotal = 0; $weightsTotal = 0; @@ -57,29 +56,4 @@ class CalculateScoreSheetTotal throw new TabulationException('Invalid judge provided'); } } - - protected function getSubscores($mode, Audition $audition) - { - static $seatingSubscores = []; - static $advancementSubscores = []; - - if ($mode === 'seating') { - if (! isset($seatingSubscores[$audition->id])) { - $seatingSubscores[$audition->id] = $audition->scoringGuide->subscores->where('for_seating', - true)->sortBy('tiebreak_order'); - } - - return $seatingSubscores[$audition->id]; - } - if ($mode === 'advancement') { - if (! isset($advancementSubscores[$audition->id])) { - $advancementSubscores[$audition->id] = $audition->scoringGuide->subscores->where('for_advance', - true)->sortBy('tiebreak_order'); - } - - return $advancementSubscores[$audition->id]; - } - - throw new TabulationException('Invalid mode requested. Mode must be seating or advancement'); - } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index fbed6f1..5ba6a3d 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -4,7 +4,7 @@ namespace App\Services; use App\Exceptions\AuditionServiceException; use App\Models\Audition; -use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Cache; class AuditionService { @@ -21,26 +21,34 @@ class AuditionService */ public function getSubscores(Audition $audition, $mode = 'seating', $sort = 'tiebreak') { - static $auditionSubscores = []; - $this->validateAudition($audition); - $this->validateMode($mode); - $this->validateSort($sort); + $cacheKey = 'auditionSubscores-'.$audition->id.'-'.$mode.'-'.$sort; - $sortColumn = match ($sort) { - 'tiebreak' => 'tiebreak_order', - 'display' => 'display_order', - }; - $modeColumn = match ($mode) { - 'seating' => 'for_seating', - 'advancement' => 'for_advance', - }; - if (! isset($auditionSubscores[$mode][$sort])) { - $auditionSubscores[$mode][$sort] = $audition->scoringGuide->subscores->where($modeColumn, true)->sortBy($sortColumn); - } else { - Log::debug('Using cached subscores'); - } + return Cache::remember($cacheKey, 10, function () use ($audition, $mode, $sort) { + $this->validateAudition($audition); + $this->validateMode($mode); + $this->validateSort($sort); - return $auditionSubscores[$mode][$sort]; + $sortColumn = match ($sort) { + 'tiebreak' => 'tiebreak_order', + 'display' => 'display_order', + }; + $modeColumn = match ($mode) { + 'seating' => 'for_seating', + 'advancement' => 'for_advance', + }; + + return $audition->scoringGuide->subscores->where($modeColumn, true)->sortBy($sortColumn); + }); + } + + public function getJudges(Audition $audition) + { + $cacheKey = 'auditionJudges-'.$audition->id; + return Cache::remember($cacheKey, 10, function () use ($audition) { + $this->validateAudition($audition); + + return $audition->judges; + }); } protected function validateAudition($audition) -- 2.39.5 From d803b7fd095dbbc8ed31d41eb7c8c4be641d46c9 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 11 Jul 2024 16:17:39 -0500 Subject: [PATCH 16/43] Implementing some services to reduce queries --- .../Tabulation/CalculateScoreSheetTotal.php | 12 +++-- .../Tabulation/RankAuditionEntries.php | 5 +- app/Http/Controllers/Admin/DrawController.php | 2 +- app/Http/Controllers/Admin/RoomController.php | 2 +- app/Http/Controllers/EntryController.php | 1 + app/Http/Controllers/JudgingController.php | 4 +- app/Providers/AppServiceProvider.php | 46 ++++++------------- app/Services/AuditionService.php | 11 ++++- app/Services/EntryService.php | 16 +++++-- app/Services/UserService.php | 23 ++++++++++ .../Actions/RankAuditionEntriesTest.php | 2 + 11 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 app/Services/UserService.php diff --git a/app/Actions/Tabulation/CalculateScoreSheetTotal.php b/app/Actions/Tabulation/CalculateScoreSheetTotal.php index ce626e2..3d2d506 100644 --- a/app/Actions/Tabulation/CalculateScoreSheetTotal.php +++ b/app/Actions/Tabulation/CalculateScoreSheetTotal.php @@ -9,14 +9,20 @@ use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; use App\Services\AuditionService; +use App\Services\EntryService; +use App\Services\UserService; class CalculateScoreSheetTotal { protected AuditionService $auditionService; + protected EntryService $entryService; + protected UserService $userService; - public function __construct(AuditionService $auditionService) + public function __construct(AuditionService $auditionService, EntryService $entryService, UserService $userService) { $this->auditionService = $auditionService; + $this->entryService = $entryService; + $this->userService = $userService; } public function __invoke(string $mode, Entry $entry, User $judge): array @@ -49,10 +55,10 @@ class CalculateScoreSheetTotal if ($mode !== 'seating' and $mode !== 'advancement') { throw new TabulationException('Invalid mode requested. Mode must be seating or advancement'); } - if (! $entry->exists()) { + if (! $this->entryService->entryExists($entry)) { throw new TabulationException('Invalid entry provided'); } - if (! $judge->exists()) { + if (! $this->userService->userExists($judge)) { throw new TabulationException('Invalid judge provided'); } } diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index aee334c..0c13cf3 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -26,11 +26,12 @@ class RankAuditionEntries { $this->basicValidation($mode, $audition); $entries = match ($mode) { - 'seating' => $audition->entries()->forSeating()->get(), - 'advancement' => $audition->entries()->forAdvancement()->get(), + 'seating' => $audition->entries()->forSeating()->with('scoreSheets')->get(), + 'advancement' => $audition->entries()->forAdvancement()->with('scoreSheets')->get(), }; foreach ($entries as $entry) { + $entry->setRelation('audition', $audition); try { $entry->score_totals = $this->calculator->calculate($mode, $entry); } catch (TabulationException $ex) { diff --git a/app/Http/Controllers/Admin/DrawController.php b/app/Http/Controllers/Admin/DrawController.php index 6578e94..64d15e8 100644 --- a/app/Http/Controllers/Admin/DrawController.php +++ b/app/Http/Controllers/Admin/DrawController.php @@ -25,7 +25,7 @@ class DrawController extends Controller public function index(Request $request) { - $events = Event::with('auditions')->get(); + $events = Event::with('auditions.flags')->get(); // $drawnAuditionsExist is true if any audition->hasFlag('drawn') is true $drawnAuditionsExist = Audition::whereHas('flags', function ($query) { $query->where('flag_name', 'drawn'); diff --git a/app/Http/Controllers/Admin/RoomController.php b/app/Http/Controllers/Admin/RoomController.php index 4f1cee1..0b64b1f 100644 --- a/app/Http/Controllers/Admin/RoomController.php +++ b/app/Http/Controllers/Admin/RoomController.php @@ -17,7 +17,7 @@ class RoomController extends Controller if (! Auth::user()->is_admin) { abort(403); } - $rooms = Room::with('auditions.entries')->orderBy('name')->get(); + $rooms = Room::with('auditions.entries','entries')->orderBy('name')->get(); return view('admin.rooms.index', ['rooms' => $rooms]); } diff --git a/app/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php index 9396f2e..c2ac9c7 100644 --- a/app/Http/Controllers/EntryController.php +++ b/app/Http/Controllers/EntryController.php @@ -21,6 +21,7 @@ class EntryController extends Controller }); $auditions = Audition::open()->get(); $students = Auth::user()->students; + $students->load('school'); return view('entries.index', ['entries' => $entries, 'students' => $students, 'auditions' => $auditions]); } diff --git a/app/Http/Controllers/JudgingController.php b/app/Http/Controllers/JudgingController.php index 56c09a0..0f0a6ee 100644 --- a/app/Http/Controllers/JudgingController.php +++ b/app/Http/Controllers/JudgingController.php @@ -5,11 +5,11 @@ namespace App\Http\Controllers; use App\Models\Audition; use App\Models\Entry; use App\Models\JudgeAdvancementVote; +use App\Models\ScoreSheet; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; -use App\Models\ScoreSheet; use function compact; use function redirect; @@ -20,6 +20,7 @@ class JudgingController extends Controller public function index() { $rooms = Auth::user()->judgingAssignments; + $rooms->load('auditions'); return view('judging.index', compact('rooms')); } @@ -150,6 +151,7 @@ class JudgingController extends Controller return redirect(url()->previous())->with('error', 'Error saving advancement vote'); } } + return null; } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5e51ef1..b9769f5 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,9 @@ namespace App\Providers; +use App\Actions\Tabulation\AllJudgesCount; +use App\Actions\Tabulation\CalculateEntryScore; +use App\Actions\Tabulation\CalculateScoreSheetTotal; use App\Models\Audition; use App\Models\Entry; use App\Models\Room; @@ -25,12 +28,11 @@ use App\Observers\StudentObserver; use App\Observers\SubscoreDefinitionObserver; use App\Observers\UserObserver; use App\Services\AuditionService; -use App\Services\DoublerService; use App\Services\DrawService; use App\Services\EntryService; use App\Services\ScoreService; -use App\Services\SeatingService; -use App\Services\TabulationService; +use App\Services\UserService; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -40,36 +42,13 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - $this->app->singleton(DrawService::class, function () { - return new DrawService(); - }); - // - $this->app->singleton(AuditionService::class, function () { - return new AuditionService(); - }); - // - // $this->app->singleton(SeatingService::class, function ($app) { - // return new SeatingService($app->make(TabulationService::class)); - // }); - // - $this->app->singleton(EntryService::class, function () { - return new EntryService(); - }); - - $this->app->singleton(ScoreService::class, function () { - return new ScoreService(); - }); - // - // $this->app->singleton(TabulationService::class, function ($app) { - // return new TabulationService( - // $app->make(AuditionService::class), - // $app->make(ScoreService::class), - // $app->make(EntryService::class)); - // }); - // - // $this->app->singleton(DoublerService::class, function ($app) { - // return new DoublerService($app->make(AuditionService::class), $app->make(TabulationService::class), $app->make(SeatingService::class)); - // }); + $this->app->singleton(CalculateScoreSheetTotal::class, CalculateScoreSheetTotal::class); + $this->app->singleton(CalculateEntryScore::class, AllJudgesCount::class); + $this->app->singleton(DrawService::class, DrawService::class); + $this->app->singleton(AuditionService::class, AuditionService::class); + $this->app->singleton(EntryService::class, EntryService::class); + $this->app->singleton(ScoreService::class, ScoreService::class); + $this->app->singleton(UserService::class, UserService::class); } /** @@ -90,5 +69,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); + Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index 5ba6a3d..988a3b9 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Exceptions\AuditionServiceException; use App\Models\Audition; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; class AuditionService @@ -11,9 +12,11 @@ class AuditionService /** * Create a new class instance. */ + public static Collection $allAuditionIds; + public function __construct() { - // + self::$allAuditionIds = Audition::pluck('id'); } /** @@ -44,6 +47,7 @@ class AuditionService public function getJudges(Audition $audition) { $cacheKey = 'auditionJudges-'.$audition->id; + return Cache::remember($cacheKey, 10, function () use ($audition) { $this->validateAudition($audition); @@ -71,4 +75,9 @@ class AuditionService throw new AuditionServiceException('Invalid sort requested. Sort must be tiebreak or weight'); } } + + public function auditionExists($audition) + { + return self::$allAuditionIds->contains($audition->id); + } } diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index 0954889..6e4890c 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -3,18 +3,15 @@ namespace App\Services; use App\Models\Entry; -use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Cache; class EntryService { - /** * Create a new class instance. */ public function __construct() { - // + } public function isEntryLate(Entry $entry): bool @@ -25,4 +22,15 @@ class EntryService return $entry->created_at > $entry->audition->entry_deadline; } + + public function entryExists(Entry $entry): bool + { + static $allEntryIds = null; + + if ($allEntryIds === null) { + $allEntryIds = Entry::pluck('id'); + } + + return $allEntryIds->contains($entry->id); + } } diff --git a/app/Services/UserService.php b/app/Services/UserService.php new file mode 100644 index 0000000..463ec4d --- /dev/null +++ b/app/Services/UserService.php @@ -0,0 +1,23 @@ +contains($user->id); + } +} diff --git a/tests/Feature/Actions/RankAuditionEntriesTest.php b/tests/Feature/Actions/RankAuditionEntriesTest.php index 5e1a677..b8fb87f 100644 --- a/tests/Feature/Actions/RankAuditionEntriesTest.php +++ b/tests/Feature/Actions/RankAuditionEntriesTest.php @@ -8,6 +8,7 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Artisan; uses(RefreshDatabase::class); @@ -52,6 +53,7 @@ it('places entries in the proper order', function () { enterScore($judge, $entries[2], $scoreArray3); enterScore($judge, $entries[3], $scoreArray4); enterScore($judge, $entries[4], $scoreArray5); + Artisan::call('cache:clear'); $ranker = new RankAuditionEntries(new AllJudgesCount()); $expectedOrder = [4, 1, 3, 5, 2]; // Act -- 2.39.5 From 3e6048c5ccb8c864417ad8eb14b663ee7d138fb8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Thu, 11 Jul 2024 22:52:37 -0500 Subject: [PATCH 17/43] Implement some short term caching --- app/Actions/Tabulation/AllJudgesCount.php | 19 ++++++++++++------- .../Tabulation/RankAuditionEntries.php | 12 +++++++++--- app/Providers/AppServiceProvider.php | 2 +- app/Services/AuditionService.php | 10 ++++------ app/Services/EntryService.php | 10 +++++----- app/Services/UserService.php | 9 +++++---- app/helpers.php | 3 ++- config/cache.php | 1 + .../AllJudgesCountTest.php | 19 +++++++++++++------ .../Actions/CalculateScoreSheetTotalTest.php | 13 +++++++++---- tests/Feature/Actions/EnterScoreTest.php | 4 +++- .../Actions/RankAuditionEntriesTest.php | 13 +++++++++---- tests/Feature/Models/EntryTest.php | 1 + .../Feature/Pages/Admin/SchoolsIndexTest.php | 6 +++++- tests/Feature/Pages/Setup/DrawStoreTest.php | 7 +++++-- .../Feature/Services/AuditionServiceTest.php | 14 ++++++++++---- tests/Feature/Services/ScoreServiceTest.php | 4 +++- 17 files changed, 97 insertions(+), 50 deletions(-) diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 619d817..9e40fee 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -7,25 +7,30 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Entry; use App\Services\AuditionService; +use Illuminate\Support\Facades\Cache; class AllJudgesCount implements CalculateEntryScore { protected CalculateScoreSheetTotal $calculator; protected AuditionService $auditionService; - public function __construct() + public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService) { - $this->calculator = app(CalculateScoreSheetTotal::class); - $this->auditionService = app(AuditionService::class); + $this->calculator = $calculator; + $this->auditionService = $auditionService; } public function calculate(string $mode, Entry $entry): array { - $this->basicValidation($mode, $entry); - $this->areAllJudgesIn($entry); - $this->areAllJudgesValid($entry); + $cacheKey = 'entryScore-'.$entry->id.'-'.$mode; + return Cache::remember($cacheKey, 10, function () use ($mode, $entry) { + $this->basicValidation($mode, $entry); + $this->areAllJudgesIn($entry); + $this->areAllJudgesValid($entry); + + return $this->getJudgeTotals($mode, $entry); + }); - return $this->getJudgeTotals($mode, $entry); } protected function getJudgeTotals($mode, Entry $entry) diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index 0c13cf3..32a81e2 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -7,6 +7,7 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Audition; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Cache; class RankAuditionEntries { @@ -17,12 +18,17 @@ class RankAuditionEntries $this->calculator = $calculator; } - public function booo() + public function rank(string $mode, Audition $audition): Collection { - return 'blah'; + $cacheKey = 'audition'.$audition->id.$mode; + + return Cache::remember($cacheKey, 30, function () use ($mode, $audition) { + return $this->calculateRank($mode, $audition); + }); + } - public function rank(string $mode, Audition $audition): Collection + public function calculateRank(string $mode, Audition $audition): Collection { $this->basicValidation($mode, $audition); $entries = match ($mode) { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b9769f5..195fbc4 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -69,6 +69,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - Model::preventLazyLoading(! app()->isProduction()); + #Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index 988a3b9..1b18787 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -16,7 +16,10 @@ class AuditionService public function __construct() { - self::$allAuditionIds = Audition::pluck('id'); + $cacheKey = 'allAuditionIds'; + self::$allAuditionIds = Cache::remember($cacheKey, 60, function () { + return Audition::pluck('id'); + }); } /** @@ -75,9 +78,4 @@ class AuditionService throw new AuditionServiceException('Invalid sort requested. Sort must be tiebreak or weight'); } } - - public function auditionExists($audition) - { - return self::$allAuditionIds->contains($audition->id); - } } diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index 6e4890c..8d1ba71 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Models\Entry; +use Illuminate\Support\Facades\Cache; class EntryService { @@ -25,11 +26,10 @@ class EntryService public function entryExists(Entry $entry): bool { - static $allEntryIds = null; - - if ($allEntryIds === null) { - $allEntryIds = Entry::pluck('id'); - } + $cacheKey = 'allEntryIds'; + $allEntryIds = Cache::remember($cacheKey, 60, function () { + return Entry::pluck('id'); + }); return $allEntryIds->contains($entry->id); } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 463ec4d..545e069 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Models\User; +use Illuminate\Support\Facades\Cache; class UserService { @@ -13,10 +14,10 @@ class UserService public function userExists(User $user): bool { - static $allUserIds = null; - if ($allUserIds === null) { - $allUserIds = User::pluck('id'); - } + $cacheKey = 'allUserIds'; + $allUserIds = Cache::remember($cacheKey, 60, function () { + return User::pluck('id'); + }); return $allUserIds->contains($user->id); } diff --git a/app/helpers.php b/app/helpers.php index abe2a72..09e1327 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -5,6 +5,7 @@ use App\Exceptions\ScoreEntryException; use App\Models\Entry; use App\Models\User; use App\Settings; +use Illuminate\Support\Facades\App; function tw_max_width_class_array(): array { @@ -39,6 +40,6 @@ function auditionSetting($key) */ function enterScore(User $user, Entry $entry, array $scores): \App\Models\ScoreSheet { - $scoreEntry = new EnterScore(); + $scoreEntry = App::make(EnterScore::class); return $scoreEntry($user, $entry, $scores); } diff --git a/config/cache.php b/config/cache.php index 6b57b18..12aa893 100644 --- a/config/cache.php +++ b/config/cache.php @@ -74,6 +74,7 @@ return [ 'driver' => 'redis', 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + 'prefix' => env('REDIS_CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), ], 'dynamodb' => [ diff --git a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php index f916a38..efb7e99 100644 --- a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php +++ b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php @@ -8,17 +8,20 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); it('throws an exception if mode is not seating or advancement', function () { - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); $calculator->calculate('WRONG', Entry::factory()->create()); })->throws(TabulationException::class, 'Mode must be seating or advancement'); it('throws an exception if entry is not valid', function () { // Arrange - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); // Act $calculator->calculate('seating', Entry::factory()->make()); // Assert @@ -38,7 +41,8 @@ it('throws an exception if entry is missing judge scores', function () { 1004 => 80, 1005 => 90, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); // Act $calculator->calculate('seating', $entry); @@ -61,7 +65,8 @@ it('throws an exception if a score exists from an invalid judge', function () { 1004 => 80, 1005 => 90, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); $scoreSheetToSpoof = enterScore($judge2, $entry, $scores); $scoreSheetToSpoof->update(['user_id' => $judge3->id]); @@ -92,7 +97,8 @@ it('correctly calculates scores for seating', function () { 1004 => 85, 1005 => 95, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); enterScore($judge2, $entry, $scores2); // Act @@ -124,7 +130,8 @@ it('correctly calculates scores for advancement', function () { 1004 => 85, 1005 => 95, ]; - $calculator = new AllJudgesCount(); + #$calculator = new AllJudgesCount(); + $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); enterScore($judge2, $entry, $scores2); // Act diff --git a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php index 870d729..a3ce1c1 100644 --- a/tests/Feature/Actions/CalculateScoreSheetTotalTest.php +++ b/tests/Feature/Actions/CalculateScoreSheetTotalTest.php @@ -8,11 +8,11 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Artisan; uses(RefreshDatabase::class); -beforeEach(function () { - $this->calculator = app(CalculateScoreSheetTotal::class); -}); + + it('throws an exception if an invalid mode is called for', function () { $calculator = app(CalculateScoreSheetTotal::class); $calculator('anything', Entry::factory()->create(), User::factory()->create()); @@ -27,9 +27,14 @@ it('throws an exception if an invalid entry is provided', function () { })->throws(TabulationException::class, 'Invalid entry provided'); it('throws an exception if the specified judge has not scored the entry', function () { // Arrange + loadSampleAudition(); + $judge = User::factory()->create(); + Room::find(1000)->addJudge($judge); + $entry = Entry::factory()->create(['audition_id' => 1000]); + Artisan::call('cache:clear'); $calculator = app(CalculateScoreSheetTotal::class); // Act - $calculator('seating', Entry::factory()->create(), User::factory()->create()); + $calculator('seating', $entry, $judge); //Assert })->throws(TabulationException::class, 'No score sheet by that judge for that entry'); it('correctly calculates final score for seating', function () { diff --git a/tests/Feature/Actions/EnterScoreTest.php b/tests/Feature/Actions/EnterScoreTest.php index 18f1dde..b9dd140 100644 --- a/tests/Feature/Actions/EnterScoreTest.php +++ b/tests/Feature/Actions/EnterScoreTest.php @@ -9,11 +9,13 @@ use App\Models\Room; use App\Models\ScoreSheet; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); beforeEach(function () { - $this->scoreEntry = new EnterScore(); + #$this->scoreEntry = new EnterScore(); + $this->scoreEntry = App::make(EnterScore::class); }); test('throws an exception if the user does not exist', function () { diff --git a/tests/Feature/Actions/RankAuditionEntriesTest.php b/tests/Feature/Actions/RankAuditionEntriesTest.php index b8fb87f..6ced42e 100644 --- a/tests/Feature/Actions/RankAuditionEntriesTest.php +++ b/tests/Feature/Actions/RankAuditionEntriesTest.php @@ -8,17 +8,20 @@ use App\Models\Entry; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Artisan; uses(RefreshDatabase::class); it('throws an exception if an invalid mode is specified', function () { - $ranker = new RankAuditionEntries(new AllJudgesCount()); + #$ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); $ranker->rank('wrong', Audition::factory()->create()); })->throws(TabulationException::class, 'Mode must be seating or advancement'); it('throws an exception if an invalid audition is provided', function () { // Arrange - $ranker = new RankAuditionEntries(new AllJudgesCount()); + #$ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); // Act & Assert $ranker->rank('seating', Audition::factory()->make()); })->throws(TabulationException::class, 'Invalid audition provided'); @@ -26,7 +29,8 @@ it('includes all entries of the given mode in the return', function () { $audition = Audition::factory()->create(); $entries = Entry::factory()->seatingOnly()->count(10)->create(['audition_id' => $audition->id]); $otherEntries = Entry::factory()->advanceOnly()->count(10)->create(['audition_id' => $audition->id]); - $ranker = new RankAuditionEntries(new AllJudgesCount()); + #$ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); // Act $return = $ranker->rank('seating', $audition); // Assert @@ -39,6 +43,7 @@ it('includes all entries of the given mode in the return', function () { }); it('places entries in the proper order', function () { // Arrange + Artisan::call('cache:clear'); loadSampleAudition(); $judge = User::factory()->create(); Room::find(1000)->addJudge($judge); @@ -54,7 +59,7 @@ it('places entries in the proper order', function () { enterScore($judge, $entries[3], $scoreArray4); enterScore($judge, $entries[4], $scoreArray5); Artisan::call('cache:clear'); - $ranker = new RankAuditionEntries(new AllJudgesCount()); + $ranker = App::make(RankAuditionEntries::class); $expectedOrder = [4, 1, 3, 5, 2]; // Act $return = $ranker->rank('seating', Audition::find(1000)); diff --git a/tests/Feature/Models/EntryTest.php b/tests/Feature/Models/EntryTest.php index c0e21a9..4d30f30 100644 --- a/tests/Feature/Models/EntryTest.php +++ b/tests/Feature/Models/EntryTest.php @@ -169,6 +169,7 @@ it('has a forAdvancement scope that only returns those entries entered for advan Entry::factory()->count(10)->create(['for_seating' => true, 'for_advancement' => true]); Entry::factory()->count(5)->create(['for_seating' => false, 'for_advancement' => true]); // Act & Assert + expect(Entry::forAdvancement()->count())->toBe(16) ->and(Entry::forAdvancement()->get()->first())->toBeInstanceOf(Entry::class) ->and(Entry::forAdvancement()->get()->first()->student->first_name)->toBe('Advance Only'); diff --git a/tests/Feature/Pages/Admin/SchoolsIndexTest.php b/tests/Feature/Pages/Admin/SchoolsIndexTest.php index bc05450..d7d8e9a 100644 --- a/tests/Feature/Pages/Admin/SchoolsIndexTest.php +++ b/tests/Feature/Pages/Admin/SchoolsIndexTest.php @@ -4,9 +4,12 @@ use App\Models\Entry; use App\Models\School; use App\Models\Student; use App\Models\User; +use App\Services\EntryService; +use App\Services\Invoice\InvoiceOneFeePerEntry; use App\Settings; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; use function Pest\Laravel\actingAs; use function Pest\Laravel\get; @@ -62,7 +65,8 @@ it('has a new school link', function () { }); it('shows school data', function () { // Arrange - $invoiceDataService = new App\Services\Invoice\InvoiceOneFeePerEntry(new App\Services\EntryService(new App\Services\AuditionService())); + #$invoiceDataService = new App\Services\Invoice\InvoiceOneFeePerEntry(new App\Services\EntryService(new App\Services\AuditionService())); + $invoiceDataService = App::make(InvoiceOneFeePerEntry::class); Settings::set('school_fees', 1000); Settings::set('late_fee', 2500); actingAs($this->adminUser); diff --git a/tests/Feature/Pages/Setup/DrawStoreTest.php b/tests/Feature/Pages/Setup/DrawStoreTest.php index ad3e63f..44cfe60 100644 --- a/tests/Feature/Pages/Setup/DrawStoreTest.php +++ b/tests/Feature/Pages/Setup/DrawStoreTest.php @@ -4,6 +4,7 @@ use App\Models\Audition; use App\Models\Entry; use App\Services\DrawService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); @@ -77,7 +78,8 @@ it('sets the draw_number column on each entry in the audition based on the rando // Arrange $audition = Audition::factory()->hasEntries(10)->create(); Entry::all()->each(fn ($entry) => expect($entry->draw_number)->toBeNull()); - $drawService = new DrawService(); + #$drawService = new DrawService(); + $drawService = App::make(DrawService::class); $drawService->runOneDraw($audition); // Act & Assert Entry::all()->each(fn ($entry) => expect($entry->draw_number)->not()->toBeNull()); @@ -86,7 +88,8 @@ it('only sets draw numbers in the specified audition', function () { // Arrange $audition = Audition::factory()->hasEntries(10)->create(); $bonusEntry = Entry::factory()->create(); - $drawService = new DrawService(); + #$drawService = new DrawService(); + $drawService = App::make(DrawService::class); $drawService->runOneDraw($audition); // Act & Assert expect($bonusEntry->draw_number)->toBeNull(); diff --git a/tests/Feature/Services/AuditionServiceTest.php b/tests/Feature/Services/AuditionServiceTest.php index 8804a14..5bbf8c0 100644 --- a/tests/Feature/Services/AuditionServiceTest.php +++ b/tests/Feature/Services/AuditionServiceTest.php @@ -3,25 +3,30 @@ /** @noinspection PhpUnhandledExceptionInspection */ use App\Models\Audition; +use App\Services\AuditionService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); it('throws an exception when an invalid mode is requested', function () { - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); $auditionService->getSubscores(new Audition(), 'invalid_mode'); }); it('throws an exception when an invalid sort is requested', function () { // Arrange - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); // Act $auditionService->getSubscores(new Audition(), 'seating', 'invalid_sort'); }); it('throws an exception when an invalid audition is provided', function () { // Arrange - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); $auditionService->getSubscores(new Audition(), 'seating', 'tiebreak'); // Act & Assert @@ -30,7 +35,8 @@ it('throws an exception when an invalid audition is provided', function () { it('gets subscores for an audition', function () { // Arrange loadSampleAudition(); - $auditionService = new \App\Services\AuditionService(); + #$auditionService = new \App\Services\AuditionService(); + $auditionService = App::make(AuditionService::class); // Act $subscores = $auditionService->getSubscores(Audition::find(1000), 'seating', 'tiebreak'); // Assert diff --git a/tests/Feature/Services/ScoreServiceTest.php b/tests/Feature/Services/ScoreServiceTest.php index 64f544f..df9d3e3 100644 --- a/tests/Feature/Services/ScoreServiceTest.php +++ b/tests/Feature/Services/ScoreServiceTest.php @@ -7,11 +7,13 @@ use App\Models\ScoreSheet; use App\Models\User; use App\Services\ScoreService; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); beforeEach(function () { - $this->scoreService = new ScoreService(); + #$this->scoreService = new ScoreService(); + $this->scoreService = App::make(ScoreService::class); }); it('can check if an entry is fully scored', function () { -- 2.39.5 From b250105dd612036131eb61e41929e47977180083 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 01:42:11 -0500 Subject: [PATCH 18/43] DoublerService can now identify doublers --- app/Models/Event.php | 3 +- app/Models/User.php | 2 +- app/Providers/AppServiceProvider.php | 2 ++ app/Services/DoublerService.php | 44 +++++++++++++++------------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/app/Models/Event.php b/app/Models/Event.php index b391c21..07fe9df 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; class Event extends Model { @@ -22,7 +23,7 @@ class Event extends Model ->orderBy('rank'); } - public function entries() + public function entries(): HasManyThrough { return $this->hasManyThrough(Entry::class, Audition::class); } diff --git a/app/Models/User.php b/app/Models/User.php index 350792f..eeb74cb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -125,7 +125,7 @@ class User extends Authenticatable implements MustVerifyEmail public function isJudge(): bool { - return $this->judgingAssignments->count() > 0; + return $this->judgingAssignments()->count() > 0; } /** diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 195fbc4..a33fc4d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -28,6 +28,7 @@ use App\Observers\StudentObserver; use App\Observers\SubscoreDefinitionObserver; use App\Observers\UserObserver; use App\Services\AuditionService; +use App\Services\DoublerService; use App\Services\DrawService; use App\Services\EntryService; use App\Services\ScoreService; @@ -49,6 +50,7 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton(EntryService::class, EntryService::class); $this->app->singleton(ScoreService::class, ScoreService::class); $this->app->singleton(UserService::class, UserService::class); + $this->app->singleton(DoublerService::class, DoublerService::class); } /** diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index 2f7fe3f..0b195c3 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -3,32 +3,36 @@ namespace App\Services; use App\Models\Entry; +use App\Models\Event; use App\Models\Student; -use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\Cache; class DoublerService { - protected $doublersCacheKey = 'doublers'; - - protected $auditionService; - - protected $tabulationService; - - protected $seatingService; - - /** - * Create a new class instance. - */ - public function __construct( - AuditionService $auditionService, - TabulationService $tabulationService, - SeatingService $seatingService - ) { - $this->auditionService = $auditionService; - $this->tabulationService = $tabulationService; - $this->seatingService = $seatingService; + public function doublersForEvent(Event $event) + { + $cacheKey = 'event'.$event->id.'doublers'; + return Cache::remember($cacheKey, 60, function () use ($event) { + return $this->findDoublersForEvent($event); + }); } + protected function findDoublersForEvent(Event $event): array + { + $entries = $event->entries; + $entries->load('student.school'); + $entries->load('audition'); + $grouped = $entries->groupBy('student_id'); + // Filter out student groups with only one entry in the event + $grouped = $grouped->filter(fn ($s) => $s->count() > 1); + $doubler_array = []; + foreach ($grouped as $student_id => $entries) { + $doubler_array[$student_id] = [ + 'student' => $entries[0]->student, + 'entries' => $entries, + ]; + } + return $doubler_array; + } } -- 2.39.5 From ad98687ddc1dd500a18ba7b7b09379cbcb5cff5b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 01:43:32 -0500 Subject: [PATCH 19/43] Stop tracking test files and update .gitignore --- .gitignore | 2 ++ app/Http/Controllers/TestController.php | 22 ---------------------- resources/views/test.blade.php | 15 --------------- 3 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 app/Http/Controllers/TestController.php delete mode 100644 resources/views/test.blade.php diff --git a/.gitignore b/.gitignore index 46340a6..5a8b744 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ yarn-error.log /.fleet /.idea /.vscode +/app/Http/Controllers/TestController.php +/resources/views/test.blade.php diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php deleted file mode 100644 index 9a39421..0000000 --- a/app/Http/Controllers/TestController.php +++ /dev/null @@ -1,22 +0,0 @@ -rankomatic = $rankomatic; - } - - public function flashTest() - { - dd($this->rankomatic->booo()); - return view('test'); - } -} diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php deleted file mode 100644 index 0eb3464..0000000 --- a/resources/views/test.blade.php +++ /dev/null @@ -1,15 +0,0 @@ -@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\Event;use App\Models\User; @endphp -@php @endphp -@inject('scoreservice','App\Services\ScoreService'); -@inject('auditionService','App\Services\AuditionService'); -@inject('entryService','App\Services\EntryService') -@inject('seatingService','App\Services\SeatingService') -@inject('drawService', 'App\Services\DrawService') - - Test Page - {{ $bam ?? '' }} - - @foreach(Event::first()->entries->groupBy('student_id') as $student) - Name: {{ $student[0]->student->full_name() }}
- Entry Count: {{ $student->count() }}
- @endforeach -
-- 2.39.5 From 8a2e0c589fef12d9d66780daf8ffb087530b4b0f Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 01:52:57 -0500 Subject: [PATCH 20/43] Correct link issue on seating status page --- app/Http/Controllers/Tabulation/SeatingStatusController.php | 2 ++ resources/views/tabulation/status.blade.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Tabulation/SeatingStatusController.php b/app/Http/Controllers/Tabulation/SeatingStatusController.php index 2131536..63465dd 100644 --- a/app/Http/Controllers/Tabulation/SeatingStatusController.php +++ b/app/Http/Controllers/Tabulation/SeatingStatusController.php @@ -24,9 +24,11 @@ class SeatingStatusController extends Controller 'scoredPercentage' => $audition->entries_count > 0 ? ($audition->entries_count - $audition->unscored_entries_count) / $audition->entries_count * 100 : 100, 'scoringComplete' => $audition->unscored_entries_count === 0, 'seatsPublished' => $audition->hasFlag('seats_published'), + 'audition' => $audition, ]; } $auditionData = collect($auditionData); + return view('tabulation.status', compact('auditionData')); } } diff --git a/resources/views/tabulation/status.blade.php b/resources/views/tabulation/status.blade.php index af469ad..55cde57 100644 --- a/resources/views/tabulation/status.blade.php +++ b/resources/views/tabulation/status.blade.php @@ -16,7 +16,7 @@ @foreach($auditionData as $audition) - +
{{ $audition['name'] }} {{ $audition['scoredEntriesCount'] }} / {{ $audition['totalEntriesCount'] }} Scored -- 2.39.5 From 0eebd541a0344cbb6e18b00a3e394235d6387210 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 02:14:37 -0500 Subject: [PATCH 21/43] add rankOfEntry function to EntryService --- app/Providers/AppServiceProvider.php | 2 +- app/Services/EntryService.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index a33fc4d..ca321fa 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -71,6 +71,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - #Model::preventLazyLoading(! app()->isProduction()); + //Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index 8d1ba71..d0ba84f 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -2,7 +2,9 @@ namespace App\Services; +use App\Actions\Tabulation\RankAuditionEntries; use App\Models\Entry; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; class EntryService @@ -33,4 +35,11 @@ class EntryService return $allEntryIds->contains($entry->id); } + + public function rankOfEntry(string $mode, Entry $entry) + { + $ranker = App::make(RankAuditionEntries::class); + $rankings = $ranker->rank($mode, $entry->audition); + return $rankings->find($entry->id)->rank; + } } -- 2.39.5 From 10a17510196c32a63c768303d8ec75d18c152bb0 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 11:16:57 -0500 Subject: [PATCH 22/43] Test AuditionService --- app/Services/AuditionService.php | 1 - .../Feature/Services/AuditionServiceTest.php | 31 ++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index 1b18787..d542ef5 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -53,7 +53,6 @@ class AuditionService return Cache::remember($cacheKey, 10, function () use ($audition) { $this->validateAudition($audition); - return $audition->judges; }); } diff --git a/tests/Feature/Services/AuditionServiceTest.php b/tests/Feature/Services/AuditionServiceTest.php index 5bbf8c0..c707ed0 100644 --- a/tests/Feature/Services/AuditionServiceTest.php +++ b/tests/Feature/Services/AuditionServiceTest.php @@ -3,21 +3,24 @@ /** @noinspection PhpUnhandledExceptionInspection */ use App\Models\Audition; +use App\Models\Room; +use App\Models\User; use App\Services\AuditionService; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\App; uses(RefreshDatabase::class); +// getSubscores() it('throws an exception when an invalid mode is requested', function () { - #$auditionService = new \App\Services\AuditionService(); + //$auditionService = new \App\Services\AuditionService(); $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); $auditionService->getSubscores(new Audition(), 'invalid_mode'); }); it('throws an exception when an invalid sort is requested', function () { // Arrange - #$auditionService = new \App\Services\AuditionService(); + //$auditionService = new \App\Services\AuditionService(); $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); // Act @@ -25,7 +28,7 @@ it('throws an exception when an invalid sort is requested', function () { }); it('throws an exception when an invalid audition is provided', function () { // Arrange - #$auditionService = new \App\Services\AuditionService(); + //$auditionService = new \App\Services\AuditionService(); $auditionService = App::make(AuditionService::class); $this->expectException(\App\Exceptions\AuditionServiceException::class); $auditionService->getSubscores(new Audition(), 'seating', 'tiebreak'); @@ -35,10 +38,28 @@ it('throws an exception when an invalid audition is provided', function () { it('gets subscores for an audition', function () { // Arrange loadSampleAudition(); - #$auditionService = new \App\Services\AuditionService(); + //$auditionService = new \App\Services\AuditionService(); $auditionService = App::make(AuditionService::class); // Act $subscores = $auditionService->getSubscores(Audition::find(1000), 'seating', 'tiebreak'); // Assert - expect($subscores->toArray())->toBe(Audition::find(1000)->scoringGuide->subscores->where('for_seating', true)->sortBy('tiebreak_order')->toArray()); + expect($subscores->toArray())->toBe(Audition::find(1000)->scoringGuide->subscores->where('for_seating', + true)->sortBy('tiebreak_order')->toArray()); +}); +// getJudges() +it('gets judges for an audition', function () { + loadSampleAudition(); + $auditionService = App::make(AuditionService::class); + $judge = User::factory()->create(); + $notJudge = User::factory()->create(); + Room::find(1000)->addJudge($judge); + $testValue = $auditionService->getJudges(Audition::find(1000)); + $test = $testValue->contains(function ($item) use ($judge) { + return $item->id === $judge->id; + }); + $negativeTest = $testValue->contains(function ($item) use ($notJudge) { + return $item->id === $notJudge->id; + }); + expect($test)->toBeTrue(); + expect($negativeTest)->toBeFalse(); }); -- 2.39.5 From c080f05bf3c077d8428dbffd4d3f8689e7ef96c8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 11:58:23 -0500 Subject: [PATCH 23/43] DoublerService test --- app/Services/DoublerService.php | 19 +++++ tests/Feature/Services/DoublerServiceTest.php | 79 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 tests/Feature/Services/DoublerServiceTest.php diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index 0b195c3..d286fec 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -2,11 +2,16 @@ namespace App\Services; +use App\Exceptions\TabulationException; use App\Models\Entry; use App\Models\Event; use App\Models\Student; use Illuminate\Support\Facades\Cache; +/** + * returns a collection of doublers for the event in the form of + * [studentId => [student=>student, entries=>[entries]] + */ class DoublerService { public function doublersForEvent(Event $event) @@ -17,8 +22,12 @@ class DoublerService }); } + /** + * @throws TabulationException + */ protected function findDoublersForEvent(Event $event): array { + $this->validateEvent($event); $entries = $event->entries; $entries->load('student.school'); $entries->load('audition'); @@ -35,4 +44,14 @@ class DoublerService return $doubler_array; } + + /** + * @throws TabulationException + */ + protected function validateEvent(Event $event) + { + if (! $event->exists) { + throw new TabulationException('Invalid event provided'); + } + } } diff --git a/tests/Feature/Services/DoublerServiceTest.php b/tests/Feature/Services/DoublerServiceTest.php new file mode 100644 index 0000000..694068e --- /dev/null +++ b/tests/Feature/Services/DoublerServiceTest.php @@ -0,0 +1,79 @@ +doublerService = App::make(DoublerService::class); +}); + +it('throws an error if an invalid event is provided', function () { + $event = Event::factory()->make(); + $this->doublerService->doublersForEvent($event); +})->throws(TabulationException::class, 'Invalid event provided'); +it('returns doublers for an event', function () { + $concertEvent = Event::factory()->create(['name' => 'Concert Band', 'id' => 1000]); + $jazzEvent = Event::factory()->create(['name' => 'Jazz Band', 'id' => 1001]); + + $auditionAS = Audition::factory()->create([ + 'event_id' => 1000, 'name' => 'Alto Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1000, + ]); + $auditionTS = Audition::factory()->create([ + 'event_id' => 1000, 'name' => 'Tenor Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1001, + ]); + $auditionBS = Audition::factory()->create([ + 'event_id' => 1000, 'name' => 'Baritone Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1002, + ]); + $auditionCL = Audition::factory()->create([ + 'event_id' => 1000, 'name' => 'Clarinet', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1003, + ]); + $auditionBCL = Audition::factory()->create([ + 'event_id' => 1000, 'name' => 'Bass Clarinet', + 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1004, + ]); + $auditionJAS = Audition::factory()->create([ + 'event_id' => 1001, 'name' => 'Jazz Alto', 'minimum_grade' => 7, + 'maximum_grade' => 12, 'id' => 1005, + ]); + $auditionJTS = Audition::factory()->create([ + 'event_id' => 1001, 'name' => 'Jazz Tenor', 'minimum_grade' => 7, + 'maximum_grade' => 12, 'id' => 1006, + ]); + $auditionJBS = Audition::factory()->create([ + 'event_id' => 1001, 'name' => 'Jazz Baritone', + 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1007, + ]); + $allSaxDude = Student::factory()->create(['grade' => 11, 'id' => 1000]); + $clarinetGal = Student::factory()->create(['grade' => 9, 'id' => 1001]); + $justAlto = Student::factory()->create(['grade' => 9, 'id' => 1002]); + Entry::create(['student_id' => 1000, 'audition_id' => 1000]); + Entry::create(['student_id' => 1000, 'audition_id' => 1001]); + Entry::create(['student_id' => 1000, 'audition_id' => 1002]); + Entry::create(['student_id' => 1000, 'audition_id' => 1005]); + Entry::create(['student_id' => 1000, 'audition_id' => 1006]); + Entry::create(['student_id' => 1000, 'audition_id' => 1007]); + Entry::create(['student_id' => 1001, 'audition_id' => 1003]); + Entry::create(['student_id' => 1001, 'audition_id' => 1004]); + Entry::create(['student_id' => 1002, 'audition_id' => 1000]); + Entry::create(['student_id' => 1002, 'audition_id' => 1005]); + + $return = $this->doublerService->doublersForEvent($concertEvent); + expect(count($return))->toBe(2); + expect($return[1000]['student']->id)->toBe($allSaxDude->id); + expect($return[1000]['entries']->count())->toBe(3); + expect($return[1001]['entries']->count())->toBe(2); + assertArrayNotHasKey(1002, $return); + $return = $this->doublerService->doublersForEvent($jazzEvent); + expect(count($return))->toBe(1); + expect($return[1000]['student']->id)->toBe($allSaxDude->id); + expect($return[1000]['entries']->count())->toBe(3); +}); -- 2.39.5 From c5860b20045c2c25b9c392408b6f81164b2fd25b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 12:01:09 -0500 Subject: [PATCH 24/43] Remove unneeded files --- app/Services/SeatingService.php | 24 ----------------------- app/Services/TabulationService.php | 31 ------------------------------ 2 files changed, 55 deletions(-) delete mode 100644 app/Services/SeatingService.php delete mode 100644 app/Services/TabulationService.php diff --git a/app/Services/SeatingService.php b/app/Services/SeatingService.php deleted file mode 100644 index 993adeb..0000000 --- a/app/Services/SeatingService.php +++ /dev/null @@ -1,24 +0,0 @@ -tabulationService = $tabulationService; - } - -} diff --git a/app/Services/TabulationService.php b/app/Services/TabulationService.php deleted file mode 100644 index 287c741..0000000 --- a/app/Services/TabulationService.php +++ /dev/null @@ -1,31 +0,0 @@ -auditionService = $auditionService; - $this->scoreService = $scoreService; - $this->entryService = $entryService; - } - -} -- 2.39.5 From 22297b8cefa9b706a46acc71d3f3ec736aa32d74 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 12:04:32 -0500 Subject: [PATCH 25/43] UserService Test --- tests/Feature/Services/UserServiceTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/Feature/Services/UserServiceTest.php diff --git a/tests/Feature/Services/UserServiceTest.php b/tests/Feature/Services/UserServiceTest.php new file mode 100644 index 0000000..bab8fe8 --- /dev/null +++ b/tests/Feature/Services/UserServiceTest.php @@ -0,0 +1,19 @@ +userService = App::make(UserService::class); +}); + +it('checks if a user exists', function() { + $realUser = User::factory()->create(); + $fakeUser = User::factory()->make(); + expect ($this->userService->userExists($realUser))->toBeTrue(); + expect ($this->userService->userExists($fakeUser))->toBeFalse(); +}); -- 2.39.5 From a14bfbf1689ab68859648d12d1bf4b3e32cd7918 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 12:14:37 -0500 Subject: [PATCH 26/43] Create EntryServiceTest.php --- tests/Feature/Services/EntryServiceTest.php | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/Feature/Services/EntryServiceTest.php diff --git a/tests/Feature/Services/EntryServiceTest.php b/tests/Feature/Services/EntryServiceTest.php new file mode 100644 index 0000000..f26245f --- /dev/null +++ b/tests/Feature/Services/EntryServiceTest.php @@ -0,0 +1,24 @@ +entryService = App::make(EntryService::class); +}); + +it('checks if an entry is late', function() { + $openAudition = Audition::factory()->create(['entry_deadline' => Carbon::tomorrow()]); + $closedAudition = Audition::factory()->create(['entry_deadline' => Carbon::yesterday()]); + + $onTime = Entry::factory()->create(['audition_id' => $openAudition->id]); + $late = Entry::factory()->create(['audition_id' => $closedAudition]); + expect($this->entryService->isEntryLate($onTime))->toBeFalse(); + expect($this->entryService->isEntryLate($late))->toBeTrue(); +}); -- 2.39.5 From 009c85e0443328eb30a3b29cab38dda362445d05 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 15:36:51 -0500 Subject: [PATCH 27/43] Successfully accessing doubler information --- app/Actions/Tabulation/AllJudgesCount.php | 3 +- .../Tabulation/SeatAuditionController.php | 53 +++++++++++++++---- app/Services/DoublerService.php | 14 +++-- app/Services/EntryService.php | 2 +- .../auditionSeating-results-table.blade.php | 6 +++ .../Pages/Seating/auditionSeatingTest.php | 36 +++++++++++++ 6 files changed, 97 insertions(+), 17 deletions(-) diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 9e40fee..9ee70b7 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -22,6 +22,7 @@ class AllJudgesCount implements CalculateEntryScore public function calculate(string $mode, Entry $entry): array { + $cacheKey = 'entryScore-'.$entry->id.'-'.$mode; return Cache::remember($cacheKey, 10, function () use ($mode, $entry) { $this->basicValidation($mode, $entry); @@ -35,6 +36,7 @@ class AllJudgesCount implements CalculateEntryScore protected function getJudgeTotals($mode, Entry $entry) { + $scores = []; foreach ($this->auditionService->getJudges($entry->audition) as $judge) { $scores[] = $this->calculator->__invoke($mode, $entry, $judge); @@ -49,7 +51,6 @@ class AllJudgesCount implements CalculateEntryScore $index++; } } - return $sums; } diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index 91dec57..7e8970a 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -4,32 +4,64 @@ namespace App\Http\Controllers\Tabulation; use App\Actions\Tabulation\CalculateEntryScore; use App\Actions\Tabulation\RankAuditionEntries; -use App\Exceptions\TabulationException; use App\Http\Controllers\Controller; use App\Models\Audition; -use App\Models\Entry; +use App\Services\DoublerService; +use App\Services\EntryService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; class SeatAuditionController extends Controller { protected CalculateEntryScore $calc; + + protected DoublerService $doublerService; + protected RankAuditionEntries $ranker; - public function __construct(CalculateEntryScore $calc, RankAuditionEntries $ranker) - { + protected EntryService $entryService; + + public function __construct( + CalculateEntryScore $calc, + RankAuditionEntries $ranker, + DoublerService $doublerService, + EntryService $entryService + ) { $this->calc = $calc; $this->ranker = $ranker; + $this->doublerService = $doublerService; + $this->entryService = $entryService; } public function __invoke(Request $request, Audition $audition) { + $doublers = $this->doublerService->doublersForEvent($audition->event); $entryData = []; - #$entries = Entry::forSeating()->with('student.school')->where('audition_id', $audition->id)->get(); $entries = $this->ranker->rank('seating', $audition); $entries->load('student.school'); + foreach ($entries as $entry) { - $totalScoreColumn = $entry->score_totals[0] >= 0 ? - $entry->score_totals[0] : $entry->score_message; + $isDoubler = false; + if (array_key_exists($entry->student->id, $doublers)) { + $isDoubler = true; + $doubleData = []; + $doublerEntries = $doublers[$entry->student->id]['entries']; + foreach ($doublerEntries as $doublerEntry) { + Log::debug('doubler check for entry ' . $doublerEntry->id); + log::debug('Rank: ' . $this->entryService->rankOfEntry('seating', $doublerEntry)); + $doubleData[] = [ + 'name' => $doublerEntry->audition->name, + 'entryId' => $doublerEntry->id, + 'rank' => $this->entryService->rankOfEntry('seating', $doublerEntry), + ]; + } + } + $totalScoreColumn = 'No Score'; + $fullyScored = false; + if ($entry->score_totals) { + $totalScoreColumn = $entry->score_totals[0] >= 0 ? $entry->score_totals[0] : $entry->score_message; + $fullyScored = $entry->score_totals[0] >= 0; + } $entryData[] = [ 'rank' => $entry->rank, 'id' => $entry->id, @@ -37,11 +69,12 @@ class SeatAuditionController extends Controller 'schoolName' => $entry->student->school->name, 'drawNumber' => $entry->draw_number, 'totalScore' => $totalScoreColumn, - 'fullyScored' => $entry->score_totals[0] >= 0, + 'fullyScored' => $fullyScored, + 'isDoubler' => $isDoubler, + 'doubleData' => $doubleData ?? [], ]; } - //dd($entryData); - return view('tabulation.auditionSeating', ['entryData' => $entryData, 'audition' => $audition]); + return view('tabulation.auditionSeating', compact('entryData', 'audition', 'doublers')); } } diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index d286fec..6601da8 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -14,21 +14,25 @@ use Illuminate\Support\Facades\Cache; */ class DoublerService { - public function doublersForEvent(Event $event) + public function doublersForEvent(Event $event, string $mode = 'seating') { - $cacheKey = 'event'.$event->id.'doublers'; - return Cache::remember($cacheKey, 60, function () use ($event) { - return $this->findDoublersForEvent($event); + $cacheKey = 'event'.$event->id.'doublers-'.$mode; + return Cache::remember($cacheKey, 60, function () use ($event, $mode) { + return $this->findDoublersForEvent($event, $mode); }); } /** * @throws TabulationException */ - protected function findDoublersForEvent(Event $event): array + protected function findDoublersForEvent(Event $event, string $mode): array { $this->validateEvent($event); $entries = $event->entries; + $entries = match ($mode) { + 'seating' => $entries->filter(fn ($entry) => $entry->for_seating === 1), + 'advancement' => $entries->filter(fn ($entry) => $entry->for_advance === 1), + }; $entries->load('student.school'); $entries->load('audition'); $grouped = $entries->groupBy('student_id'); diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index d0ba84f..aa9ba17 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -40,6 +40,6 @@ class EntryService { $ranker = App::make(RankAuditionEntries::class); $rankings = $ranker->rank($mode, $entry->audition); - return $rankings->find($entry->id)->rank; + return $rankings->find($entry->id)->rank ?? 'No Rank'; } } diff --git a/resources/views/tabulation/auditionSeating-results-table.blade.php b/resources/views/tabulation/auditionSeating-results-table.blade.php index 6845dc9..610b33f 100644 --- a/resources/views/tabulation/auditionSeating-results-table.blade.php +++ b/resources/views/tabulation/auditionSeating-results-table.blade.php @@ -26,6 +26,12 @@ {{ $entry['schoolName'] }} + @if($entry['isDoubler']) + DOUBLER
+ @foreach($entry['doubleData'] as $double) + ID: {{ $double['entryId'] }} - {{ $double['name'] }} - {{ $double['rank'] }}
+ @endforeach + @endif {{-- @if($doublerService->studentIsDoubler($entry->student_id))--}} {{-- @include('tabulation.auditionSeating-doubler-block')--}} {{-- @endif--}} diff --git a/tests/Feature/Pages/Seating/auditionSeatingTest.php b/tests/Feature/Pages/Seating/auditionSeatingTest.php index d46e0ed..9a37742 100644 --- a/tests/Feature/Pages/Seating/auditionSeatingTest.php +++ b/tests/Feature/Pages/Seating/auditionSeatingTest.php @@ -1,6 +1,8 @@ r)->assertOk(); }); +// TODO make tests with varied information +it('returns the audition object and an array of info on each entry', function () { + // Arrange + $entry = Entry::factory()->create(['audition_id' => $this->audition->id]); + actAsAdmin(); + // Act + $response = get($this->r); + $response + ->assertOk() + ->assertViewHas('audition', $this->audition); + $viewData = $response->viewData('entryData'); + expect($viewData[0]['rank'])->toBe(1); + expect($viewData[0]['id'])->toBe($entry->id); + expect($viewData[0]['studentName'])->toBe($entry->student->full_name()); + expect($viewData[0]['schoolName'])->toBe($entry->student->school->name); + expect($viewData[0]['drawNumber'])->toBe($entry->draw_number); + expect($viewData[0]['totalScore'])->toBe('No Score'); + expect($viewData[0]['fullyScored'])->toBeFalse(); +}); +it('identifies a doubler', function () { + // Arrange + $audition1 = Audition::factory()->create(['event_id' => $this->audition->event_id]); + $audition2 = Audition::factory()->create(['event_id' => $this->audition->event_id]); + $student = Student::factory()->create(); + Entry::factory()->create(['audition_id' => $audition1->id, 'student_id' => $student->id]); + Entry::factory()->create(['audition_id' => $audition2->id, 'student_id' => $student->id]); + Entry::factory()->create(['audition_id' => $this->audition->id, 'student_id' => $student->id]); + actAsAdmin(); + // Act & Assert + $response = get($this->r); + $response->assertOk(); + $viewData = $response->viewData('entryData'); + expect($viewData[0]['isDoubler'])->toBeTrue(); +}); -- 2.39.5 From 719b4054d8c05fe7eb70ecfa8ce5279c6a2c2ed9 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 17:03:44 -0500 Subject: [PATCH 28/43] Clean up lazy loading --- app/Actions/Tabulation/AllJudgesCount.php | 7 ++- app/Providers/AppServiceProvider.php | 2 +- app/Services/AuditionService.php | 48 ++++++++++++++++++- .../AllJudgesCountTest.php | 1 - 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/app/Actions/Tabulation/AllJudgesCount.php b/app/Actions/Tabulation/AllJudgesCount.php index 9ee70b7..aa76b98 100644 --- a/app/Actions/Tabulation/AllJudgesCount.php +++ b/app/Actions/Tabulation/AllJudgesCount.php @@ -7,17 +7,20 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; use App\Models\Entry; use App\Services\AuditionService; +use App\Services\EntryService; use Illuminate\Support\Facades\Cache; class AllJudgesCount implements CalculateEntryScore { protected CalculateScoreSheetTotal $calculator; protected AuditionService $auditionService; + protected EntryService $entryService; - public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService) + public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService, EntryService $entryService) { $this->calculator = $calculator; $this->auditionService = $auditionService; + $this->entryService = $entryService; } public function calculate(string $mode, Entry $entry): array @@ -60,7 +63,7 @@ class AllJudgesCount implements CalculateEntryScore throw new TabulationException('Mode must be seating or advancement'); } - if (! $entry->exists()) { + if (! $this->entryService->entryExists($entry)) { throw new TabulationException('Invalid entry specified'); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ca321fa..3bd6621 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -71,6 +71,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - //Model::preventLazyLoading(! app()->isProduction()); + Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index d542ef5..d38648c 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -42,21 +42,65 @@ class AuditionService 'seating' => 'for_seating', 'advancement' => 'for_advance', }; - + $audition->load('scoringGuide.subscores'); return $audition->scoringGuide->subscores->where($modeColumn, true)->sortBy($sortColumn); }); } - public function getJudges(Audition $audition) + public function getSubscoresNEW(Audition $audition, $mode = 'seating', $sort = 'tiebreak') + { + $this->validateMode($mode); + $this->validateSort($sort); + $cacheKey = 'auditionSubscores-'.$mode.'-'.$sort; + $assignments = Cache::remember($cacheKey, 60, function () use ($audition, $mode, $sort) { + $this->validateAudition($audition); + $sortColumn = match ($sort) { + 'tiebreak' => 'tiebreak_order', + 'display' => 'display_order', + }; + $modeColumn = match ($mode) { + 'seating' => 'for_seating', + 'advancement' => 'for_advance', + }; + $allAuditions = Audition::with(['scoringGuide.subscores' => function ($query) use ($modeColumn, $sortColumn) { + $query->where($modeColumn, 1)->orderBy($sortColumn); + }])->get(); + $return = []; + foreach ( $allAuditions as $audition) { + $return[$audition->id] = $audition->scoringGuide->subscores; + } + return $return; + }); + return $assignments[$audition->id]; + } + + public function getJudgesOLD(Audition $audition) { $cacheKey = 'auditionJudges-'.$audition->id; return Cache::remember($cacheKey, 10, function () use ($audition) { $this->validateAudition($audition); + return $audition->judges; }); } + public function getJudges(Audition $audition) + { + $cacheKey = 'auditionJudgeAssignments'; + $assignments = Cache::remember($cacheKey, 60, function () { + $allAuditions = Audition::with('judges')->get(); + $return = []; + foreach ($allAuditions as $audition) { + $return[$audition->id] = $audition->judges; + } + + return $return; + }); + + return $assignments[$audition->id]; + } + protected function validateAudition($audition) { if (! $audition->exists()) { diff --git a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php index efb7e99..17c1dbd 100644 --- a/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php +++ b/tests/Feature/Actions/CalculateEntryScore/AllJudgesCountTest.php @@ -130,7 +130,6 @@ it('correctly calculates scores for advancement', function () { 1004 => 85, 1005 => 95, ]; - #$calculator = new AllJudgesCount(); $calculator = App::make(AllJudgesCount::class); enterScore($judge1, $entry, $scores); enterScore($judge2, $entry, $scores2); -- 2.39.5 From b2bb3654ff282ac37425e75890447af67d204b57 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 21:47:05 -0500 Subject: [PATCH 29/43] Showing doublers working --- .../Tabulation/SeatAuditionController.php | 18 +++++- app/Providers/AppServiceProvider.php | 2 +- app/Services/AuditionService.php | 62 +++++++++++-------- .../auditionSeating-doubler-block.blade.php | 24 +++---- .../auditionSeating-results-table.blade.php | 14 +++-- 5 files changed, 73 insertions(+), 47 deletions(-) diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index 7e8970a..fe1ced3 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -6,6 +6,7 @@ use App\Actions\Tabulation\CalculateEntryScore; use App\Actions\Tabulation\RankAuditionEntries; use App\Http\Controllers\Controller; use App\Models\Audition; +use App\Services\AuditionService; use App\Services\DoublerService; use App\Services\EntryService; use Illuminate\Http\Request; @@ -21,16 +22,20 @@ class SeatAuditionController extends Controller protected EntryService $entryService; + protected AuditionService $auditionService; + public function __construct( CalculateEntryScore $calc, RankAuditionEntries $ranker, DoublerService $doublerService, - EntryService $entryService + EntryService $entryService, + AuditionService $auditionService, ) { $this->calc = $calc; $this->ranker = $ranker; $this->doublerService = $doublerService; $this->entryService = $entryService; + $this->auditionService = $auditionService; } public function __invoke(Request $request, Audition $audition) @@ -46,13 +51,20 @@ class SeatAuditionController extends Controller $isDoubler = true; $doubleData = []; $doublerEntries = $doublers[$entry->student->id]['entries']; + foreach ($doublerEntries as $doublerEntry) { - Log::debug('doubler check for entry ' . $doublerEntry->id); - log::debug('Rank: ' . $this->entryService->rankOfEntry('seating', $doublerEntry)); + $limits = $this->auditionService->getSeatingLimits($doublerEntry->audition); + $limits = $limits->reject(function ($lim) { + return $lim['limit'] === 0; + }); $doubleData[] = [ + 'audition' => $doublerEntry->audition, 'name' => $doublerEntry->audition->name, 'entryId' => $doublerEntry->id, 'rank' => $this->entryService->rankOfEntry('seating', $doublerEntry), + 'limits' => $limits, + 'status' => 'undecided', // Will be undecided, accepted, or declined + 'unscored_in_audition' => $doublerEntry->audition->unscoredEntries()->count(), ]; } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3bd6621..ca321fa 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -71,6 +71,6 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); SeatingLimit::observe(SeatingLimitObserver::class); - Model::preventLazyLoading(! app()->isProduction()); + //Model::preventLazyLoading(! app()->isProduction()); } } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index d38648c..1e16873 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -4,6 +4,8 @@ namespace App\Services; use App\Exceptions\AuditionServiceException; use App\Models\Audition; +use App\Models\Ensemble; +use App\Models\SeatingLimit; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; @@ -43,37 +45,11 @@ class AuditionService 'advancement' => 'for_advance', }; $audition->load('scoringGuide.subscores'); + return $audition->scoringGuide->subscores->where($modeColumn, true)->sortBy($sortColumn); }); } - public function getSubscoresNEW(Audition $audition, $mode = 'seating', $sort = 'tiebreak') - { - $this->validateMode($mode); - $this->validateSort($sort); - $cacheKey = 'auditionSubscores-'.$mode.'-'.$sort; - $assignments = Cache::remember($cacheKey, 60, function () use ($audition, $mode, $sort) { - $this->validateAudition($audition); - $sortColumn = match ($sort) { - 'tiebreak' => 'tiebreak_order', - 'display' => 'display_order', - }; - $modeColumn = match ($mode) { - 'seating' => 'for_seating', - 'advancement' => 'for_advance', - }; - $allAuditions = Audition::with(['scoringGuide.subscores' => function ($query) use ($modeColumn, $sortColumn) { - $query->where($modeColumn, 1)->orderBy($sortColumn); - }])->get(); - $return = []; - foreach ( $allAuditions as $audition) { - $return[$audition->id] = $audition->scoringGuide->subscores; - } - return $return; - }); - return $assignments[$audition->id]; - } - public function getJudgesOLD(Audition $audition) { $cacheKey = 'auditionJudges-'.$audition->id; @@ -101,6 +77,38 @@ class AuditionService return $assignments[$audition->id]; } + public function getSeatingLimits(Audition $audition) + { + $cacheKey = 'auditionSeatingLimits'; + $allLimits = Cache::remember($cacheKey, 60, function () { + $lims = []; + $auditions = Audition::all(); + $ensembles = Ensemble::orderBy('rank')->get(); + foreach ($auditions as $audition) { + foreach ($ensembles as $ensemble) { + if ($ensemble->event_id !== $audition->event_id) { + continue; + } + $lims[$audition->id][$ensemble->id] = [ + 'ensemble' => $ensemble, + 'limit' => 0, + ]; + } + } + $limits = SeatingLimit::all(); + + foreach ($limits as $limit) { + $lims[$limit->audition_id][$limit->ensemble_id] = collect([ + 'ensemble' => $ensembles->find($limit->ensemble_id), + 'limit' =>$limit->maximum_accepted, + ]); + } + return collect($lims); + }); + + return collect($allLimits[$audition->id]) ?? []; + } + protected function validateAudition($audition) { if (! $audition->exists()) { diff --git a/resources/views/tabulation/auditionSeating-doubler-block.blade.php b/resources/views/tabulation/auditionSeating-doubler-block.blade.php index 9783156..85ddbfa 100644 --- a/resources/views/tabulation/auditionSeating-doubler-block.blade.php +++ b/resources/views/tabulation/auditionSeating-doubler-block.blade.php @@ -1,14 +1,14 @@ -@php($doublerEntryInfo = $doublerService->getDoublerInfo($entry->student_id)) @php($doublerButtonClasses = 'hidden rounded-md bg-white px-2.5 py-1.5 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:block')
    - @foreach($doublerEntryInfo as $info) - @php($isopen = $info['status'] == 'undecided') + @foreach($entry['doubleData'] as $double) + @php($isopen = $double['status'] == 'undecided')
  • - - {{ $info['auditionName'] }} - {{ $info['status'] }} + {{-- TODO put in link --}} + + {{ $double['name'] }} - {{ $double['status'] }}

    @@ -22,25 +22,25 @@
    • -

      Ranked {{ $info['rank'] }}

      +

      Ranked {{ $double['rank'] }}

      -

      {{ $info['unscored'] }} Unscored

      +

      {{ $double['unscored_in_audition'] }} Unscored

    • - @foreach($info['limits'] as $limit) -
    • {{ $limit->ensemble->name }} accepts {{ $limit->maximum_accepted }}
    • + @foreach($double['limits'] as $limit) +
    • {{$limit['ensemble']->name}} accepts {{ $limit['limit'] }}
    • @endforeach
    - @if ($info['status'] === 'undecided') -
    + @if ($double['status'] == 'undecided') + @csrf
    -
    + @csrf
    diff --git a/resources/views/tabulation/auditionSeating-results-table.blade.php b/resources/views/tabulation/auditionSeating-results-table.blade.php index 610b33f..24c39ce 100644 --- a/resources/views/tabulation/auditionSeating-results-table.blade.php +++ b/resources/views/tabulation/auditionSeating-results-table.blade.php @@ -27,10 +27,16 @@ @if($entry['isDoubler']) - DOUBLER
    - @foreach($entry['doubleData'] as $double) - ID: {{ $double['entryId'] }} - {{ $double['name'] }} - {{ $double['rank'] }}
    - @endforeach + @include('tabulation.auditionSeating-doubler-block') +{{-- DOUBLER
    --}} +{{-- @foreach($entry['doubleData'] as $double)--}} +{{-- ID: {{ $double['entryId'] }} - {{ $double['name'] }} - {{ $double['rank'] }}
    --}} +{{-- Unscored Entries: {{ $double['unscored_in_audition'] }}
    --}} +{{-- @foreach($double['limits'] as $limit)--}} +{{-- {{$limit['ensemble']->name}}: {{ $limit['limit'] }}
    --}} +{{-- @endforeach--}} +{{--
    --}} +{{-- @endforeach--}} @endif {{-- @if($doublerService->studentIsDoubler($entry->student_id))--}} {{-- @include('tabulation.auditionSeating-doubler-block')--}} -- 2.39.5 From a1f5191a1904c33a23255390ac71c96b63b0578d Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 12 Jul 2024 23:24:14 -0500 Subject: [PATCH 30/43] Cleanup doubler info --- .../Tabulation/DoublerDecisionController.php | 3 - .../Tabulation/SeatAuditionController.php | 26 +------ app/Services/AuditionService.php | 8 +- app/Services/DoublerService.php | 75 +++++++++++++++++-- app/Services/EntryService.php | 4 + .../auditionSeating-doubler-block.blade.php | 18 ++--- .../auditionSeating-results-table.blade.php | 2 +- tests/Feature/Services/DoublerServiceTest.php | 35 +++++---- 8 files changed, 104 insertions(+), 67 deletions(-) diff --git a/app/Http/Controllers/Tabulation/DoublerDecisionController.php b/app/Http/Controllers/Tabulation/DoublerDecisionController.php index 7d95056..73141e5 100644 --- a/app/Http/Controllers/Tabulation/DoublerDecisionController.php +++ b/app/Http/Controllers/Tabulation/DoublerDecisionController.php @@ -36,7 +36,6 @@ class DoublerDecisionController extends Controller } } - $this->doublerService->refreshDoublerCache(); $returnMessage = $entry->student->full_name().' accepted seating in '.$entry->audition->name; @@ -52,8 +51,6 @@ class DoublerDecisionController extends Controller $entry->addFlag('declined'); - $this->doublerService->refreshDoublerCache(); - $returnMessage = $entry->student->full_name().' declined seating in '.$entry->audition->name; return redirect()->back()->with('success', $returnMessage); diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index fe1ced3..1dea67c 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -10,7 +10,6 @@ use App\Services\AuditionService; use App\Services\DoublerService; use App\Services\EntryService; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Log; class SeatAuditionController extends Controller { @@ -46,28 +45,6 @@ class SeatAuditionController extends Controller $entries->load('student.school'); foreach ($entries as $entry) { - $isDoubler = false; - if (array_key_exists($entry->student->id, $doublers)) { - $isDoubler = true; - $doubleData = []; - $doublerEntries = $doublers[$entry->student->id]['entries']; - - foreach ($doublerEntries as $doublerEntry) { - $limits = $this->auditionService->getSeatingLimits($doublerEntry->audition); - $limits = $limits->reject(function ($lim) { - return $lim['limit'] === 0; - }); - $doubleData[] = [ - 'audition' => $doublerEntry->audition, - 'name' => $doublerEntry->audition->name, - 'entryId' => $doublerEntry->id, - 'rank' => $this->entryService->rankOfEntry('seating', $doublerEntry), - 'limits' => $limits, - 'status' => 'undecided', // Will be undecided, accepted, or declined - 'unscored_in_audition' => $doublerEntry->audition->unscoredEntries()->count(), - ]; - } - } $totalScoreColumn = 'No Score'; $fullyScored = false; if ($entry->score_totals) { @@ -82,8 +59,7 @@ class SeatAuditionController extends Controller 'drawNumber' => $entry->draw_number, 'totalScore' => $totalScoreColumn, 'fullyScored' => $fullyScored, - 'isDoubler' => $isDoubler, - 'doubleData' => $doubleData ?? [], + 'doubleData' => $this->doublerService->entryDoublerData($entry), ]; } diff --git a/app/Services/AuditionService.php b/app/Services/AuditionService.php index 1e16873..09edccf 100644 --- a/app/Services/AuditionService.php +++ b/app/Services/AuditionService.php @@ -98,15 +98,15 @@ class AuditionService $limits = SeatingLimit::all(); foreach ($limits as $limit) { - $lims[$limit->audition_id][$limit->ensemble_id] = collect([ + $lims[$limit->audition_id][$limit->ensemble_id] = [ 'ensemble' => $ensembles->find($limit->ensemble_id), 'limit' =>$limit->maximum_accepted, - ]); + ]; } - return collect($lims); + return $lims; }); - return collect($allLimits[$audition->id]) ?? []; + return $allLimits[$audition->id] ?? []; } protected function validateAudition($audition) diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index 6601da8..bc97006 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -6,17 +6,29 @@ use App\Exceptions\TabulationException; use App\Models\Entry; use App\Models\Event; use App\Models\Student; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; -/** - * returns a collection of doublers for the event in the form of - * [studentId => [student=>student, entries=>[entries]] - */ class DoublerService { + protected EntryService $entryService; + + protected AuditionService $auditionService; + + public function __construct(EntryService $entryService, AuditionService $auditionService) + { + $this->entryService = $entryService; + $this->auditionService = $auditionService; + } + + /** + * returns a collection of doublers for the event in the form of + * [studentId => [student=>student, entries=>[entries]] + */ public function doublersForEvent(Event $event, string $mode = 'seating') { $cacheKey = 'event'.$event->id.'doublers-'.$mode; + return Cache::remember($cacheKey, 60, function () use ($event, $mode) { return $this->findDoublersForEvent($event, $mode); }); @@ -25,7 +37,7 @@ class DoublerService /** * @throws TabulationException */ - protected function findDoublersForEvent(Event $event, string $mode): array + protected function findDoublersForEvent(Event $event, string $mode = 'seating'): array { $this->validateEvent($event); $entries = $event->entries; @@ -49,6 +61,59 @@ class DoublerService return $doubler_array; } + public function entryDoublerData(Entry $primaryEntry) + { + if (! isset($this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) { + return false; + } + $entries = $this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries']; + $entryData = collect([]); + /** @var Collection $entries */ + foreach ($entries as $entry) { + $status = 'undecided'; + if ($entry->hasFlag('declined')) { + $status = 'declined'; + } + if ($entry->hasFlag('no_show')) { + $status = 'no_show'; + } + + $lims = $this->auditionService->getSeatingLimits($entry->audition); + $limits = []; + foreach ($lims as $lim) { + $limits[] = [ + 'ensemble_name' => $lim['ensemble']->name, + 'accepts' => $lim['limit'], + 'ensemble' => $lim['ensemble'], + ]; + } + + $entryData[$entry->id] = [ + 'entry' => $entry, + 'audition' => $entry->audition, + 'auditionName' => $entry->audition->name, + 'status' => $status, + 'rank' => $this->entryService->rankOfEntry('seating', $entry), + 'unscored_entries' => $entry->audition->unscoredEntries()->count(), + 'seating_limits' => $limits, + ]; + } + // find out how many items in the collection $entryData have a status of 'undecided' + $undecided_count = $entryData->filter(fn ($entry) => $entry['status'] === 'undecided')->count(); + // if $undecided_count is 1 set the item where status is 'undecided' to 'accepted' + if ($undecided_count === 1) { + $entryData->transform(function ($entry) { + if ($entry['status'] === 'undecided') { + $entry['status'] = 'accepted'; + } + + return $entry; + }); + } + + return $entryData; + } + /** * @throws TabulationException */ diff --git a/app/Services/EntryService.php b/app/Services/EntryService.php index aa9ba17..0f719e0 100644 --- a/app/Services/EntryService.php +++ b/app/Services/EntryService.php @@ -40,6 +40,10 @@ class EntryService { $ranker = App::make(RankAuditionEntries::class); $rankings = $ranker->rank($mode, $entry->audition); + $rankedEntry = $rankings->find($entry->id); + if (isset($rankedEntry->score_message)) { + return $rankedEntry->score_message; + } return $rankings->find($entry->id)->rank ?? 'No Rank'; } } diff --git a/resources/views/tabulation/auditionSeating-doubler-block.blade.php b/resources/views/tabulation/auditionSeating-doubler-block.blade.php index 85ddbfa..9f7635d 100644 --- a/resources/views/tabulation/auditionSeating-doubler-block.blade.php +++ b/resources/views/tabulation/auditionSeating-doubler-block.blade.php @@ -6,9 +6,8 @@
  • - {{-- TODO put in link --}} - {{ $double['name'] }} - {{ $double['status'] }} + {{ $double['auditionName'] }} - {{ $double['status'] }}

    @@ -21,26 +20,23 @@
      -
    • +
    • Ranked {{ $double['rank'] }}

      - - - -

      {{ $double['unscored_in_audition'] }} Unscored

      +

      {{ $double['unscored_entries'] }} Unscored

    • - @foreach($double['limits'] as $limit) -
    • {{$limit['ensemble']->name}} accepts {{ $limit['limit'] }}
    • + @foreach($double['seating_limits'] as $limit) +
    • {{$limit['ensemble_name']}} accepts {{ $limit['accepts'] }}
    • @endforeach
    @if ($double['status'] == 'undecided') -
    + @csrf
    -
    + @csrf
    diff --git a/resources/views/tabulation/auditionSeating-results-table.blade.php b/resources/views/tabulation/auditionSeating-results-table.blade.php index 24c39ce..1279f96 100644 --- a/resources/views/tabulation/auditionSeating-results-table.blade.php +++ b/resources/views/tabulation/auditionSeating-results-table.blade.php @@ -26,7 +26,7 @@ {{ $entry['schoolName'] }} - @if($entry['isDoubler']) + @if($entry['doubleData']) @include('tabulation.auditionSeating-doubler-block') {{-- DOUBLER
    --}} {{-- @foreach($entry['doubleData'] as $double)--}} diff --git a/tests/Feature/Services/DoublerServiceTest.php b/tests/Feature/Services/DoublerServiceTest.php index 694068e..edd5df3 100644 --- a/tests/Feature/Services/DoublerServiceTest.php +++ b/tests/Feature/Services/DoublerServiceTest.php @@ -23,38 +23,37 @@ it('throws an error if an invalid event is provided', function () { it('returns doublers for an event', function () { $concertEvent = Event::factory()->create(['name' => 'Concert Band', 'id' => 1000]); $jazzEvent = Event::factory()->create(['name' => 'Jazz Band', 'id' => 1001]); - - $auditionAS = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1000, 'name' => 'Alto Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1000, ]); - $auditionTS = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1000, 'name' => 'Tenor Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1001, ]); - $auditionBS = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1000, 'name' => 'Baritone Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1002, ]); - $auditionCL = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1000, 'name' => 'Clarinet', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1003, ]); - $auditionBCL = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1000, 'name' => 'Bass Clarinet', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1004, ]); - $auditionJAS = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1001, 'name' => 'Jazz Alto', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1005, ]); - $auditionJTS = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1001, 'name' => 'Jazz Tenor', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1006, ]); - $auditionJBS = Audition::factory()->create([ + Audition::factory()->create([ 'event_id' => 1001, 'name' => 'Jazz Baritone', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1007, ]); $allSaxDude = Student::factory()->create(['grade' => 11, 'id' => 1000]); - $clarinetGal = Student::factory()->create(['grade' => 9, 'id' => 1001]); - $justAlto = Student::factory()->create(['grade' => 9, 'id' => 1002]); + Student::factory()->create(['grade' => 9, 'id' => 1001]); + Student::factory()->create(['grade' => 9, 'id' => 1002]); Entry::create(['student_id' => 1000, 'audition_id' => 1000]); Entry::create(['student_id' => 1000, 'audition_id' => 1001]); Entry::create(['student_id' => 1000, 'audition_id' => 1002]); @@ -67,13 +66,13 @@ it('returns doublers for an event', function () { Entry::create(['student_id' => 1002, 'audition_id' => 1005]); $return = $this->doublerService->doublersForEvent($concertEvent); - expect(count($return))->toBe(2); - expect($return[1000]['student']->id)->toBe($allSaxDude->id); - expect($return[1000]['entries']->count())->toBe(3); - expect($return[1001]['entries']->count())->toBe(2); + expect(count($return))->toBe(2) + ->and($return[1000]['student']->id)->toBe($allSaxDude->id) + ->and($return[1000]['entries']->count())->toBe(3) + ->and($return[1001]['entries']->count())->toBe(2); assertArrayNotHasKey(1002, $return); $return = $this->doublerService->doublersForEvent($jazzEvent); - expect(count($return))->toBe(1); - expect($return[1000]['student']->id)->toBe($allSaxDude->id); - expect($return[1000]['entries']->count())->toBe(3); + expect(count($return))->toBe(1) + ->and($return[1000]['student']->id)->toBe($allSaxDude->id) + ->and($return[1000]['entries']->count())->toBe(3); }); -- 2.39.5 From e1aa8521426c99bd8de71d88aa85be29be667279 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 00:02:11 -0500 Subject: [PATCH 31/43] Accept/Decline buttons are working --- .../Tabulation/DoublerDecisionController.php | 19 ++++++------------- app/Models/Entry.php | 4 +--- app/Services/DoublerService.php | 12 ++++++++++-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/Tabulation/DoublerDecisionController.php b/app/Http/Controllers/Tabulation/DoublerDecisionController.php index 73141e5..2e79bf5 100644 --- a/app/Http/Controllers/Tabulation/DoublerDecisionController.php +++ b/app/Http/Controllers/Tabulation/DoublerDecisionController.php @@ -21,22 +21,15 @@ class DoublerDecisionController extends Controller public function accept(Entry $entry) { - $doublerInfo = $this->doublerService->getDoublerInfo($entry->student_id); - foreach ($doublerInfo as $info) { - $this->entryService->clearEntryCacheForAudition($info['auditionID']); - if ($info['entryID'] != $entry->id) { - try { - EntryFlag::create([ - 'entry_id' => $info['entryID'], - 'flag_name' => 'declined', - ]); - } catch (\Exception $e) { - session()->flash('error', 'Entry ID'.$info['entryID'].' has already been declined.'); - } - + $doublerInfo = $this->doublerService->simpleDoubleInfo($entry); + foreach ($doublerInfo as $doublerEntry) { + /** @var Entry $doublerEntry */ + if ($doublerEntry->id !== $entry->id) { + $doublerEntry->addFlag('declined'); } } + $returnMessage = $entry->student->full_name().' accepted seating in '.$entry->audition->name; return redirect()->back()->with('success', $returnMessage); diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 0acd0cd..bd3ad71 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -10,8 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOneThrough; -use Staudenmeir\BelongsToThrough; -use App\Models\ScoreSheet; +use Illuminate\Support\Facades\Cache; class Entry extends Model { @@ -39,7 +38,6 @@ class Entry extends Model return $this->belongsTo(Audition::class); } - public function school(): HasOneThrough { return $this->hasOneThrough( diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index bc97006..b58b547 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -45,8 +45,8 @@ class DoublerService 'seating' => $entries->filter(fn ($entry) => $entry->for_seating === 1), 'advancement' => $entries->filter(fn ($entry) => $entry->for_advance === 1), }; - $entries->load('student.school'); - $entries->load('audition'); + #$entries->load('student.school'); + #$entries->load('audition'); $grouped = $entries->groupBy('student_id'); // Filter out student groups with only one entry in the event $grouped = $grouped->filter(fn ($s) => $s->count() > 1); @@ -61,6 +61,14 @@ class DoublerService return $doubler_array; } + public function simpleDoubleInfo(Entry $primaryEntry) + { + if (! isset($this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) { + return false; + } + return $this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries']; + } + public function entryDoublerData(Entry $primaryEntry) { if (! isset($this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) { -- 2.39.5 From b2fdfc34a38eb5fca0f477e0b822f011996b954c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 10:29:26 -0500 Subject: [PATCH 32/43] Update SeatAuditionController.php --- app/Http/Controllers/Tabulation/SeatAuditionController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index 1dea67c..a277173 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -39,7 +39,6 @@ class SeatAuditionController extends Controller public function __invoke(Request $request, Audition $audition) { - $doublers = $this->doublerService->doublersForEvent($audition->event); $entryData = []; $entries = $this->ranker->rank('seating', $audition); $entries->load('student.school'); @@ -63,6 +62,6 @@ class SeatAuditionController extends Controller ]; } - return view('tabulation.auditionSeating', compact('entryData', 'audition', 'doublers')); + return view('tabulation.auditionSeating', compact('entryData', 'audition')); } } -- 2.39.5 From 93c970c26e2c53e37eeecb70c84aa64e4baeed5b Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 13:03:00 -0500 Subject: [PATCH 33/43] Right Side panels added to seating form --- .../Tabulation/DoublerDecisionController.php | 14 ++++- .../Tabulation/SeatAuditionController.php | 54 +++++++++++++++++-- app/Providers/AppServiceProvider.php | 1 + app/Services/DoublerService.php | 14 ++--- .../auditionSeating-fill-seats-form.blade.php | 13 +++-- ...ing-right-complete-not-published.blade.php | 2 + ...itionSeating-show-proposed-seats.blade.php | 9 ++-- ...itionSeating-unable-to-seat-card.blade.php | 4 +- .../tabulation/auditionSeating.blade.php | 3 ++ .../Pages/Seating/auditionSeatingTest.php | 2 +- 10 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 resources/views/tabulation/auditionSeating-right-complete-not-published.blade.php diff --git a/app/Http/Controllers/Tabulation/DoublerDecisionController.php b/app/Http/Controllers/Tabulation/DoublerDecisionController.php index 2e79bf5..4bc67ad 100644 --- a/app/Http/Controllers/Tabulation/DoublerDecisionController.php +++ b/app/Http/Controllers/Tabulation/DoublerDecisionController.php @@ -4,13 +4,14 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; use App\Models\Entry; -use App\Models\EntryFlag; use App\Services\DoublerService; use App\Services\EntryService; +use Illuminate\Support\Facades\Cache; class DoublerDecisionController extends Controller { protected $doublerService; + protected $entryService; public function __construct(DoublerService $doublerService, EntryService $entryService) @@ -29,8 +30,8 @@ class DoublerDecisionController extends Controller } } - $returnMessage = $entry->student->full_name().' accepted seating in '.$entry->audition->name; + $this->clearCache($entry); return redirect()->back()->with('success', $returnMessage); @@ -45,7 +46,16 @@ class DoublerDecisionController extends Controller $entry->addFlag('declined'); $returnMessage = $entry->student->full_name().' declined seating in '.$entry->audition->name; + $this->clearCache($entry); return redirect()->back()->with('success', $returnMessage); } + + protected function clearCache($entry) + { + $cacheKey = 'event'.$entry->audition->event_id.'doublers-seating'; + Cache::forget($cacheKey); + $cacheKey = 'event'.$entry->audition->event_id.'doublers-advancement'; + Cache::forget($cacheKey); + } } diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionController.php index a277173..e75ee9f 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionController.php @@ -42,7 +42,10 @@ class SeatAuditionController extends Controller $entryData = []; $entries = $this->ranker->rank('seating', $audition); $entries->load('student.school'); - + $seatable = [ + 'allScored' => true, + 'doublersResolved' => true, + ]; foreach ($entries as $entry) { $totalScoreColumn = 'No Score'; $fullyScored = false; @@ -50,6 +53,7 @@ class SeatAuditionController extends Controller $totalScoreColumn = $entry->score_totals[0] >= 0 ? $entry->score_totals[0] : $entry->score_message; $fullyScored = $entry->score_totals[0] >= 0; } + $doublerData = $this->doublerService->entryDoublerData($entry); $entryData[] = [ 'rank' => $entry->rank, 'id' => $entry->id, @@ -58,10 +62,54 @@ class SeatAuditionController extends Controller 'drawNumber' => $entry->draw_number, 'totalScore' => $totalScoreColumn, 'fullyScored' => $fullyScored, - 'doubleData' => $this->doublerService->entryDoublerData($entry), + 'doubleData' => $doublerData, ]; + // If this entries double decision isn't made, block seating + if ($doublerData && $doublerData[$entry->id]['status'] == 'undecided') { + $seatable['doublersResolved'] = false; + } + // If entry is unscored, block seating + if (! $fullyScored) { + $seatable['allScored'] = false; + } } - return view('tabulation.auditionSeating', compact('entryData', 'audition')); + $rightPanel = $this->pickRightPanel($audition, $seatable); + $seatableEntries = []; + if ($seatable['doublersResolved'] && $seatable['allScored']) { + $seatableEntries = $entries->reject(function ($entry) { + if ($entry->hasFlag('declined')) { + return true; + } + if ($entry->hasFlag('no_show')) { + return true; + } + + return false; + }); + } + + return view('tabulation.auditionSeating', compact('entryData', 'audition', 'rightPanel', 'seatableEntries')); + } + + protected function pickRightPanel(Audition $audition, array $seatable) + { + if ($audition->hasFlag('seats_published')) { + $rightPanel['view'] = 'tabulation.auditionSeating-show-published-seats'; + $rightPanel['data'] = ''; + + return $rightPanel; + } + if ($seatable['allScored'] == false || $seatable['doublersResolved'] == false) { + $rightPanel['view'] = 'tabulation.auditionSeating-unable-to-seat-card'; + $rightPanel['data'] = $seatable; + + return $rightPanel; + } + + $rightPanel['view'] = 'tabulation.auditionSeating-right-complete-not-published'; + $rightPanel['data'] = $this->auditionService->getSeatingLimits($audition); + + return $rightPanel; } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ca321fa..845e62b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -32,6 +32,7 @@ use App\Services\DoublerService; use App\Services\DrawService; use App\Services\EntryService; use App\Services\ScoreService; +use App\Services\StudentService; use App\Services\UserService; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index b58b547..24efd99 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -40,20 +40,19 @@ class DoublerService protected function findDoublersForEvent(Event $event, string $mode = 'seating'): array { $this->validateEvent($event); - $entries = $event->entries; + $entries = $event->entries()->with('audition')->with('student')->get(); $entries = match ($mode) { 'seating' => $entries->filter(fn ($entry) => $entry->for_seating === 1), 'advancement' => $entries->filter(fn ($entry) => $entry->for_advance === 1), }; - #$entries->load('student.school'); - #$entries->load('audition'); + $grouped = $entries->groupBy('student_id'); // Filter out student groups with only one entry in the event $grouped = $grouped->filter(fn ($s) => $s->count() > 1); $doubler_array = []; foreach ($grouped as $student_id => $entries) { $doubler_array[$student_id] = [ - 'student' => $entries[0]->student, + 'student_id' => $student_id, 'entries' => $entries, ]; } @@ -66,15 +65,16 @@ class DoublerService if (! isset($this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) { return false; } + return $this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries']; } public function entryDoublerData(Entry $primaryEntry) { - if (! isset($this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) { + if (! isset($this->doublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) { return false; } - $entries = $this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries']; + $entries = $this->doublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries']; $entryData = collect([]); /** @var Collection $entries */ foreach ($entries as $entry) { @@ -102,7 +102,7 @@ class DoublerService 'auditionName' => $entry->audition->name, 'status' => $status, 'rank' => $this->entryService->rankOfEntry('seating', $entry), - 'unscored_entries' => $entry->audition->unscoredEntries()->count(), + 'unscored_entries' => $entry->audition->unscored_entries_count, 'seating_limits' => $limits, ]; } diff --git a/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php b/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php index 8f1a0d5..b2a4b71 100644 --- a/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php +++ b/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php @@ -1,17 +1,20 @@ + @php + @endphp Seating
    @csrf - @foreach($ensembleLimits as $ensembleLimit) + @foreach($rightPanel['data'] as $ensembleLimit) @php - $value = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit->maximum_accepted; + //$value = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit['limit']; + $value = $ensembleLimit['limit']; @endphp - @endforeach diff --git a/resources/views/tabulation/auditionSeating-right-complete-not-published.blade.php b/resources/views/tabulation/auditionSeating-right-complete-not-published.blade.php new file mode 100644 index 0000000..27797b3 --- /dev/null +++ b/resources/views/tabulation/auditionSeating-right-complete-not-published.blade.php @@ -0,0 +1,2 @@ +@include('tabulation.auditionSeating-fill-seats-form') +@include('tabulation.auditionSeating-show-proposed-seats') diff --git a/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php b/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php index e312c13..72d7ca0 100644 --- a/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php +++ b/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php @@ -2,19 +2,20 @@ $seatingProposal = []; @endphp -@foreach($ensembleLimits as $ensembleLimit) +@foreach($rightPanel['data'] as $ensembleLimit) - {{ $ensembleLimit->ensemble->name }} - DRAFT + {{ $ensembleLimit['ensemble']->name }} - DRAFT @php - $maxAccepted = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit->maximum_accepted; +// $maxAccepted = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit->maximum_accepted; + $maxAccepted = $ensembleLimit['limit']; @endphp @for($n=1; $n <= $maxAccepted; $n++) @php $entry = $seatableEntries->shift(); if (is_null($entry)) continue; $seatingProposal[] = [ - 'ensemble_id' => $ensembleLimit->ensemble->id, + 'ensemble_id' => $ensembleLimit['ensemble']->id, 'audition_id' => $audition->id, 'seat' => $n, 'entry_id' => $entry->id, diff --git a/resources/views/tabulation/auditionSeating-unable-to-seat-card.blade.php b/resources/views/tabulation/auditionSeating-unable-to-seat-card.blade.php index d243c37..54711ea 100644 --- a/resources/views/tabulation/auditionSeating-unable-to-seat-card.blade.php +++ b/resources/views/tabulation/auditionSeating-unable-to-seat-card.blade.php @@ -1,10 +1,10 @@ Unable to seat this audition - @if(! $scoringComplete) + @if(! $rightPanel['data']['allScored'])

    The audition cannot be seated while it has unscored entries.

    @endif - @if(! $doublerComplete) + @if(! $rightPanel['data']['doublersResolved'])

    The audition cannot be seated while it has unresolved doublers.

    @endif
    diff --git a/resources/views/tabulation/auditionSeating.blade.php b/resources/views/tabulation/auditionSeating.blade.php index 17292b9..065ae86 100644 --- a/resources/views/tabulation/auditionSeating.blade.php +++ b/resources/views/tabulation/auditionSeating.blade.php @@ -9,6 +9,9 @@
    @include('tabulation.auditionSeating-results-table')
    +
    + @include($rightPanel['view']) +
    {{--
    --}} {{-- @if($audition->hasFlag('seats_published'))--}} {{-- @include('tabulation.auditionSeating-show-published-seats')--}} diff --git a/tests/Feature/Pages/Seating/auditionSeatingTest.php b/tests/Feature/Pages/Seating/auditionSeatingTest.php index 9a37742..4858a9c 100644 --- a/tests/Feature/Pages/Seating/auditionSeatingTest.php +++ b/tests/Feature/Pages/Seating/auditionSeatingTest.php @@ -69,5 +69,5 @@ it('identifies a doubler', function () { $response = get($this->r); $response->assertOk(); $viewData = $response->viewData('entryData'); - expect($viewData[0]['isDoubler'])->toBeTrue(); + expect($viewData[0]['doubleData'])->toBeTruthy(); }); -- 2.39.5 From 83ee56717d0ac7e79976825a67ceb010cc6f219c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 13:17:25 -0500 Subject: [PATCH 34/43] Account for entry flags when ranking --- app/Actions/Tabulation/RankAuditionEntries.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index 32a81e2..83f8b87 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -8,6 +8,7 @@ use App\Exceptions\TabulationException; use App\Models\Audition; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Cache; +use function is_numeric; class RankAuditionEntries { @@ -60,12 +61,25 @@ class RankAuditionEntries $rank = 1; foreach ($entries as $entry) { $entry->rank = $rank; - $rank++; + if ($mode === 'seating') { + if ($entry->hasFlag('declined')) { + $entry->rank = 'Declined'; + } elseif ($entry->hasFlag('no_show')) { + $entry->rank = 'No Show'; + } elseif ($entry->hasFlag('failed_prelim')) { + $entry->rank = 'Failed Prelim'; + } + } + + if (is_numeric($entry->rank)) { + $rank++; + } } return $entries; } + protected function basicValidation($mode, Audition $audition): void { if ($mode !== 'seating' && $mode !== 'advancement') { -- 2.39.5 From fab34992df6151ed93b0afa6f2a154d47bb8c1c8 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 13:18:24 -0500 Subject: [PATCH 35/43] Update RankAuditionEntries.php --- app/Actions/Tabulation/RankAuditionEntries.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Actions/Tabulation/RankAuditionEntries.php b/app/Actions/Tabulation/RankAuditionEntries.php index 83f8b87..aef64e3 100644 --- a/app/Actions/Tabulation/RankAuditionEntries.php +++ b/app/Actions/Tabulation/RankAuditionEntries.php @@ -61,6 +61,7 @@ class RankAuditionEntries $rank = 1; foreach ($entries as $entry) { $entry->rank = $rank; + // We don't really get a rank for seating if we have certain flags if ($mode === 'seating') { if ($entry->hasFlag('declined')) { $entry->rank = 'Declined'; -- 2.39.5 From 79bc3bb46d979eb08c9c8513f71dfed336b534b3 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 15:16:12 -0500 Subject: [PATCH 36/43] Seats Publish and Unpublish Working --- app/Actions/Tabulation/GetAuditionSeats.php | 40 +++++++++++++++ app/Actions/Tabulation/PublishSeats.php | 31 +++++++++++ app/Actions/Tabulation/UnpublishSeats.php | 21 ++++++++ ...ler.php => SeatAuditionFormController.php} | 16 ++++-- .../SeatingPublicationController.php | 33 ++++++++++++ .../Tabulation/TabulationController.php | 51 ------------------- .../auditionSeating-fill-seats-form.blade.php | 6 +-- ...itionSeating-show-proposed-seats.blade.php | 6 +-- ...tionSeating-show-published-seats.blade.php | 39 +++++++++----- routes/tabulation.php | 19 +++---- 10 files changed, 178 insertions(+), 84 deletions(-) create mode 100644 app/Actions/Tabulation/GetAuditionSeats.php create mode 100644 app/Actions/Tabulation/PublishSeats.php create mode 100644 app/Actions/Tabulation/UnpublishSeats.php rename app/Http/Controllers/Tabulation/{SeatAuditionController.php => SeatAuditionFormController.php} (87%) create mode 100644 app/Http/Controllers/Tabulation/SeatingPublicationController.php diff --git a/app/Actions/Tabulation/GetAuditionSeats.php b/app/Actions/Tabulation/GetAuditionSeats.php new file mode 100644 index 0000000..0dc1101 --- /dev/null +++ b/app/Actions/Tabulation/GetAuditionSeats.php @@ -0,0 +1,40 @@ +getSeats($audition); + } + + protected function getSeats(Audition $audition) + { + $ensembles = Ensemble::where('event_id', $audition->event_id)->orderBy('rank')->get(); + $seats = Seat::with('student.school')->where('audition_id', $audition->id)->orderBy('seat')->get(); + $return = []; + foreach ($ensembles as $ensemble) { + $ensembleSeats = $seats->filter(fn ($seat) => $seat->ensemble_id === $ensemble->id); + foreach ($ensembleSeats as $seat) { + $return[] = [ + 'ensemble' => $ensemble->name, + 'seat' => $seat->seat, + 'student_name' => $seat->student->full_name(), + 'school_name' => $seat->student->school->name, + ]; + } + } + + return $return; + } +} diff --git a/app/Actions/Tabulation/PublishSeats.php b/app/Actions/Tabulation/PublishSeats.php new file mode 100644 index 0000000..7c37b23 --- /dev/null +++ b/app/Actions/Tabulation/PublishSeats.php @@ -0,0 +1,31 @@ +id + Seat::where('audition_id', $audition->id)->delete(); + foreach ($seats as $seat) { + Seat::create([ + 'ensemble_id' => $seat['ensemble_id'], + 'audition_id' => $seat['audition_id'], + 'seat' => $seat['seat'], + 'entry_id' => $seat['entry_id'], + ]); + } + $audition->addFlag('seats_published'); + Cache::forget('resultsSeatList'); + } +} diff --git a/app/Actions/Tabulation/UnpublishSeats.php b/app/Actions/Tabulation/UnpublishSeats.php new file mode 100644 index 0000000..e00cbbc --- /dev/null +++ b/app/Actions/Tabulation/UnpublishSeats.php @@ -0,0 +1,21 @@ +removeFlag('seats_published'); + Cache::forget('resultsSeatList'); + Seat::where('audition_id', $audition->id)->delete(); + } +} diff --git a/app/Http/Controllers/Tabulation/SeatAuditionController.php b/app/Http/Controllers/Tabulation/SeatAuditionFormController.php similarity index 87% rename from app/Http/Controllers/Tabulation/SeatAuditionController.php rename to app/Http/Controllers/Tabulation/SeatAuditionFormController.php index e75ee9f..f06c392 100644 --- a/app/Http/Controllers/Tabulation/SeatAuditionController.php +++ b/app/Http/Controllers/Tabulation/SeatAuditionFormController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Tabulation; use App\Actions\Tabulation\CalculateEntryScore; +use App\Actions\Tabulation\GetAuditionSeats; use App\Actions\Tabulation\RankAuditionEntries; use App\Http\Controllers\Controller; use App\Models\Audition; @@ -11,7 +12,7 @@ use App\Services\DoublerService; use App\Services\EntryService; use Illuminate\Http\Request; -class SeatAuditionController extends Controller +class SeatAuditionFormController extends Controller { protected CalculateEntryScore $calc; @@ -39,6 +40,13 @@ class SeatAuditionController extends Controller public function __invoke(Request $request, Audition $audition) { + // If a seating proposal was posted, deal wth it + if ($request->method() == 'POST') { + $requestedEnsembleAccepts = $request->input('ensembleAccept'); + } else { + $requestedEnsembleAccepts = false; + } + $entryData = []; $entries = $this->ranker->rank('seating', $audition); $entries->load('student.school'); @@ -89,15 +97,15 @@ class SeatAuditionController extends Controller }); } - return view('tabulation.auditionSeating', compact('entryData', 'audition', 'rightPanel', 'seatableEntries')); + return view('tabulation.auditionSeating', compact('entryData', 'audition', 'rightPanel', 'seatableEntries', 'requestedEnsembleAccepts')); } protected function pickRightPanel(Audition $audition, array $seatable) { if ($audition->hasFlag('seats_published')) { + $resultsWindow = new GetAuditionSeats; $rightPanel['view'] = 'tabulation.auditionSeating-show-published-seats'; - $rightPanel['data'] = ''; - + $rightPanel['data'] = $resultsWindow($audition); return $rightPanel; } if ($seatable['allScored'] == false || $seatable['doublersResolved'] == false) { diff --git a/app/Http/Controllers/Tabulation/SeatingPublicationController.php b/app/Http/Controllers/Tabulation/SeatingPublicationController.php new file mode 100644 index 0000000..2adfda6 --- /dev/null +++ b/app/Http/Controllers/Tabulation/SeatingPublicationController.php @@ -0,0 +1,33 @@ +id.'seatingProposal'; + $seats = $request->session()->get($sessionKey); + + $publisher($audition, $seats); + + $request->session()->forget($sessionKey); + + return redirect()->route('seating.audition', ['audition' => $audition->id]); + } + + public function unpublishSeats(Request $request, Audition $audition) + { + $publisher = new UnpublishSeats; + $publisher($audition); + + return redirect()->route('seating.audition', ['audition' => $audition->id]); + } +} diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php index e49c324..f162968 100644 --- a/app/Http/Controllers/Tabulation/TabulationController.php +++ b/app/Http/Controllers/Tabulation/TabulationController.php @@ -35,57 +35,6 @@ class TabulationController extends Controller $this->auditionService = $auditionService; } - public function status() - { - // Needs to provide a collection of auditions - $auditions = $this->tabulationService->getAuditionsWithStatus('seating'); - - return view('tabulation.status', compact('auditions')); - } - - public function auditionSeating(Request $request, Audition $audition) - { - if ($request->method() == 'POST') { - $requestedEnsembleAccepts = $request->input('ensembleAccept'); - } else { - $requestedEnsembleAccepts = false; - } - - $entries = $this->tabulationService->auditionEntries($audition->id); - $entries = $entries->filter(function ($entry) { - return $entry->for_seating; - }); - - $doublerComplete = true; - foreach ($entries as $entry) { - - if ($this->doublerService->studentIsDoubler($entry->student_id)) { // If this entry is a doubler - if ($this->doublerService->getDoublerInfo($entry->student_id)[$entry->id]['status'] === 'undecided') { // If there is no decision for this entry - $doublerComplete = false; - } - } - } - - $scoringComplete = $entries->every(function ($entry) { - return $entry->scoring_complete; - }); - $ensembleLimits = $this->seatingService->getLimitForAudition($audition->id); - $auditionComplete = $scoringComplete && $doublerComplete; - - $seatableEntries = $this->seatingService->getSeatableEntries($audition->id); - $seatableEntries = $seatableEntries->filter(function ($entry) { - return $entry->for_seating; - }); - - return view('tabulation.auditionSeating', compact('audition', - 'entries', - 'scoringComplete', - 'doublerComplete', - 'auditionComplete', - 'ensembleLimits', - 'seatableEntries', - 'requestedEnsembleAccepts')); - } public function publishSeats(Request $request, Audition $audition) { diff --git a/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php b/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php index b2a4b71..dd5794c 100644 --- a/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php +++ b/resources/views/tabulation/auditionSeating-fill-seats-form.blade.php @@ -3,12 +3,12 @@ @endphp Seating
    - + @csrf @foreach($rightPanel['data'] as $ensembleLimit) @php - //$value = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit['limit']; - $value = $ensembleLimit['limit']; + $value = $requestedEnsembleAccepts[$ensembleLimit['ensemble']->id] ?? $ensembleLimit['limit']; +// $value = $ensembleLimit['limit']; @endphp {{ $ensembleLimit['ensemble']->name }} - DRAFT @php -// $maxAccepted = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit->maximum_accepted; - $maxAccepted = $ensembleLimit['limit']; + $maxAccepted = $requestedEnsembleAccepts[$ensembleLimit['ensemble']->id] ?? $ensembleLimit['limit']; +// $maxAccepted = $ensembleLimit['limit']; @endphp @for($n=1; $n <= $maxAccepted; $n++) @php @@ -29,7 +29,7 @@ @endforeach -
    + @csrf Seat and Publish
    diff --git a/resources/views/tabulation/auditionSeating-show-published-seats.blade.php b/resources/views/tabulation/auditionSeating-show-published-seats.blade.php index 2ce2ac7..ef920a1 100644 --- a/resources/views/tabulation/auditionSeating-show-published-seats.blade.php +++ b/resources/views/tabulation/auditionSeating-show-published-seats.blade.php @@ -1,12 +1,10 @@ -@inject('seatingService','App\Services\SeatingService') - Seats are Published Unpublish @@ -15,17 +13,34 @@ -@foreach($ensembleLimits as $ensembleLimit) + @php - $ensembleSeats = $seatingService->getSeatsForAudition($audition->id)[$ensembleLimit->ensemble->id] ?? array(); + $previousEnsemble = null; @endphp - - {{ $ensembleLimit->ensemble->name }} - @foreach($ensembleSeats as $seat) + @foreach($rightPanel['data'] as $seat) + @if($seat['ensemble'] !== $previousEnsemble) + {{$seat['ensemble']}} + @endif - {{ $seat->seat }} - {{ $seat->student->full_name() }} + {{ $seat['seat'] }} - {{ $seat['student_name'] }} - @endforeach + @php - -@endforeach + $previousEnsemble = $seat['ensemble']; + @endphp + @endforeach + +{{--@foreach($ensembleLimits as $ensembleLimit)--}} +{{-- @php--}} +{{-- $ensembleSeats = $seatingService->getSeatsForAudition($audition->id)[$ensembleLimit->ensemble->id] ?? array();--}} +{{-- @endphp--}} +{{-- --}} +{{-- {{ $ensembleLimit->ensemble->name }}--}} +{{-- @foreach($ensembleSeats as $seat)--}} +{{-- --}} +{{-- {{ $seat->seat }} - {{ $seat->student->full_name() }}--}} +{{-- --}} +{{-- @endforeach--}} + +{{-- --}} +{{--@endforeach--}} diff --git a/routes/tabulation.php b/routes/tabulation.php index 85a39f3..95d65bc 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -5,11 +5,11 @@ use App\Http\Controllers\Tabulation\AdvancementController; use App\Http\Controllers\Tabulation\DoublerDecisionController; use App\Http\Controllers\Tabulation\EntryFlagController; use App\Http\Controllers\Tabulation\ScoreController; -use App\Http\Controllers\Tabulation\SeatAuditionController; +use App\Http\Controllers\Tabulation\SeatAuditionFormController; +use App\Http\Controllers\Tabulation\SeatingPublicationController; use App\Http\Controllers\Tabulation\SeatingStatusController; use App\Http\Controllers\Tabulation\TabulationController; use App\Http\Middleware\CheckIfCanTab; -use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Route; Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () { @@ -24,8 +24,7 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () }); // Entry Flagging - Route::prefix('entry-flags/')->controller(EntryFlagController::class)->group(function ( - ) { + Route::prefix('entry-flags/')->controller(EntryFlagController::class)->group(function () { Route::get('/choose_no_show', 'noShowSelect')->name('entry-flags.noShowSelect'); Route::get('/propose-no-show', 'noShowConfirm')->name('entry-flags.confirmNoShow'); Route::post('/no-show/{entry}', 'enterNoShow')->name('entry-flags.enterNoShow'); @@ -35,21 +34,19 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () // Seating Routes Route::prefix('seating/')->group(function () { Route::get('/', SeatingStatusController::class)->name('seating.status'); - Route::get('/{audition}', SeatAuditionController::class)->name('seating.audition'); + Route::match(['get', 'post'], '/{audition}', SeatAuditionFormController::class)->name('seating.audition'); + Route::post('/{audition}/publish', [SeatingPublicationController::class, 'publishSeats'])->name('seating.audition.publish'); + Route::post('/{audition}/unpublish', [SeatingPublicationController::class, 'unpublishSeats'])->name('seating.audition.unpublish'); }); // Generic Tabulation Routes (TO BE REPLACED) - Route::prefix('tabulation/')->controller(TabulationController::class)->group(function ( - ) { - Route::get('/status', 'status')->name('tabulation.status'); - Route::match(['get', 'post'], '/auditions/{audition}', 'auditionSeating')->name('tabulation.audition.seat'); + Route::prefix('tabulation/')->controller(TabulationController::class)->group(function () { Route::post('/auditions/{audition}/publish-seats', 'publishSeats')->name('tabulation.seat.publish'); Route::post('/auditions/{audition}/unpublish-seats', 'unpublishSeats')->name('tabulation.seat.unpublish'); }); // Advancement Routes - Route::prefix('advancement/')->controller(AdvancementController::class)->group(function ( - ) { + Route::prefix('advancement/')->controller(AdvancementController::class)->group(function () { Route::get('/status', 'status')->name('advancement.status'); Route::get('/{audition}', 'ranking')->name('advancement.ranking'); Route::post('/{audition}', 'setAuditionPassers')->name('advancement.setAuditionPassers'); -- 2.39.5 From 7fc89146823424f0277c21881c8541095e70bcc9 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 22:13:00 -0500 Subject: [PATCH 37/43] Remove old tabulation controller --- .../Tabulation/TabulationController.php | 69 ------------------- routes/tabulation.php | 6 -- 2 files changed, 75 deletions(-) delete mode 100644 app/Http/Controllers/Tabulation/TabulationController.php diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php deleted file mode 100644 index f162968..0000000 --- a/app/Http/Controllers/Tabulation/TabulationController.php +++ /dev/null @@ -1,69 +0,0 @@ -tabulationService = $tabulationService; - $this->doublerService = $doublerService; - $this->seatingService = $seatingService; - $this->auditionService = $auditionService; - } - - - public function publishSeats(Request $request, Audition $audition) - { - // TODO move this to SeatingService - $sessionKey = 'audition'.$audition->id.'seatingProposal'; - $seats = $request->session()->get($sessionKey); - foreach ($seats as $seat) { - Seat::create([ - 'ensemble_id' => $seat['ensemble_id'], - 'audition_id' => $seat['audition_id'], - 'seat' => $seat['seat'], - 'entry_id' => $seat['entry_id'], - ]); - } - $audition->addFlag('seats_published'); - $request->session()->forget($sessionKey); - Cache::forget('resultsSeatList'); - - // TODO move the previous Cache functions here and in unplublish to the services, need to add an event for publishing an audition as well - return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]); - } - - public function unpublishSeats(Request $request, Audition $audition) - { - // TODO move this to SeatingService - $audition->removeFlag('seats_published'); - Cache::forget('resultsSeatList'); - Seat::where('audition_id', $audition->id)->delete(); - - return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]); - } -} diff --git a/routes/tabulation.php b/routes/tabulation.php index 95d65bc..2bde4eb 100644 --- a/routes/tabulation.php +++ b/routes/tabulation.php @@ -39,12 +39,6 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () Route::post('/{audition}/unpublish', [SeatingPublicationController::class, 'unpublishSeats'])->name('seating.audition.unpublish'); }); - // Generic Tabulation Routes (TO BE REPLACED) - Route::prefix('tabulation/')->controller(TabulationController::class)->group(function () { - Route::post('/auditions/{audition}/publish-seats', 'publishSeats')->name('tabulation.seat.publish'); - Route::post('/auditions/{audition}/unpublish-seats', 'unpublishSeats')->name('tabulation.seat.unpublish'); - }); - // Advancement Routes Route::prefix('advancement/')->controller(AdvancementController::class)->group(function () { Route::get('/status', 'status')->name('advancement.status'); -- 2.39.5 From 1501764a51ba79c3b53a2ecd8ca66aa2787624d6 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 23:27:13 -0500 Subject: [PATCH 38/43] Update DoublerService test --- app/Services/DoublerService.php | 1 + tests/Feature/Services/DoublerServiceTest.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Services/DoublerService.php b/app/Services/DoublerService.php index 24efd99..f7a114c 100644 --- a/app/Services/DoublerService.php +++ b/app/Services/DoublerService.php @@ -39,6 +39,7 @@ class DoublerService */ protected function findDoublersForEvent(Event $event, string $mode = 'seating'): array { + // TODO add scoped entry queries to the event model $this->validateEvent($event); $entries = $event->entries()->with('audition')->with('student')->get(); $entries = match ($mode) { diff --git a/tests/Feature/Services/DoublerServiceTest.php b/tests/Feature/Services/DoublerServiceTest.php index edd5df3..691b5aa 100644 --- a/tests/Feature/Services/DoublerServiceTest.php +++ b/tests/Feature/Services/DoublerServiceTest.php @@ -67,12 +67,12 @@ it('returns doublers for an event', function () { $return = $this->doublerService->doublersForEvent($concertEvent); expect(count($return))->toBe(2) - ->and($return[1000]['student']->id)->toBe($allSaxDude->id) + ->and($return[1000]['student_id'])->toBe($allSaxDude->id) ->and($return[1000]['entries']->count())->toBe(3) ->and($return[1001]['entries']->count())->toBe(2); assertArrayNotHasKey(1002, $return); $return = $this->doublerService->doublersForEvent($jazzEvent); expect(count($return))->toBe(1) - ->and($return[1000]['student']->id)->toBe($allSaxDude->id) + ->and($return[1000]['student_id'])->toBe($allSaxDude->id) ->and($return[1000]['entries']->count())->toBe(3); }); -- 2.39.5 From a14330ff0d6cc1caf366d4f9ff639bb0a27f8ee2 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 23:27:31 -0500 Subject: [PATCH 39/43] Update Audition.php --- app/Models/Audition.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Models/Audition.php b/app/Models/Audition.php index 9376bc4..aba117d 100644 --- a/app/Models/Audition.php +++ b/app/Models/Audition.php @@ -17,6 +17,9 @@ class Audition extends Model { use HasFactory; + /** + * @var int|mixed + */ protected $guarded = []; public function event(): BelongsTo -- 2.39.5 From 026f2002e5a58b06056bb9324a67ea934eb107f9 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 23:28:02 -0500 Subject: [PATCH 40/43] SeatingStatus updates to limit entries --- .../Tabulation/SeatingStatusController.php | 10 +++++++++- tests/Feature/Pages/Seating/statusTest.php | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Tabulation/SeatingStatusController.php b/app/Http/Controllers/Tabulation/SeatingStatusController.php index 63465dd..dd8f6f8 100644 --- a/app/Http/Controllers/Tabulation/SeatingStatusController.php +++ b/app/Http/Controllers/Tabulation/SeatingStatusController.php @@ -13,7 +13,15 @@ class SeatingStatusController extends Controller */ public function __invoke(Request $request) { - $auditions = Audition::forSeating()->withCount(['entries', 'unscoredEntries'])->with('flags')->get(); + $auditions = Audition::forSeating() + ->withCount(['entries'=> function ($query) { + $query->where('for_seating', 1); + }]) + ->withCount(['unscoredEntries'=>function ($query) { + $query->where('for_seating', 1); + }]) + ->with('flags') + ->get(); $auditionData = []; foreach ($auditions as $audition) { $auditionData[$audition->id] = [ diff --git a/tests/Feature/Pages/Seating/statusTest.php b/tests/Feature/Pages/Seating/statusTest.php index a05a09a..686da05 100644 --- a/tests/Feature/Pages/Seating/statusTest.php +++ b/tests/Feature/Pages/Seating/statusTest.php @@ -145,5 +145,18 @@ it('correctly shows a flag when the audition is flagged as seated', function () $response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) { return $viewAuditionData[$audition->id]['seatsPublished'] === true; }); - +}); +it('shows seating auditions', function() { + $audition = Audition::factory()->create(); + actAsAdmin(); + get(route('seating.status')) + ->assertOk() + ->assertSee($audition->name); +}); +it('does not show advancement only auditions', function() { + $audition = Audition::factory()->advancementOnly()->create(); + actAsAdmin(); + get(route('seating.status')) + ->assertOk() + ->assertDontSee($audition->name); }); -- 2.39.5 From cdb7f9b04f4a05046ba07f9aec40a2707f6f22da Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 13 Jul 2024 23:29:29 -0500 Subject: [PATCH 41/43] AdvancementStatus page working --- .../Tabulation/AdvancementController.php | 46 +++++++++++++++---- .../tabulation/advancement/status.blade.php | 20 +++----- .../Feature/Pages/Advancement/statusTest.php | 41 +++++++++++++++++ 3 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 tests/Feature/Pages/Advancement/statusTest.php diff --git a/app/Http/Controllers/Tabulation/AdvancementController.php b/app/Http/Controllers/Tabulation/AdvancementController.php index 93eac92..e0abc34 100644 --- a/app/Http/Controllers/Tabulation/AdvancementController.php +++ b/app/Http/Controllers/Tabulation/AdvancementController.php @@ -5,23 +5,48 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; use App\Models\Audition; use App\Models\Entry; -use App\Services\TabulationService; use Illuminate\Http\Request; class AdvancementController extends Controller { - protected TabulationService $tabulationService; - - public function __construct(TabulationService $tabulationService) + public function __construct() { - $this->tabulationService = $tabulationService; + } public function status() { - $auditions = $this->tabulationService->getAuditionsWithStatus('advancement'); + $auditions = Audition::forAdvancement() + ->with('flags') + ->withCount([ + 'entries' => function ($query) { + $query->where('for_advancement', 1); + }, + ]) + ->withCount([ + 'unscoredEntries' => function ($query) { + $query->where('for_advancement', 1); + }, + ]) + ->get(); + $auditionData = []; + $auditions->each(function ($audition) use (&$auditionData) { + $scoredPercent = ($audition->entries_count > 0) ? + round((($audition->entries_count - $audition->unscored_entries_count) / $audition->entries_count) * 100) + : 100; + $auditionData[] = [ + 'id' => $audition->id, + 'name' => $audition->name, + 'entries_count' => $audition->entries_count, + 'unscored_entries_count' => $audition->unscored_entries_count, + 'scored_entries_count' => $audition->entries_count - $audition->unscored_entries_count, + 'scored_percentage' => $scoredPercent, + 'scoring_complete' => $audition->unscored_entries_count == 0, + 'published' => $audition->hasFlag('advancement_published'), + ]; + }); - return view('tabulation.advancement.status', compact('auditions')); + return view('tabulation.advancement.status', compact('auditionData')); } public function ranking(Request $request, Audition $audition) @@ -29,7 +54,6 @@ class AdvancementController extends Controller $entries = $this->tabulationService->auditionEntries($audition->id, 'advancement'); $entries->load('advancementVotes'); - $scoringComplete = $entries->every(function ($entry) { return $entry->scoring_complete; }); @@ -47,7 +71,8 @@ class AdvancementController extends Controller $entry->addFlag('will_advance'); } - return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been set successfully'); + return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', + 'Passers have been set successfully'); } public function clearAuditionPassers(Request $request, Audition $audition) @@ -57,6 +82,7 @@ class AdvancementController extends Controller $entry->removeFlag('will_advance'); } - return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been cleared successfully'); + return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', + 'Passers have been cleared successfully'); } } diff --git a/resources/views/tabulation/advancement/status.blade.php b/resources/views/tabulation/advancement/status.blade.php index d2e269a..2385bef 100644 --- a/resources/views/tabulation/advancement/status.blade.php +++ b/resources/views/tabulation/advancement/status.blade.php @@ -13,33 +13,27 @@ - @foreach($auditions as $audition) - @php - $percent = 100; - if($audition->advancement_entries_count > 0) { - $percent = round(($audition->scored_entries_count / $audition->advancement_entries_count) * 100); - } - @endphp + @foreach($auditionData as $audition) - +
    - {{ $audition->name }} - {{ $audition->scored_entries_count }} / {{ $audition->advancement_entries_count }} Scored + {{ $audition['name'] }} + {{ $audition['scored_entries_count'] }} / {{ $audition['entries_count'] }} Scored
    -
    +
    - @if( $audition->scored_entries_count == $audition->advancement_entries_count) + @if( $audition['scoring_complete']) @endif - @if( $audition->hasFlag('advancement_published')) + @if( $audition['published']) @endif diff --git a/tests/Feature/Pages/Advancement/statusTest.php b/tests/Feature/Pages/Advancement/statusTest.php new file mode 100644 index 0000000..c83969e --- /dev/null +++ b/tests/Feature/Pages/Advancement/statusTest.php @@ -0,0 +1,41 @@ +assertRedirect(route('home')); + actingAs(User::factory()->create()); + get(route('advancement.status')) + ->assertRedirect(route('dashboard')) + ->assertSessionHas('error', 'You are not authorized to perform this action'); +}); +it('responds to an admin or tab user', function () { + actAsAdmin(); + get(route('advancement.status')) + ->assertOk(); + actAsTab(); + get(route('advancement.status')) + ->assertOk(); +}); +it('includes advancement auditions', function () { + $audition = Audition::factory()->create(); + actAsAdmin(); + get(route('advancement.status')) + ->assertOk() + ->assertSee($audition->name); +}); +it('does not include auditions not for advancement', function () { + $audition = Audition::factory()->seatingOnly()->create(); + actAsAdmin(); + get(route('advancement.status')) + ->assertOk() + ->assertDontSee($audition->name); +}); -- 2.39.5 From 3c9a890be317dd8776504dc63102686d170149eb Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sun, 14 Jul 2024 00:26:24 -0500 Subject: [PATCH 42/43] Results Table working on advancement --- .../Tabulation/CalculateScoreSheetTotal.php | 1 - .../Tabulation/AdvancementController.php | 8 +++++--- .../advancement/results-table.blade.php | 16 +++++++++------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/Actions/Tabulation/CalculateScoreSheetTotal.php b/app/Actions/Tabulation/CalculateScoreSheetTotal.php index 3d2d506..322aec0 100644 --- a/app/Actions/Tabulation/CalculateScoreSheetTotal.php +++ b/app/Actions/Tabulation/CalculateScoreSheetTotal.php @@ -46,7 +46,6 @@ class CalculateScoreSheetTotal $finalScore = $scoreTotal / $weightsTotal; // put $final score at the beginning of the $ScoreArray array_unshift($scoreArray, $finalScore); - return $scoreArray; } diff --git a/app/Http/Controllers/Tabulation/AdvancementController.php b/app/Http/Controllers/Tabulation/AdvancementController.php index e0abc34..7120070 100644 --- a/app/Http/Controllers/Tabulation/AdvancementController.php +++ b/app/Http/Controllers/Tabulation/AdvancementController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Tabulation; +use App\Actions\Tabulation\RankAuditionEntries; use App\Http\Controllers\Controller; use App\Models\Audition; use App\Models\Entry; @@ -9,9 +10,10 @@ use Illuminate\Http\Request; class AdvancementController extends Controller { - public function __construct() + protected RankAuditionEntries $ranker; + public function __construct(RankAuditionEntries $ranker) { - + $this->ranker = $ranker; } public function status() @@ -51,7 +53,7 @@ class AdvancementController extends Controller public function ranking(Request $request, Audition $audition) { - $entries = $this->tabulationService->auditionEntries($audition->id, 'advancement'); + $entries = $this->ranker->rank('advancement', $audition); $entries->load('advancementVotes'); $scoringComplete = $entries->every(function ($entry) { diff --git a/resources/views/tabulation/advancement/results-table.blade.php b/resources/views/tabulation/advancement/results-table.blade.php index 049e6e3..4c024aa 100644 --- a/resources/views/tabulation/advancement/results-table.blade.php +++ b/resources/views/tabulation/advancement/results-table.blade.php @@ -7,7 +7,6 @@ Draw # Student Name Total Score - All Scores? Votes @if($scoringComplete) Pass? @@ -17,6 +16,13 @@ @foreach($entries as $entry) + @php + if ($entry->score_totals[0] < 0) { + $score = $entry->score_message; + } else { + $score = number_format($entry->score_totals[0] ?? 0,4); + } + @endphp {{ $entry->rank }} {{ $entry->id }} @@ -25,12 +31,8 @@ {{ $entry->student->full_name() }} {{ $entry->student->school->name }} - {{ number_format($entry->final_score_array[0] ?? 0,4) }} - - @if($entry->scoring_complete) - - @endif - + {{ $score }} + @foreach($entry->advancementVotes as $vote)
    Date: Sun, 14 Jul 2024 00:35:32 -0500 Subject: [PATCH 43/43] Marking advancement and publishing is working. --- app/Http/Controllers/Tabulation/AdvancementController.php | 7 ++++--- .../views/tabulation/advancement/results-table.blade.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Tabulation/AdvancementController.php b/app/Http/Controllers/Tabulation/AdvancementController.php index 7120070..b5499fc 100644 --- a/app/Http/Controllers/Tabulation/AdvancementController.php +++ b/app/Http/Controllers/Tabulation/AdvancementController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Controller; use App\Models\Audition; use App\Models\Entry; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; class AdvancementController extends Controller { @@ -57,7 +58,7 @@ class AdvancementController extends Controller $entries->load('advancementVotes'); $scoringComplete = $entries->every(function ($entry) { - return $entry->scoring_complete; + return $entry->score_totals[0] >= 0; }); return view('tabulation.advancement.ranking', compact('audition', 'entries', 'scoringComplete')); @@ -72,7 +73,7 @@ class AdvancementController extends Controller foreach ($entries as $entry) { $entry->addFlag('will_advance'); } - + Cache::forget('audition'.$audition->id.'advancement'); return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been set successfully'); } @@ -83,7 +84,7 @@ class AdvancementController extends Controller foreach ($audition->entries as $entry) { $entry->removeFlag('will_advance'); } - + Cache::forget('audition'.$audition->id.'advancement'); return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been cleared successfully'); } diff --git a/resources/views/tabulation/advancement/results-table.blade.php b/resources/views/tabulation/advancement/results-table.blade.php index 4c024aa..0cba0b4 100644 --- a/resources/views/tabulation/advancement/results-table.blade.php +++ b/resources/views/tabulation/advancement/results-table.blade.php @@ -32,7 +32,7 @@ {{ $entry->student->school->name }} {{ $score }} - + @foreach($entry->advancementVotes as $vote)