Rewrite tabulation #14
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -83,4 +83,3 @@ class UserController extends Controller
|
|||
}
|
||||
|
||||
|
||||
//TODO allow users to modify their profile information. RoomJudgeChange::dispatch(); when they do
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,106 +20,15 @@ class DoublerService
|
|||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(AuditionService $auditionService, TabulationService $tabulationService, SeatingService $seatingService)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class Settings
|
|||
public static function get($key, $default = null)
|
||||
{
|
||||
$settings = Cache::get(self::$cacheKey, []);
|
||||
|
||||
return $settings[$key] ?? $default;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue