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)