Action to enter prelim score sheet implemented.
This commit is contained in:
parent
83eff8feee
commit
ca80260bda
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Tabulation;
|
||||||
|
|
||||||
|
use App\Exceptions\AuditionAdminException;
|
||||||
|
use App\Models\Entry;
|
||||||
|
use App\Models\PrelimDefinition;
|
||||||
|
use App\Models\PrelimScoreSheet;
|
||||||
|
use App\Models\User;
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
use function auditionLog;
|
||||||
|
|
||||||
|
class EnterPrelimScore
|
||||||
|
{
|
||||||
|
public function __invoke(
|
||||||
|
User $user,
|
||||||
|
Entry $entry,
|
||||||
|
array $scores,
|
||||||
|
PrelimScoreSheet|false $prelimScoreSheet = false
|
||||||
|
): PrelimScoreSheet {
|
||||||
|
$scores = collect($scores);
|
||||||
|
|
||||||
|
// Basic Validity Checks
|
||||||
|
if (! User::where('id', $user->id)->exists()) {
|
||||||
|
throw new AuditionAdminException('User does not exist');
|
||||||
|
}
|
||||||
|
if (! Entry::where('id', $entry->id)->exists()) {
|
||||||
|
throw new AuditionAdminException('Entry does not exist');
|
||||||
|
}
|
||||||
|
if ($entry->audition->hasFlag('seats_published')) {
|
||||||
|
throw new AuditionAdminException('Cannot score an entry in an audition where seats are published');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the entries audition has a prelim definition
|
||||||
|
if (! PrelimDefinition::where('audition_id', $entry->audition->id)->exists()) {
|
||||||
|
throw new AuditionAdminException('The entries audition does not have a prelim');
|
||||||
|
}
|
||||||
|
$prelimDefinition = PrelimDefinition::where('audition_id', $entry->audition->id)->first();
|
||||||
|
|
||||||
|
// Check that the specified user is assigned to judge this entry in prelims
|
||||||
|
$check = DB::table('room_user')
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->where('room_id', $prelimDefinition->room_id)->exists();
|
||||||
|
if (! $check) {
|
||||||
|
throw new AuditionAdminException('This judge is not assigned to judge this entry in prelims');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a score already exists
|
||||||
|
if (! $prelimScoreSheet) {
|
||||||
|
if (PrelimScoreSheet::where('user_id', $user->id)->where('entry_id', $entry->id)->exists()) {
|
||||||
|
throw new AuditionAdminException('That judge has already entered a prelim score for that entry');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($prelimScoreSheet->user_id != $user->id) {
|
||||||
|
throw new AuditionAdminException('Existing score sheet is from a different judge');
|
||||||
|
}
|
||||||
|
if ($prelimScoreSheet->entry_id != $entry->id) {
|
||||||
|
throw new AuditionAdminException('Existing score sheet is for a different entry');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the validity of submitted subscores, format array for storage, and sum score
|
||||||
|
$subscoresRequired = $prelimDefinition->scoringGuide->subscores;
|
||||||
|
$subscoresStorageArray = [];
|
||||||
|
$totalScore = 0;
|
||||||
|
$maxPossibleTotal = 0;
|
||||||
|
if ($scores->count() !== $subscoresRequired->count()) {
|
||||||
|
throw new AuditionAdminException('Invalid number of scores');
|
||||||
|
}
|
||||||
|
|
||||||
|
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 AuditionAdminException('Invalid Score Submission');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scores[$subscore->id] > $subscore->max_score) {
|
||||||
|
throw new AuditionAdminException('Supplied subscore exceeds maximum allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add subscore to the storage array
|
||||||
|
$subscoresStorageArray[$subscore->id] = [
|
||||||
|
'score' => $scores[$subscore->id],
|
||||||
|
'subscore_id' => $subscore->id,
|
||||||
|
'subscore_name' => $subscore->name,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Multiply subscore by weight then add to total
|
||||||
|
$totalScore += ($subscore->weight * $scores[$subscore->id]);
|
||||||
|
$maxPossibleTotal += ($subscore->weight * $subscore->max_score);
|
||||||
|
}
|
||||||
|
$finalTotalScore = ($maxPossibleTotal === 0) ? 0 : (($totalScore / $maxPossibleTotal) * 100);
|
||||||
|
|
||||||
|
$entry->removeFlag('no_show');
|
||||||
|
if ($prelimScoreSheet instanceof PrelimScoreSheet) {
|
||||||
|
$prelimScoreSheet->update([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'entry_id' => $entry->id,
|
||||||
|
'subscores' => $subscoresStorageArray,
|
||||||
|
'total' => $finalTotalScore,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$prelimScoreSheet = PrelimScoreSheet::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'entry_id' => $entry->id,
|
||||||
|
'subscores' => $subscoresStorageArray,
|
||||||
|
'total' => $finalTotalScore,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the prelim score entry
|
||||||
|
$log_message = 'Entered prelim score for entry id '.$entry->id.'.<br />';
|
||||||
|
$log_message .= 'Judge: '.$user->full_name().'<br />';
|
||||||
|
foreach ($prelimScoreSheet->subscores as $subscore) {
|
||||||
|
$log_message .= $subscore['subscore_name'].': '.$subscore['score'].'<br />';
|
||||||
|
}
|
||||||
|
$log_message .= 'Total :'.$prelimScoreSheet->total.'<br />';
|
||||||
|
auditionLog($log_message, [
|
||||||
|
'entries' => [$entry->id],
|
||||||
|
'users' => [$user->id],
|
||||||
|
'auditions' => [$entry->audition_id],
|
||||||
|
'students' => [$entry->student_id],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $prelimScoreSheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -136,6 +136,11 @@ class Entry extends Model
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function prelimScoreSheets(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(PrelimScoreSheet::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function bonusScores(): HasMany
|
public function bonusScores(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(BonusScore::class);
|
return $this->hasMany(BonusScore::class);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,15 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
|
||||||
class PrelimScoreSheet extends Model
|
class PrelimScoreSheet extends Model
|
||||||
{
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'entry_id',
|
||||||
|
'subscores',
|
||||||
|
'total',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = ['subscores' => 'json'];
|
||||||
|
|
||||||
public function user(): HasOne
|
public function user(): HasOne
|
||||||
{
|
{
|
||||||
return $this->hasOne(User::class);
|
return $this->hasOne(User::class);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Actions\Tabulation\EnterPrelimScore;
|
||||||
|
use App\Exceptions\AuditionAdminException;
|
||||||
|
use App\Models\Audition;
|
||||||
|
use App\Models\AuditLogEntry;
|
||||||
|
use App\Models\Entry;
|
||||||
|
use App\Models\PrelimDefinition;
|
||||||
|
use App\Models\PrelimScoreSheet;
|
||||||
|
use App\Models\Room;
|
||||||
|
use App\Models\ScoringGuide;
|
||||||
|
use App\Models\SubscoreDefinition;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Generate Scoring Guide
|
||||||
|
$this->prelimScoringGuide = ScoringGuide::factory()->create(['id' => 1000]);
|
||||||
|
SubscoreDefinition::create([
|
||||||
|
'id' => 1001,
|
||||||
|
'scoring_guide_id' => $this->prelimScoringGuide->id,
|
||||||
|
'name' => 'Scale',
|
||||||
|
'max_score' => 100,
|
||||||
|
'weight' => 1,
|
||||||
|
'display_order' => 1,
|
||||||
|
'tiebreak_order' => 3,
|
||||||
|
'for_seating' => '1',
|
||||||
|
'for_advance' => '0',
|
||||||
|
]);
|
||||||
|
SubscoreDefinition::create([
|
||||||
|
'id' => 1002,
|
||||||
|
'scoring_guide_id' => $this->prelimScoringGuide->id,
|
||||||
|
'name' => 'Etude 1',
|
||||||
|
'max_score' => 100,
|
||||||
|
'weight' => 2,
|
||||||
|
'display_order' => 2,
|
||||||
|
'tiebreak_order' => 1,
|
||||||
|
'for_seating' => '1',
|
||||||
|
'for_advance' => '1',
|
||||||
|
]);
|
||||||
|
SubscoreDefinition::create([
|
||||||
|
'id' => 1003,
|
||||||
|
'scoring_guide_id' => $this->prelimScoringGuide->id,
|
||||||
|
'name' => 'Etude 2',
|
||||||
|
'max_score' => 100,
|
||||||
|
'weight' => 2,
|
||||||
|
'display_order' => 3,
|
||||||
|
'tiebreak_order' => 2,
|
||||||
|
'for_seating' => '0',
|
||||||
|
'for_advance' => '1',
|
||||||
|
]);
|
||||||
|
SubscoreDefinition::where('id', '<', 900)->delete();
|
||||||
|
$this->finalsRoom = Room::factory()->create();
|
||||||
|
$this->prelimRoom = Room::factory()->create();
|
||||||
|
$this->audition = Audition::factory()->create(['room_id' => $this->finalsRoom->id]);
|
||||||
|
$this->prelimDefinition = PrelimDefinition::create([
|
||||||
|
'room_id' => $this->prelimRoom->id,
|
||||||
|
'audition_id' => $this->audition->id,
|
||||||
|
'scoring_guide_id' => $this->prelimScoringGuide->id,
|
||||||
|
'passing_score' => 60,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->judge1 = User::factory()->create();
|
||||||
|
$this->judge2 = User::factory()->create();
|
||||||
|
$this->prelimRoom->judges()->attach($this->judge1->id);
|
||||||
|
$this->prelimRoom->judges()->attach($this->judge2->id);
|
||||||
|
$this->entry1 = Entry::factory()->create(['audition_id' => $this->audition->id]);
|
||||||
|
$this->entry2 = Entry::factory()->create(['audition_id' => $this->audition->id]);
|
||||||
|
$this->scribe = app(EnterPrelimScore::class);
|
||||||
|
$this->possibleScoreArray = [
|
||||||
|
1001 => 10,
|
||||||
|
1002 => 11,
|
||||||
|
1003 => 12,
|
||||||
|
];
|
||||||
|
$this->anotherPossibleScoreArray = [
|
||||||
|
1001 => 20,
|
||||||
|
1002 => 21,
|
||||||
|
1003 => 22,
|
||||||
|
];
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can enter a prelim score', function () {
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
expect($this->entry1->prelimScoreSheets()->count())->toBe(1)
|
||||||
|
->and($this->entry1->prelimScoreSheets()->first()->total)->toBe(11.2);
|
||||||
|
|
||||||
|
($this->scribe)($this->judge1, $this->entry2, $this->anotherPossibleScoreArray);
|
||||||
|
expect($this->entry2->prelimScoreSheets()->count())->toBe(1)
|
||||||
|
->and($this->entry2->prelimScoreSheets()->first()->total)->toBe(21.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will not enter a score for a judge that does not exist', function () {
|
||||||
|
$fakeJudge = User::factory()->make();
|
||||||
|
($this->scribe)($fakeJudge, $this->entry1, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'User does not exist');
|
||||||
|
|
||||||
|
it('will not enter a score for an entry that does not exist', function () {
|
||||||
|
$fakeEntry = Entry::factory()->make();
|
||||||
|
($this->scribe)($this->judge1, $fakeEntry, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'Entry does not exist');
|
||||||
|
|
||||||
|
it('will not score an entry if the audition seats are published', function () {
|
||||||
|
$this->audition->addFlag('seats_published');
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'Cannot score an entry in an audition where seats are published');
|
||||||
|
|
||||||
|
it('will not score an entry if the judge is not assigned to judge the entry', function () {
|
||||||
|
$fakeJudge = User::factory()->create();
|
||||||
|
($this->scribe)($fakeJudge, $this->entry1, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'This judge is not assigned to judge this entry');
|
||||||
|
|
||||||
|
it('can modify an existing score sheet', function () {
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
$scoreSheet = PrelimScoreSheet::first();
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->anotherPossibleScoreArray, $scoreSheet);
|
||||||
|
expect($this->entry1->prelimScoreSheets()->count())->toBe(1)
|
||||||
|
->and($this->entry1->prelimScoreSheets()->first()->total)->toBe(21.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will not change the judge on a score sheet', function () {
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
$scoreSheet = PrelimScoreSheet::first();
|
||||||
|
($this->scribe)($this->judge2, $this->entry1, $this->anotherPossibleScoreArray, $scoreSheet);
|
||||||
|
})->throws(AuditionAdminException::class, 'Existing score sheet is from a different judge');
|
||||||
|
|
||||||
|
it('will not accept a second score sheet for a judge ane entry', function () {
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->anotherPossibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'That judge has already entered a prelim score for that entry');
|
||||||
|
|
||||||
|
it('will not change the entry on a score sheet', function () {
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
$scoreSheet = PrelimScoreSheet::first();
|
||||||
|
($this->scribe)($this->judge1, $this->entry2, $this->anotherPossibleScoreArray, $scoreSheet);
|
||||||
|
})->throws(AuditionAdminException::class, 'Existing score sheet is for a different entry');
|
||||||
|
|
||||||
|
it('will not accept an incorrect number of subscores', function () {
|
||||||
|
array_pop($this->possibleScoreArray);
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'Invalid number of scores');
|
||||||
|
|
||||||
|
it('will not accept an invalid subscores', function () {
|
||||||
|
array_pop($this->possibleScoreArray);
|
||||||
|
$this->possibleScoreArray[3001] = 100;
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'Invalid Score Submission');
|
||||||
|
|
||||||
|
it('will. not accept a subscore in excess of its maximum', function () {
|
||||||
|
$this->possibleScoreArray[1001] = 1500;
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
})->throws(AuditionAdminException::class, 'Supplied subscore exceeds maximum allowed');
|
||||||
|
|
||||||
|
it('removes a no-show flag from an entry', function () {
|
||||||
|
$this->entry1->addFlag('no_show');
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
expect($this->entry1->hasFlag('no_show'))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs score entry', function () {
|
||||||
|
($this->scribe)($this->judge1, $this->entry1, $this->possibleScoreArray);
|
||||||
|
$logEntry = AuditLogEntry::orderBy('id', 'desc')->first();
|
||||||
|
expect($logEntry->message)->toStartWith('Entered prelim score for entry id ');
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue