diff --git a/app/Http/Controllers/Admin/RoomController.php b/app/Http/Controllers/Admin/RoomController.php index d3509cc..6bc804a 100644 --- a/app/Http/Controllers/Admin/RoomController.php +++ b/app/Http/Controllers/Admin/RoomController.php @@ -40,11 +40,11 @@ class RoomController extends Controller if($request->isMethod('post')) { // attach judge on post - $room->judges()->attach($judge->id); + $room->addJudge($judge->id); $message = "Assigned " . $judge->full_name() . " to " . $room->name; } elseif ($request->isMethod('delete')) { // detach judge on delete - $room->judges()->detach($judge->id); + $room->removeJudge($judge->id); $message = "Removed " . $judge->full_name() . " from " . $room->name; } else { return redirect('/admin/rooms/judging_assignments')->with('error', 'Invalid request method.'); diff --git a/app/Models/Audition.php b/app/Models/Audition.php index e4933e9..6facb5b 100644 --- a/app/Models/Audition.php +++ b/app/Models/Audition.php @@ -20,6 +20,7 @@ class Audition extends Model protected $rankedEntries = null; protected static $completeAuditions = null; protected $fully_scored; // Set by TabulationService + protected $scored_entries_count; //Set by TabulationService public static function getCompleteAuditions() { diff --git a/app/Models/Room.php b/app/Models/Room.php index 0f9ce67..b8576cf 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\AuditionChange; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -40,4 +41,16 @@ class Room extends Model { return $this->belongsToMany(User::class,'room_user','room_id','user_id'); } + + public function addJudge($userId): void + { + $this->judges()->attach($userId); + AuditionChange::dispatch(); + } + + public function removeJudge($userId): void + { + $this->judges()->detach($userId); + AuditionChange::dispatch(); + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e8d74c2..98a1bfb 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -56,7 +56,10 @@ class AppServiceProvider extends ServiceProvider }); $this->app->singleton(TabulationService::class, function($app) { - return new TabulationService($app->make(AuditionCacheService::class)); + return new TabulationService( + $app->make(AuditionCacheService::class), + $app->make(ScoreService::class), + $app->make(EntryCacheService::class)); }); diff --git a/app/Services/AuditionCacheService.php b/app/Services/AuditionCacheService.php index 1a4a908..f058888 100644 --- a/app/Services/AuditionCacheService.php +++ b/app/Services/AuditionCacheService.php @@ -43,6 +43,7 @@ class AuditionCacheService return $this->getAuditions()->firstWhere('id',$id); } + public function refreshCache() { Cache::forget($this->cacheKey); diff --git a/app/Services/EntryCacheService.php b/app/Services/EntryCacheService.php index 901a075..5f2fe6c 100644 --- a/app/Services/EntryCacheService.php +++ b/app/Services/EntryCacheService.php @@ -24,7 +24,7 @@ class EntryCacheService * @return \Illuminate\Database\Eloquent\Collection */ public function getEntriesForAudition($auditionId) { - + // 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'; return Cache::remember($cacheKey, 3600, function () use ($auditionId) { return Entry::where('audition_id',$auditionId) @@ -53,8 +53,8 @@ class EntryCacheService public function getAllEntries() { $cacheKey = 'allEntries'; - return Cache::remember($cacheKey, 3600, function() { - return Entry::with(['student.school'])->get()->keyBy('id'); + return Cache::remember($cacheKey, 5, function() { + return Entry::all(); }); } diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index e6b1f1e..2004fec 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -3,9 +3,11 @@ 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 function array_unshift; class ScoreService @@ -50,6 +52,30 @@ class ScoreService 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 + $entryScoreSheetCounts = ScoreSheet::select('entry_id', DB::raw('count(*) as count')) + ->groupBy('entry_id') + ->get() + ->pluck('count','entry_id'); + 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 @@ -82,7 +108,7 @@ class ScoreService } /** - * Calculate final score using teh provided scoring guide and score sheets. Returns an array of scores + * 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. * @param $scoringGuideId @@ -120,4 +146,28 @@ class ScoreService array_unshift($finalScoresArray,$totalScore); return $finalScoresArray; } + + /** + * Validate that the judge on the score sheet is actually assigned to judge + * then entry + * @param ScoreSheet $sheet + * @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/TabulationService.php b/app/Services/TabulationService.php index 320dc29..266e1fc 100644 --- a/app/Services/TabulationService.php +++ b/app/Services/TabulationService.php @@ -2,27 +2,31 @@ namespace App\Services; -use App\Exceptions\TabulationException; -use App\Models\Audition; + use App\Models\Entry; use App\Models\ScoreSheet; use App\Models\User; use Illuminate\Support\Facades\Cache; -use App\Services\AuditionCacheService; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Session; class TabulationService { - protected $cacheKey = 'entries'; - protected $auditionCacheService; + protected AuditionCacheService $auditionCacheService; + protected EntryCacheService $entryCacheService; + protected ScoreService $scoreService; /** * Create a new class instance. */ - public function __construct(AuditionCacheService $scoringGuideCacheService) + public function __construct( + AuditionCacheService $scoringGuideCacheService, + ScoreService $scoreService, + EntryCacheService $entryCacheService) { $this->auditionCacheService = $scoringGuideCacheService; + $this->scoreService = $scoreService; + $this->entryCacheService = $entryCacheService; } public function entryRank(Entry $entry) { @@ -39,7 +43,8 @@ class TabulationService foreach ($entries as $entry) { - $entry->final_score_array = $this->entryFinalScores($entry); +// $entry->final_score_array = $this->entryFinalScores($entry); + $entry->final_score_array = $this->scoreService->entryTotalScores($entry); $entry->scoring_complete = ($entry->score_sheets_count == $audition->judges_count) ? true:false; } @@ -49,7 +54,7 @@ class TabulationService return $entry['final_score_array'][$n]; }); } - //TODO verify this actually sorts by subscores correcty + //TODO verify this actually sorts by subscores correctly $n = 1; foreach ($entries as $entry) { $entry->rank = $n; @@ -59,40 +64,9 @@ class TabulationService }); } - /** - * @throws TabulationException - */ - public function entryFinalScores(Entry $entry) { - $audition = $this->auditionCacheService->getAudition($entry->audition_id); - if (! $this->entryScoreSheetsAreValid($entry)) return null; - - $subscores = $audition->scoringGuide->subscores->sortBy('tiebreak_order'); - $finalScoresArray = []; - foreach($subscores as $subscore) { - $finalScoresArray[$subscore->id] = 0; - - } - - foreach ($entry->scoreSheets as $sheet) { - foreach ($sheet->subscores as $ss) { - $finalScoresArray[$ss['subscore_id']] += $ss['score']; - } - } - - // calculate weighted final score - $totalScore = 0; - $totalWeight = 0; - foreach ($subscores as $subscore) { - $totalScore += ($finalScoresArray[$subscore->id] * $subscore->weight); - $totalWeight += $subscore->weight; - } - $totalScore = ($totalScore / $totalWeight); - array_unshift($finalScoresArray,$totalScore); - return $finalScoresArray; - } public function entryScoreSheetsAreValid(Entry $entry): bool { - //TODO consider making this move the invalid score to another database for further investication + //TODO consider making this move the invalid score to another database for further investigation $validJudges = $this->auditionCacheService->getAudition($entry->audition_id)->judges; foreach ($entry->scoreSheets as $sheet) { if (! $validJudges->contains($sheet->user_id)) { @@ -110,38 +84,31 @@ class TabulationService 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() { return Cache::remember('auditionsWithStatus',30,function() { - // Create an array with the number of scores for each entry - $scoreCountByEntry = ScoreSheet::select('entry_id', DB::raw('count(*) as count')) - ->groupBy('entry_id') - ->get() - ->pluck('count','entry_id'); // Retrieve auditions from the cache and load entry IDs $auditions = $this->auditionCacheService->getAuditions(); - $auditions->load('entries'); - - // Eager load the count of related models - $auditions->loadCount(['judges', 'entries']); // Iterate over the auditions and calculate the scored_entries_count - return $auditions->map(function ($audition) use ($scoreCountByEntry) { - $audition->scored_entries_count = $audition->entries->reduce(function ($carry, $entry) use ($audition, $scoreCountByEntry) { - $entry->fully_scored = $audition->judges_count == $scoreCountByEntry->has($entry->id) ? $scoreCountByEntry[$entry->id] : false; - return $carry + ($entry->fully_scored ? 1 : 0); - }, 0); - return $audition; - }); + foreach($auditions as $audition) { + $scored_entries_count = 0; + foreach ($this->entryCacheService->getEntriesForAudition($audition->id) as $entry) { + if ($this->scoreService->entryScoreSheetCounts()[$entry->id] ?? 0 == $audition->judges_count) { + $scored_entries_count++; + } + } + + $audition->scored_entries_count = $scored_entries_count; + } + return $auditions; }); } - - public function allResults() { - $auditions = $this->getAuditionsWithStatus(); - foreach ($auditions as $audition) { - $audition->entries = $this->auditionEntries($audition->id); - } - return $auditions; - } } diff --git a/config/debugbar.php b/config/debugbar.php index 44aff57..c58b89f 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -224,7 +224,7 @@ return [ 'show_copy' => false, // Show copy button next to the query, 'slow_threshold' => false, // Only track queries that last longer than this time in ms 'memory_usage' => false, // Show queries memory usage - 'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured + 'soft_limit' => 200, // After the soft limit, no parameters/backtrace are captured 'hard_limit' => 500, // After the hard limit, queries are ignored ], 'mail' => [ diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index 832aa02..a285732 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -10,14 +10,15 @@ use Illuminate\Support\Facades\Session; @endphp @inject('scoreservice','App\Services\ScoreService'); +@inject('auditionService','App\Services\AuditionCacheService'); +@inject('entryService','App\Services\EntryCacheService') Test Page @php - $entry = Entry::first(); - $scoreservice->calculateScoresForAudition(6); + $testedSheet = ScoreSheet::find(37); @endphp - Entry 392 scores: {{ $scoreservice->entryTotalScores(Entry::find(392))[0] }} +