diff --git a/app/Actions/EnterScore.php b/app/Actions/EnterScore.php
new file mode 100644
index 0000000..25c357b
--- /dev/null
+++ b/app/Actions/EnterScore.php
@@ -0,0 +1,101 @@
+basicChecks($user, $entry, $scores);
+ $this->checkJudgeAssignment($user, $entry);
+ $this->checkForExistingScore($user, $entry);
+ $this->validateScoresSubmitted($entry, $scores);
+ $entry->removeFlag('no_show');
+ $newScoreSheet = ScoreSheet::create([
+ 'user_id' => $user->id,
+ 'entry_id' => $entry->id,
+ 'subscores' => $this->subscoresForStorage($entry, $scores),
+ ]);
+
+ return $newScoreSheet;
+ }
+
+ protected function subscoresForStorage(Entry $entry, Collection $scores)
+ {
+ $subscores = [];
+ foreach ($entry->audition->scoringGuide->subscores as $subscore) {
+ $subscores[$subscore->id] = [
+ 'score' => $scores[$subscore->id],
+ 'subscore_id' => $subscore->id,
+ 'subscore_name' => $subscore->name,
+ ];
+ }
+
+ return $subscores;
+ }
+
+ protected function checkForExistingScore(User $user, Entry $entry)
+ {
+ if (ScoreSheet::where('user_id', $user->id)->where('entry_id', $entry->id)->exists()) {
+ throw new ScoreEntryException('That judge has already entered scores for that entry');
+ }
+ }
+
+ protected function validateScoresSubmitted(Entry $entry, Collection $scores)
+ {
+ $subscoresRequired = $entry->audition->scoringGuide->subscores;
+
+ foreach ($subscoresRequired as $subscore) {
+ // check that there is an element in the $scores collection with the key = $subscore->id
+ if (! $scores->keys()->contains($subscore->id)) {
+ throw new ScoreEntryException('Invalid Score Submission');
+ }
+ if ($scores[$subscore->id] > $subscore->max_score) {
+ throw new ScoreEntryException('Supplied subscore exceeds maximum allowed');
+ }
+ }
+ }
+
+ protected function checkJudgeAssignment(User $user, Entry $entry)
+ {
+ $check = DB::table('room_user')
+ ->where('room_id', $entry->audition->room_id)
+ ->where('user_id', $user->id)->exists();
+ if (! $check) {
+ throw new ScoreEntryException('This judge is not assigned to judge this entry');
+ }
+ }
+
+ protected function basicChecks(User $user, Entry $entry, Collection $scores)
+ {
+ if (! $user->exists()) {
+ throw new ScoreEntryException('User does not exist');
+ }
+ if (! $entry->exists()) {
+ throw new ScoreEntryException('Entry does not exist');
+ }
+ if ($entry->audition->hasFlag('seats_published')) {
+ throw new ScoreEntryException('Cannot score an entry in an audition with published seats');
+ }
+ if ($entry->audition->hasFlag('advancement_published')) {
+ throw new ScoreEntryException('Cannot score an entry in an audition with published advancement');
+ }
+ $requiredScores = $entry->audition->scoringGuide->subscores()->count();
+ if ($scores->count() !== $requiredScores) {
+ throw new ScoreEntryException('Invalid number of scores');
+ }
+ }
+}
diff --git a/app/Exceptions/ScoreEntryException.php b/app/Exceptions/ScoreEntryException.php
new file mode 100644
index 0000000..9086c34
--- /dev/null
+++ b/app/Exceptions/ScoreEntryException.php
@@ -0,0 +1,10 @@
+auditionCache = $auditionCache;
- $this->entryCache = $entryCache;
+
+ }
+ public function isEntryFullyScored(Entry $entry): bool
+ {
+ $requiredJudges = $entry->audition->judges()->count();
+ $scoreSheets = $entry->scoreSheets()->count();
+
+ return $requiredJudges === $scoreSheets;
}
+
}
diff --git a/app/helpers.php b/app/helpers.php
index ef50236..22f435f 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -1,5 +1,9 @@
create(['id' => 1000]);
+ $sg = ScoringGuide::factory()->create(['id' => 1000]);
+ SubscoreDefinition::create([
+ 'id' => 1001,
+ 'scoring_guide_id' => $sg->id,
+ 'name' => 'Scale',
+ 'max_score' => 100,
+ 'weight' => 1,
+ 'display_order' => 1,
+ 'tiebreak_order' => 5,
+ 'for_seating' => 1,
+ 'for_advance' => 0,
+ ]);
+ SubscoreDefinition::create([
+ 'id' => 1002,
+ 'scoring_guide_id' => $sg->id,
+ 'name' => 'Etude 1',
+ 'max_score' => 100,
+ 'weight' => 2,
+ 'display_order' => 2,
+ 'tiebreak_order' => 3,
+ 'for_seating' => 1,
+ 'for_advance' => 1,
+ ]);
+ SubscoreDefinition::create([
+ 'id' => 1003,
+ 'scoring_guide_id' => $sg->id,
+ 'name' => 'Etude 2',
+ 'max_score' => 100,
+ 'weight' => 2,
+ 'display_order' => 3,
+ 'tiebreak_order' => 4,
+ 'for_seating' => 1,
+ 'for_advance' => 1,
+ ]);
+ SubscoreDefinition::create([
+ 'id' => 1004,
+ 'scoring_guide_id' => $sg->id,
+ 'name' => 'Sight Reading',
+ 'max_score' => 100,
+ 'weight' => 3,
+ 'display_order' => 4,
+ 'tiebreak_order' => 2,
+ 'for_seating' => 1,
+ 'for_advance' => 1,
+ ]);
+ SubscoreDefinition::create([
+ 'id' => 1005,
+ 'scoring_guide_id' => $sg->id,
+ 'name' => 'Tone',
+ 'max_score' => 100,
+ 'weight' => 1,
+ 'display_order' => 5,
+ 'tiebreak_order' => 1,
+ 'for_seating' => 0,
+ 'for_advance' => 1,
+ ]);
+ Audition::factory()->create([
+ 'id' => 1000,
+ 'room_id' => $room->id,
+ 'scoring_guide_id' => $sg->id,
+ 'name' => 'Test Audition',
+ ]);
+ }
+}
diff --git a/database/seeders/RoomSeeder.php b/database/seeders/RoomSeeder.php
deleted file mode 100644
index bdf439e..0000000
--- a/database/seeders/RoomSeeder.php
+++ /dev/null
@@ -1,17 +0,0 @@
-
Enter Scores
Enter No-Shows
- Audition Status
+ Audition Status
{{ auditionSetting('advanceTo') }} Status
diff --git a/resources/views/test.blade.php b/resources/views/test.blade.php
index 44f7d68..bd114cc 100644
--- a/resources/views/test.blade.php
+++ b/resources/views/test.blade.php
@@ -1,4 +1,4 @@
-@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag; @endphp
+@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag;use App\Models\Entry;use App\Models\User; @endphp
@php @endphp
@inject('scoreservice','App\Services\ScoreService');
@inject('auditionService','App\Services\AuditionService');
@@ -8,8 +8,17 @@
Test Page
@php
- $audition = Audition::first();
- $audition->addFlag('drawn');
+ $entry = Entry::find(1127);
+ $judge = User::find(65);
+ $scoreArray = [
+ 1 => 50,
+ 2 => 60,
+ 3 => 70,
+ 4 => 80,
+ 5 => 90,
+ ];
+ enterScore($judge, $entry, $scoreArray);
+ dump($entry->audition->name);
@endphp
diff --git a/tests/Feature/Actions/EnterScoreTest.php b/tests/Feature/Actions/EnterScoreTest.php
new file mode 100644
index 0000000..43cf3af
--- /dev/null
+++ b/tests/Feature/Actions/EnterScoreTest.php
@@ -0,0 +1,185 @@
+scoreEntry = new EnterScore();
+});
+
+test('throws an exception if the user does not exist', function () {
+ $user = User::factory()->make();
+ $entry = Entry::factory()->create();
+ enterScore($user, $entry, []);
+})->throws(ScoreEntryException::class, 'User does not exist');
+test('throws an exception if the entry does not exist', function () {
+ $user = User::factory()->create();
+ $entry = Entry::factory()->make();
+ enterScore($user, $entry, []);
+})->throws(ScoreEntryException::class, 'Entry does not exist');
+it('throws an exception if the seats for the entries audition are published', function () {
+ // Arrange
+ $user = User::factory()->create();
+ $entry = Entry::factory()->create();
+ $entry->audition->addFlag('seats_published');
+ // Act & Assert
+ enterScore($user, $entry, []);
+})->throws(ScoreEntryException::class, 'Cannot score an entry in an audition with published seats');
+it('throws an exception if the advancement for the entries audition is published', function () {
+ // Arrange
+ $user = User::factory()->create();
+ $entry = Entry::factory()->create();
+ $entry->audition->addFlag('advancement_published');
+ // Act & Assert
+ enterScore($user, $entry, []);
+})->throws(ScoreEntryException::class, 'Cannot score an entry in an audition with published advancement');
+it('throws an exception if too many scores are submitted', function () {
+ // Arrange
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ Room::find(1000)->addJudge($judge);
+ // Act & Assert
+ enterScore($judge, $entry, [1, 2, 5, 3, 2, 1]);
+})->throws(ScoreEntryException::class, 'Invalid number of scores');
+it('throws an exception if too few scores are submitted', function () {
+ // Arrange
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ Room::find(1000)->addJudge($judge);
+ // Act & Assert
+ enterScore($judge, $entry, [1, 2, 5, 3]);
+})->throws(ScoreEntryException::class, 'Invalid number of scores');
+it('throws an exception if the user is not assigned to judge the entry', function () {
+ // Arrange
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ // Act & Assert
+ enterScore($judge, $entry, [1, 2, 5, 3, 7]);
+})->throws(ScoreEntryException::class, 'This judge is not assigned to judge this entry');
+it('throws an exception if an invalid subscore is provided', function () {
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ Room::find(1000)->addJudge($judge);
+ $scores = [
+ 1001 => 98,
+ 1002 => 90,
+ 1003 => 87,
+ 1004 => 78,
+ 1006 => 88,
+ ];
+ enterScore($judge, $entry, $scores);
+})->throws(ScoreEntryException::class, 'Invalid Score Submission');
+it('throws an exception if a submitted subscore exceeds the maximum allowed', function () {
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ Room::find(1000)->addJudge($judge);
+ $scores = [
+ 1001 => 98,
+ 1002 => 90,
+ 1003 => 87,
+ 1004 => 78,
+ 1005 => 101,
+ ];
+ enterScore($judge, $entry, $scores);
+})->throws(ScoreEntryException::class, 'Supplied subscore exceeds maximum allowed');
+it('removes an existing no_show flag from the entry if one exists', function () {
+ // Arrange
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ $entry->addFlag('no_show');
+ Room::find(1000)->addJudge($judge);
+ $scores = [
+ 1001 => 98,
+ 1002 => 90,
+ 1003 => 87,
+ 1004 => 78,
+ 1005 => 98,
+ ];
+ // Act
+ enterScore($judge, $entry, $scores);
+ // Assert
+ expect($entry->hasFlag('no_show'))->toBeFalse();
+});
+it('saves the score with a properly formatted subscore object', function () {
+ // Arrange
+ // Arrange
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ $entry->addFlag('no_show');
+ Room::find(1000)->addJudge($judge);
+ $scores = [
+ 1001 => 98,
+ 1002 => 90,
+ 1003 => 87,
+ 1004 => 78,
+ 1005 => 98,
+ ];
+ $desiredReturn = [
+ 1001 => [
+ 'score' => 98,
+ 'subscore_id' => 1001,
+ 'subscore_name' => 'Scale',
+ ],
+ 1002 => [
+ 'score' => 90,
+ 'subscore_id' => 1002,
+ 'subscore_name' => 'Etude 1',
+ ],
+ 1003 => [
+ 'score' => 87,
+ 'subscore_id' => 1003,
+ 'subscore_name' => 'Etude 2',
+ ],
+ 1004 => [
+ 'score' => 78,
+ 'subscore_id' => 1004,
+ 'subscore_name' => 'Sight Reading',
+ ],
+ 1005 => [
+ 'score' => 98,
+ 'subscore_id' => 1005,
+ 'subscore_name' => 'Tone',
+ ],
+ ];
+ // Act
+ $newScore = enterScore($judge, $entry, $scores);
+ // Assert
+ $checkScoreSheet = ScoreSheet::find($newScore->id);
+ expect($checkScoreSheet->exists())->toBeTrue()
+ ->and($checkScoreSheet->subscores)->toBe($desiredReturn);
+});
+it('throws an exception of the entry already has a score by the judge', function() {
+ // Arrange
+ loadSampleAudition();
+ $judge = User::factory()->create();
+ $entry = Entry::factory()->create(['audition_id' => 1000]);
+ $entry->addFlag('no_show');
+ Room::find(1000)->addJudge($judge);
+ $scores = [
+ 1001 => 98,
+ 1002 => 90,
+ 1003 => 87,
+ 1004 => 78,
+ 1005 => 98,
+ ];
+ // Act
+ enterScore($judge, $entry, $scores);
+ // Assert
+ enterScore($judge, $entry, $scores);
+})->throws(ScoreEntryException::class, 'That judge has already entered scores for that entry');
diff --git a/tests/Feature/Seating/indexTest.php b/tests/Feature/Pages/Seating/statusTest.php
similarity index 100%
rename from tests/Feature/Seating/indexTest.php
rename to tests/Feature/Pages/Seating/statusTest.php
diff --git a/tests/Feature/Services/ScoreServiceTest.php b/tests/Feature/Services/ScoreServiceTest.php
new file mode 100644
index 0000000..97728f6
--- /dev/null
+++ b/tests/Feature/Services/ScoreServiceTest.php
@@ -0,0 +1,44 @@
+scoreService = new ScoreService();
+});
+it('can record a score', function () {
+ // Arrange
+ // run the seeder AuditionWithScoringGuideAndRoom
+ artisan('db:seed', ['--class' => 'AuditionWithScoringGuideAndRoom']);
+ // Act & Assert
+ expect(Audition::find(1000)->name)->toBe('Test Audition');
+});
+
+it('can check if an entry is fully scored', function () {
+ $room = Room::factory()->create();
+ $judges = User::factory()->count(2)->create();
+ $judges->each(fn ($judge) => $room->addJudge($judge));
+ $audition = Audition::factory()->create(['room_id' => $room->id]);
+ $entry = Entry::factory()->create(['audition_id' => $audition->id]);
+ expect($this->scoreService->isEntryFullyScored($entry))->toBeFalse();
+ ScoreSheet::create([
+ 'user_id' => $judges->first()->id,
+ 'entry_id' => $entry->id,
+ 'subscores' => 7,
+ ]);
+ expect($this->scoreService->isEntryFullyScored($entry))->toBeFalse();
+ ScoreSheet::create([
+ 'user_id' => $judges->last()->id,
+ 'entry_id' => $entry->id,
+ 'subscores' => 7,
+ ]);
+ expect($this->scoreService->isEntryFullyScored($entry))->toBeTrue();
+});
diff --git a/tests/Pest.php b/tests/Pest.php
index 6cd9f4c..a07cc90 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -15,6 +15,7 @@ use App\Models\User;
use App\Settings;
use Illuminate\Foundation\Testing\TestCase;
use function Pest\Laravel\actingAs;
+use function Pest\Laravel\artisan;
uses(
Tests\TestCase::class,
@@ -59,6 +60,10 @@ function actAsNormal()
{
actingAs(User::factory()->create());
}
+function loadSampleAudition()
+{
+ artisan('db:seed', ['--class' => 'AuditionWithScoringGuideAndRoom']);
+}
uses()->beforeEach(function () {
Settings::set('auditionName', 'Somewhere Band Directors Association');