Flags for entries to store declined. Seating page will correctly display doubler status

This commit is contained in:
Matt Young 2024-06-21 10:44:38 -05:00
parent 30c2813ecf
commit f9e936fd07
5 changed files with 103 additions and 10 deletions

View File

@ -2,23 +2,28 @@
namespace App\Models; namespace App\Models;
use App\Exceptions\TabulationException;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\HasOneThrough;
class Entry extends Model class Entry extends Model
{ {
use HasFactory; use HasFactory;
protected $guarded = []; protected $guarded = [];
protected $hasCheckedScoreSheets = false; protected $hasCheckedScoreSheets = false;
public $final_scores_array; // Set by TabulationService public $final_scores_array; // Set by TabulationService
public $scoring_complete; // Set by TabulationService public $scoring_complete; // Set by TabulationService
public $is_doubler; // Set by DoublerService public $is_doubler; // Set by DoublerService
protected $with = ['flags'];
public function student(): BelongsTo public function student(): BelongsTo
{ {
return $this->belongsTo(Student::class); return $this->belongsTo(Student::class);
@ -46,14 +51,27 @@ class Entry extends Model
} }
/* public function flags(): HasMany
{
return $this->hasMany(EntryFlag::class);
}
public function hasFlag($flag)
{
// return true if any flag in $this->flags has a flag_name of declined without making another db query if flags are loaded
return $this->flags->contains('flag_name', $flag);
}
/**
* Ensures score_sheets_count property is always available * Ensures score_sheets_count property is always available
*/ */
public function getScoreSheetsCountAttribute() public function getScoreSheetsCountAttribute()
{ {
if (!isset($this->attributes['score_sheets_count'])) { if (! isset($this->attributes['score_sheets_count'])) {
$this->attributes['score_sheets_count'] = $this->scoreSheets()->count(); $this->attributes['score_sheets_count'] = $this->scoreSheets()->count();
} }
return $this->attributes['score_sheets_count']; return $this->attributes['score_sheets_count'];
} }
} }

16
app/Models/EntryFlag.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class EntryFlag extends Model
{
// Possible flags include
// - declined: used if a doubler declines a seat in this audition. Checked by DoublerService
public function entry(): BelongsTo
{
return $this->belongsTo(Entry::class);
}
}

View File

@ -9,9 +9,13 @@ use Illuminate\Support\Facades\Cache;
class DoublerService class DoublerService
{ {
protected $doublersCacheKey = 'doublers'; protected $doublersCacheKey = 'doublers';
protected $auditionCacheService; protected $auditionCacheService;
protected $tabulationService; protected $tabulationService;
protected $seatingService; protected $seatingService;
/** /**
* Create a new class instance. * Create a new class instance.
*/ */
@ -24,12 +28,11 @@ class DoublerService
/** /**
* Returns a collection of students that have more than one entry * Returns a collection of students that have more than one entry
* @return \Illuminate\Database\Eloquent\Collection
*/ */
public function getDoublers(): \Illuminate\Database\Eloquent\Collection public function getDoublers(): \Illuminate\Database\Eloquent\Collection
{ {
// TODO creating or destroying an entry should refresh the doubler cache // TODO creating or destroying an entry should refresh the doubler cache
return Cache::remember($this->doublersCacheKey, 3600, function () { return Cache::remember($this->doublersCacheKey, 60, function () {
return Student::withCount('entries') return Student::withCount('entries')
->with('entries') ->with('entries')
->havingRaw('entries_count > ?', [1]) ->havingRaw('entries_count > ?', [1])
@ -37,6 +40,12 @@ class DoublerService
}); });
} }
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 * Returns an array of information about each entry for a specific doubler. Info for each entry includes
* auditionID * auditionID
@ -45,25 +54,45 @@ class DoublerService
* unscored => How many entries remain to be scored in this audition * unscored => How many entries remain to be scored in this audition
* *
* @param int $studentId The ID of the doubler * @param int $studentId The ID of the doubler
* @return array
*/ */
public function getDoublerInfo($studentId): array 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 // When getting a doubler we need to know
// 1) What their entries are // 1) What their entries are
// 2) For each audition they're entered in, what is their rank // 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 // 3) For each audition they're entered in, how many entries are unscored
// 4) How many are accepted on that instrument // 4) How many are accepted on that instrument
$doubler = $this->getDoublers()->firstWhere('id',$studentId); // 5) Status - accepted, declined or undecided
$info = []; $info = [];
foreach ($doubler->entries as $entry) { foreach ($doubler->entries as $entry) {
if ($entry->hasFlag('declined')) {
$status = 'declined';
} elseif ($entry === $acceptedEntry) {
$status = 'accepted';
} else {
$status = 'undecided';
}
$info[$entry->id] = [ $info[$entry->id] = [
'auditionID' => $entry->audition_id, 'auditionID' => $entry->audition_id,
'auditionName' => $this->auditionCacheService->getAudition($entry->audition_id)->name, 'auditionName' => $this->auditionCacheService->getAudition($entry->audition_id)->name,
'rank' => $this->tabulationService->entryRank($entry), 'rank' => $this->tabulationService->entryRank($entry),
'unscored' => $this->tabulationService->remainingEntriesForAudition($entry->audition_id), 'unscored' => $this->tabulationService->remainingEntriesForAudition($entry->audition_id),
'limits' => $this->seatingService->getLimitForAudition($entry->audition_id), 'limits' => $this->seatingService->getLimitForAudition($entry->audition_id),
'status' => $status,
]; ];
$entry->audition = $this->auditionCacheService->getAudition($entry->audition_id); $entry->audition = $this->auditionCacheService->getAudition($entry->audition_id);
} }
@ -71,7 +100,6 @@ class DoublerService
return $info; return $info;
} }
/** /**
* Checks if a student is a doubler based on the given student ID * Checks if a student is a doubler based on the given student ID
* *

View File

@ -0,0 +1,31 @@
<?php
use App\Models\Entry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('entry_flags', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Entry::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
$table->string('flag_name');
$table->unique(['entry_id', 'flag_name']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('entry_flags');
}
};

View File

@ -8,7 +8,7 @@
<div class="flex items-start gap-x-3"> <div class="flex items-start gap-x-3">
<p class="text-sm font-semibold leading-6 text-gray-900"> <p class="text-sm font-semibold leading-6 text-gray-900">
<a href="/tabulation/auditions/{{ $info['auditionID'] }}"> <a href="/tabulation/auditions/{{ $info['auditionID'] }}">
{{ $info['auditionName'] }} {{ $info['auditionName'] }} - {{ $info['status'] }}
</a> </a>
</p> </p>