diff --git a/app/Actions/Tabulation/AllowForOlympicScoring.php b/app/Actions/Tabulation/AllowForOlympicScoring.php index a5346cf..82bfecc 100644 --- a/app/Actions/Tabulation/AllowForOlympicScoring.php +++ b/app/Actions/Tabulation/AllowForOlympicScoring.php @@ -5,20 +5,27 @@ namespace App\Actions\Tabulation; use App\Exceptions\TabulationException; +use App\Models\BonusScore; use App\Models\Entry; use App\Services\AuditionService; use App\Services\EntryService; use Illuminate\Support\Facades\Cache; + use function auditionSetting; class AllowForOlympicScoring implements CalculateEntryScore { protected CalculateScoreSheetTotal $calculator; + protected AuditionService $auditionService; + protected EntryService $entryService; - public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService, EntryService $entryService) - { + public function __construct( + CalculateScoreSheetTotal $calculator, + AuditionService $auditionService, + EntryService $entryService + ) { $this->calculator = $calculator; $this->auditionService = $auditionService; $this->entryService = $entryService; @@ -28,6 +35,7 @@ class AllowForOlympicScoring implements CalculateEntryScore { $cacheKey = 'entryScore-'.$entry->id.'-'.$mode; + return Cache::remember($cacheKey, 10, function () use ($mode, $entry) { $this->basicValidation($mode, $entry); $this->areAllJudgesIn($entry); @@ -38,7 +46,7 @@ class AllowForOlympicScoring implements CalculateEntryScore } - protected function getJudgeTotals($mode, Entry $entry) + protected function getJudgeTotals($mode, Entry $entry): array { $scores = []; @@ -55,7 +63,7 @@ class AllowForOlympicScoring implements CalculateEntryScore // remove the highest and lowest scores array_pop($scores); array_shift($scores); - } + } $sums = []; // Sum each subscore from the judges foreach ($scores as $score) { @@ -66,9 +74,35 @@ class AllowForOlympicScoring implements CalculateEntryScore $index++; } } + // add the bonus points for a seating mode + if ($mode === 'seating' && $sums) { + + $sums[0] += $this->getBonusPoints($entry); + } + return $sums; } + protected function getBonusPoints(Entry $entry) + { + + $bonusScoreDefinition = $entry->audition->bonusScore()->first(); + if (! $bonusScoreDefinition) { + return 0; + } + /** @noinspection PhpPossiblePolymorphicInvocationInspection */ + $bonusJudges = $bonusScoreDefinition->judges; + $bonusScoreSheets = BonusScore::where('entry_id', $entry->id)->get(); + foreach ($bonusScoreSheets as $sheet) { + if (! $bonusJudges->contains($sheet->user_id)) { + throw new TabulationException('Entry has a bonus score from unassigned judge'); + } + } + + // sum the score property of the $bonusScoreSheets + return $bonusScoreSheets->sum('score'); + } + protected function basicValidation($mode, $entry): void { if ($mode !== 'seating' && $mode !== 'advancement') { diff --git a/app/Actions/Tabulation/EnterBonusScore.php b/app/Actions/Tabulation/EnterBonusScore.php new file mode 100644 index 0000000..2706f34 --- /dev/null +++ b/app/Actions/Tabulation/EnterBonusScore.php @@ -0,0 +1,79 @@ +basicValidations($judge, $entry); + $this->validateJudgeValidity($judge, $entry, $score); + $entries = $getRelatedEntries($entry); + + // Create the score for each related entry + foreach ($entries as $relatedEntry) { + BonusScore::create([ + 'entry_id' => $relatedEntry->id, + 'user_id' => $judge->id, + 'originally_scored_entry' => $entry->id, + 'score' => $score, + ]); + } + + } + + protected function getRelatedEntries(Entry $entry): Collection + { + $bonusScore = $entry->audition->bonusScore->first(); + $relatedAuditions = $bonusScore->auditions; + + // Get all entries that have a student_id equal to that of entry and an audition_id in the related auditions + return Entry::where('student_id', $entry->student_id) + ->whereIn('audition_id', $relatedAuditions->pluck('id')) + ->get(); + } + + protected function basicValidations(User $judge, Entry $entry): void + { + if (! $judge->exists) { + throw new ScoreEntryException('Invalid judge provided'); + } + if (! $entry->exists) { + throw new ScoreEntryException('Invalid entry provided'); + } + if ($entry->audition->bonusScore->count() === 0) { + throw new ScoreEntryException('Entry does not have a bonus score'); + } + + } + + protected function validateJudgeValidity(User $judge, Entry $entry, $score): void + { + if (BonusScore::where('entry_id', $entry->id)->where('user_id', $judge->id)->exists()) { + throw new ScoreEntryException('That judge has already scored that entry'); + } + + $bonusScore = $entry->audition->bonusScore->first(); + if (! $bonusScore->judges->contains($judge)) { + throw new ScoreEntryException('That judge is not assigned to judge that bonus score'); + } + if ($score > $bonusScore->max_score) { + throw new ScoreEntryException('That score exceeds the maximum'); + } + } +} diff --git a/app/Actions/Tabulation/GetBonusScoreRelatedEntries.php b/app/Actions/Tabulation/GetBonusScoreRelatedEntries.php new file mode 100644 index 0000000..93fbbb1 --- /dev/null +++ b/app/Actions/Tabulation/GetBonusScoreRelatedEntries.php @@ -0,0 +1,29 @@ +getRelatedEntries($entry); + } + + public function getRelatedEntries(Entry $entry): Collection + { + $bonusScore = $entry->audition->bonusScore->first(); + $relatedAuditions = $bonusScore->auditions; + + // Get all entries that have a student_id equal to that of entry and an audition_id in the related auditions + return Entry::where('student_id', $entry->student_id) + ->whereIn('audition_id', $relatedAuditions->pluck('id')) + ->get(); + } +} diff --git a/app/Http/Controllers/Admin/BonusScoreDefinitionController.php b/app/Http/Controllers/Admin/BonusScoreDefinitionController.php new file mode 100644 index 0000000..73ad346 --- /dev/null +++ b/app/Http/Controllers/Admin/BonusScoreDefinitionController.php @@ -0,0 +1,117 @@ +get(); + // Set auditions equal to the collection of auditions that do not have a related bonus score + $unassignedAuditions = Audition::orderBy('score_order')->doesntHave('bonusScore')->get(); + + return view('admin.bonus-scores.index', compact('bonusScores', 'unassignedAuditions')); + } + + public function store() + { + $validData = request()->validate([ + 'name' => 'required', + 'max_score' => 'required|numeric', + 'weight' => 'required|numeric', + ]); + + BonusScoreDefinition::create($validData); + + return to_route('admin.bonus-scores.index')->with('success', 'Bonus Score Created'); + } + + public function destroy(BonusScoreDefinition $bonusScore) + { + if ($bonusScore->auditions()->count() > 0) { + return to_route('admin.bonus-scores.index')->with('error', 'Bonus Score has auditions attached'); + } + $bonusScore->delete(); + + return to_route('admin.bonus-scores.index')->with('success', 'Bonus Score Deleted'); + } + + public function assignAuditions(Request $request) + { + $validData = $request->validate([ + 'bonus_score_id' => 'required|exists:bonus_score_definitions,id', + 'audition' => 'required|array', + 'audition.*' => ['required', new ValidateAuditionKey()], + ]); + $bonusScore = BonusScoreDefinition::find($validData['bonus_score_id']); + + foreach ($validData['audition'] as $auditionId => $value) { + try { + $bonusScore->auditions()->attach($auditionId); + } catch (Exception) { + return redirect()->route('admin.bonus-scores.index')->with('error', + 'Error assigning auditions to bonus score'); + } + } + + return redirect()->route('admin.bonus-scores.index')->with('success', 'Auditions assigned to bonus score'); + } + + public function unassignAudition(Audition $audition) + { + if (! $audition->exists()) { + return redirect()->route('admin.bonus-scores.index')->with('error', 'Audition not found'); + } + if (! $audition->bonusScore()->count() > 0) { + return redirect()->route('admin.bonus-scores.index')->with('error', 'Audition does not have a bonus score'); + } + $audition->bonusScore()->detach(); + + return redirect()->route('admin.bonus-scores.index')->with('success', 'Audition unassigned from bonus score'); + } + + public function judges() + { + $bonusScores = BonusScoreDefinition::all(); + $users = User::orderBy('last_name')->orderBy('first_name')->get(); + + return view('admin.bonus-scores.judge-assignments', compact('bonusScores', 'users')); + } + + public function assignJudge(BonusScoreDefinition $bonusScore) + { + if (! $bonusScore->exists()) { + return redirect()->route('admin.bonus-scores.judges')->with('error', 'Bonus Score not found'); + } + $validData = request()->validate([ + 'judge' => 'required|exists:users,id', + ]); + $bonusScore->judges()->attach($validData['judge']); + + return redirect()->route('admin.bonus-scores.judges')->with('success', 'Judge assigned to bonus score'); + } + + public function removeJudge(BonusScoreDefinition $bonusScore) + { + if (! $bonusScore->exists()) { + return redirect()->route('admin.bonus-scores.judges')->with('error', 'Bonus Score not found'); + } + $validData = request()->validate([ + 'judge' => 'required|exists:users,id', + ]); + $bonusScore->judges()->detach($validData['judge']); + + return redirect()->route('admin.bonus-scores.judges')->with('success', 'Judge removed from bonus score'); + } +} diff --git a/app/Http/Controllers/Admin/RoomController.php b/app/Http/Controllers/Admin/RoomController.php index 0b64b1f..6d301d0 100644 --- a/app/Http/Controllers/Admin/RoomController.php +++ b/app/Http/Controllers/Admin/RoomController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Models\BonusScoreDefinition; use App\Models\Room; use App\Models\User; use Illuminate\Http\Request; @@ -17,7 +18,7 @@ class RoomController extends Controller if (! Auth::user()->is_admin) { abort(403); } - $rooms = Room::with('auditions.entries','entries')->orderBy('name')->get(); + $rooms = Room::with('auditions.entries', 'entries')->orderBy('name')->get(); return view('admin.rooms.index', ['rooms' => $rooms]); } @@ -27,8 +28,9 @@ class RoomController extends Controller $usersWithoutRooms = User::doesntHave('rooms')->orderBy('last_name')->orderBy('first_name')->get(); $usersWithRooms = User::has('rooms')->orderBy('last_name')->orderBy('first_name')->get(); $rooms = Room::with(['judges.school', 'auditions'])->get(); + $bonusScoresExist = BonusScoreDefinition::count() > 0; - return view('admin.rooms.judge_assignments', compact('usersWithoutRooms', 'usersWithRooms', 'rooms')); + return view('admin.rooms.judge_assignments', compact('usersWithoutRooms', 'usersWithRooms', 'rooms', 'bonusScoresExist')); } public function updateJudgeAssignment(Request $request, Room $room) diff --git a/app/Http/Controllers/Judging/BonusScoreEntryController.php b/app/Http/Controllers/Judging/BonusScoreEntryController.php new file mode 100644 index 0000000..447d160 --- /dev/null +++ b/app/Http/Controllers/Judging/BonusScoreEntryController.php @@ -0,0 +1,31 @@ +id)->where('user_id', Auth::user()->id)->exists()) { + return redirect()->route('judging.bonusScore.EntryList', $entry->audition)->with('error', 'You have already judged that entry'); + } + /** @var BonusScoreDefinition $bonusScore */ + $bonusScore = $entry->audition->bonusScore()->first(); + if (! $bonusScore->judges->contains(auth()->id())) { + return redirect()->route('judging.index')->with('error', 'You are not assigned to judge this entry'); + } + $maxScore = $bonusScore->max_score; + $bonusName = $bonusScore->name; + + return view('judging.bonus_entry_score_sheet', compact('entry', 'maxScore', 'bonusName')); + + } +} diff --git a/app/Http/Controllers/Judging/BonusScoreEntryListController.php b/app/Http/Controllers/Judging/BonusScoreEntryListController.php new file mode 100644 index 0000000..ae5f30c --- /dev/null +++ b/app/Http/Controllers/Judging/BonusScoreEntryListController.php @@ -0,0 +1,32 @@ +bonusScore()->first(); + if (! $bonusScore->judges->contains(auth()->id())) { + return redirect()->route('dashboard')->with('error', 'You are not assigned to judge this bonus score'); + } + $entries = $audition->entries()->orderBy('draw_number')->get(); + $entries = $entries->reject(fn ($entry) => $entry->hasFlag('no_show')); + $entries->each(fn ($entry) => $entry->audition = $audition); + + $scores = BonusScore::where('user_id', Auth::user()->id) + ->with('entry.audition') + ->with('originallyScoredEntry.audition') + ->get() + ->keyBy('entry_id'); + + return view('judging.bonus_score_entry_list', compact('audition', 'entries', 'scores')); + } +} diff --git a/app/Http/Controllers/Judging/BonusScoreRecordController.php b/app/Http/Controllers/Judging/BonusScoreRecordController.php new file mode 100644 index 0000000..911fe3d --- /dev/null +++ b/app/Http/Controllers/Judging/BonusScoreRecordController.php @@ -0,0 +1,28 @@ +validate([ + 'score' => 'required|integer', + ]); + try { + $enterBonusScore(Auth::user(), $entry, $validData['score']); + } catch (ScoreEntryException $ex) { + return redirect()->back()->with('error', 'Score Entry Error - '.$ex->getMessage()); + } + + return redirect()->route('judging.bonusScore.EntryList', $entry->audition)->with('Score Recorded Successfully'); + } +} diff --git a/app/Http/Controllers/JudgingController.php b/app/Http/Controllers/Judging/JudgingController.php similarity index 94% rename from app/Http/Controllers/JudgingController.php rename to app/Http/Controllers/Judging/JudgingController.php index 292617e..84f6fab 100644 --- a/app/Http/Controllers/JudgingController.php +++ b/app/Http/Controllers/Judging/JudgingController.php @@ -1,10 +1,11 @@ judgingAssignments; - $rooms->load('auditions'); + $rooms = Auth::user()->judgingAssignments()->with('auditions')->get(); + $bonusScoresToJudge = Auth::user()->bonusJudgingAssignments()->with('auditions')->get(); - return view('judging.index', compact('rooms')); + //$rooms->load('auditions'); + return view('judging.index', compact('rooms', 'bonusScoresToJudge')); } public function auditionEntryList(Request $request, Audition $audition) diff --git a/app/Http/Controllers/Tabulation/BonusScoreController.php b/app/Http/Controllers/Tabulation/BonusScoreController.php new file mode 100644 index 0000000..57cfbaa --- /dev/null +++ b/app/Http/Controllers/Tabulation/BonusScoreController.php @@ -0,0 +1,95 @@ +validate([ + 'entry_id' => 'required|exists:entries,id', + ]); + $entry = Entry::find($validData['entry_id']); + $bonusScoreDefinition = $entry->audition->bonusScore->first(); + $assignedJudges = $bonusScoreDefinition->judges; + $relatedEntries = $getRelatedEntries($entry); + $existingScores = []; + foreach ($relatedEntries as $related) { + $existingScores[$related->id] = BonusScore::where('entry_id', $related->id) + ->with('judge') + ->with('entry') + ->with('originallyScoredEntry') + ->get(); + } + + return view('tabulation.bonus-score-sheet', + compact('entry', 'bonusScoreDefinition', 'assignedJudges', 'existingScores', 'relatedEntries')); + } + + public function saveEntryBonusScoreSheet(Entry $entry, GetBonusScoreRelatedEntries $getRelatedEntries, EnterBonusScore $saveBonusScore) + { + $validData = request()->validate([ + 'judge_id' => 'required|exists:users,id', + 'entry_id' => 'required|exists:entries,id', + 'score' => 'nullable|numeric', + ]); + + $judge = User::find($validData['judge_id']); + $entry = Entry::find($validData['entry_id']); + $relatedEntries = $getRelatedEntries($entry); + try { + DB::beginTransaction(); + + // Delete existing bonus scores for the entries by the judge + foreach ($relatedEntries as $related) { + BonusScore::where('entry_id', $related->id)->where('user_id', $judge->id)->delete(); + } + + // If no score was submitted, were going to just stop at deleting the scores + if (! $validData['score'] == null) { + // Set the new score + try { + $saveBonusScore($judge, $entry, $validData['score']); + } catch (ScoreEntryException $ex) { + DB::rollBack(); + + return redirect()->route('bonus-scores.entryBonusScoreSheet', + ['entry_id' => $entry->id])->with('error', 'Error entering score - '.$ex->getMessage()); + } + } + + DB::commit(); + } catch (\Exception) { + DB::rollBack(); + + return redirect()->route('bonus-scores.entryBonusScoreSheet', ['entry_id' => $entry->id])->with('error', 'Error entering score - '.$ex->getMessage()); + } + + return redirect()->route('bonus-scores.entryBonusScoreSheet', ['entry_id' => $entry->id])->with('success', 'New bonus score entered'); + } + + public function destroyBonusScore() + { + + } +} diff --git a/app/Http/Controllers/Tabulation/EntryFlagController.php b/app/Http/Controllers/Tabulation/EntryFlagController.php index 74b582c..bd0aed0 100644 --- a/app/Http/Controllers/Tabulation/EntryFlagController.php +++ b/app/Http/Controllers/Tabulation/EntryFlagController.php @@ -15,8 +15,9 @@ class EntryFlagController extends Controller { $method = 'GET'; $formRoute = 'entry-flags.confirmNoShow'; + $title = 'No Show'; - return view('tabulation.choose_entry', compact('method', 'formRoute')); + return view('tabulation.choose_entry', compact('method', 'formRoute', 'title')); } public function noShowConfirm(Request $request) diff --git a/app/Http/Controllers/Tabulation/ScoreController.php b/app/Http/Controllers/Tabulation/ScoreController.php index 35b1fda..4620418 100644 --- a/app/Http/Controllers/Tabulation/ScoreController.php +++ b/app/Http/Controllers/Tabulation/ScoreController.php @@ -14,8 +14,9 @@ class ScoreController extends Controller { $method = 'GET'; $formRoute = 'scores.entryScoreSheet'; + $title = 'Enter Scores'; - return view('tabulation.choose_entry', compact('method', 'formRoute')); + return view('tabulation.choose_entry', compact('method', 'formRoute', 'title')); } public function destroyScore(ScoreSheet $score) diff --git a/app/Models/Audition.php b/app/Models/Audition.php index aba117d..e7a243b 100644 --- a/app/Models/Audition.php +++ b/app/Models/Audition.php @@ -48,6 +48,11 @@ class Audition extends Model return $this->belongsTo(ScoringGuide::class); } + public function bonusScore(): BelongsToMany + { + return $this->belongsToMany(BonusScoreDefinition::class, 'bonus_score_audition_assignment'); + } + public function display_fee(): string { return '$'.number_format($this->entry_fee / 100, 2); diff --git a/app/Models/BonusScore.php b/app/Models/BonusScore.php new file mode 100644 index 0000000..0423629 --- /dev/null +++ b/app/Models/BonusScore.php @@ -0,0 +1,26 @@ +belongsTo(Entry::class); + } + + public function judge(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } + + public function originallyScoredEntry(): BelongsTo + { + return $this->belongsTo(Entry::class, 'originally_scored_entry'); + } +} diff --git a/app/Models/BonusScoreDefinition.php b/app/Models/BonusScoreDefinition.php new file mode 100644 index 0000000..8cc82a9 --- /dev/null +++ b/app/Models/BonusScoreDefinition.php @@ -0,0 +1,24 @@ +belongsToMany(Audition::class, 'bonus_score_audition_assignment')->orderBy('score_order'); + } + + public function judges(): BelongsToMany + { + return $this->belongsToMany(User::class, 'bonus_score_judge_assignment'); + } +} diff --git a/app/Models/Entry.php b/app/Models/Entry.php index bd3ad71..1b6eaee 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -7,10 +7,10 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOneThrough; -use Illuminate\Support\Facades\Cache; class Entry extends Model { @@ -55,6 +55,11 @@ class Entry extends Model } + public function bonusScores(): BelongsToMany + { + return $this->belongsToMany(BonusScore::class); + } + public function advancementVotes(): HasMany { return $this->hasMany(JudgeAdvancementVote::class); diff --git a/app/Models/User.php b/app/Models/User.php index eeb74cb..b1c4697 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,7 +11,6 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; -use App\Models\ScoreSheet; class User extends Authenticatable implements MustVerifyEmail { @@ -118,6 +117,11 @@ class User extends Authenticatable implements MustVerifyEmail return $this->rooms(); } + public function bonusJudgingAssignments(): BelongsToMany + { + return $this->belongsToMany(BonusScoreDefinition::class, 'bonus_score_judge_assignment'); + } + public function advancementVotes(): HasMany { return $this->hasMany(JudgeAdvancementVote::class); @@ -125,7 +129,7 @@ class User extends Authenticatable implements MustVerifyEmail public function isJudge(): bool { - return $this->judgingAssignments()->count() > 0; + return $this->judgingAssignments()->count() > 0 || $this->bonusJudgingAssignments()->count() > 0; } /** diff --git a/app/Rules/ValidateAuditionKey.php b/app/Rules/ValidateAuditionKey.php new file mode 100644 index 0000000..fcdea0f --- /dev/null +++ b/app/Rules/ValidateAuditionKey.php @@ -0,0 +1,19 @@ +where('id', $key)->exists()) { + $fail('Invalid audition id provided'); + } + } +} diff --git a/database/factories/BonusScoreDefinitionFactory.php b/database/factories/BonusScoreDefinitionFactory.php new file mode 100644 index 0000000..4dbb036 --- /dev/null +++ b/database/factories/BonusScoreDefinitionFactory.php @@ -0,0 +1,25 @@ + + */ +class BonusScoreDefinitionFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => $this->faker->word, + 'max_score' => $this->faker->randomNumber(2), + 'weight' => $this->faker->randomFloat(2, 0, 2), + ]; + } +} diff --git a/database/migrations/2024_07_15_040312_create_bonus_score_definitions_table.php b/database/migrations/2024_07_15_040312_create_bonus_score_definitions_table.php new file mode 100644 index 0000000..8500c3d --- /dev/null +++ b/database/migrations/2024_07_15_040312_create_bonus_score_definitions_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name'); + $table->integer('max_score'); + $table->float('weight'); + $table->boolean('for_seating')->default(true); + $table->boolean('for_attendance')->default(false); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bonus_score_definitions'); + } +}; diff --git a/database/migrations/2024_07_15_042419_create_bonus_score_audition_assignment_table.php b/database/migrations/2024_07_15_042419_create_bonus_score_audition_assignment_table.php new file mode 100644 index 0000000..5dd73e6 --- /dev/null +++ b/database/migrations/2024_07_15_042419_create_bonus_score_audition_assignment_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignIdFor(BonusScoreDefinition::class) + ->constrained('bonus_score_definitions', 'id', 'bs_audition_assignment_bonus_score_definition_id') + ->onDelete('cascade')->onUpdate('cascade'); + $table->foreignIdFor(Audition::class)->unique() + ->constrained()->onDelete('cascade')->onUpdate('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bonus_score_audition_assignment'); + } +}; diff --git a/database/migrations/2024_07_15_042700_create_bonus_scores_table.php b/database/migrations/2024_07_15_042700_create_bonus_scores_table.php new file mode 100644 index 0000000..c44e38c --- /dev/null +++ b/database/migrations/2024_07_15_042700_create_bonus_scores_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignIdFor(Entry::class); + $table->foreignIdFor(User::class); + $table->foreignId('originally_scored_entry')->nullable()->constrained('entries')->nullOnDelete(); + $table->integer('score'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bonus_scores'); + } +}; diff --git a/database/migrations/2024_07_15_194558_create_bonus_score_judge_assignment_table.php b/database/migrations/2024_07_15_194558_create_bonus_score_judge_assignment_table.php new file mode 100644 index 0000000..8e19376 --- /dev/null +++ b/database/migrations/2024_07_15_194558_create_bonus_score_judge_assignment_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignIdFor(BonusScoreDefinition::class) + ->constrained('bonus_score_definitions', 'id', 'bs_judge_assignment_bonus_score_definition_id') + ->onDelete('cascade')->onUpdate('cascade'); + $table->foreignIdFor(User::class) + ->constrained()->onDelete('cascade')->onUpdate('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bonus_score_judge_assignment'); + } +}; diff --git a/resources/views/admin/bonus-scores/index-add-auditions-to-bonus-modal.blade.php b/resources/views/admin/bonus-scores/index-add-auditions-to-bonus-modal.blade.php new file mode 100644 index 0000000..f258fa5 --- /dev/null +++ b/resources/views/admin/bonus-scores/index-add-auditions-to-bonus-modal.blade.php @@ -0,0 +1,15 @@ + + Add auditions to + + +
+ @foreach($unassignedAuditions as $audition) +
+ +
+ @endforeach +
+ Add Checked Auditions +
+ +
diff --git a/resources/views/admin/bonus-scores/index-add-bonus-score-modal.blade.php b/resources/views/admin/bonus-scores/index-add-bonus-score-modal.blade.php new file mode 100644 index 0000000..9f514e3 --- /dev/null +++ b/resources/views/admin/bonus-scores/index-add-bonus-score-modal.blade.php @@ -0,0 +1,16 @@ + + + Add Bonus Score + + + + + + +
+ Create Bonus Score +
+ +
+
+
diff --git a/resources/views/admin/bonus-scores/index-help-modal.blade.php b/resources/views/admin/bonus-scores/index-help-modal.blade.php new file mode 100644 index 0000000..1235f11 --- /dev/null +++ b/resources/views/admin/bonus-scores/index-help-modal.blade.php @@ -0,0 +1,24 @@ + +

Bonus scores are most often used for an improvisation score for jazz band auditions. A bonus score + earned by an entry will be directly added + to that entries final score. When you create a bonus score, you will also specify to which auditions that bonus + score should apply. When a student + earns a bonus score for one entry, that bonus will be applied to all entries that receive that bonus score.

+ +

+ Let's say you create a bonus score called, "Saxophone Improvisation," and assign Jazz Alto, Jazz Tenor, and Jazz + Bari auditions to that bonus + score. If a student is entered on all three saxes, when they receive an improv score on one sax, that score will + apply to all 3. The system + will not allow another improv score to be assigned by the same judge unless the first one is deleted. If you + want that student to improv on each instrument + separately, you will need to create a separate bonus score for each instrument. +

+ +

+ The weight allows you to control how much influence the bonus score has on the outcome of the audition. The + bonus score is + multiplied by the weight then added to the final score. The weight may be any positive number, including + decimals. +

+
diff --git a/resources/views/admin/bonus-scores/index-no-bonus-scores-message.blade.php b/resources/views/admin/bonus-scores/index-no-bonus-scores-message.blade.php new file mode 100644 index 0000000..ed3c5fe --- /dev/null +++ b/resources/views/admin/bonus-scores/index-no-bonus-scores-message.blade.php @@ -0,0 +1,22 @@ +
+ + + +

No bonus scores have been created

+

Get started by creating a new bonus score.

+
+ +
+
+ + diff --git a/resources/views/admin/bonus-scores/index.blade.php b/resources/views/admin/bonus-scores/index.blade.php new file mode 100644 index 0000000..4e42676 --- /dev/null +++ b/resources/views/admin/bonus-scores/index.blade.php @@ -0,0 +1,53 @@ + +Bonus Score Management + + @include('admin.bonus-scores.index-help-modal') + + @if($bonusScores->count() === 0) + @include('admin.bonus-scores.index-no-bonus-scores-message') + @endif + + @foreach($bonusScores as $bonusScore) + + + + {{ $bonusScore->name }} + + Max Points: {{ $bonusScore->max_score }} | Weight: {{ $bonusScore->weight }} + + + @if($bonusScore->auditions()->count() === 0) + + Confirm you want to delete the bonus score {{ $bonusScore->name }} + + @endif + + +
+ @foreach($bonusScore->auditions as $audition) +
+
+ @csrf + @method('DELETE') + +
+ {{ $audition->name }} +
+ @endforeach + +
+ + Add Auditions to {{ $bonusScore->name }} + +
+ @endforeach + @if($bonusScores->count() !== 0) + Add Bonus Score + @endif + @include('admin.bonus-scores.index-add-auditions-to-bonus-modal') + @include('admin.bonus-scores.index-add-bonus-score-modal') +
diff --git a/resources/views/admin/bonus-scores/judge-assignments.blade.php b/resources/views/admin/bonus-scores/judge-assignments.blade.php new file mode 100644 index 0000000..116d14d --- /dev/null +++ b/resources/views/admin/bonus-scores/judge-assignments.blade.php @@ -0,0 +1,111 @@ + +
+ +
+ + +
diff --git a/resources/views/admin/rooms/judge_assignments.blade.php b/resources/views/admin/rooms/judge_assignments.blade.php index 6ae2aec..eada06f 100644 --- a/resources/views/admin/rooms/judge_assignments.blade.php +++ b/resources/views/admin/rooms/judge_assignments.blade.php @@ -1,5 +1,17 @@ -