Scobda nomination ensembles #106

Merged
okorpheus merged 25 commits from scobda_first_year into master 2025-02-12 21:51:10 +00:00
13 changed files with 322 additions and 2 deletions
Showing only changes of commit 997a6cf8b1 - Show all commits

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\NominationEnsembles;
use App\Models\NominationEnsembleEntry;
interface NominationEnsembleEntryController
{
public function index();
public function show(NominationEnsembleEntry $ensemble);
public function create();
public function store();
public function edit(NominationEnsembleEntry $ensemble);
public function update(NominationEnsembleEntry $ensemble);
public function destroy(NominationEnsembleEntry $ensemble);
}

View File

@ -14,7 +14,7 @@ class ScobdaNominationEnsembleController extends Controller implements Nominatio
{
$ensembles = NominationEnsemble::all();
return view('nomination_ensembles.scobda.index', compact('ensembles'));
return view('nomination_ensembles.scobda.admin.ensembles.index', compact('ensembles'));
}
public function show(NominationEnsemble $ensemble)

View File

@ -0,0 +1,155 @@
<?php
namespace App\Http\Controllers\NominationEnsembles;
use App\Http\Controllers\Controller;
use App\Models\NominationEnsemble;
use App\Models\NominationEnsembleEntry;
use App\Models\School;
use App\Models\Student;
class ScobdaNominationEnsembleEntryController extends Controller implements NominationEnsembleEntryController
{
public function index()
{
$ensembles = NominationEnsemble::all();
// populate an array with each ensemble id as a key. Each item will be a collection of students available to be nominated
$availableStudents = [];
// populate an array with each ensemble id as a key. Each item will be a collection of available instruments
$availableInstruments = [];
// populate an array with each ensemble id as a key. Each item will be a collection of nominationEntries already made
$nominatedStudents = [];
// an array of bool values with each ensemble id as a key. It will be true if additional nominations are available
$nominationsAvailable = [];
foreach ($ensembles as $ensemble) {
// Gather a collection of students who may be nominated for this ensemble
$availableStudents[$ensemble->id] = Student::where('grade', '<=', $ensemble->maximum_grade)
->where('grade', '>=', $ensemble->minimum_grade)
->where('school_id', auth()->user()->school_id)
->orderBy('last_name')
->orderBy('first_name')
->get();
$availableInstruments[$ensemble->id] = $ensemble->data['instruments'];
$nominatedStudents[$ensemble->id] = $this->collapseNominations(auth()->user()->school, $ensemble,
'nominations');
$nominatedStudentIds = [];
// Removed students already nominated from available students
foreach ($nominatedStudents[$ensemble->id] as $nominatedStudent) {
$nominatedStudentIds[] = $nominatedStudent->student_id;
}
$availableStudents[$ensemble->id] = $availableStudents[$ensemble->id]->reject(function ($student) use (
$nominatedStudentIds
) {
return in_array($student->id, $nominatedStudentIds);
});
$nominationsAvailable[$ensemble->id] = $ensemble->data['max_nominations'] > count($nominatedStudents[$ensemble->id]);
}
return view('nomination_ensembles.scobda.entries.index',
compact('ensembles', 'availableStudents', 'availableInstruments', 'nominatedStudents', 'nominationsAvailable'));
}
public function show(NominationEnsembleEntry $ensemble)
{
// TODO: Implement show() method.
}
public function create()
{
// TODO: Implement create() method.
}
public function store()
{
$validData = request()->validate([
'ensemble' => [
'required',
'exists:App\Models\NominationEnsemble,id',
],
'new_student' => [
'required',
'exists:App\Models\Student,id',
],
'new_instrument' => 'required',
]);
if (NominationEnsembleEntry::where('student_id', $validData['new_student'])
->where('nomination_ensemble_id', $validData['ensemble'])
->count() > 0) {
return redirect()->route('nomination.entry.index')->with('error',
'Student already nominated for that ensemble');
}
$proposedEnsemble = NominationEnsemble::find($validData['ensemble']);
if (! in_array($validData['new_instrument'], $proposedEnsemble->data['instruments'])) {
return redirect()->route('nomination.entry.index')->with('error',
'Invalid Instrument specified');
}
$student = Student::find($validData['new_student']);
$nextRank = $this->collapseNominations($student->school, $proposedEnsemble, 'next');
if ($nextRank > $proposedEnsemble->data['max_nominations']) {
return redirect()->route('nomination.entry.index')->with('error',
'You have already used all of your nominations');
}
$entry = new NominationEnsembleEntry();
$entry->student_id = $validData['new_student'];
$entry->nomination_ensemble_id = $validData['ensemble'];
$data = [];
$data['rank'] = $nextRank;
$data['instrument'] = $validData['new_instrument'];
$entry->data = $data;
$entry->save();
return redirect()->route('nomination.entry.index')->with('success',
'Nomination Recorded');
}
public function edit(NominationEnsembleEntry $ensemble)
{
// TODO: Implement edit() method.
}
public function update(NominationEnsembleEntry $ensemble)
{
// TODO: Implement update() method.
}
public function destroy(NominationEnsembleEntry $ensemble)
{
// TODO: Implement destroy() method.
}
/**
* Given a school and nomination ensemble, consolidate the rank valuek
*
* if returnType is next, the next available rank will be returned
* if returnType is nominations, a collection of nominations will be returned
*
* @return int|array
*
* @var returnType = next|nominations
*/
private function collapseNominations(School $school, NominationEnsemble $ensemble, $returnType)
{
$nominations = $school->nominations()->get()->where('nomination_ensemble_id',
$ensemble->id)->sortBy('data.rank');
$n = 1;
foreach ($nominations as $nomination) {
$nomination->update(['data->rank' => $n]);
$n++;
}
if ($returnType == 'next') {
return $n;
}
return $nominations;
}
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class NominationEnsemble extends Model
{
@ -15,4 +16,9 @@ class NominationEnsemble extends Model
'data' => 'array',
];
}
public function entries(): HasMany
{
return $this->hasMany(NominationEnsembleEntry::class);
}
}

