diff --git a/app/Http/Controllers/Tabulation/DoublerDecisionController.php b/app/Http/Controllers/Tabulation/DoublerDecisionController.php index 4eb0b66..76d4308 100644 --- a/app/Http/Controllers/Tabulation/DoublerDecisionController.php +++ b/app/Http/Controllers/Tabulation/DoublerDecisionController.php @@ -49,10 +49,8 @@ class DoublerDecisionController extends Controller if ($entry->hasFlag('declined')) { return redirect()->back()->with('caution', 'Entry is already declined'); } - EntryFlag::create([ - 'entry_id' => $entry->id, - 'flag_name' => 'declined', - ]); + + $entry->addFlag('declined'); $this->doublerService->refreshDoublerCache(); diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php index 6919770..e157deb 100644 --- a/app/Http/Controllers/Tabulation/TabulationController.php +++ b/app/Http/Controllers/Tabulation/TabulationController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Tabulation; use App\Http\Controllers\Controller; use App\Models\Audition; +use App\Models\Seat; use App\Services\DoublerService; use App\Services\SeatingService; use App\Services\TabulationService; @@ -69,4 +70,32 @@ class TabulationController extends Controller 'seatableEntries', 'requestedEnsembleAccepts')); } + + 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); + return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]); + } + + public function unpublishSeats(Request $request, Audition $audition) + { + // TODO move this to SeatingService + $this->seatingService->forgetSeatsForAudition($audition->id); + Seat::where('audition_id', $audition->id)->delete(); + $audition->removeFlag('seats_published'); + + return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]); + } } diff --git a/app/Models/Audition.php b/app/Models/Audition.php index 4e873da..d02e0bf 100644 --- a/app/Models/Audition.php +++ b/app/Models/Audition.php @@ -158,4 +158,30 @@ class Audition extends Model return $this->attributes['judges_count']; } + + public function flags(): HasMany + { + return $this->hasMany(AuditionFlag::class); + } + + public function hasFlag($flag): bool + { + // 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); + } + + public function addFlag($flag) + { + if ($this->hasFlag($flag)) { + return; + } + + $this->flags()->create(['flag_name' => $flag]); + } + + public function removeFlag($flag) + { + // remove related auditionFlag where flag_name = $flag + $this->flags()->where('flag_name', $flag)->delete(); + } } diff --git a/app/Models/AuditionFlag.php b/app/Models/AuditionFlag.php new file mode 100644 index 0000000..7b11fba --- /dev/null +++ b/app/Models/AuditionFlag.php @@ -0,0 +1,17 @@ +belongsTo(Audition::class); + } +} diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 1a21fc3..d63a7c2 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -56,13 +56,28 @@ class Entry extends Model return $this->hasMany(EntryFlag::class); } - public function hasFlag($flag) + public function hasFlag($flag): bool { // 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); } + public function addFlag($flag) + { + if ($this->hasFlag($flag)) { + return; + } + + $this->flags()->create(['flag_name' => $flag]); + } + + public function removeFlag($flag) + { + // remove related auditionFlag where flag_name = $flag + $this->flags()->where('flag_name', $flag)->delete(); + } + /** * Ensures score_sheets_count property is always available */ diff --git a/app/Models/Seat.php b/app/Models/Seat.php index 19d2440..315d251 100644 --- a/app/Models/Seat.php +++ b/app/Models/Seat.php @@ -4,8 +4,39 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasOneThrough; class Seat extends Model { use HasFactory; + + protected $guarded = []; + + public function ensemble(): BelongsTo + { + return $this->belongsTo(Ensemble::class); + } + + public function audition(): BelongsTo + { + return $this->belongsTo(Audition::class); + } + + public function entry(): BelongsTo + { + return $this->belongsTo(Entry::class); + } + + public function student(): HasOneThrough + { + return $this->hasOneThrough( + Student::class, + Entry::class, + 'id', + 'id', + 'entry_id', + 'student_id' + ); + } } diff --git a/app/Services/SeatingService.php b/app/Services/SeatingService.php index 4c613a1..7f3c0ed 100644 --- a/app/Services/SeatingService.php +++ b/app/Services/SeatingService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Models\Seat; use App\Models\SeatingLimit; use Illuminate\Support\Facades\Cache; @@ -38,7 +39,7 @@ class SeatingService return $this->getAcceptanceLimits()[$auditionId]; } - public function refershLimits() + public function refreshLimits(): void { Cache::forget($this->limitsCacheKey); } @@ -46,8 +47,24 @@ class SeatingService 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'; + + return Cache::remember($cacheKey, now()->addHour(), function () use ($auditionId) { + return Seat::with('entry.student')->where('audition_id', $auditionId)->orderBy('seat')->get()->groupBy('ensemble_id'); + }); + } + + public function forgetSeatsForAudition($auditionId) + { + $cacheKey = 'audition'.$auditionId.'seats'; + Cache::forget($cacheKey); + } } diff --git a/database/migrations/2024_06_23_205912_create_audition_flags_table.php b/database/migrations/2024_06_23_205912_create_audition_flags_table.php new file mode 100644 index 0000000..c7e7ef5 --- /dev/null +++ b/database/migrations/2024_06_23_205912_create_audition_flags_table.php @@ -0,0 +1,30 @@ +\id(); + $table->foreignIdFor(Audition::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate(); + $table->string('flag_name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('audition_flags'); + } +}; diff --git a/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php b/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php index 2084e17..e312c13 100644 --- a/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php +++ b/resources/views/tabulation/auditionSeating-show-proposed-seats.blade.php @@ -1,3 +1,7 @@ +@php + $seatingProposal = []; +@endphp + @foreach($ensembleLimits as $ensembleLimit) {{ $ensembleLimit->ensemble->name }} - DRAFT @@ -9,6 +13,12 @@ @php $entry = $seatableEntries->shift(); if (is_null($entry)) continue; + $seatingProposal[] = [ + 'ensemble_id' => $ensembleLimit->ensemble->id, + 'audition_id' => $audition->id, + 'seat' => $n, + 'entry_id' => $entry->id, + ]; @endphp @@ -18,3 +28,10 @@ @endforeach +
+ @csrf +Seat and Publish +
+@php + session(['audition' . $audition->id . 'seatingProposal' => $seatingProposal]); +@endphp diff --git a/resources/views/tabulation/auditionSeating-show-published-seats.blade.php b/resources/views/tabulation/auditionSeating-show-published-seats.blade.php new file mode 100644 index 0000000..7eed45f --- /dev/null +++ b/resources/views/tabulation/auditionSeating-show-published-seats.blade.php @@ -0,0 +1,31 @@ +@inject('seatingService','App\Services\SeatingService') + + + + Seats are Published + + + + + Unpublish + + + + + +@foreach($ensembleLimits as $ensembleLimit) + @php + $ensembleSeats = $seatingService->getSeatsForAudition($audition->id)[$ensembleLimit->ensemble->id]; + @endphp + + {{ $ensembleLimit->ensemble->name }} + @foreach($ensembleSeats as $seat) + + {{ $seat->seat }} - {{ $seat->student->full_name() }} + + @endforeach + + +@endforeach diff --git a/resources/views/tabulation/auditionSeating.blade.php b/resources/views/tabulation/auditionSeating.blade.php index 7e9af3f..9e54b53 100644 --- a/resources/views/tabulation/auditionSeating.blade.php +++ b/resources/views/tabulation/auditionSeating.blade.php @@ -10,7 +10,9 @@ @include('tabulation.auditionSeating-results-table')
- @if(! $auditionComplete) + @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') diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php index 8107905..f42f2f0 100644 --- a/resources/views/test.blade.php +++ b/resources/views/test.blade.php @@ -16,7 +16,8 @@ Test Page @php - dump($seatingService->getLimitForAudition(47)); + $audition = Audition::find(2); + $audition->removeFlag('testFlag'); @endphp diff --git a/routes/web.php b/routes/web.php index 31f6b93..a6c5fa1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -45,6 +45,8 @@ Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function () { Route::get('/status', 'status'); Route::match(['get', 'post'], '/auditions/{audition}', 'auditionSeating')->name('tabulation.audition.seat'); + Route::post('/auditions/{audition}/publish-seats', 'publishSeats')->name('tabulation.seat.publish'); + Route::post('/auditions/{audition}/unpublish-seats', 'unpublishSeats')->name('tabulation.seat.unpublish'); }); // Doubler decision routes