View File

@ -4,8 +4,28 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class NominationEnsembleEntry extends Model
{
use HasFactory;
protected $guarded = [];
protected function casts(): array
{
return [
'data' => 'array',
];
}
protected function ensemble(): BelongsTo
{
return $this->belongsTo(NominationEnsemble::class);
}
protected function student(): BelongsTo
{
return $this->belongsTo(Student::class);
}
}

View File

@ -51,4 +51,15 @@ class School extends Model
'id',
'id');
}
public function nominations(): HasManyThrough
{
return $this->hasManyThrough(
NominationEnsembleEntry::class,
Student::class,
'school_id',
'student_id',
'id',
'id');
}
}

View File

@ -35,6 +35,11 @@ class Student extends Model
];
}
public function nominations(): HasMany
{
return $this->hasMany(NominationEnsembleEntry::class);
}
public function school(): BelongsTo
{
return $this->belongsTo(School::class);

View File

@ -11,7 +11,9 @@ use App\Actions\Tabulation\CalculateScoreSheetTotal;
use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByTotalWeights;
use App\Actions\Tabulation\CalculateScoreSheetTotalDivideByWeightedPossible;
use App\Http\Controllers\NominationEnsembles\NominationEnsembleController;
use App\Http\Controllers\NominationEnsembles\NominationEnsembleEntryController;
use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleController;
use App\Http\Controllers\NominationEnsembles\ScobdaNominationEnsembleEntryController;
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Room;
@ -63,7 +65,10 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton(CreateEntry::class, CreateEntry::class);
$this->app->singleton(UpdateEntry::class, UpdateEntry::class);
$this->app->singleton(SetHeadDirector::class, SetHeadDirector::class);
// Nomination Ensemble
$this->app->bind(NominationEnsembleController::class, ScobdaNominationEnsembleController::class);
$this->app->bind(NominationEnsembleEntryController::class, ScobdaNominationEnsembleEntryController::class);
}
/**

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Factories;
use App\Models\NominationEnsembleEntry;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
class NominationEnsembleEntryFactory extends Factory
{
protected $model = NominationEnsembleEntry::class;
public function definition(): array
{
return [
'student_id' => $this->faker->randomNumber(),
'nomination_ensemble_id' => $this->faker->randomNumber(),
'data' => $this->faker->words(),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
];
}
}

View File

@ -29,6 +29,9 @@
@if(Auth::user()->school_id)
<a href="{{route('students.index')}}" class="block p-2 hover:text-indigo-600">My Students</a>
<a href="{{route('entries.index')}}" class="block p-2 hover:text-indigo-600">My Entries</a>
@if(auditionSetting('nomination_ensemble_rules') !== 'disabled')
<x-layout.navbar.menus.menu-item :href="route('nomination.entry.index')">My Nominations</x-layout.navbar.menus.menu-item>
@endif
<a href="{{route('doubler_request.index')}}" class="block p-2 hover:text-indigo-600">My Doubler Requests</a>
<a href="{{route('my_school')}}" class="block p-2 hover:text-indigo-600">My School</a>
@if(auditionSetting('invoicing_enabled'))

View File

@ -0,0 +1,66 @@
@php($n=1)
<x-layout.app>
<x-slot:page_title>Nomination Entries</x-slot:page_title>
<x-layout.page-section-container>
@foreach($ensembles as $ensemble)
<x-layout.page-section>
<x-slot:section_name>{{ $ensemble->name }}</x-slot:section_name>
<x-slot:section_description>{{ $ensemble->data['max_nominations'] }} nominations accepted</x-slot:section_description>
<x-table.table>
<thead>
<tr>
<x-table.th>Rank</x-table.th>
<x-table.th>Student</x-table.th>
<x-table.th>Instrument</x-table.th>
</tr>
</thead>
<x-table.body>
@foreach($nominatedStudents[$ensemble->id] as $nomination)
<tr>
<x-table.td>{{ $nomination->data['rank'] }}</x-table.td>
<x-table.td>{{ $nomination->student->full_name() }}</x-table.td>
<x-table.td>{{ $nomination->data['instrument'] }}</x-table.td>
</tr>
@endforeach
{{-- LINE TO ADD A NOMINATION--}}
@if($nominationsAvailable[$ensemble->id] && $availableStudents[$ensemble->id]->count() > 0)
<tr>
<x-form.form method="POST" action="{{ route('nomination.entry.store') }}">
<input type="hidden" name="ensemble" value="{{ $ensemble->id }}"/>
<x-table.th>NEW</x-table.th>
<x-table.td>
<x-form.select name="new_student">
@foreach($availableStudents[$ensemble->id] as $student)
<option value="{{$student->id}}">{{ $student->full_name() }}
(Grade {{ $student->grade }})
</option>
@endforeach
</x-form.select>
</x-table.td>
<x-table.td>
<x-form.select name="new_instrument">
@foreach($availableInstruments[$ensemble->id] as $instrument)
<option value="{{$instrument}}">{{$instrument}}</option>
@endforeach
</x-form.select>
</x-table.td>
<x-table.td>
<x-form.button class="bg-green-800">Add</x-form.button>
</x-table.td>
</x-form.form>
</tr>
@endif
</x-table.body>
</x-table.table>
</x-layout.page-section>
@endforeach
</x-layout.page-section-container>
</x-layout.app>

View File

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\NominationEnsembles\NominationEnsembleController;
use App\Http\Controllers\NominationEnsembles\NominationEnsembleEntryController;
use App\Http\Middleware\CheckIfAdmin;
use Illuminate\Support\Facades\Route;
@ -14,5 +15,8 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('nomination
});
Route::middleware(['auth', 'verified'])->prefix('nominations/')->group(function () {
Route::controller(NominationEnsembleEntryController::class)->group(function () {
Route::get('/', 'index')->name('nomination.entry.index');
Route::post('/', 'store')->name('nomination.entry.store');
});
});