Rewrite tabulation #14
|
|
@ -18,3 +18,5 @@ yarn-error.log
|
|||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
/app/Http/Controllers/TestController.php
|
||||
/resources/views/test.blade.php
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AllJudgesCount implements CalculateEntryScore
|
||||
{
|
||||
protected CalculateScoreSheetTotal $calculator;
|
||||
protected AuditionService $auditionService;
|
||||
protected EntryService $entryService;
|
||||
|
||||
public function __construct(CalculateScoreSheetTotal $calculator, AuditionService $auditionService, EntryService $entryService)
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
$this->auditionService = $auditionService;
|
||||
$this->entryService = $entryService;
|
||||
}
|
||||
|
||||
public function calculate(string $mode, Entry $entry): array
|
||||
{
|
||||
|
||||
$cacheKey = 'entryScore-'.$entry->id.'-'.$mode;
|
||||
return Cache::remember($cacheKey, 10, function () use ($mode, $entry) {
|
||||
$this->basicValidation($mode, $entry);
|
||||
$this->areAllJudgesIn($entry);
|
||||
$this->areAllJudgesValid($entry);
|
||||
|
||||
return $this->getJudgeTotals($mode, $entry);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected function getJudgeTotals($mode, Entry $entry)
|
||||
{
|
||||
|
||||
$scores = [];
|
||||
foreach ($this->auditionService->getJudges($entry->audition) as $judge) {
|
||||
$scores[] = $this->calculator->__invoke($mode, $entry, $judge);
|
||||
}
|
||||
$sums = [];
|
||||
// Sum each subscore from the judges
|
||||
foreach ($scores as $score) {
|
||||
$index = 0;
|
||||
foreach ($score as $value) {
|
||||
$sums[$index] = $sums[$index] ?? 0;
|
||||
$sums[$index] += $value;
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
return $sums;
|
||||
}
|
||||
|
||||
protected function basicValidation($mode, $entry): void
|
||||
{
|
||||
if ($mode !== 'seating' && $mode !== 'advancement') {
|
||||
throw new TabulationException('Mode must be seating or advancement');
|
||||
}
|
||||
|
||||
if (! $this->entryService->entryExists($entry)) {
|
||||
throw new TabulationException('Invalid entry specified');
|
||||
}
|
||||
}
|
||||
|
||||
protected function areAllJudgesIn(Entry $entry): void
|
||||
{
|
||||
$assignedJudgeCount = $this->auditionService->getJudges($entry->audition)->count();
|
||||
if ($entry->scoreSheets->count() !== $assignedJudgeCount) {
|
||||
throw new TabulationException('Not all score sheets are in');
|
||||
}
|
||||
}
|
||||
|
||||
protected function areAllJudgesValid(Entry $entry): void
|
||||
{
|
||||
$validJudgeIds = $this->auditionService->getJudges($entry->audition)->sort()->pluck('id')->toArray();
|
||||
$existingJudgeIds = $entry->scoreSheets->sort()->pluck('user_id')->toArray();
|
||||
if ($validJudgeIds !== $existingJudgeIds) {
|
||||
throw new TabulationException('Score exists from a judge not assigned to this audition');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Models\Entry;
|
||||
|
||||
interface CalculateEntryScore
|
||||
{
|
||||
|
||||
public function calculate(string $mode, Entry $entry): array;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\User;
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\EntryService;
|
||||
use App\Services\UserService;
|
||||
|
||||
class CalculateScoreSheetTotal
|
||||
{
|
||||
protected AuditionService $auditionService;
|
||||
protected EntryService $entryService;
|
||||
protected UserService $userService;
|
||||
|
||||
public function __construct(AuditionService $auditionService, EntryService $entryService, UserService $userService)
|
||||
{
|
||||
$this->auditionService = $auditionService;
|
||||
$this->entryService = $entryService;
|
||||
$this->userService = $userService;
|
||||
}
|
||||
|
||||
public function __invoke(string $mode, Entry $entry, User $judge): array
|
||||
{
|
||||
$this->basicValidations($mode, $entry, $judge);
|
||||
$scoreSheet = ScoreSheet::where('entry_id', $entry->id)->where('user_id', $judge->id)->first();
|
||||
if (! $scoreSheet) {
|
||||
throw new TabulationException('No score sheet by that judge for that entry');
|
||||
}
|
||||
$subscores = $this->auditionService->getSubscores($entry->audition, $mode);
|
||||
$scoreTotal = 0;
|
||||
$weightsTotal = 0;
|
||||
$scoreArray = [];
|
||||
foreach ($subscores as $subscore) {
|
||||
$weight = $subscore['weight'];
|
||||
$score = $scoreSheet->subscores[$subscore->id]['score'];
|
||||
$scoreArray[] = $score;
|
||||
$scoreTotal += ($score * $weight);
|
||||
$weightsTotal += $weight;
|
||||
}
|
||||
$finalScore = $scoreTotal / $weightsTotal;
|
||||
// put $final score at the beginning of the $ScoreArray
|
||||
array_unshift($scoreArray, $finalScore);
|
||||
return $scoreArray;
|
||||
}
|
||||
|
||||
protected function basicValidations($mode, $entry, $judge): void
|
||||
{
|
||||
if ($mode !== 'seating' and $mode !== 'advancement') {
|
||||
throw new TabulationException('Invalid mode requested. Mode must be seating or advancement');
|
||||
}
|
||||
if (! $this->entryService->entryExists($entry)) {
|
||||
throw new TabulationException('Invalid entry provided');
|
||||
}
|
||||
if (! $this->userService->userExists($judge)) {
|
||||
throw new TabulationException('Invalid judge provided');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
/** @noinspection PhpMissingReturnTypeInspection */
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Exceptions\ScoreEntryException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EnterScore
|
||||
{
|
||||
// TODO implement this action every place that can save a score
|
||||
public function __invoke(User $user, Entry $entry, array $scores): ScoreSheet
|
||||
{
|
||||
$scores = collect($scores);
|
||||
$this->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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Ensemble;
|
||||
use App\Models\Seat;
|
||||
use function dd;
|
||||
|
||||
class GetAuditionSeats
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(Audition $audition): array
|
||||
{
|
||||
return $this->getSeats($audition);
|
||||
}
|
||||
|
||||
protected function getSeats(Audition $audition)
|
||||
{
|
||||
$ensembles = Ensemble::where('event_id', $audition->event_id)->orderBy('rank')->get();
|
||||
$seats = Seat::with('student.school')->where('audition_id', $audition->id)->orderBy('seat')->get();
|
||||
$return = [];
|
||||
foreach ($ensembles as $ensemble) {
|
||||
$ensembleSeats = $seats->filter(fn ($seat) => $seat->ensemble_id === $ensemble->id);
|
||||
foreach ($ensembleSeats as $seat) {
|
||||
$return[] = [
|
||||
'ensemble' => $ensemble->name,
|
||||
'seat' => $seat->seat,
|
||||
'student_name' => $seat->student->full_name(),
|
||||
'school_name' => $seat->student->school->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Seat;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class PublishSeats
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function __invoke(Audition $audition, array $seats): void
|
||||
{
|
||||
// Delete from the seats table where audition_id = $audition->id
|
||||
Seat::where('audition_id', $audition->id)->delete();
|
||||
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');
|
||||
Cache::forget('resultsSeatList');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Audition;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use function is_numeric;
|
||||
|
||||
class RankAuditionEntries
|
||||
{
|
||||
protected CalculateEntryScore $calculator;
|
||||
|
||||
public function __construct(CalculateEntryScore $calculator)
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
}
|
||||
|
||||
public function rank(string $mode, Audition $audition): Collection
|
||||
{
|
||||
$cacheKey = 'audition'.$audition->id.$mode;
|
||||
|
||||
return Cache::remember($cacheKey, 30, function () use ($mode, $audition) {
|
||||
return $this->calculateRank($mode, $audition);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function calculateRank(string $mode, Audition $audition): Collection
|
||||
{
|
||||
$this->basicValidation($mode, $audition);
|
||||
$entries = match ($mode) {
|
||||
'seating' => $audition->entries()->forSeating()->with('scoreSheets')->get(),
|
||||
'advancement' => $audition->entries()->forAdvancement()->with('scoreSheets')->get(),
|
||||
};
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setRelation('audition', $audition);
|
||||
try {
|
||||
$entry->score_totals = $this->calculator->calculate($mode, $entry);
|
||||
} catch (TabulationException $ex) {
|
||||
$entry->score_totals = [-1];
|
||||
$entry->score_message = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
// Sort entries based on their total score, then by subscores in tiebreak order
|
||||
$entries = $entries->sort(function ($a, $b) {
|
||||
for ($i = 0; $i < count($a->score_totals); $i++) {
|
||||
if ($a->score_totals[$i] > $b->score_totals[$i]) {
|
||||
return -1;
|
||||
} elseif ($a->score_totals[$i] < $b->score_totals[$i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
$rank = 1;
|
||||
foreach ($entries as $entry) {
|
||||
$entry->rank = $rank;
|
||||
// We don't really get a rank for seating if we have certain flags
|
||||
if ($mode === 'seating') {
|
||||
if ($entry->hasFlag('declined')) {
|
||||
$entry->rank = 'Declined';
|
||||
} elseif ($entry->hasFlag('no_show')) {
|
||||
$entry->rank = 'No Show';
|
||||
} elseif ($entry->hasFlag('failed_prelim')) {
|
||||
$entry->rank = 'Failed Prelim';
|
||||
}
|
||||
}
|
||||
|
||||
if (is_numeric($entry->rank)) {
|
||||
$rank++;
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
|
||||
protected function basicValidation($mode, Audition $audition): void
|
||||
{
|
||||
if ($mode !== 'seating' && $mode !== 'advancement') {
|
||||
throw new TabulationException('Mode must be seating or advancement');
|
||||
}
|
||||
if (! $audition->exists()) {
|
||||
throw new TabulationException('Invalid audition provided');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Actions\Tabulation;
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Seat;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UnpublishSeats
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(Audition $audition): void
|
||||
{
|
||||
$audition->removeFlag('seats_published');
|
||||
Cache::forget('resultsSeatList');
|
||||
Seat::where('audition_id', $audition->id)->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AuditionChange
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
public bool $refreshCache;
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(bool $refreshCache = false)
|
||||
{
|
||||
$this->refreshCache = $refreshCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EntryChange
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
public int $auditionId;
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct($auditionId = null)
|
||||
{
|
||||
$this->auditionId = $auditionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ScoreSheetChange
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
public int $entryId;
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct($entryId = null)
|
||||
{
|
||||
$this->entryId = $entryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ScoringGuideChange
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SeatingLimitChange
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AuditionServiceException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ScoreEntryException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ class DrawController extends Controller
|
|||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$events = Event::with('auditions')->get();
|
||||
$events = Event::with('auditions.flags')->get();
|
||||
// $drawnAuditionsExist is true if any audition->hasFlag('drawn') is true
|
||||
$drawnAuditionsExist = Audition::whereHas('flags', function ($query) {
|
||||
$query->where('flag_name', 'drawn');
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class RoomController extends Controller
|
|||
if (! Auth::user()->is_admin) {
|
||||
abort(403);
|
||||
}
|
||||
$rooms = Room::with('auditions.entries')->orderBy('name')->get();
|
||||
$rooms = Room::with('auditions.entries','entries')->orderBy('name')->get();
|
||||
|
||||
return view('admin.rooms.index', ['rooms' => $rooms]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,9 @@ class SchoolController extends Controller
|
|||
|
||||
public function index()
|
||||
{
|
||||
if (! Auth::user()->is_admin) {
|
||||
abort(403);
|
||||
}
|
||||
$schools = School::with(['users', 'students', 'entries'])->orderBy('name')->get();
|
||||
$schoolTotalFees = [];
|
||||
|
||||
foreach ($schools as $school) {
|
||||
$schoolTotalFees[$school->id] = $this->invoiceService->getGrandTotal($school->id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class EntryController extends Controller
|
|||
});
|
||||
$auditions = Audition::open()->get();
|
||||
$students = Auth::user()->students;
|
||||
$students->load('school');
|
||||
|
||||
return view('entries.index', ['entries' => $entries, 'students' => $students, 'auditions' => $auditions]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ namespace App\Http\Controllers;
|
|||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\JudgeAdvancementVote;
|
||||
use App\Models\ScoreSheet;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Models\ScoreSheet;
|
||||
|
||||
use function compact;
|
||||
use function redirect;
|
||||
|
|
@ -20,6 +20,7 @@ class JudgingController extends Controller
|
|||
public function index()
|
||||
{
|
||||
$rooms = Auth::user()->judgingAssignments;
|
||||
$rooms->load('auditions');
|
||||
|
||||
return view('judging.index', compact('rooms'));
|
||||
}
|
||||
|
|
@ -150,6 +151,7 @@ class JudgingController extends Controller
|
|||
return redirect(url()->previous())->with('error', 'Error saving advancement vote');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,36 +2,63 @@
|
|||
|
||||
namespace App\Http\Controllers\Tabulation;
|
||||
|
||||
use App\Actions\Tabulation\RankAuditionEntries;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Services\TabulationService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AdvancementController extends Controller
|
||||
{
|
||||
protected TabulationService $tabulationService;
|
||||
|
||||
public function __construct(TabulationService $tabulationService)
|
||||
protected RankAuditionEntries $ranker;
|
||||
public function __construct(RankAuditionEntries $ranker)
|
||||
{
|
||||
$this->tabulationService = $tabulationService;
|
||||
$this->ranker = $ranker;
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
$auditions = $this->tabulationService->getAuditionsWithStatus('advancement');
|
||||
$auditions = Audition::forAdvancement()
|
||||
->with('flags')
|
||||
->withCount([
|
||||
'entries' => function ($query) {
|
||||
$query->where('for_advancement', 1);
|
||||
},
|
||||
])
|
||||
->withCount([
|
||||
'unscoredEntries' => function ($query) {
|
||||
$query->where('for_advancement', 1);
|
||||
},
|
||||
])
|
||||
->get();
|
||||
$auditionData = [];
|
||||
$auditions->each(function ($audition) use (&$auditionData) {
|
||||
$scoredPercent = ($audition->entries_count > 0) ?
|
||||
round((($audition->entries_count - $audition->unscored_entries_count) / $audition->entries_count) * 100)
|
||||
: 100;
|
||||
$auditionData[] = [
|
||||
'id' => $audition->id,
|
||||
'name' => $audition->name,
|
||||
'entries_count' => $audition->entries_count,
|
||||
'unscored_entries_count' => $audition->unscored_entries_count,
|
||||
'scored_entries_count' => $audition->entries_count - $audition->unscored_entries_count,
|
||||
'scored_percentage' => $scoredPercent,
|
||||
'scoring_complete' => $audition->unscored_entries_count == 0,
|
||||
'published' => $audition->hasFlag('advancement_published'),
|
||||
];
|
||||
});
|
||||
|
||||
return view('tabulation.advancement.status', compact('auditions'));
|
||||
return view('tabulation.advancement.status', compact('auditionData'));
|
||||
}
|
||||
|
||||
public function ranking(Request $request, Audition $audition)
|
||||
{
|
||||
$entries = $this->tabulationService->auditionEntries($audition->id, 'advancement');
|
||||
$entries = $this->ranker->rank('advancement', $audition);
|
||||
$entries->load('advancementVotes');
|
||||
|
||||
|
||||
$scoringComplete = $entries->every(function ($entry) {
|
||||
return $entry->scoring_complete;
|
||||
return $entry->score_totals[0] >= 0;
|
||||
});
|
||||
|
||||
return view('tabulation.advancement.ranking', compact('audition', 'entries', 'scoringComplete'));
|
||||
|
|
@ -46,8 +73,9 @@ class AdvancementController extends Controller
|
|||
foreach ($entries as $entry) {
|
||||
$entry->addFlag('will_advance');
|
||||
}
|
||||
|
||||
return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been set successfully');
|
||||
Cache::forget('audition'.$audition->id.'advancement');
|
||||
return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success',
|
||||
'Passers have been set successfully');
|
||||
}
|
||||
|
||||
public function clearAuditionPassers(Request $request, Audition $audition)
|
||||
|
|
@ -56,7 +84,8 @@ class AdvancementController extends Controller
|
|||
foreach ($audition->entries as $entry) {
|
||||
$entry->removeFlag('will_advance');
|
||||
}
|
||||
|
||||
return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success', 'Passers have been cleared successfully');
|
||||
Cache::forget('audition'.$audition->id.'advancement');
|
||||
return redirect()->route('advancement.ranking', ['audition' => $audition->id])->with('success',
|
||||
'Passers have been cleared successfully');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ namespace App\Http\Controllers\Tabulation;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFlag;
|
||||
use App\Services\DoublerService;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class DoublerDecisionController extends Controller
|
||||
{
|
||||
protected $doublerService;
|
||||
|
||||
protected $entryService;
|
||||
|
||||
public function __construct(DoublerService $doublerService, EntryService $entryService)
|
||||
|
|
@ -21,24 +22,16 @@ class DoublerDecisionController extends Controller
|
|||
|
||||
public function accept(Entry $entry)
|
||||
{
|
||||
$doublerInfo = $this->doublerService->getDoublerInfo($entry->student_id);
|
||||
foreach ($doublerInfo as $info) {
|
||||
$this->entryService->clearEntryCacheForAudition($info['auditionID']);
|
||||
if ($info['entryID'] != $entry->id) {
|
||||
try {
|
||||
EntryFlag::create([
|
||||
'entry_id' => $info['entryID'],
|
||||
'flag_name' => 'declined',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Entry ID'.$info['entryID'].' has already been declined.');
|
||||
}
|
||||
|
||||
$doublerInfo = $this->doublerService->simpleDoubleInfo($entry);
|
||||
foreach ($doublerInfo as $doublerEntry) {
|
||||
/** @var Entry $doublerEntry */
|
||||
if ($doublerEntry->id !== $entry->id) {
|
||||
$doublerEntry->addFlag('declined');
|
||||
}
|
||||
}
|
||||
$this->doublerService->refreshDoublerCache();
|
||||
|
||||
$returnMessage = $entry->student->full_name().' accepted seating in '.$entry->audition->name;
|
||||
$this->clearCache($entry);
|
||||
|
||||
return redirect()->back()->with('success', $returnMessage);
|
||||
|
||||
|
|
@ -52,10 +45,17 @@ class DoublerDecisionController extends Controller
|
|||
|
||||
$entry->addFlag('declined');
|
||||
|
||||
$this->doublerService->refreshDoublerCache();
|
||||
|
||||
$returnMessage = $entry->student->full_name().' declined seating in '.$entry->audition->name;
|
||||
$this->clearCache($entry);
|
||||
|
||||
return redirect()->back()->with('success', $returnMessage);
|
||||
}
|
||||
|
||||
protected function clearCache($entry)
|
||||
{
|
||||
$cacheKey = 'event'.$entry->audition->event_id.'doublers-seating';
|
||||
Cache::forget($cacheKey);
|
||||
$cacheKey = 'event'.$entry->audition->event_id.'doublers-advancement';
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Tabulation;
|
||||
|
||||
use App\Actions\Tabulation\CalculateEntryScore;
|
||||
use App\Actions\Tabulation\GetAuditionSeats;
|
||||
use App\Actions\Tabulation\RankAuditionEntries;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audition;
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\DoublerService;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SeatAuditionFormController extends Controller
|
||||
{
|
||||
protected CalculateEntryScore $calc;
|
||||
|
||||
protected DoublerService $doublerService;
|
||||
|
||||
protected RankAuditionEntries $ranker;
|
||||
|
||||
protected EntryService $entryService;
|
||||
|
||||
protected AuditionService $auditionService;
|
||||
|
||||
public function __construct(
|
||||
CalculateEntryScore $calc,
|
||||
RankAuditionEntries $ranker,
|
||||
DoublerService $doublerService,
|
||||
EntryService $entryService,
|
||||
AuditionService $auditionService,
|
||||
) {
|
||||
$this->calc = $calc;
|
||||
$this->ranker = $ranker;
|
||||
$this->doublerService = $doublerService;
|
||||
$this->entryService = $entryService;
|
||||
$this->auditionService = $auditionService;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Audition $audition)
|
||||
{
|
||||
// If a seating proposal was posted, deal wth it
|
||||
if ($request->method() == 'POST') {
|
||||
$requestedEnsembleAccepts = $request->input('ensembleAccept');
|
||||
} else {
|
||||
$requestedEnsembleAccepts = false;
|
||||
}
|
||||
|
||||
$entryData = [];
|
||||
$entries = $this->ranker->rank('seating', $audition);
|
||||
$entries->load('student.school');
|
||||
$seatable = [
|
||||
'allScored' => true,
|
||||
'doublersResolved' => true,
|
||||
];
|
||||
foreach ($entries as $entry) {
|
||||
$totalScoreColumn = 'No Score';
|
||||
$fullyScored = false;
|
||||
if ($entry->score_totals) {
|
||||
$totalScoreColumn = $entry->score_totals[0] >= 0 ? $entry->score_totals[0] : $entry->score_message;
|
||||
$fullyScored = $entry->score_totals[0] >= 0;
|
||||
}
|
||||
$doublerData = $this->doublerService->entryDoublerData($entry);
|
||||
$entryData[] = [
|
||||
'rank' => $entry->rank,
|
||||
'id' => $entry->id,
|
||||
'studentName' => $entry->student->full_name(),
|
||||
'schoolName' => $entry->student->school->name,
|
||||
'drawNumber' => $entry->draw_number,
|
||||
'totalScore' => $totalScoreColumn,
|
||||
'fullyScored' => $fullyScored,
|
||||
'doubleData' => $doublerData,
|
||||
];
|
||||
// If this entries double decision isn't made, block seating
|
||||
if ($doublerData && $doublerData[$entry->id]['status'] == 'undecided') {
|
||||
$seatable['doublersResolved'] = false;
|
||||
}
|
||||
// If entry is unscored, block seating
|
||||
if (! $fullyScored) {
|
||||
$seatable['allScored'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$rightPanel = $this->pickRightPanel($audition, $seatable);
|
||||
$seatableEntries = [];
|
||||
if ($seatable['doublersResolved'] && $seatable['allScored']) {
|
||||
$seatableEntries = $entries->reject(function ($entry) {
|
||||
if ($entry->hasFlag('declined')) {
|
||||
return true;
|
||||
}
|
||||
if ($entry->hasFlag('no_show')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return view('tabulation.auditionSeating', compact('entryData', 'audition', 'rightPanel', 'seatableEntries', 'requestedEnsembleAccepts'));
|
||||
}
|
||||
|
||||
protected function pickRightPanel(Audition $audition, array $seatable)
|
||||
{
|
||||
if ($audition->hasFlag('seats_published')) {
|
||||
$resultsWindow = new GetAuditionSeats;
|
||||
$rightPanel['view'] = 'tabulation.auditionSeating-show-published-seats';
|
||||
$rightPanel['data'] = $resultsWindow($audition);
|
||||
return $rightPanel;
|
||||
}
|
||||
if ($seatable['allScored'] == false || $seatable['doublersResolved'] == false) {
|
||||
$rightPanel['view'] = 'tabulation.auditionSeating-unable-to-seat-card';
|
||||
$rightPanel['data'] = $seatable;
|
||||
|
||||
return $rightPanel;
|
||||
}
|
||||
|
||||
$rightPanel['view'] = 'tabulation.auditionSeating-right-complete-not-published';
|
||||
$rightPanel['data'] = $this->auditionService->getSeatingLimits($audition);
|
||||
|
||||
return $rightPanel;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Tabulation;
|
||||
|
||||
use App\Actions\Tabulation\PublishSeats;
|
||||
use App\Actions\Tabulation\UnpublishSeats;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audition;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SeatingPublicationController extends Controller
|
||||
{
|
||||
public function publishSeats(Request $request, Audition $audition)
|
||||
{
|
||||
$publisher = new PublishSeats;
|
||||
$sessionKey = 'audition'.$audition->id.'seatingProposal';
|
||||
$seats = $request->session()->get($sessionKey);
|
||||
|
||||
$publisher($audition, $seats);
|
||||
|
||||
$request->session()->forget($sessionKey);
|
||||
|
||||
return redirect()->route('seating.audition', ['audition' => $audition->id]);
|
||||
}
|
||||
|
||||
public function unpublishSeats(Request $request, Audition $audition)
|
||||
{
|
||||
$publisher = new UnpublishSeats;
|
||||
$publisher($audition);
|
||||
|
||||
return redirect()->route('seating.audition', ['audition' => $audition->id]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Tabulation;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audition;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SeatingStatusController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$auditions = Audition::forSeating()
|
||||
->withCount(['entries'=> function ($query) {
|
||||
$query->where('for_seating', 1);
|
||||
}])
|
||||
->withCount(['unscoredEntries'=>function ($query) {
|
||||
$query->where('for_seating', 1);
|
||||
}])
|
||||
->with('flags')
|
||||
->get();
|
||||
$auditionData = [];
|
||||
foreach ($auditions as $audition) {
|
||||
$auditionData[$audition->id] = [
|
||||
'id' => $audition->id,
|
||||
'name' => $audition->name,
|
||||
'scoredEntriesCount' => $audition->entries_count - $audition->unscored_entries_count,
|
||||
'totalEntriesCount' => $audition->entries_count,
|
||||
'scoredPercentage' => $audition->entries_count > 0 ? ($audition->entries_count - $audition->unscored_entries_count) / $audition->entries_count * 100 : 100,
|
||||
'scoringComplete' => $audition->unscored_entries_count === 0,
|
||||
'seatsPublished' => $audition->hasFlag('seats_published'),
|
||||
'audition' => $audition,
|
||||
];
|
||||
}
|
||||
$auditionData = collect($auditionData);
|
||||
|
||||
return view('tabulation.status', compact('auditionData'));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Tabulation;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audition;
|
||||
use App\Models\Seat;
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\DoublerService;
|
||||
use App\Services\SeatingService;
|
||||
use App\Services\TabulationService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
use function compact;
|
||||
|
||||
class TabulationController extends Controller
|
||||
{
|
||||
protected $tabulationService;
|
||||
|
||||
protected $doublerService;
|
||||
|
||||
protected $seatingService;
|
||||
|
||||
protected $auditionService;
|
||||
|
||||
public function __construct(TabulationService $tabulationService,
|
||||
DoublerService $doublerService,
|
||||
SeatingService $seatingService,
|
||||
AuditionService $auditionService)
|
||||
{
|
||||
$this->tabulationService = $tabulationService;
|
||||
$this->doublerService = $doublerService;
|
||||
$this->seatingService = $seatingService;
|
||||
$this->auditionService = $auditionService;
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
$auditions = $this->tabulationService->getAuditionsWithStatus('seating');
|
||||
|
||||
return view('tabulation.status', compact('auditions'));
|
||||
}
|
||||
|
||||
public function auditionSeating(Request $request, Audition $audition)
|
||||
{
|
||||
if ($request->method() == 'POST') {
|
||||
$requestedEnsembleAccepts = $request->input('ensembleAccept');
|
||||
} else {
|
||||
$requestedEnsembleAccepts = false;
|
||||
}
|
||||
|
||||
$entries = $this->tabulationService->auditionEntries($audition->id);
|
||||
$entries = $entries->filter(function ($entry) {
|
||||
return $entry->for_seating;
|
||||
});
|
||||
|
||||
$doublerComplete = true;
|
||||
foreach ($entries as $entry) {
|
||||
|
||||
if ($this->doublerService->studentIsDoubler($entry->student_id)) { // If this entry is a doubler
|
||||
if ($this->doublerService->getDoublerInfo($entry->student_id)[$entry->id]['status'] === 'undecided') { // If there is no decision for this entry
|
||||
$doublerComplete = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scoringComplete = $entries->every(function ($entry) {
|
||||
return $entry->scoring_complete;
|
||||
});
|
||||
$ensembleLimits = $this->seatingService->getLimitForAudition($audition->id);
|
||||
$auditionComplete = $scoringComplete && $doublerComplete;
|
||||
|
||||
$seatableEntries = $this->seatingService->getSeatableEntries($audition->id);
|
||||
$seatableEntries = $seatableEntries->filter(function ($entry) {
|
||||
return $entry->for_seating;
|
||||
});
|
||||
|
||||
return view('tabulation.auditionSeating', compact('audition',
|
||||
'entries',
|
||||
'scoringComplete',
|
||||
'doublerComplete',
|
||||
'auditionComplete',
|
||||
'ensembleLimits',
|
||||
'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);
|
||||
Cache::forget('resultsSeatList');
|
||||
Cache::forget('publishedAuditions');
|
||||
Cache::forget('audition'.$audition->id.'seats');
|
||||
|
||||
// TODO move the previous Cache functions here and in unplublish to the services, need to add an event for publishing an audition as well
|
||||
return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]);
|
||||
}
|
||||
|
||||
public function unpublishSeats(Request $request, Audition $audition)
|
||||
{
|
||||
// TODO move this to SeatingService
|
||||
$audition->removeFlag('seats_published');
|
||||
Cache::forget('resultsSeatList');
|
||||
Cache::forget('publishedAuditions');
|
||||
Cache::forget('audition'.$audition->id.'seats');
|
||||
$this->seatingService->forgetSeatsForAudition($audition->id);
|
||||
Seat::where('audition_id', $audition->id)->delete();
|
||||
|
||||
return redirect()->route('tabulation.audition.seat', ['audition' => $audition->id]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\Invoice\InvoiceDataService;
|
||||
use App\Services\TabulationService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class TestController extends Controller
|
||||
{
|
||||
protected $scoringGuideCacheService;
|
||||
protected $tabulationService;
|
||||
protected $invoiceService;
|
||||
|
||||
public function __construct(
|
||||
AuditionService $scoringGuideCacheService,
|
||||
TabulationService $tabulationService,
|
||||
InvoiceDataService $invoiceService
|
||||
) {
|
||||
$this->scoringGuideCacheService = $scoringGuideCacheService;
|
||||
$this->tabulationService = $tabulationService;
|
||||
$this->invoiceService = $invoiceService;
|
||||
}
|
||||
|
||||
public function flashTest(Request $request)
|
||||
{
|
||||
$lines = $this->invoiceService->getLines(12);
|
||||
$totalFees = $this->invoiceService->getGrandTotal(12);
|
||||
return view('test', compact('lines','totalFees'));
|
||||
}
|
||||
}
|
||||
|
|
@ -83,4 +83,3 @@ class UserController extends Controller
|
|||
}
|
||||
|
||||
|
||||
//TODO allow users to modify their profile information. RoomJudgeChange::dispatch(); when they do
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\AuditionChange;
|
||||
use App\Services\AuditionService;
|
||||
|
||||
class RefreshAuditionCache
|
||||
{
|
||||
protected $auditionService;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct(AuditionService $cacheService)
|
||||
{
|
||||
$this->auditionService = $cacheService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(AuditionChange $event): void
|
||||
{
|
||||
if ($event->refreshCache) {
|
||||
$this->auditionService->refreshCache();
|
||||
} else {
|
||||
$this->auditionService->clearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\AuditionChange;
|
||||
use App\Events\EntryChange;
|
||||
use App\Services\AuditionService;
|
||||
use App\Services\EntryService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class RefreshEntryCache
|
||||
{
|
||||
protected $entryService;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct(EntryService $cacheService)
|
||||
{
|
||||
$this->entryService = $cacheService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(EntryChange $event): void
|
||||
{
|
||||
if ($event->auditionId) {
|
||||
$this->entryService->clearEntryCacheForAudition($event->auditionId);
|
||||
} else {
|
||||
$this->entryService->clearEntryCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ScoreSheetChange;
|
||||
use App\Services\ScoreService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class RefreshScoreSheetCache
|
||||
{
|
||||
protected $scoreService;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct(ScoreService $scoreService)
|
||||
{
|
||||
$this->scoreService = $scoreService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(ScoreSheetChange $event): void
|
||||
{
|
||||
$this->scoreService->clearScoreSheetCountCache();
|
||||
if ($event->entryId) {
|
||||
$this->scoreService->clearEntryTotalScoresCache($event->entryId);
|
||||
}
|
||||
// If we are in local environment, send a success flash message
|
||||
if (config('app.env') === 'local') {
|
||||
session()->flash('success','Cleared cache for entry ID ' . $event->entryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ScoringGuideChange;
|
||||
use App\Services\ScoreService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class RefreshScoringGuideCache
|
||||
{
|
||||
protected $scoreService;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct(ScoreService $scoreService)
|
||||
{
|
||||
$this->scoreService = $scoreService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(ScoringGuideChange $event): void
|
||||
{
|
||||
$this->scoreService->clearScoringGuideCache();
|
||||
$this->scoreService->clearAllCachedTotalScores();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\SeatingLimitChange;
|
||||
use App\Services\SeatingService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class RefreshSeatingLimitCache
|
||||
{
|
||||
protected $seatingService;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct(SeatingService $seatingService)
|
||||
{
|
||||
$this->seatingService = $seatingService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(SeatingLimitChange $event): void
|
||||
{
|
||||
$this->seatingService->refreshLimits();
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@ class Audition extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @var int|mixed
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
public function event(): BelongsTo
|
||||
|
|
@ -29,6 +32,12 @@ class Audition extends Model
|
|||
return $this->hasMany(Entry::class);
|
||||
}
|
||||
|
||||
public function unscoredEntries(): HasMany
|
||||
{
|
||||
return $this->hasMany(Entry::class)
|
||||
->whereDoesntHave('scoreSheets');
|
||||
}
|
||||
|
||||
public function room(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Room::class);
|
||||
|
|
@ -114,12 +123,12 @@ class Audition extends Model
|
|||
|
||||
public function scopeForSeating(Builder $query): void
|
||||
{
|
||||
$query->where('for_seating', 1);
|
||||
$query->where('for_seating', 1)->orderBy('score_order');
|
||||
}
|
||||
|
||||
public function scopeForAdvancement(Builder $query): void
|
||||
{
|
||||
$query->where('for_advancement', 1);
|
||||
$query->where('for_advancement', 1)->orderBy('score_order');
|
||||
}
|
||||
|
||||
public function scopeSeatsPublished(Builder $query): Builder
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use App\Models\ScoreSheet;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Entry extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Models;
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
|
||||
class Event extends Model
|
||||
{
|
||||
|
|
@ -21,4 +22,9 @@ class Event extends Model
|
|||
return $this->hasMany(Ensemble::class)
|
||||
->orderBy('rank');
|
||||
}
|
||||
|
||||
public function entries(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Entry::class, Audition::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,11 @@ class Room extends Model
|
|||
{
|
||||
$this->judges()->attach($userId);
|
||||
$this->load('judges');
|
||||
AuditionChange::dispatch();
|
||||
}
|
||||
|
||||
public function removeJudge($userId): void
|
||||
{
|
||||
$this->judges()->detach($userId);
|
||||
$this->load('judges');
|
||||
AuditionChange::dispatch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
|
||||
public function isJudge(): bool
|
||||
{
|
||||
return $this->judgingAssignments->count() > 0;
|
||||
return $this->judgingAssignments()->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ class AuditionObserver
|
|||
*/
|
||||
public function created(Audition $audition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
EntryChange::dispatch($audition->id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -22,8 +21,7 @@ class AuditionObserver
|
|||
*/
|
||||
public function updated(Audition $audition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
EntryChange::dispatch($audition->id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,8 +29,7 @@ class AuditionObserver
|
|||
*/
|
||||
public function deleted(Audition $audition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
EntryChange::dispatch($audition->id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -40,8 +37,7 @@ class AuditionObserver
|
|||
*/
|
||||
public function restored(Audition $audition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
EntryChange::dispatch($audition->id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -49,7 +45,6 @@ class AuditionObserver
|
|||
*/
|
||||
public function forceDeleted(Audition $audition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
EntryChange::dispatch($audition->id);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class RoomObserver
|
|||
*/
|
||||
public function updated(Room $room): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +28,7 @@ class RoomObserver
|
|||
*/
|
||||
public function deleted(Room $room): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +36,7 @@ class RoomObserver
|
|||
*/
|
||||
public function restored(Room $room): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,6 +44,6 @@ class RoomObserver
|
|||
*/
|
||||
public function forceDeleted(Room $room): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class RoomUserObserver
|
|||
*/
|
||||
public function created(RoomUser $roomUser): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +20,7 @@ class RoomUserObserver
|
|||
*/
|
||||
public function updated(RoomUser $roomUser): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +28,7 @@ class RoomUserObserver
|
|||
*/
|
||||
public function deleted(RoomUser $roomUser): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +36,7 @@ class RoomUserObserver
|
|||
*/
|
||||
public function restored(RoomUser $roomUser): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,6 +44,6 @@ class RoomUserObserver
|
|||
*/
|
||||
public function forceDeleted(RoomUser $roomUser): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class ScoreSheetObserver
|
|||
*/
|
||||
public function created(ScoreSheet $scoreSheet): void
|
||||
{
|
||||
ScoreSheetChange::dispatch($scoreSheet->entry_id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +20,7 @@ class ScoreSheetObserver
|
|||
*/
|
||||
public function updated(ScoreSheet $scoreSheet): void
|
||||
{
|
||||
ScoreSheetChange::dispatch($scoreSheet->entry_id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +28,7 @@ class ScoreSheetObserver
|
|||
*/
|
||||
public function deleted(ScoreSheet $scoreSheet): void
|
||||
{
|
||||
ScoreSheetChange::dispatch($scoreSheet->entry_id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +36,7 @@ class ScoreSheetObserver
|
|||
*/
|
||||
public function restored(ScoreSheet $scoreSheet): void
|
||||
{
|
||||
ScoreSheetChange::dispatch($scoreSheet->entry_id);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,6 +44,6 @@ class ScoreSheetObserver
|
|||
*/
|
||||
public function forceDeleted(ScoreSheet $scoreSheet): void
|
||||
{
|
||||
ScoreSheetChange::dispatch($scoreSheet->entry_id);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class ScoringGuideObserver
|
|||
*/
|
||||
public function created(ScoringGuide $scoringGuide): void
|
||||
{
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -21,8 +21,7 @@ class ScoringGuideObserver
|
|||
*/
|
||||
public function updated(ScoringGuide $scoringGuide): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -30,8 +29,7 @@ class ScoringGuideObserver
|
|||
*/
|
||||
public function deleted(ScoringGuide $scoringGuide): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,8 +37,7 @@ class ScoringGuideObserver
|
|||
*/
|
||||
public function restored(ScoringGuide $scoringGuide): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +45,6 @@ class ScoringGuideObserver
|
|||
*/
|
||||
public function forceDeleted(ScoringGuide $scoringGuide): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class SeatingLimitObserver
|
|||
*/
|
||||
public function created(SeatingLimit $seatingLimit): void
|
||||
{
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +20,7 @@ class SeatingLimitObserver
|
|||
*/
|
||||
public function updated(SeatingLimit $seatingLimit): void
|
||||
{
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +28,7 @@ class SeatingLimitObserver
|
|||
*/
|
||||
public function deleted(SeatingLimit $seatingLimit): void
|
||||
{
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +36,7 @@ class SeatingLimitObserver
|
|||
*/
|
||||
public function restored(SeatingLimit $seatingLimit): void
|
||||
{
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,6 +44,6 @@ class SeatingLimitObserver
|
|||
*/
|
||||
public function forceDeleted(SeatingLimit $seatingLimit): void
|
||||
{
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ class SubscoreDefinitionObserver
|
|||
*/
|
||||
public function created(SubscoreDefinition $subscoreDefinition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -22,8 +21,7 @@ class SubscoreDefinitionObserver
|
|||
*/
|
||||
public function updated(SubscoreDefinition $subscoreDefinition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,8 +29,7 @@ class SubscoreDefinitionObserver
|
|||
*/
|
||||
public function deleted(SubscoreDefinition $subscoreDefinition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -40,8 +37,7 @@ class SubscoreDefinitionObserver
|
|||
*/
|
||||
public function restored(SubscoreDefinition $subscoreDefinition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -49,7 +45,6 @@ class SubscoreDefinitionObserver
|
|||
*/
|
||||
public function forceDeleted(SubscoreDefinition $subscoreDefinition): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
ScoringGuideChange::dispatch();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class UserObserver
|
|||
*/
|
||||
public function updated(User $user): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +28,7 @@ class UserObserver
|
|||
*/
|
||||
public function deleted(User $user): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,6 +44,6 @@ class UserObserver
|
|||
*/
|
||||
public function forceDeleted(User $user): void
|
||||
{
|
||||
AuditionChange::dispatch();
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,15 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\AuditionChange;
|
||||
use App\Events\EntryChange;
|
||||
use App\Events\ScoreSheetChange;
|
||||
use App\Events\ScoringGuideChange;
|
||||
use App\Events\SeatingLimitChange;
|
||||
use App\Listeners\RefreshAuditionCache;
|
||||
use App\Listeners\RefreshEntryCache;
|
||||
use App\Listeners\RefreshScoreSheetCache;
|
||||
use App\Listeners\RefreshScoringGuideCache;
|
||||
use App\Listeners\RefreshSeatingLimitCache;
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\CalculateEntryScore;
|
||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
use App\Models\RoomUser;
|
||||
use App\Models\School;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\ScoringGuide;
|
||||
use App\Models\SeatingLimit;
|
||||
use App\Models\Student;
|
||||
|
|
@ -38,12 +32,10 @@ use App\Services\DoublerService;
|
|||
use App\Services\DrawService;
|
||||
use App\Services\EntryService;
|
||||
use App\Services\ScoreService;
|
||||
use App\Services\SeatingService;
|
||||
use App\Services\TabulationService;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use App\Services\StudentService;
|
||||
use App\Services\UserService;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Models\ScoreSheet;
|
||||
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -52,36 +44,14 @@ class AppServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(DrawService::class, function () {
|
||||
return new DrawService();
|
||||
});
|
||||
|
||||
$this->app->singleton(AuditionService::class, function () {
|
||||
return new AuditionService();
|
||||
});
|
||||
|
||||
$this->app->singleton(SeatingService::class, function ($app) {
|
||||
return new SeatingService($app->make(TabulationService::class));
|
||||
});
|
||||
|
||||
$this->app->singleton(EntryService::class, function ($app) {
|
||||
return new EntryService($app->make(AuditionService::class));
|
||||
});
|
||||
|
||||
$this->app->singleton(ScoreService::class, function ($app) {
|
||||
return new ScoreService($app->make(AuditionService::class), $app->make(EntryService::class));
|
||||
});
|
||||
|
||||
$this->app->singleton(TabulationService::class, function ($app) {
|
||||
return new TabulationService(
|
||||
$app->make(AuditionService::class),
|
||||
$app->make(ScoreService::class),
|
||||
$app->make(EntryService::class));
|
||||
});
|
||||
|
||||
$this->app->singleton(DoublerService::class, function ($app) {
|
||||
return new DoublerService($app->make(AuditionService::class), $app->make(TabulationService::class), $app->make(SeatingService::class));
|
||||
});
|
||||
$this->app->singleton(CalculateScoreSheetTotal::class, CalculateScoreSheetTotal::class);
|
||||
$this->app->singleton(CalculateEntryScore::class, AllJudgesCount::class);
|
||||
$this->app->singleton(DrawService::class, DrawService::class);
|
||||
$this->app->singleton(AuditionService::class, AuditionService::class);
|
||||
$this->app->singleton(EntryService::class, EntryService::class);
|
||||
$this->app->singleton(ScoreService::class, ScoreService::class);
|
||||
$this->app->singleton(UserService::class, UserService::class);
|
||||
$this->app->singleton(DoublerService::class, DoublerService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -102,29 +72,6 @@ class AppServiceProvider extends ServiceProvider
|
|||
User::observe(UserObserver::class);
|
||||
SeatingLimit::observe(SeatingLimitObserver::class);
|
||||
|
||||
Event::listen(
|
||||
AuditionChange::class,
|
||||
RefreshAuditionCache::class
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
EntryChange::class,
|
||||
RefreshEntryCache::class
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
ScoringGuideChange::class,
|
||||
RefreshScoringGuideCache::class
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
ScoreSheetChange::class,
|
||||
RefreshScoreSheetCache::class
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
SeatingLimitChange::class,
|
||||
RefreshSeatingLimitCache::class
|
||||
);
|
||||
//Model::preventLazyLoading(! app()->isProduction());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\CalculateEntryScore;
|
||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class CalculateEntryScoreProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(CalculateScoreSheetTotal::class, CalculateScoreSheetTotal::class);
|
||||
$this->app->singleton(CalculateEntryScore::class, AllJudgesCount::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -2,102 +2,131 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\AuditionServiceException;
|
||||
use App\Models\Audition;
|
||||
use App\Models\ScoringGuide;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Models\Ensemble;
|
||||
use App\Models\SeatingLimit;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class AuditionService
|
||||
{
|
||||
protected $cacheKey = 'auditions';
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public static Collection $allAuditionIds;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
$cacheKey = 'allAuditionIds';
|
||||
self::$allAuditionIds = Cache::remember($cacheKey, 60, function () {
|
||||
return Audition::pluck('id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return or fill cache of auditions including the audition,
|
||||
* scoringGuide.subscores, judges, judges_count, and entries_count
|
||||
* @throws AuditionServiceException
|
||||
*/
|
||||
public function getAuditions($mode = 'seating'): \Illuminate\Database\Eloquent\Collection
|
||||
public function getSubscores(Audition $audition, $mode = 'seating', $sort = 'tiebreak')
|
||||
{
|
||||
$auditions = Cache::remember($this->cacheKey, 3600, function () {
|
||||
if (App::environment('local')) {
|
||||
Session::flash('success', 'Audition Cache Updated');
|
||||
}
|
||||
$cacheKey = 'auditionSubscores-'.$audition->id.'-'.$mode.'-'.$sort;
|
||||
|
||||
return Audition::with(['scoringGuide.subscores', 'judges'])
|
||||
->withCount('judges')
|
||||
->withCount('entries')
|
||||
->withCount(['entries as seating_entries_count' => function (Builder $query) {
|
||||
$query->where('for_seating', true);
|
||||
}])
|
||||
->withCount(['entries as advancement_entries_count' => function (Builder $query) {
|
||||
$query->where('for_advancement', true);
|
||||
}])
|
||||
->orderBy('score_order')
|
||||
->get()
|
||||
->keyBy('id');
|
||||
});
|
||||
return Cache::remember($cacheKey, 10, function () use ($audition, $mode, $sort) {
|
||||
$this->validateAudition($audition);
|
||||
$this->validateMode($mode);
|
||||
$this->validateSort($sort);
|
||||
|
||||
switch ($mode) {
|
||||
case 'seating':
|
||||
return $auditions->filter(fn ($audition) => $audition->for_seating);
|
||||
case 'advancement':
|
||||
return $auditions->filter(fn ($audition) => $audition->for_advancement);
|
||||
default:
|
||||
return $auditions;
|
||||
}
|
||||
}
|
||||
$sortColumn = match ($sort) {
|
||||
'tiebreak' => 'tiebreak_order',
|
||||
'display' => 'display_order',
|
||||
};
|
||||
$modeColumn = match ($mode) {
|
||||
'seating' => 'for_seating',
|
||||
'advancement' => 'for_advance',
|
||||
};
|
||||
$audition->load('scoringGuide.subscores');
|
||||
|
||||
public function getAudition($id): Audition
|
||||
{
|
||||
return $this->getAuditions()->firstWhere('id', $id);
|
||||
}
|
||||
|
||||
public function refreshCache(): void
|
||||
{
|
||||
Cache::forget($this->cacheKey);
|
||||
$this->getAuditions();
|
||||
}
|
||||
|
||||
public function clearCache(): void
|
||||
{
|
||||
Cache::forget($this->cacheKey);
|
||||
}
|
||||
|
||||
public function getPublishedAuditions()
|
||||
{
|
||||
$cacheKey = 'publishedAuditions';
|
||||
|
||||
return Cache::remember(
|
||||
$cacheKey,
|
||||
now()->addHour(),
|
||||
function () {
|
||||
return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition) => $audition->hasFlag('seats_published'));
|
||||
return $audition->scoringGuide->subscores->where($modeColumn, true)->sortBy($sortColumn);
|
||||
});
|
||||
}
|
||||
|
||||
public function getPublishedAdvancementAuditions()
|
||||
public function getJudgesOLD(Audition $audition)
|
||||
{
|
||||
$cacheKey = 'publishedAdvancementAuditions';
|
||||
return Cache::remember(
|
||||
$cacheKey,
|
||||
now()->addHour(),
|
||||
function () {
|
||||
return Audition::with('flags')->orderBy('score_order')->get()->filter(fn ($audition) => $audition->hasFlag('advancement_published'));
|
||||
$cacheKey = 'auditionJudges-'.$audition->id;
|
||||
|
||||
return Cache::remember($cacheKey, 10, function () use ($audition) {
|
||||
$this->validateAudition($audition);
|
||||
|
||||
return $audition->judges;
|
||||
});
|
||||
}
|
||||
|
||||
public function getJudges(Audition $audition)
|
||||
{
|
||||
$cacheKey = 'auditionJudgeAssignments';
|
||||
$assignments = Cache::remember($cacheKey, 60, function () {
|
||||
$allAuditions = Audition::with('judges')->get();
|
||||
$return = [];
|
||||
foreach ($allAuditions as $audition) {
|
||||
$return[$audition->id] = $audition->judges;
|
||||
}
|
||||
|
||||
return $return;
|
||||
});
|
||||
|
||||
return $assignments[$audition->id];
|
||||
}
|
||||
|
||||
public function clearPublishedAuditionsCache(): void
|
||||
public function getSeatingLimits(Audition $audition)
|
||||
{
|
||||
Cache::forget('publishedAuditions');
|
||||
$cacheKey = 'auditionSeatingLimits';
|
||||
$allLimits = Cache::remember($cacheKey, 60, function () {
|
||||
$lims = [];
|
||||
$auditions = Audition::all();
|
||||
$ensembles = Ensemble::orderBy('rank')->get();
|
||||
foreach ($auditions as $audition) {
|
||||
foreach ($ensembles as $ensemble) {
|
||||
if ($ensemble->event_id !== $audition->event_id) {
|
||||
continue;
|
||||
}
|
||||
$lims[$audition->id][$ensemble->id] = [
|
||||
'ensemble' => $ensemble,
|
||||
'limit' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
$limits = SeatingLimit::all();
|
||||
|
||||
foreach ($limits as $limit) {
|
||||
$lims[$limit->audition_id][$limit->ensemble_id] = [
|
||||
'ensemble' => $ensembles->find($limit->ensemble_id),
|
||||
'limit' =>$limit->maximum_accepted,
|
||||
];
|
||||
}
|
||||
return $lims;
|
||||
});
|
||||
|
||||
return $allLimits[$audition->id] ?? [];
|
||||
}
|
||||
|
||||
protected function validateAudition($audition)
|
||||
{
|
||||
if (! $audition->exists()) {
|
||||
throw new AuditionServiceException('Invalid audition provided');
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateMode($mode)
|
||||
{
|
||||
if ($mode !== 'seating' && $mode !== 'advancement') {
|
||||
throw new AuditionServiceException('Invalid mode requested. Mode must be seating or advancement');
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateSort($sort)
|
||||
{
|
||||
if ($sort !== 'tiebreak' && $sort !== 'display') {
|
||||
throw new AuditionServiceException('Invalid sort requested. Sort must be tiebreak or weight');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,124 +2,134 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Event;
|
||||
use App\Models\Student;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class DoublerService
|
||||
{
|
||||
protected $doublersCacheKey = 'doublers';
|
||||
protected EntryService $entryService;
|
||||
|
||||
protected $auditionService;
|
||||
protected AuditionService $auditionService;
|
||||
|
||||
protected $tabulationService;
|
||||
|
||||
protected $seatingService;
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(AuditionService $auditionService, TabulationService $tabulationService, SeatingService $seatingService)
|
||||
public function __construct(EntryService $entryService, AuditionService $auditionService)
|
||||
{
|
||||
$this->entryService = $entryService;
|
||||
$this->auditionService = $auditionService;
|
||||
$this->tabulationService = $tabulationService;
|
||||
$this->seatingService = $seatingService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of students that have more than one entry
|
||||
* returns a collection of doublers for the event in the form of
|
||||
* [studentId => [student=>student, entries=>[entries]]
|
||||
*/
|
||||
public function getDoublers(): \Illuminate\Database\Eloquent\Collection
|
||||
public function doublersForEvent(Event $event, string $mode = 'seating')
|
||||
{
|
||||
// TODO creating or destroying an entry should refresh the doubler cache
|
||||
// TODO needs to split by event so that a doubler may enter jazz and concert events for example
|
||||
$doublers = Cache::remember($this->doublersCacheKey, 60, function () {
|
||||
return Student::withCount(['entries' => function (Builder $query) {
|
||||
$query->where('for_seating', true);
|
||||
}])
|
||||
->with(['entries' => function (Builder $query) {
|
||||
$query->where('for_seating', true);
|
||||
}])
|
||||
->havingRaw('entries_count > ?', [1])
|
||||
->get();
|
||||
$cacheKey = 'event'.$event->id.'doublers-'.$mode;
|
||||
|
||||
return Cache::remember($cacheKey, 60, function () use ($event, $mode) {
|
||||
return $this->findDoublersForEvent($event, $mode);
|
||||
});
|
||||
|
||||
return $doublers;
|
||||
}
|
||||
|
||||
public function refreshDoublerCache()
|
||||
{
|
||||
Cache::forget($this->doublersCacheKey);
|
||||
$this->getDoublers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of information about each entry for a specific doubler. Info for each entry includes
|
||||
* entryID
|
||||
* auditionID
|
||||
* auditionName
|
||||
* rank => This student's rank in the given audition
|
||||
* unscored => How many entries remain to be scored in this audition
|
||||
* limits => acceptance limits for this audition
|
||||
* status => accepted, declined, or undecided
|
||||
*
|
||||
* @param int $studentId The ID of the doubler
|
||||
* @throws TabulationException
|
||||
*/
|
||||
public function getDoublerInfo($studentId): array
|
||||
protected function findDoublersForEvent(Event $event, string $mode = 'seating'): array
|
||||
{
|
||||
$doubler = $this->getDoublers()->firstWhere('id', $studentId);
|
||||
// TODO add scoped entry queries to the event model
|
||||
$this->validateEvent($event);
|
||||
$entries = $event->entries()->with('audition')->with('student')->get();
|
||||
$entries = match ($mode) {
|
||||
'seating' => $entries->filter(fn ($entry) => $entry->for_seating === 1),
|
||||
'advancement' => $entries->filter(fn ($entry) => $entry->for_advance === 1),
|
||||
};
|
||||
|
||||
// Split $doubler->entries into two arrays based on the result of hasFlag('declined')
|
||||
$undecidedEntries = $doubler->entries->filter(function ($entry) {
|
||||
return ! $entry->hasFlag('declined');
|
||||
});
|
||||
$acceptedEntry = null;
|
||||
if ($undecidedEntries->count() == 1) {
|
||||
$acceptedEntry = $undecidedEntries->first();
|
||||
$grouped = $entries->groupBy('student_id');
|
||||
// Filter out student groups with only one entry in the event
|
||||
$grouped = $grouped->filter(fn ($s) => $s->count() > 1);
|
||||
$doubler_array = [];
|
||||
foreach ($grouped as $student_id => $entries) {
|
||||
$doubler_array[$student_id] = [
|
||||
'student_id' => $student_id,
|
||||
'entries' => $entries,
|
||||
];
|
||||
}
|
||||
// TODO can I rewrite this?
|
||||
|
||||
// When getting a doubler we need to know
|
||||
// 1) What their entries are
|
||||
// 2) For each audition they're entered in, what is their rank
|
||||
// 3) For each audition they're entered in, how many entries are unscored
|
||||
// 4) How many are accepted on that instrument
|
||||
// 5) Status - accepted, declined or undecided
|
||||
return $doubler_array;
|
||||
}
|
||||
|
||||
$info = [];
|
||||
public function simpleDoubleInfo(Entry $primaryEntry)
|
||||
{
|
||||
if (! isset($this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($doubler->entries as $entry) {
|
||||
return $this->findDoublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries'];
|
||||
}
|
||||
|
||||
public function entryDoublerData(Entry $primaryEntry)
|
||||
{
|
||||
if (! isset($this->doublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id])) {
|
||||
return false;
|
||||
}
|
||||
$entries = $this->doublersForEvent($primaryEntry->audition->event)[$primaryEntry->student_id]['entries'];
|
||||
$entryData = collect([]);
|
||||
/** @var Collection $entries */
|
||||
foreach ($entries as $entry) {
|
||||
$status = 'undecided';
|
||||
if ($entry->hasFlag('declined')) {
|
||||
$status = 'declined';
|
||||
} elseif ($entry === $acceptedEntry) {
|
||||
$status = 'accepted';
|
||||
} else {
|
||||
$status = 'undecided';
|
||||
}
|
||||
$info[$entry->id] = [
|
||||
'entryID' => $entry->id,
|
||||
'auditionID' => $entry->audition_id,
|
||||
'auditionName' => $this->auditionService->getAudition($entry->audition_id)->name,
|
||||
'rank' => $this->tabulationService->entryRank($entry),
|
||||
'unscored' => $this->tabulationService->remainingEntriesForAudition($entry->audition_id),
|
||||
'limits' => $this->seatingService->getLimitForAudition($entry->audition_id),
|
||||
'status' => $status,
|
||||
];
|
||||
$entry->audition = $this->auditionService->getAudition($entry->audition_id);
|
||||
if ($entry->hasFlag('no_show')) {
|
||||
$status = 'no_show';
|
||||
}
|
||||
|
||||
return $info;
|
||||
$lims = $this->auditionService->getSeatingLimits($entry->audition);
|
||||
$limits = [];
|
||||
foreach ($lims as $lim) {
|
||||
$limits[] = [
|
||||
'ensemble_name' => $lim['ensemble']->name,
|
||||
'accepts' => $lim['limit'],
|
||||
'ensemble' => $lim['ensemble'],
|
||||
];
|
||||
}
|
||||
|
||||
$entryData[$entry->id] = [
|
||||
'entry' => $entry,
|
||||
'audition' => $entry->audition,
|
||||
'auditionName' => $entry->audition->name,
|
||||
'status' => $status,
|
||||
'rank' => $this->entryService->rankOfEntry('seating', $entry),
|
||||
'unscored_entries' => $entry->audition->unscored_entries_count,
|
||||
'seating_limits' => $limits,
|
||||
];
|
||||
}
|
||||
// find out how many items in the collection $entryData have a status of 'undecided'
|
||||
$undecided_count = $entryData->filter(fn ($entry) => $entry['status'] === 'undecided')->count();
|
||||
// if $undecided_count is 1 set the item where status is 'undecided' to 'accepted'
|
||||
if ($undecided_count === 1) {
|
||||
$entryData->transform(function ($entry) {
|
||||
if ($entry['status'] === 'undecided') {
|
||||
$entry['status'] = 'accepted';
|
||||
}
|
||||
|
||||
return $entry;
|
||||
});
|
||||
}
|
||||
|
||||
return $entryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a student is a doubler based on the given student ID
|
||||
*
|
||||
* @param int $studentId The ID of the student to check
|
||||
* @return bool Returns true if the student is a doubler, false otherwise
|
||||
* @throws TabulationException
|
||||
*/
|
||||
public function studentIsDoubler($studentId): bool
|
||||
protected function validateEvent(Event $event)
|
||||
{
|
||||
return $this->getDoublers()->contains('id', $studentId);
|
||||
if (! $event->exists) {
|
||||
throw new TabulationException('Invalid event provided');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,95 +2,22 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Actions\Tabulation\RankAuditionEntries;
|
||||
use App\Models\Entry;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class EntryService
|
||||
{
|
||||
protected $auditionCache;
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(AuditionService $auditionCache)
|
||||
public function __construct()
|
||||
{
|
||||
$this->auditionCache = $auditionCache;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a collection of all entries for the provided auditionId along with the
|
||||
* student.school for each entry.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function getEntriesForAudition($auditionId, $mode = 'seating')
|
||||
{
|
||||
// TODO this invokes a lot of lazy loading. Perhaps cache the data for all entries then draw from that for each audition
|
||||
$cacheKey = 'audition'.$auditionId.'entries';
|
||||
|
||||
$entries = Cache::remember($cacheKey, 3600, function () use ($auditionId) {
|
||||
return Entry::where('audition_id', $auditionId)
|
||||
->with('student.school')
|
||||
->get()
|
||||
->keyBy('id');
|
||||
});
|
||||
|
||||
switch ($mode) {
|
||||
case 'seating':
|
||||
return $entries->filter(function ($entry) {
|
||||
return $entry->for_seating;
|
||||
});
|
||||
case 'advancement':
|
||||
return $entries->filter(function ($entry) {
|
||||
return $entry->for_advancement;
|
||||
});
|
||||
default:
|
||||
return $entries;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of collections of entries, one collection for each audition.
|
||||
* The outer collection is keyed by the audition ID. The included entries are
|
||||
* with their student.school.
|
||||
*/
|
||||
public function getAllEntriesByAudition(): Collection
|
||||
{
|
||||
$auditions = $this->auditionCache->getAuditions();
|
||||
$allEntries = [];
|
||||
foreach ($auditions as $audition) {
|
||||
$allEntries[$audition->id] = $this->getEntriesForAudition($audition->id);
|
||||
}
|
||||
|
||||
return collect($allEntries);
|
||||
}
|
||||
|
||||
public function getAllEntries()
|
||||
{
|
||||
$cacheKey = 'allEntries';
|
||||
|
||||
return Cache::remember($cacheKey, 5, function () {
|
||||
return Entry::all();
|
||||
});
|
||||
}
|
||||
|
||||
public function clearEntryCacheForAudition($auditionId): void
|
||||
{
|
||||
$cacheKey = 'audition'.$auditionId.'entries';
|
||||
Cache::forget($cacheKey);
|
||||
Cache::forget('allEntries');
|
||||
}
|
||||
|
||||
public function clearEntryCaches(): void
|
||||
{
|
||||
$auditions = $this->auditionCache->getAuditions();
|
||||
foreach ($auditions as $audition) {
|
||||
$this->clearEntryCacheForAudition($audition->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function entryIsLate(Entry $entry): bool
|
||||
public function isEntryLate(Entry $entry): bool
|
||||
{
|
||||
if ($entry->hasFlag('wave_late_fee')) {
|
||||
return false;
|
||||
|
|
@ -98,4 +25,25 @@ class EntryService
|
|||
|
||||
return $entry->created_at > $entry->audition->entry_deadline;
|
||||
}
|
||||
|
||||
public function entryExists(Entry $entry): bool
|
||||
{
|
||||
$cacheKey = 'allEntryIds';
|
||||
$allEntryIds = Cache::remember($cacheKey, 60, function () {
|
||||
return Entry::pluck('id');
|
||||
});
|
||||
|
||||
return $allEntryIds->contains($entry->id);
|
||||
}
|
||||
|
||||
public function rankOfEntry(string $mode, Entry $entry)
|
||||
{
|
||||
$ranker = App::make(RankAuditionEntries::class);
|
||||
$rankings = $ranker->rank($mode, $entry->audition);
|
||||
$rankedEntry = $rankings->find($entry->id);
|
||||
if (isset($rankedEntry->score_message)) {
|
||||
return $rankedEntry->score_message;
|
||||
}
|
||||
return $rankings->find($entry->id)->rank ?? 'No Rank';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class InvoiceOneFeePerEntry implements InvoiceDataService
|
|||
foreach ($school->students as $student) {
|
||||
foreach ($entries[$student->id] ?? [] as $entry) {
|
||||
$entryFee = $entry->audition->entry_fee / 100;
|
||||
$lateFee = $this->entryService->entryIsLate($entry) ? auditionSetting('late_fee') / 100 : 0;
|
||||
$lateFee = $this->entryService->isEntryLate($entry) ? auditionSetting('late_fee') / 100 : 0;
|
||||
|
||||
$invoiceData['lines'][] = [
|
||||
'student_name' => $student->full_name(true),
|
||||
|
|
|
|||
|
|
@ -3,216 +3,22 @@
|
|||
namespace App\Services;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\ScoringGuide;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\ScoreSheet;
|
||||
use function array_unshift;
|
||||
use App\Models\User;
|
||||
|
||||
class ScoreService
|
||||
{
|
||||
protected $auditionCache;
|
||||
|
||||
protected $entryCache;
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(AuditionService $auditionCache, EntryService $entryCache)
|
||||
{
|
||||
$this->auditionCache = $auditionCache;
|
||||
$this->entryCache = $entryCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache all scoring guides
|
||||
*/
|
||||
public function getScoringGuides(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
$cacheKey = 'scoringGuides';
|
||||
|
||||
return Cache::remember($cacheKey, 3600, fn () => ScoringGuide::with('subscores')->withCount('subscores')->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single scoring guide from the cache
|
||||
*/
|
||||
public function getScoringGuide($id): ScoringGuide
|
||||
{
|
||||
return $this->getScoringGuides()->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the scoring guide cache
|
||||
*/
|
||||
public function clearScoringGuideCache(): void
|
||||
{
|
||||
Cache::forget('scoringGuides');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array where each key is an entry id and the value is the number
|
||||
* of score sheets assigned to that entry.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function entryScoreSheetCounts()
|
||||
{
|
||||
$cacheKey = 'entryScoreSheetCounts';
|
||||
|
||||
return Cache::remember($cacheKey, 10, function () {
|
||||
// For each Entry get the number of ScoreSheets associated with it
|
||||
$scoreSheetCountsByEntry = ScoreSheet::select('entry_id', DB::raw('count(*) as count'))
|
||||
->groupBy('entry_id')
|
||||
->get()
|
||||
->pluck('count', 'entry_id');
|
||||
|
||||
$entryScoreSheetCounts = [];
|
||||
$entries = $this->entryCache->getAllEntries();
|
||||
foreach ($entries as $entry) {
|
||||
$entryScoreSheetCounts[$entry->id] = $scoreSheetCountsByEntry[$entry->id] ?? 0;
|
||||
}
|
||||
|
||||
return $entryScoreSheetCounts;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get final scores array for the requested entry. The first element is the total score. The following elements are sums
|
||||
* of each subscore in tiebreaker order
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function entryTotalScores(Entry $entry)
|
||||
{
|
||||
$cacheKey = 'entry'.$entry->id.'totalScores';
|
||||
|
||||
return Cache::remember($cacheKey, 3600, function () use ($entry) {
|
||||
return $this->calculateFinalScoreArray($entry->audition->scoring_guide_id, $entry->scoreSheets);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and cache scores for all entries for the provided audition ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function calculateScoresForAudition($auditionId, $mode= 'seating')
|
||||
{
|
||||
static $alreadyChecked = [];
|
||||
// if $auditionId is in the array $alreadyChecked return
|
||||
if (in_array($auditionId, $alreadyChecked)) {
|
||||
return;
|
||||
}
|
||||
$alreadyChecked[] = $auditionId;
|
||||
$audition = $this->auditionCache->getAudition($auditionId);
|
||||
$scoringGuideId = $audition->scoring_guide_id;
|
||||
$entries = $this->entryCache->getEntriesForAudition($auditionId, $mode);
|
||||
$entries->load('scoreSheets'); // TODO Cache this somehow, it's expensive and repetitive on the seating page
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$cacheKey = 'entry'.$entry->id.'totalScores';
|
||||
if (Cache::has($cacheKey)) {
|
||||
continue;
|
||||
}
|
||||
$thisTotalScore = $this->calculateFinalScoreArray($scoringGuideId, $entry->scoreSheets);
|
||||
Cache::put($cacheKey, $thisTotalScore, 3600);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearScoreSheetCountCache()
|
||||
{
|
||||
$cacheKey = 'entryScoreSheetCounts';
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
|
||||
public function clearEntryTotalScoresCache($entryId)
|
||||
{
|
||||
$cacheKey = 'entry'.$entryId.'totalScores';
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
|
||||
public function clearAllCachedTotalScores()
|
||||
{
|
||||
foreach ($this->entryCache->getAllEntries() as $entry) {
|
||||
$cacheKey = 'entry'.$entry->id.'totalScores';
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate final score using the provided scoring guide and score sheets. Returns an array of scores
|
||||
* The first element is the total score. The following elements are the sum of each subscore
|
||||
* in tiebreaker order.
|
||||
*/
|
||||
public function calculateFinalScoreArray($scoringGuideId, array|Collection $scoreSheets): array
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$sg = $this->getScoringGuide($scoringGuideId);
|
||||
|
||||
// TODO cache the scoring guides with their subscores
|
||||
$subscores = $sg->subscores->sortBy('tiebreak_order');
|
||||
|
||||
$ignoredSubscores = []; // This will be subscores not used for seating
|
||||
|
||||
// Init final scores array
|
||||
$finalScoresArray = [];
|
||||
foreach ($subscores as $subscore) {
|
||||
if (! $subscore->for_seating) { // Ignore scores that are not for seating
|
||||
$ignoredSubscores[] = $subscore->id;
|
||||
|
||||
continue;
|
||||
}
|
||||
$finalScoresArray[$subscore->id] = 0;
|
||||
}
|
||||
|
||||
foreach ($scoreSheets as $sheet) {
|
||||
foreach ($sheet->subscores as $ss) {
|
||||
if (in_array($ss['subscore_id'], $ignoredSubscores)) { // Ignore scores that are not for seating
|
||||
continue;
|
||||
}
|
||||
$finalScoresArray[$ss['subscore_id']] += $ss['score'];
|
||||
}
|
||||
}
|
||||
|
||||
// calculate weighted final score
|
||||
$totalScore = 0;
|
||||
$totalWeight = 0;
|
||||
foreach ($subscores as $subscore) {
|
||||
if (in_array($subscore->id, $ignoredSubscores)) { // Ignore scores that are not for seating
|
||||
continue;
|
||||
}
|
||||
$totalScore += ($finalScoresArray[$subscore->id] * $subscore->weight);
|
||||
$totalWeight += $subscore->weight;
|
||||
}
|
||||
$totalScore = ($totalScore / $totalWeight);
|
||||
array_unshift($finalScoresArray, $totalScore);
|
||||
|
||||
return $finalScoresArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the judge on the score sheet is actually assigned to judge
|
||||
* then entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateScoreSheet(ScoreSheet $sheet)
|
||||
public function isEntryFullyScored(Entry $entry): bool
|
||||
{
|
||||
// TODO use this when calculating scores
|
||||
$entry = $this->entryCache->getAllEntries()->find($sheet->entry_id);
|
||||
$audition = $this->auditionCache->getAudition($entry->audition_id);
|
||||
$validJudges = $audition->judges;
|
||||
// send a laravel flash message with an error if the $sheet->user_id is not in the collection $validJudges
|
||||
if (! $validJudges->contains('id', $sheet->user_id)) {
|
||||
session()->flash('error', 'Entry ID '.$sheet->entry_id.' has an invalid score entered by '.$sheet->judge->full_name());
|
||||
}
|
||||
|
||||
// check if $sheet->user_id is in the collection $validJudges, return false if not, true if it is
|
||||
return $validJudges->contains('id', $sheet->user_id);
|
||||
$requiredJudges = $entry->audition->judges()->count();
|
||||
$scoreSheets = $entry->scoreSheets()->count();
|
||||
|
||||
return $requiredJudges === $scoreSheets;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Seat;
|
||||
use App\Models\SeatingLimit;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SeatingService
|
||||
{
|
||||
protected $limitsCacheKey = 'acceptanceLimits';
|
||||
|
||||
protected $tabulationService;
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(TabulationService $tabulationService)
|
||||
{
|
||||
$this->tabulationService = $tabulationService;
|
||||
}
|
||||
|
||||
public function getAcceptanceLimits()
|
||||
{
|
||||
return Cache::remember($this->limitsCacheKey, now()->addDay(), function () {
|
||||
$limits = SeatingLimit::with('ensemble')->get();
|
||||
// Sort limits by ensemble->rank
|
||||
$limits = $limits->sortBy(function ($limit) {
|
||||
return $limit->ensemble->rank;
|
||||
});
|
||||
|
||||
return $limits->groupBy('audition_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function getLimitForAudition($auditionId)
|
||||
{
|
||||
if (! $this->getAcceptanceLimits()->has($auditionId)) {
|
||||
return new \Illuminate\Database\Eloquent\Collection();
|
||||
}
|
||||
return $this->getAcceptanceLimits()[$auditionId];
|
||||
}
|
||||
|
||||
public function refreshLimits(): void
|
||||
{
|
||||
Cache::forget($this->limitsCacheKey);
|
||||
}
|
||||
|
||||
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';
|
||||
// TODO rework to pull entry info from cache
|
||||
return Cache::remember($cacheKey, now()->addHour(), function () use ($auditionId) {
|
||||
return Seat::with('entry.student.school')
|
||||
->where('audition_id', $auditionId)
|
||||
->orderBy('seat')
|
||||
->get()
|
||||
->groupBy('ensemble_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function forgetSeatsForAudition($auditionId)
|
||||
{
|
||||
$cacheKey = 'audition'.$auditionId.'seats';
|
||||
Cache::forget($cacheKey);
|
||||
}
|
||||
|
||||
public function getEnsemblesForEvent($eventId)
|
||||
{
|
||||
static $eventEnsembles = [];
|
||||
|
||||
if (array_key_exists($eventId, $eventEnsembles)) {
|
||||
return $eventEnsembles[$eventId];
|
||||
}
|
||||
$event = Event::find($eventId);
|
||||
$eventEnsembles[$eventId] = $event->ensembles;
|
||||
|
||||
return $eventEnsembles[$eventId];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class TabulationService
|
||||
{
|
||||
protected AuditionService $auditionService;
|
||||
|
||||
protected EntryService $entryService;
|
||||
|
||||
protected ScoreService $scoreService;
|
||||
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(
|
||||
AuditionService $auditionService,
|
||||
ScoreService $scoreService,
|
||||
EntryService $entryService)
|
||||
{
|
||||
$this->auditionService = $auditionService;
|
||||
$this->scoreService = $scoreService;
|
||||
$this->entryService = $entryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rank of the entry in its audition
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function entryRank(Entry $entry)
|
||||
{
|
||||
return $this->auditionEntries($entry->audition_id)[$entry->id]->rank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of entries including their calculated final_score_array and ranked
|
||||
* based upon their scores.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection|mixed
|
||||
*/
|
||||
public function auditionEntries(int $auditionId, $mode = 'seating')
|
||||
{
|
||||
static $cache = [];
|
||||
if (isset($cache[$auditionId])) {
|
||||
return $cache[$auditionId];
|
||||
}
|
||||
|
||||
$audition = $this->auditionService->getAudition($auditionId);
|
||||
$entries = $this->entryService->getEntriesForAudition($auditionId, $mode);
|
||||
$this->scoreService->calculateScoresForAudition($auditionId);
|
||||
// TODO will need to pass a mode to the above function to only use subscores for hte appropriate mode
|
||||
foreach ($entries as $entry) {
|
||||
$entry->final_score_array = $this->scoreService->entryTotalScores($entry);
|
||||
$entry->scoring_complete = ($this->scoreService->entryScoreSheetCounts()[$entry->id] == $audition->judges_count);
|
||||
}
|
||||
// Sort the array $entries by the first element in the final_score_array on each entry, then by the second element in that array continuing through each element in the final_score_array for each entry
|
||||
$entries = $entries->sort(function ($a, $b) {
|
||||
for ($i = 0; $i < count($a->final_score_array); $i++) {
|
||||
if ($a->final_score_array[$i] != $b->final_score_array[$i]) {
|
||||
return $b->final_score_array[$i] > $a->final_score_array[$i] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
//TODO verify this actually sorts by subscores correctly
|
||||
|
||||
// Assign a rank to each entry. In the case of a declined seat by a doubler, indicate as so and do not increment rank
|
||||
$n = 1;
|
||||
/** @var Entry $entry */
|
||||
foreach ($entries as $entry) {
|
||||
if (! $entry->hasFlag('declined') or $mode != 'seating') {
|
||||
$entry->rank = $n;
|
||||
$n++;
|
||||
} else {
|
||||
$entry->rank = $n.' - declined';
|
||||
}
|
||||
}
|
||||
|
||||
$cache[$auditionId] = $entries->keyBy('id');
|
||||
|
||||
return $entries->keyBy('id');
|
||||
}
|
||||
|
||||
public function entryScoreSheetsAreValid(Entry $entry): bool
|
||||
{
|
||||
//TODO consider making this move the invalid score to another database for further investigation
|
||||
$validJudges = $this->auditionService->getAudition($entry->audition_id)->judges;
|
||||
foreach ($entry->scoreSheets as $sheet) {
|
||||
if (! $validJudges->contains($sheet->user_id)) {
|
||||
$invalidJudge = User::find($sheet->user_id);
|
||||
Session::flash('error', 'Invalid scores for entry '.$entry->id.' exist from '.$invalidJudge->full_name());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of un-scored entries for the audition with the given ID.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function remainingEntriesForAudition($auditionId, $mode = 'seating')
|
||||
{
|
||||
$audition = $this->getAuditionsWithStatus($mode)[$auditionId];
|
||||
|
||||
switch ($mode) {
|
||||
case 'seating':
|
||||
return $audition->seating_entries_count - $audition->scored_entries_count;
|
||||
case 'advancement':
|
||||
return $audition->advancement_entries_count - $audition->scored_entries_count;
|
||||
}
|
||||
|
||||
return $audition->entries_count - $audition->scored_entries_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of all auditions from the cache. For each one, set a property
|
||||
* scored_entries_count that indicates the number of entries for that audition that
|
||||
* have a number of score sheets equal to the number of judges for that audition.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuditionsWithStatus($mode = 'seating')
|
||||
{
|
||||
return Cache::remember('auditionsWithStatus', 30, function () use ($mode) {
|
||||
|
||||
// Retrieve auditions from the cache and load entry IDs
|
||||
$auditions = $this->auditionService->getAuditions($mode);
|
||||
// Iterate over the auditions and calculate the scored_entries_count
|
||||
foreach ($auditions as $audition) {
|
||||
$scored_entries_count = 0;
|
||||
$entries_to_check = $this->entryService->getEntriesForAudition($audition->id);
|
||||
|
||||
switch ($mode) {
|
||||
case 'seating':
|
||||
$entries_to_check = $entries_to_check->filter(function ($entry) {
|
||||
return $entry->for_seating;
|
||||
});
|
||||
$auditions = $auditions->filter(function ($audition) {
|
||||
return $audition->for_seating;
|
||||
});
|
||||
break;
|
||||
case 'advancement':
|
||||
$entries_to_check = $entries_to_check->filter(function ($entry) {
|
||||
return $entry->for_advancement;
|
||||
});
|
||||
$auditions = $auditions->filter(function ($audition) {
|
||||
return $audition->for_advancement;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($entries_to_check as $entry) {
|
||||
if ($this->scoreService->entryScoreSheetCounts()[$entry->id] - $audition->judges_count == 0) {
|
||||
$scored_entries_count++;
|
||||
}
|
||||
}
|
||||
|
||||
$audition->scored_entries_count = $scored_entries_count;
|
||||
}
|
||||
|
||||
return $auditions;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function userExists(User $user): bool
|
||||
{
|
||||
$cacheKey = 'allUserIds';
|
||||
$allUserIds = Cache::remember($cacheKey, 60, function () {
|
||||
return User::pluck('id');
|
||||
});
|
||||
|
||||
return $allUserIds->contains($user->id);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ class Settings
|
|||
public static function get($key, $default = null)
|
||||
{
|
||||
$settings = Cache::get(self::$cacheKey, []);
|
||||
|
||||
return $settings[$key] ?? $default;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Tabulation\EnterScore;
|
||||
use App\Exceptions\ScoreEntryException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\User;
|
||||
use App\Settings;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
function tw_max_width_class_array(): array
|
||||
{
|
||||
|
|
@ -25,7 +30,16 @@ function tw_max_width_class_array(): array
|
|||
return $return;
|
||||
}
|
||||
|
||||
|
||||
function auditionSetting($key) {
|
||||
function auditionSetting($key)
|
||||
{
|
||||
return Settings::get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ScoreEntryException
|
||||
*/
|
||||
function enterScore(User $user, Entry $entry, array $scores): \App\Models\ScoreSheet
|
||||
{
|
||||
$scoreEntry = App::make(EnterScore::class);
|
||||
return $scoreEntry($user, $entry, $scores);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\CalculateEntryScoreProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
App\Providers\InvoiceDataServiceProvider::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"laravel/pail": "^1.1",
|
||||
"laravel/tinker": "^2.9",
|
||||
"predis/predis": "^2.2",
|
||||
"staudenmeir/belongs-to-through": "^2.5",
|
||||
"symfony/http-client": "^7.1",
|
||||
"symfony/mailgun-mailer": "^7.1"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "7aab57ef52f0152526434decd76ef1e1",
|
||||
"content-hash": "cd8959ab9db27e12c6fce8cf87c52d90",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
|
|
@ -3601,6 +3601,71 @@
|
|||
],
|
||||
"time": "2024-04-27T21:32:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "staudenmeir/belongs-to-through",
|
||||
"version": "v2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/staudenmeir/belongs-to-through.git",
|
||||
"reference": "79667db6660fa0065b24415bab29a5f85a0128c7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/79667db6660fa0065b24415bab29a5f85a0128c7",
|
||||
"reference": "79667db6660fa0065b24415bab29a5f85a0128c7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^11.0",
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.0",
|
||||
"orchestra/testbench": "^9.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Staudenmeir\\BelongsToThrough\\IdeHelperServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Znck\\Eloquent\\": "src/",
|
||||
"Staudenmeir\\BelongsToThrough\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Rahul Kadyan",
|
||||
"email": "hi@znck.me"
|
||||
},
|
||||
{
|
||||
"name": "Jonas Staudenmeir",
|
||||
"email": "mail@jonas-staudenmeir.de"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Eloquent BelongsToThrough relationships",
|
||||
"support": {
|
||||
"issues": "https://github.com/staudenmeir/belongs-to-through/issues",
|
||||
"source": "https://github.com/staudenmeir/belongs-to-through/tree/v2.16"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/JonasStaudenmeir",
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-09T09:53:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.0.7",
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ return [
|
|||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
'prefix' => env('REDIS_CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Room;
|
||||
use App\Models\ScoringGuide;
|
||||
use App\Models\SubscoreDefinition;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class AuditionWithScoringGuideAndRoom extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
// TiebreakOrder: Tone, Sightreading, Etude 1, Etude 2, Scale
|
||||
// Scale is only for seating, Tone is only for advancement
|
||||
public function run(): void
|
||||
{
|
||||
$room = Room::factory()->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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class RoomSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SchoolSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ScoreSheetSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ScoringGuideSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class StudentSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SubscoreDefinitionSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<div class="w-56 shrink rounded-xl bg-white p-4 text-sm font-semibold leading-6 text-gray-900 shadow-lg ring-1 ring-gray-900/5">
|
||||
<a href="{{ route('scores.chooseEntry') }}" class="block p-2 hover:text-indigo-600">Enter Scores</a>
|
||||
<a href="{{ route('entry-flags.noShowSelect') }}" class="block p-2 hover:text-indigo-600">Enter No-Shows</a>
|
||||
<a href="{{ route('tabulation.status') }}" class="block p-2 hover:text-indigo-600">Audition Status</a>
|
||||
<a href="{{ route('seating.status') }}" class="block p-2 hover:text-indigo-600">Audition Status</a>
|
||||
<a href="{{ route('advancement.status') }}" class="block p-2 hover:text-indigo-600">{{ auditionSetting('advanceTo') }} Status</a>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<x-table.th>Draw #</x-table.th>
|
||||
<x-table.th>Student Name</x-table.th>
|
||||
<x-table.th>Total Score</x-table.th>
|
||||
<x-table.th>All Scores?</x-table.th>
|
||||
<x-table.th>Votes</x-table.th>
|
||||
@if($scoringComplete)
|
||||
<x-table.th>Pass?</x-table.th>
|
||||
|
|
@ -17,6 +16,13 @@
|
|||
|
||||
<x-table.body>
|
||||
@foreach($entries as $entry)
|
||||
@php
|
||||
if ($entry->score_totals[0] < 0) {
|
||||
$score = $entry->score_message;
|
||||
} else {
|
||||
$score = number_format($entry->score_totals[0] ?? 0,4);
|
||||
}
|
||||
@endphp
|
||||
<tr>
|
||||
<x-table.td>{{ $entry->rank }}</x-table.td>
|
||||
<x-table.td>{{ $entry->id }}</x-table.td>
|
||||
|
|
@ -25,12 +31,8 @@
|
|||
<span>{{ $entry->student->full_name() }}</span>
|
||||
<span class="text-xs text-gray-400">{{ $entry->student->school->name }}</span>
|
||||
</x-table.td>
|
||||
<x-table.td>{{ number_format($entry->final_score_array[0] ?? 0,4) }}</x-table.td>
|
||||
<x-table.td>
|
||||
@if($entry->scoring_complete)
|
||||
<x-icons.checkmark color="green"/>
|
||||
@endif
|
||||
</x-table.td>
|
||||
<x-table.td>{{ $score }}</x-table.td>
|
||||
|
||||
<x-table.td class="flex space-x-1">
|
||||
@foreach($entry->advancementVotes as $vote)
|
||||
<div x-data="{ showJudgeName: false, timeout: null }"
|
||||
|
|
|
|||
|
|
@ -13,33 +13,27 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<x-table.body>
|
||||
@foreach($auditions as $audition)
|
||||
@php
|
||||
$percent = 100;
|
||||
if($audition->advancement_entries_count > 0) {
|
||||
$percent = round(($audition->scored_entries_count / $audition->advancement_entries_count) * 100);
|
||||
}
|
||||
@endphp
|
||||
@foreach($auditionData as $audition)
|
||||
<tr class="hover:bg-gray-50">
|
||||
|
||||
<x-table.td class="">
|
||||
<a href="{{ route('advancement.ranking', ['audition' => $audition->id]) }}">
|
||||
<a href="{{ route('advancement.ranking', ['audition' => $audition['id']]) }}">
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-base font-medium text-indigo-700 dark:text-white">{{ $audition->name }}</span>
|
||||
<span class="text-sm font-medium text-indigo-700 dark:text-white">{{ $audition->scored_entries_count }} / {{ $audition->advancement_entries_count }} Scored</span>
|
||||
<span class="text-base font-medium text-indigo-700 dark:text-white">{{ $audition['name'] }}</span>
|
||||
<span class="text-sm font-medium text-indigo-700 dark:text-white">{{ $audition['scored_entries_count'] }} / {{ $audition['entries_count'] }} Scored</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
|
||||
<div class="bg-indigo-600 h-2.5 rounded-full" style="width: {{ $percent }}%"></div>
|
||||
<div class="bg-indigo-600 h-2.5 rounded-full" style="width: {{ $audition['scored_percentage'] }}%"></div>
|
||||
</div>
|
||||
</a>
|
||||
</x-table.td>
|
||||
<td class="px-8">
|
||||
@if( $audition->scored_entries_count == $audition->advancement_entries_count)
|
||||
@if( $audition['scoring_complete'])
|
||||
<x-icons.checkmark color="green"/>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-8">
|
||||
@if( $audition->hasFlag('advancement_published'))
|
||||
@if( $audition['published'])
|
||||
<x-icons.checkmark color="green"/>
|
||||
@endif
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
@php($doublerEntryInfo = $doublerService->getDoublerInfo($entry->student_id))
|
||||
@php($doublerButtonClasses = 'hidden rounded-md bg-white px-2.5 py-1.5 text-xs text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:block')
|
||||
<ul role="list" class="">
|
||||
|
||||
@foreach($doublerEntryInfo as $info)
|
||||
@php($isopen = $info['status'] == 'undecided')
|
||||
@foreach($entry['doubleData'] as $double)
|
||||
@php($isopen = $double['status'] == 'undecided')
|
||||
<li class="pb-2 pt-0 px-0 my-2 rounded-xl border border-gray-200 max-w-xs" x-data="{ open: {{ $isopen ? 'true':'false' }} }">
|
||||
<div class="flex items-start gap-x-3 bg-gray-100 px-3 py-2 rounded-t-xl" >
|
||||
<p class="text-sm font-semibold leading-6 text-gray-900">
|
||||
<a href="/tabulation/auditions/{{ $info['auditionID'] }}">
|
||||
{{ $info['auditionName'] }} - {{ $info['status'] }}
|
||||
<a href="{{ route('seating.audition', $double['audition']) }}">
|
||||
{{ $double['auditionName'] }} - {{ $double['status'] }}
|
||||
</a>
|
||||
</p>
|
||||
<div class="w-full flex justify-end" >
|
||||
|
|
@ -21,26 +20,23 @@
|
|||
<div class="grid grid-cols-4" x-show="open">
|
||||
<div class="mt-1 px-3 text-xs leading-5 text-gray-500 col-span-3">
|
||||
<ul>
|
||||
<li class="flex items-center gap-x-2">
|
||||
<p class="whitespace-nowrap">Ranked {{ $info['rank'] }}</p>
|
||||
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 fill-current">
|
||||
<circle cx="1" cy="1" r="1" />
|
||||
</svg>
|
||||
<p class="truncate">{{ $info['unscored'] }} Unscored</p>
|
||||
<li class="">
|
||||
<p class="whitespace-nowrap">Ranked {{ $double['rank'] }}</p>
|
||||
<p class="truncate">{{ $double['unscored_entries'] }} Unscored</p>
|
||||
</li>
|
||||
@foreach($info['limits'] as $limit)
|
||||
<li>{{ $limit->ensemble->name }} accepts {{ $limit->maximum_accepted }}</li>
|
||||
@foreach($double['seating_limits'] as $limit)
|
||||
<li>{{$limit['ensemble_name']}} accepts {{ $limit['accepts'] }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-end gap-y-1 pt-1">
|
||||
@if ($info['status'] === 'undecided')
|
||||
<form method="POST" action="{{ route('doubler.accept',['entry'=>$info['entryID']]) }}">
|
||||
@if ($double['status'] == 'undecided')
|
||||
<form method="POST" action="{{ route('doubler.accept',['entry'=>$double['entry']]) }}">
|
||||
@csrf
|
||||
<button class="{{ $doublerButtonClasses }}">Accept</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ route('doubler.decline',['entry'=>$info['entryID']]) }}">
|
||||
<form method="POST" action="{{ route('doubler.decline',['entry'=>$double['entry']]) }}">
|
||||
@csrf
|
||||
<button class="{{ $doublerButtonClasses }}">Decline</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
<x-card.card class="mb-3">
|
||||
@php
|
||||
@endphp
|
||||
<x-card.heading>Seating</x-card.heading>
|
||||
<div class="py-3 px-1">
|
||||
<x-form.form method="POST" action="{{ route('tabulation.audition.seat',['audition' => $audition]) }}">
|
||||
<x-form.form method="POST" action="{{ route('seating.audition',['audition' => $audition]) }}">
|
||||
@csrf
|
||||
@foreach($ensembleLimits as $ensembleLimit)
|
||||
@foreach($rightPanel['data'] as $ensembleLimit)
|
||||
@php
|
||||
$value = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit->maximum_accepted;
|
||||
$value = $requestedEnsembleAccepts[$ensembleLimit['ensemble']->id] ?? $ensembleLimit['limit'];
|
||||
// $value = $ensembleLimit['limit'];
|
||||
@endphp
|
||||
|
||||
<x-form.field name="ensembleAccept[{{ $ensembleLimit->ensemble->id }}]"
|
||||
label_text="{{ $ensembleLimit->ensemble->name }} - Max: {{ $ensembleLimit->maximum_accepted }}"
|
||||
<x-form.field name="ensembleAccept[{{ $ensembleLimit['ensemble']->id }}]"
|
||||
label_text="{{ $ensembleLimit['ensemble']->name }} - Max: {{ $ensembleLimit['limit'] }}"
|
||||
type="number"
|
||||
max="{{ $ensembleLimit->maximum_accepted }}"
|
||||
max="{{ $ensembleLimit['limit'] }}"
|
||||
value="{{ $value }}"
|
||||
class="mb-3"/>
|
||||
@endforeach
|
||||
|
|
|
|||
|
|
@ -16,23 +16,35 @@
|
|||
</thead>
|
||||
|
||||
<x-table.body>
|
||||
@foreach($entries as $entry)
|
||||
@foreach($entryData as $entry)
|
||||
<tr>
|
||||
<x-table.td>{{ $entry->rank }}</x-table.td>
|
||||
<x-table.td>{{ $entry->id }}</x-table.td>
|
||||
<x-table.td>{{ $entry->draw_number }}</x-table.td>
|
||||
<x-table.td>{{ $entry['rank'] }}</x-table.td>
|
||||
<x-table.td>{{ $entry['id'] }}</x-table.td>
|
||||
<x-table.td>{{ $entry['drawNumber'] }}</x-table.td>
|
||||
<x-table.td class="flex flex-col">
|
||||
<span>{{ $entry->student->full_name() }}</span>
|
||||
<span class="text-xs text-gray-400">{{ $entry->student->school->name }}</span>
|
||||
<span>{{ $entry['studentName'] }}</span>
|
||||
<span class="text-xs text-gray-400">{{ $entry['schoolName'] }}</span>
|
||||
</x-table.td>
|
||||
<x-table.td class="!py-0">
|
||||
@if($doublerService->studentIsDoubler($entry->student_id))
|
||||
@if($entry['doubleData'])
|
||||
@include('tabulation.auditionSeating-doubler-block')
|
||||
{{-- DOUBLER<br>--}}
|
||||
{{-- @foreach($entry['doubleData'] as $double)--}}
|
||||
{{-- ID: {{ $double['entryId'] }} - {{ $double['name'] }} - {{ $double['rank'] }}<br>--}}
|
||||
{{-- Unscored Entries: {{ $double['unscored_in_audition'] }}<br>--}}
|
||||
{{-- @foreach($double['limits'] as $limit)--}}
|
||||
{{-- {{$limit['ensemble']->name}}: {{ $limit['limit'] }}<br>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- <hr>--}}
|
||||
{{-- @endforeach--}}
|
||||
@endif
|
||||
{{-- @if($doublerService->studentIsDoubler($entry->student_id))--}}
|
||||
{{-- @include('tabulation.auditionSeating-doubler-block')--}}
|
||||
{{-- @endif--}}
|
||||
</x-table.td>
|
||||
<x-table.td>{{ number_format($entry->final_score_array[0] ?? 0,4) }}</x-table.td>
|
||||
<x-table.td>{{ $entry['totalScore'] }}</x-table.td>
|
||||
<x-table.td>
|
||||
@if($entry->scoring_complete)
|
||||
@if($entry['fullyScored'])
|
||||
<x-icons.checkmark color="green"/>
|
||||
@endif
|
||||
</x-table.td>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
@include('tabulation.auditionSeating-fill-seats-form')
|
||||
@include('tabulation.auditionSeating-show-proposed-seats')
|
||||
|
|
@ -2,19 +2,20 @@
|
|||
$seatingProposal = [];
|
||||
@endphp
|
||||
|
||||
@foreach($ensembleLimits as $ensembleLimit)
|
||||
@foreach($rightPanel['data'] as $ensembleLimit)
|
||||
<x-card.card class="mb-3">
|
||||
<x-card.heading>{{ $ensembleLimit->ensemble->name }} - DRAFT</x-card.heading>
|
||||
<x-card.heading>{{ $ensembleLimit['ensemble']->name }} - DRAFT</x-card.heading>
|
||||
<x-card.list.body>
|
||||
@php
|
||||
$maxAccepted = $requestedEnsembleAccepts[$ensembleLimit->ensemble->id] ?? $ensembleLimit->maximum_accepted;
|
||||
$maxAccepted = $requestedEnsembleAccepts[$ensembleLimit['ensemble']->id] ?? $ensembleLimit['limit'];
|
||||
// $maxAccepted = $ensembleLimit['limit'];
|
||||
@endphp
|
||||
@for($n=1; $n <= $maxAccepted; $n++)
|
||||
@php
|
||||
$entry = $seatableEntries->shift();
|
||||
if (is_null($entry)) continue;
|
||||
$seatingProposal[] = [
|
||||
'ensemble_id' => $ensembleLimit->ensemble->id,
|
||||
'ensemble_id' => $ensembleLimit['ensemble']->id,
|
||||
'audition_id' => $audition->id,
|
||||
'seat' => $n,
|
||||
'entry_id' => $entry->id,
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
</x-card.list.body>
|
||||
</x-card.card>
|
||||
@endforeach
|
||||
<form method="POST" action="{{ route('tabulation.seat.publish',['audition' => $audition]) }}">
|
||||
<form method="POST" action="{{ route('seating.audition.publish',['audition' => $audition]) }}">
|
||||
@csrf
|
||||
<x-form.button type="submit">Seat and Publish</x-form.button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
@inject('seatingService','App\Services\SeatingService')
|
||||
|
||||
<x-card.card class="mb-3">
|
||||
<x-card.heading>
|
||||
Seats are Published
|
||||
</x-card.heading>
|
||||
|
||||
<x-form.form method="POST"
|
||||
action="{{ route('tabulation.seat.unpublish',['audition' => $audition->id]) }}"
|
||||
action="{{ route('seating.audition.unpublish',['audition' => $audition->id]) }}"
|
||||
class="mx-5 my-2">
|
||||
<x-form.button type="submit">
|
||||
Unpublish
|
||||
|
|
@ -15,17 +13,34 @@
|
|||
|
||||
</x-card.card>
|
||||
|
||||
@foreach($ensembleLimits as $ensembleLimit)
|
||||
@php
|
||||
$ensembleSeats = $seatingService->getSeatsForAudition($audition->id)[$ensembleLimit->ensemble->id] ?? array();
|
||||
@endphp
|
||||
<x-card.card class="mb-3">
|
||||
<x-card.heading>{{ $ensembleLimit->ensemble->name }}</x-card.heading>
|
||||
@foreach($ensembleSeats as $seat)
|
||||
@php
|
||||
$previousEnsemble = null;
|
||||
@endphp
|
||||
@foreach($rightPanel['data'] as $seat)
|
||||
@if($seat['ensemble'] !== $previousEnsemble)
|
||||
<x-card.heading>{{$seat['ensemble']}}</x-card.heading>
|
||||
@endif
|
||||
<x-card.list.row class="!py-2">
|
||||
{{ $seat->seat }} - {{ $seat->student->full_name() }}
|
||||
{{ $seat['seat'] }} - {{ $seat['student_name'] }}
|
||||
</x-card.list.row>
|
||||
@endforeach
|
||||
@php
|
||||
|
||||
</x-card.card>
|
||||
$previousEnsemble = $seat['ensemble'];
|
||||
@endphp
|
||||
@endforeach
|
||||
</x-card.card>
|
||||
{{--@foreach($ensembleLimits as $ensembleLimit)--}}
|
||||
{{-- @php--}}
|
||||
{{-- $ensembleSeats = $seatingService->getSeatsForAudition($audition->id)[$ensembleLimit->ensemble->id] ?? array();--}}
|
||||
{{-- @endphp--}}
|
||||
{{-- <x-card.card class="mb-3">--}}
|
||||
{{-- <x-card.heading>{{ $ensembleLimit->ensemble->name }}</x-card.heading>--}}
|
||||
{{-- @foreach($ensembleSeats as $seat)--}}
|
||||
{{-- <x-card.list.row class="!py-2">--}}
|
||||
{{-- {{ $seat->seat }} - {{ $seat->student->full_name() }}--}}
|
||||
{{-- </x-card.list.row>--}}
|
||||
{{-- @endforeach--}}
|
||||
|
||||
{{-- </x-card.card>--}}
|
||||
{{--@endforeach--}}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<x-card.card>
|
||||
<x-card.heading>Unable to seat this audition</x-card.heading>
|
||||
@if(! $scoringComplete)
|
||||
@if(! $rightPanel['data']['allScored'])
|
||||
<p class="text-sm px-5 py-2">The audition cannot be seated while it has unscored entries.</p>
|
||||
@endif
|
||||
|
||||
@if(! $doublerComplete)
|
||||
@if(! $rightPanel['data']['doublersResolved'])
|
||||
<p class="text-sm px-5 py-2">The audition cannot be seated while it has unresolved doublers.</p>
|
||||
@endif
|
||||
</x-card.card>
|
||||
|
|
|
|||
|
|
@ -10,20 +10,21 @@
|
|||
@include('tabulation.auditionSeating-results-table')
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
@if($audition->hasFlag('seats_published'))
|
||||
@include('tabulation.auditionSeating-show-published-seats')
|
||||
@elseif(! $auditionComplete)
|
||||
@include('tabulation.auditionSeating-unable-to-seat-card')
|
||||
@else
|
||||
@include('tabulation.auditionSeating-fill-seats-form')
|
||||
@include('tabulation.auditionSeating-show-proposed-seats')
|
||||
@endif
|
||||
|
||||
|
||||
@include($rightPanel['view'])
|
||||
</div>
|
||||
{{-- <div class="ml-4">--}}
|
||||
{{-- @if($audition->hasFlag('seats_published'))--}}
|
||||
{{-- @include('tabulation.auditionSeating-show-published-seats')--}}
|
||||
{{-- @elseif(! $auditionComplete)--}}
|
||||
{{-- @include('tabulation.auditionSeating-unable-to-seat-card')--}}
|
||||
{{-- @else--}}
|
||||
{{-- @include('tabulation.auditionSeating-fill-seats-form')--}}
|
||||
{{-- @include('tabulation.auditionSeating-show-proposed-seats')--}}
|
||||
{{-- @endif--}}
|
||||
|
||||
|
||||
{{-- </div>--}}
|
||||
</div>
|
||||
|
||||
|
||||
</x-layout.app>
|
||||
|
||||
{{--TODO deal with unlikely scenario of a doubler is entered for seating on some auditions but not others--}}
|
||||
|
|
|
|||
|
|
@ -13,33 +13,26 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<x-table.body>
|
||||
@foreach($auditions as $audition)
|
||||
@php
|
||||
$percent = 100;
|
||||
if($audition->seating_entries_count > 0) {
|
||||
$percent = round(($audition->scored_entries_count / $audition->seating_entries_count) * 100);
|
||||
}
|
||||
@endphp
|
||||
@foreach($auditionData as $audition)
|
||||
<tr class="hover:bg-gray-50">
|
||||
|
||||
<x-table.td class="">
|
||||
<a href="/tabulation/auditions/{{ $audition->id }}">
|
||||
<a href="{{ route('seating.audition', ['audition' => $audition['audition']]) }}">
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-base font-medium text-indigo-700 dark:text-white">{{ $audition->name }}</span>
|
||||
<span class="text-sm font-medium text-indigo-700 dark:text-white">{{ $audition->scored_entries_count }} / {{ $audition->seating_entries_count }} Scored</span>
|
||||
<span class="text-base font-medium text-indigo-700 dark:text-white">{{ $audition['name'] }}</span>
|
||||
<span class="text-sm font-medium text-indigo-700 dark:text-white">{{ $audition['scoredEntriesCount'] }} / {{ $audition['totalEntriesCount'] }} Scored</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
|
||||
<div class="bg-indigo-600 h-2.5 rounded-full" style="width: {{ $percent }}%"></div>
|
||||
<div class="bg-indigo-600 h-2.5 rounded-full" style="width: {{ $audition['scoredPercentage'] }}%"></div>
|
||||
</div>
|
||||
</a>
|
||||
</x-table.td>
|
||||
<td class="px-8">
|
||||
@if( $audition->scored_entries_count == $audition->seating_entries_count)
|
||||
@if( $audition['scoringComplete'])
|
||||
<x-icons.checkmark color="green"/>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-8">
|
||||
@if( $audition->hasFlag('seats_published'))
|
||||
@if( $audition['seatsPublished'])
|
||||
<x-icons.checkmark color="green"/>
|
||||
@endif
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
@php use App\Enums\AuditionFlags;use App\Models\Audition;use App\Models\AuditionFlag; @endphp
|
||||
@php @endphp
|
||||
@inject('scoreservice','App\Services\ScoreService');
|
||||
@inject('auditionService','App\Services\AuditionService');
|
||||
@inject('entryService','App\Services\EntryService')
|
||||
@inject('seatingService','App\Services\SeatingService')
|
||||
@inject('drawService', 'App\Services\DrawService')
|
||||
<x-layout.app>
|
||||
<x-slot:page_title>Test Page</x-slot:page_title>
|
||||
@php
|
||||
$audition = Audition::first();
|
||||
$audition->addFlag('drawn');
|
||||
@endphp
|
||||
|
||||
|
||||
</x-layout.app>
|
||||
|
|
@ -1,37 +1,46 @@
|
|||
<?php
|
||||
|
||||
// Tabulation Routes
|
||||
use App\Http\Controllers\Tabulation\AdvancementController;
|
||||
use App\Http\Controllers\Tabulation\DoublerDecisionController;
|
||||
use App\Http\Controllers\Tabulation\EntryFlagController;
|
||||
use App\Http\Controllers\Tabulation\ScoreController;
|
||||
use App\Http\Controllers\Tabulation\SeatAuditionFormController;
|
||||
use App\Http\Controllers\Tabulation\SeatingPublicationController;
|
||||
use App\Http\Controllers\Tabulation\SeatingStatusController;
|
||||
use App\Http\Controllers\Tabulation\TabulationController;
|
||||
use App\Http\Middleware\CheckIfCanTab;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(['auth', 'verified', CheckIfCanTab::class])->group(function () {
|
||||
|
||||
// Score Management
|
||||
Route::prefix('scores/')->controller(\App\Http\Controllers\Tabulation\ScoreController::class)->group(function () {
|
||||
Route::prefix('scores/')->controller(ScoreController::class)->group(function () {
|
||||
Route::get('/choose_entry', 'chooseEntry')->name('scores.chooseEntry');
|
||||
Route::get('/entry', 'entryScoreSheet')->name('scores.entryScoreSheet');
|
||||
Route::post('/entry/{entry}', 'saveEntryScoreSheet')->name('scores.saveEntryScoreSheet');
|
||||
Route::delete('/{score}', [\App\Http\Controllers\Tabulation\ScoreController::class, 'destroyScore'])->name('scores.destroy');
|
||||
Route::delete('/{score}',
|
||||
[ScoreController::class, 'destroyScore'])->name('scores.destroy');
|
||||
});
|
||||
|
||||
// Entry Flagging
|
||||
Route::prefix('entry-flags/')->controller(\App\Http\Controllers\Tabulation\EntryFlagController::class)->group(function () {
|
||||
Route::prefix('entry-flags/')->controller(EntryFlagController::class)->group(function () {
|
||||
Route::get('/choose_no_show', 'noShowSelect')->name('entry-flags.noShowSelect');
|
||||
Route::get('/propose-no-show', 'noShowConfirm')->name('entry-flags.confirmNoShow');
|
||||
Route::post('/no-show/{entry}', 'enterNoShow')->name('entry-flags.enterNoShow');
|
||||
Route::delete('/no-show/{entry}', 'undoNoShow')->name('entry-flags.undoNoShow');
|
||||
});
|
||||
|
||||
// Generic Tabulation Routes
|
||||
Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function () {
|
||||
Route::get('/status', 'status')->name('tabulation.status');
|
||||
Route::match(['get', 'post'], '/auditions/{audition}', 'auditionSeating')->name('tabulation.audition.seat');
|
||||
Route::post('/auditions/{audition}/publish-seats', 'publishSeats')->name('tabulation.seat.publish');
|
||||
Route::post('/auditions/{audition}/unpublish-seats', 'unpublishSeats')->name('tabulation.seat.unpublish');
|
||||
// Seating Routes
|
||||
Route::prefix('seating/')->group(function () {
|
||||
Route::get('/', SeatingStatusController::class)->name('seating.status');
|
||||
Route::match(['get', 'post'], '/{audition}', SeatAuditionFormController::class)->name('seating.audition');
|
||||
Route::post('/{audition}/publish', [SeatingPublicationController::class, 'publishSeats'])->name('seating.audition.publish');
|
||||
Route::post('/{audition}/unpublish', [SeatingPublicationController::class, 'unpublishSeats'])->name('seating.audition.unpublish');
|
||||
});
|
||||
|
||||
// Advancement Routes
|
||||
Route::prefix('advancement/')->controller(\App\Http\Controllers\Tabulation\AdvancementController::class)->group(function () {
|
||||
Route::prefix('advancement/')->controller(AdvancementController::class)->group(function () {
|
||||
Route::get('/status', 'status')->name('advancement.status');
|
||||
Route::get('/{audition}', 'ranking')->name('advancement.ranking');
|
||||
Route::post('/{audition}', 'setAuditionPassers')->name('advancement.setAuditionPassers');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('throws an exception if mode is not seating or advancement', function () {
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
$calculator->calculate('WRONG', Entry::factory()->create());
|
||||
})->throws(TabulationException::class, 'Mode must be seating or advancement');
|
||||
|
||||
it('throws an exception if entry is not valid', function () {
|
||||
// Arrange
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
// Act
|
||||
$calculator->calculate('seating', Entry::factory()->make());
|
||||
// Assert
|
||||
})->throws(TabulationException::class, 'Invalid entry specified');
|
||||
it('throws an exception if entry is missing judge scores', function () {
|
||||
// Arrange
|
||||
loadSampleAudition();
|
||||
$judge1 = User::factory()->create();
|
||||
$judge2 = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge1);
|
||||
Room::find(1000)->addJudge($judge2);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
$scores = [
|
||||
1001 => 50,
|
||||
1002 => 60,
|
||||
1003 => 70,
|
||||
1004 => 80,
|
||||
1005 => 90,
|
||||
];
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
// Act
|
||||
$calculator->calculate('seating', $entry);
|
||||
// Assert
|
||||
})->throws(TabulationException::class, 'Not all score sheets are in');
|
||||
|
||||
it('throws an exception if a score exists from an invalid judge', function () {
|
||||
// Arrange
|
||||
loadSampleAudition();
|
||||
$judge1 = User::factory()->create();
|
||||
$judge2 = User::factory()->create();
|
||||
$judge3 = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge1);
|
||||
Room::find(1000)->addJudge($judge2);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
$scores = [
|
||||
1001 => 50,
|
||||
1002 => 60,
|
||||
1003 => 70,
|
||||
1004 => 80,
|
||||
1005 => 90,
|
||||
];
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
$scoreSheetToSpoof = enterScore($judge2, $entry, $scores);
|
||||
$scoreSheetToSpoof->update(['user_id' => $judge3->id]);
|
||||
// Act
|
||||
$calculator->calculate('seating', $entry);
|
||||
// Assert
|
||||
})->throws(TabulationException::class, 'Score exists from a judge not assigned to this audition');
|
||||
|
||||
it('correctly calculates scores for seating', function () {
|
||||
// Arrange
|
||||
loadSampleAudition();
|
||||
$judge1 = User::factory()->create();
|
||||
$judge2 = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge1);
|
||||
Room::find(1000)->addJudge($judge2);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
$scores = [
|
||||
1001 => 50,
|
||||
1002 => 60,
|
||||
1003 => 70,
|
||||
1004 => 80,
|
||||
1005 => 90,
|
||||
];
|
||||
$scores2 = [
|
||||
1001 => 55,
|
||||
1002 => 65,
|
||||
1003 => 75,
|
||||
1004 => 85,
|
||||
1005 => 95,
|
||||
];
|
||||
#$calculator = new AllJudgesCount();
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
enterScore($judge2, $entry, $scores2);
|
||||
// Act
|
||||
$finalScores = $calculator->calculate('seating', $entry);
|
||||
// Assert
|
||||
$expectedScores = [142.5, 165, 125, 145, 105];
|
||||
expect($finalScores)->toBe($expectedScores);
|
||||
});
|
||||
|
||||
it('correctly calculates scores for advancement', function () {
|
||||
// Arrange
|
||||
loadSampleAudition();
|
||||
$judge1 = User::factory()->create();
|
||||
$judge2 = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge1);
|
||||
Room::find(1000)->addJudge($judge2);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
$scores = [
|
||||
1001 => 50,
|
||||
1002 => 60,
|
||||
1003 => 70,
|
||||
1004 => 80,
|
||||
1005 => 90,
|
||||
];
|
||||
$scores2 = [
|
||||
1001 => 55,
|
||||
1002 => 65,
|
||||
1003 => 75,
|
||||
1004 => 85,
|
||||
1005 => 95,
|
||||
];
|
||||
$calculator = App::make(AllJudgesCount::class);
|
||||
enterScore($judge1, $entry, $scores);
|
||||
enterScore($judge2, $entry, $scores2);
|
||||
// Act
|
||||
$finalScores = $calculator->calculate('advancement', $entry);
|
||||
// Assert
|
||||
$expectedScores = [152.5, 185, 165, 125, 145];
|
||||
expect($finalScores)->toBe($expectedScores);
|
||||
});
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
use App\Actions\Tabulation\CalculateScoreSheetTotal;
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
|
||||
it('throws an exception if an invalid mode is called for', function () {
|
||||
$calculator = app(CalculateScoreSheetTotal::class);
|
||||
$calculator('anything', Entry::factory()->create(), User::factory()->create());
|
||||
})->throws(TabulationException::class, 'Invalid mode requested. Mode must be seating or advancement');
|
||||
it('throws an exception if an invalid judge is provided', function () {
|
||||
$calculator = app(CalculateScoreSheetTotal::class);
|
||||
$calculator('seating', Entry::factory()->create(), User::factory()->make());
|
||||
})->throws(TabulationException::class, 'Invalid judge provided');
|
||||
it('throws an exception if an invalid entry is provided', function () {
|
||||
$calculator = app(CalculateScoreSheetTotal::class);
|
||||
$calculator('advancement', Entry::factory()->make(), User::factory()->create());
|
||||
})->throws(TabulationException::class, 'Invalid entry provided');
|
||||
it('throws an exception if the specified judge has not scored the entry', function () {
|
||||
// Arrange
|
||||
loadSampleAudition();
|
||||
$judge = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
Artisan::call('cache:clear');
|
||||
$calculator = app(CalculateScoreSheetTotal::class);
|
||||
// Act
|
||||
$calculator('seating', $entry, $judge);
|
||||
//Assert
|
||||
})->throws(TabulationException::class, 'No score sheet by that judge for that entry');
|
||||
it('correctly calculates final score for seating', function () {
|
||||
loadSampleAudition();
|
||||
$judge = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
$scores = [
|
||||
1001 => 50,
|
||||
1002 => 60,
|
||||
1003 => 70,
|
||||
1004 => 80,
|
||||
1005 => 90,
|
||||
];
|
||||
enterScore($judge, $entry, $scores);
|
||||
$calculator = app(CalculateScoreSheetTotal::class);
|
||||
$total = $calculator('seating', $entry, $judge);
|
||||
expect($total[0])->toBe(68.75);
|
||||
$expectedArray = [68.75, 80, 60, 70, 50];
|
||||
expect($total)->toBe($expectedArray);
|
||||
});
|
||||
it('correctly calculates final score for advancement', function () {
|
||||
loadSampleAudition();
|
||||
$judge = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge);
|
||||
$entry = Entry::factory()->create(['audition_id' => 1000]);
|
||||
$scores = [
|
||||
1001 => 50,
|
||||
1002 => 60,
|
||||
1003 => 70,
|
||||
1004 => 80,
|
||||
1005 => 90,
|
||||
];
|
||||
enterScore($judge, $entry, $scores);
|
||||
$calculator = app(CalculateScoreSheetTotal::class);
|
||||
$total = $calculator('advancement', $entry, $judge);
|
||||
expect($total[0])->toBe(73.75);
|
||||
$expectedArray = [73.75, 90, 80, 60, 70];
|
||||
expect($total)->toBe($expectedArray);
|
||||
});
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
use App\Actions\Tabulation\EnterScore;
|
||||
use App\Exceptions\ScoreEntryException;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
#$this->scoreEntry = new EnterScore();
|
||||
$this->scoreEntry = App::make(EnterScore::class);
|
||||
});
|
||||
|
||||
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');
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Tabulation\AllJudgesCount;
|
||||
use App\Actions\Tabulation\RankAuditionEntries;
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('throws an exception if an invalid mode is specified', function () {
|
||||
#$ranker = new RankAuditionEntries(new AllJudgesCount());
|
||||
$ranker = App::make(RankAuditionEntries::class);
|
||||
$ranker->rank('wrong', Audition::factory()->create());
|
||||
})->throws(TabulationException::class, 'Mode must be seating or advancement');
|
||||
it('throws an exception if an invalid audition is provided', function () {
|
||||
// Arrange
|
||||
#$ranker = new RankAuditionEntries(new AllJudgesCount());
|
||||
$ranker = App::make(RankAuditionEntries::class);
|
||||
// Act & Assert
|
||||
$ranker->rank('seating', Audition::factory()->make());
|
||||
})->throws(TabulationException::class, 'Invalid audition provided');
|
||||
it('includes all entries of the given mode in the return', function () {
|
||||
$audition = Audition::factory()->create();
|
||||
$entries = Entry::factory()->seatingOnly()->count(10)->create(['audition_id' => $audition->id]);
|
||||
$otherEntries = Entry::factory()->advanceOnly()->count(10)->create(['audition_id' => $audition->id]);
|
||||
#$ranker = new RankAuditionEntries(new AllJudgesCount());
|
||||
$ranker = App::make(RankAuditionEntries::class);
|
||||
// Act
|
||||
$return = $ranker->rank('seating', $audition);
|
||||
// Assert
|
||||
foreach ($entries as $entry) {
|
||||
expect($return->pluck('id')->toArray())->toContain($entry->id);
|
||||
}
|
||||
foreach ($otherEntries as $entry) {
|
||||
expect($return->pluck('id')->toArray())->not()->toContain($entry->id);
|
||||
}
|
||||
});
|
||||
it('places entries in the proper order', function () {
|
||||
// Arrange
|
||||
Artisan::call('cache:clear');
|
||||
loadSampleAudition();
|
||||
$judge = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge);
|
||||
$entries = Entry::factory()->count(5)->create(['audition_id' => 1000]);
|
||||
$scoreArray1 = [1001 => 90, 1002 => 90, 1003 => 90, 1004 => 90, 1005 => 90];
|
||||
$scoreArray2 = [1001 => 60, 1002 => 60, 1003 => 60, 1004 => 60, 1005 => 60];
|
||||
$scoreArray3 = [1001 => 80, 1002 => 80, 1003 => 80, 1004 => 80, 1005 => 80];
|
||||
$scoreArray4 = [1001 => 100, 1002 => 100, 1003 => 100, 1004 => 100, 1005 => 100];
|
||||
$scoreArray5 = [1001 => 70, 1002 => 70, 1003 => 70, 1004 => 70, 1005 => 70];
|
||||
enterScore($judge, $entries[0], $scoreArray1);
|
||||
enterScore($judge, $entries[1], $scoreArray2);
|
||||
enterScore($judge, $entries[2], $scoreArray3);
|
||||
enterScore($judge, $entries[3], $scoreArray4);
|
||||
enterScore($judge, $entries[4], $scoreArray5);
|
||||
Artisan::call('cache:clear');
|
||||
$ranker = App::make(RankAuditionEntries::class);
|
||||
$expectedOrder = [4, 1, 3, 5, 2];
|
||||
// Act
|
||||
$return = $ranker->rank('seating', Audition::find(1000));
|
||||
// Assert
|
||||
expect($return->pluck('id')->toArray())->toBe($expectedOrder);
|
||||
});
|
||||
|
|
@ -169,6 +169,7 @@ it('has a forAdvancement scope that only returns those entries entered for advan
|
|||
Entry::factory()->count(10)->create(['for_seating' => true, 'for_advancement' => true]);
|
||||
Entry::factory()->count(5)->create(['for_seating' => false, 'for_advancement' => true]);
|
||||
// Act & Assert
|
||||
|
||||
expect(Entry::forAdvancement()->count())->toBe(16)
|
||||
->and(Entry::forAdvancement()->get()->first())->toBeInstanceOf(Entry::class)
|
||||
->and(Entry::forAdvancement()->get()->first()->student->first_name)->toBe('Advance Only');
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ use App\Models\Entry;
|
|||
use App\Models\School;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use App\Services\EntryService;
|
||||
use App\Services\Invoice\InvoiceOneFeePerEntry;
|
||||
use App\Settings;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
use function Pest\Laravel\actingAs;
|
||||
use function Pest\Laravel\get;
|
||||
|
||||
|
|
@ -62,7 +65,8 @@ it('has a new school link', function () {
|
|||
});
|
||||
it('shows school data', function () {
|
||||
// Arrange
|
||||
$invoiceDataService = new App\Services\Invoice\InvoiceOneFeePerEntry(new App\Services\EntryService(new App\Services\AuditionService()));
|
||||
#$invoiceDataService = new App\Services\Invoice\InvoiceOneFeePerEntry(new App\Services\EntryService(new App\Services\AuditionService()));
|
||||
$invoiceDataService = App::make(InvoiceOneFeePerEntry::class);
|
||||
Settings::set('school_fees', 1000);
|
||||
Settings::set('late_fee', 2500);
|
||||
actingAs($this->adminUser);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
use function Pest\Laravel\get;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('does not answer a regular user or guest', function () {
|
||||
get(route('advancement.status'))
|
||||
->assertRedirect(route('home'));
|
||||
actingAs(User::factory()->create());
|
||||
get(route('advancement.status'))
|
||||
->assertRedirect(route('dashboard'))
|
||||
->assertSessionHas('error', 'You are not authorized to perform this action');
|
||||
});
|
||||
it('responds to an admin or tab user', function () {
|
||||
actAsAdmin();
|
||||
get(route('advancement.status'))
|
||||
->assertOk();
|
||||
actAsTab();
|
||||
get(route('advancement.status'))
|
||||
->assertOk();
|
||||
});
|
||||
it('includes advancement auditions', function () {
|
||||
$audition = Audition::factory()->create();
|
||||
actAsAdmin();
|
||||
get(route('advancement.status'))
|
||||
->assertOk()
|
||||
->assertSee($audition->name);
|
||||
});
|
||||
it('does not include auditions not for advancement', function () {
|
||||
$audition = Audition::factory()->seatingOnly()->create();
|
||||
actAsAdmin();
|
||||
get(route('advancement.status'))
|
||||
->assertOk()
|
||||
->assertDontSee($audition->name);
|
||||
});
|
||||
|
|
@ -48,7 +48,7 @@ it('has appropriate students in JS array for select', function () {
|
|||
'id: ',
|
||||
$student->id,
|
||||
'name: ',
|
||||
$student->full_name(true),
|
||||
e($student->full_name(true)),
|
||||
], false); // The false parameter makes the assertion case-sensitive and allows for HTML tags
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Student;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
use function Pest\Laravel\get;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->audition = Audition::factory()->create();
|
||||
$this->r = route('seating.audition', $this->audition);
|
||||
});
|
||||
|
||||
it('denies access to a guest', function () {
|
||||
get($this->r)
|
||||
->assertRedirect(route('home'));
|
||||
});
|
||||
|
||||
it('denies access to a normal user', function () {
|
||||
actAsNormal();
|
||||
get($this->r)
|
||||
->assertRedirect(route('dashboard'))
|
||||
->assertSessionHas('error', 'You are not authorized to perform this action');
|
||||
});
|
||||
it('grants access to admin', function () {
|
||||
// Arrange
|
||||
actAsAdmin();
|
||||
// Act & Assert
|
||||
get($this->r)->assertOk();
|
||||
});
|
||||
it('grants access to tabulators', function () {
|
||||
// Arrange
|
||||
actAsTab();
|
||||
// Act & Assert
|
||||
get($this->r)->assertOk();
|
||||
});
|
||||
// TODO make tests with varied information
|
||||
it('returns the audition object and an array of info on each entry', function () {
|
||||
// Arrange
|
||||
$entry = Entry::factory()->create(['audition_id' => $this->audition->id]);
|
||||
actAsAdmin();
|
||||
// Act
|
||||
$response = get($this->r);
|
||||
$response
|
||||
->assertOk()
|
||||
->assertViewHas('audition', $this->audition);
|
||||
$viewData = $response->viewData('entryData');
|
||||
expect($viewData[0]['rank'])->toBe(1);
|
||||
expect($viewData[0]['id'])->toBe($entry->id);
|
||||
expect($viewData[0]['studentName'])->toBe($entry->student->full_name());
|
||||
expect($viewData[0]['schoolName'])->toBe($entry->student->school->name);
|
||||
expect($viewData[0]['drawNumber'])->toBe($entry->draw_number);
|
||||
expect($viewData[0]['totalScore'])->toBe('No Score');
|
||||
expect($viewData[0]['fullyScored'])->toBeFalse();
|
||||
});
|
||||
it('identifies a doubler', function () {
|
||||
// Arrange
|
||||
$audition1 = Audition::factory()->create(['event_id' => $this->audition->event_id]);
|
||||
$audition2 = Audition::factory()->create(['event_id' => $this->audition->event_id]);
|
||||
$student = Student::factory()->create();
|
||||
Entry::factory()->create(['audition_id' => $audition1->id, 'student_id' => $student->id]);
|
||||
Entry::factory()->create(['audition_id' => $audition2->id, 'student_id' => $student->id]);
|
||||
Entry::factory()->create(['audition_id' => $this->audition->id, 'student_id' => $student->id]);
|
||||
actAsAdmin();
|
||||
// Act & Assert
|
||||
$response = get($this->r);
|
||||
$response->assertOk();
|
||||
$viewData = $response->viewData('entryData');
|
||||
expect($viewData[0]['doubleData'])->toBeTruthy();
|
||||
});
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
use function Pest\Laravel\get;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('will not answer to guest or normal user', function () {
|
||||
get(route('seating.status'))
|
||||
->assertRedirect(route('home'));
|
||||
actAsNormal();
|
||||
get(route('seating.status'))
|
||||
->assertRedirect(route('dashboard'))
|
||||
->assertSessionHas('error', 'You are not authorized to perform this action');
|
||||
});
|
||||
it('responds to an admin', function () {
|
||||
// Arrange
|
||||
actAsAdmin();
|
||||
// Act & Assert
|
||||
get(route('seating.status'))
|
||||
->assertOk();
|
||||
});
|
||||
it('responds to a tabulator', function () {
|
||||
// Arrange
|
||||
actAsTab();
|
||||
// Act & Assert
|
||||
get(route('seating.status'))
|
||||
->assertOk();
|
||||
});
|
||||
it('sends the view a collection of audition data that includes data needed by the view', function () {
|
||||
// Arrange
|
||||
$auditions = Audition::factory()->count(5)->create();
|
||||
actAsAdmin();
|
||||
// Act
|
||||
$response = get(route('seating.status'));
|
||||
// Assert
|
||||
foreach ($auditions as $audition) {
|
||||
$response->assertOk()
|
||||
->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['id'] === $audition->id;
|
||||
});
|
||||
$response->assertOk()
|
||||
->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['name'] === $audition->name;
|
||||
});
|
||||
}
|
||||
});
|
||||
it('has correct count info for an audition with 5 entries none scored', function () {
|
||||
$audition = Audition::factory()->create();
|
||||
Entry::factory()->count(5)->create(['audition_id' => $audition->id]);
|
||||
|
||||
actAsAdmin();
|
||||
$response = get(route('seating.status'));
|
||||
$response->assertOk();
|
||||
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoredEntriesCount'] === 0;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['totalEntriesCount'] === 5;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoredPercentage'] === 0;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoringComplete'] === false;
|
||||
});
|
||||
});
|
||||
it('has correct count info for an audition with 8 entries 2 scored', function () {
|
||||
$judge = User::factory()->create();
|
||||
|
||||
$audition = Audition::factory()->create();
|
||||
$entries = Entry::factory()->count(2)->create(['audition_id' => $audition->id]);
|
||||
$entries->each(fn ($entry) => ScoreSheet::create([
|
||||
'user_id' => $judge->id,
|
||||
'entry_id' => $entry->id,
|
||||
'subscores' => 7,
|
||||
]));
|
||||
Entry::factory()->count(6)->create(['audition_id' => $audition->id]);
|
||||
|
||||
actAsAdmin();
|
||||
$response = get(route('seating.status'));
|
||||
$response->assertOk();
|
||||
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoredEntriesCount'] === 2;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['totalEntriesCount'] === 8;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoredPercentage'] == 25;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoringComplete'] === false;
|
||||
});
|
||||
});
|
||||
|
||||
it('has correct count info for an audition with 1 entries 1 scored', function () {
|
||||
$judge = User::factory()->create();
|
||||
|
||||
$audition = Audition::factory()->create();
|
||||
$entry = Entry::factory()->create(['audition_id' => $audition->id]);
|
||||
ScoreSheet::create([
|
||||
'user_id' => $judge->id,
|
||||
'entry_id' => $entry->id,
|
||||
'subscores' => 3,
|
||||
]);
|
||||
|
||||
actAsAdmin();
|
||||
$response = get(route('seating.status'));
|
||||
$response->assertOk();
|
||||
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoredEntriesCount'] === 1;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['totalEntriesCount'] === 1;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoredPercentage'] == 100;
|
||||
});
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['scoringComplete'] === true;
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly shows a flag when the audition is flagged as seated', function () {
|
||||
$audition = Audition::factory()->create();
|
||||
|
||||
actAsAdmin();
|
||||
$response = get(route('seating.status'));
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['seatsPublished'] === false;
|
||||
});
|
||||
$audition->addFlag('seats_published');
|
||||
$response = get(route('seating.status'));
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('auditionData', function ($viewAuditionData) use ($audition) {
|
||||
return $viewAuditionData[$audition->id]['seatsPublished'] === true;
|
||||
});
|
||||
});
|
||||
it('shows seating auditions', function() {
|
||||
$audition = Audition::factory()->create();
|
||||
actAsAdmin();
|
||||
get(route('seating.status'))
|
||||
->assertOk()
|
||||
->assertSee($audition->name);
|
||||
});
|
||||
it('does not show advancement only auditions', function() {
|
||||
$audition = Audition::factory()->advancementOnly()->create();
|
||||
actAsAdmin();
|
||||
get(route('seating.status'))
|
||||
->assertOk()
|
||||
->assertDontSee($audition->name);
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@ use App\Models\Audition;
|
|||
use App\Models\Entry;
|
||||
use App\Services\DrawService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
|
|
@ -77,7 +78,8 @@ it('sets the draw_number column on each entry in the audition based on the rando
|
|||
// Arrange
|
||||
$audition = Audition::factory()->hasEntries(10)->create();
|
||||
Entry::all()->each(fn ($entry) => expect($entry->draw_number)->toBeNull());
|
||||
$drawService = new DrawService();
|
||||
#$drawService = new DrawService();
|
||||
$drawService = App::make(DrawService::class);
|
||||
$drawService->runOneDraw($audition);
|
||||
// Act & Assert
|
||||
Entry::all()->each(fn ($entry) => expect($entry->draw_number)->not()->toBeNull());
|
||||
|
|
@ -86,7 +88,8 @@ it('only sets draw numbers in the specified audition', function () {
|
|||
// Arrange
|
||||
$audition = Audition::factory()->hasEntries(10)->create();
|
||||
$bonusEntry = Entry::factory()->create();
|
||||
$drawService = new DrawService();
|
||||
#$drawService = new DrawService();
|
||||
$drawService = App::make(DrawService::class);
|
||||
$drawService->runOneDraw($audition);
|
||||
// Act & Assert
|
||||
expect($bonusEntry->draw_number)->toBeNull();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Room;
|
||||
use App\Models\User;
|
||||
use App\Services\AuditionService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
// getSubscores()
|
||||
it('throws an exception when an invalid mode is requested', function () {
|
||||
//$auditionService = new \App\Services\AuditionService();
|
||||
$auditionService = App::make(AuditionService::class);
|
||||
$this->expectException(\App\Exceptions\AuditionServiceException::class);
|
||||
$auditionService->getSubscores(new Audition(), 'invalid_mode');
|
||||
});
|
||||
it('throws an exception when an invalid sort is requested', function () {
|
||||
// Arrange
|
||||
//$auditionService = new \App\Services\AuditionService();
|
||||
$auditionService = App::make(AuditionService::class);
|
||||
$this->expectException(\App\Exceptions\AuditionServiceException::class);
|
||||
// Act
|
||||
$auditionService->getSubscores(new Audition(), 'seating', 'invalid_sort');
|
||||
});
|
||||
it('throws an exception when an invalid audition is provided', function () {
|
||||
// Arrange
|
||||
//$auditionService = new \App\Services\AuditionService();
|
||||
$auditionService = App::make(AuditionService::class);
|
||||
$this->expectException(\App\Exceptions\AuditionServiceException::class);
|
||||
$auditionService->getSubscores(new Audition(), 'seating', 'tiebreak');
|
||||
// Act & Assert
|
||||
|
||||
});
|
||||
it('gets subscores for an audition', function () {
|
||||
// Arrange
|
||||
loadSampleAudition();
|
||||
//$auditionService = new \App\Services\AuditionService();
|
||||
$auditionService = App::make(AuditionService::class);
|
||||
// Act
|
||||
$subscores = $auditionService->getSubscores(Audition::find(1000), 'seating', 'tiebreak');
|
||||
// Assert
|
||||
expect($subscores->toArray())->toBe(Audition::find(1000)->scoringGuide->subscores->where('for_seating',
|
||||
true)->sortBy('tiebreak_order')->toArray());
|
||||
});
|
||||
// getJudges()
|
||||
it('gets judges for an audition', function () {
|
||||
loadSampleAudition();
|
||||
$auditionService = App::make(AuditionService::class);
|
||||
$judge = User::factory()->create();
|
||||
$notJudge = User::factory()->create();
|
||||
Room::find(1000)->addJudge($judge);
|
||||
$testValue = $auditionService->getJudges(Audition::find(1000));
|
||||
$test = $testValue->contains(function ($item) use ($judge) {
|
||||
return $item->id === $judge->id;
|
||||
});
|
||||
$negativeTest = $testValue->contains(function ($item) use ($notJudge) {
|
||||
return $item->id === $notJudge->id;
|
||||
});
|
||||
expect($test)->toBeTrue();
|
||||
expect($negativeTest)->toBeFalse();
|
||||
});
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
use App\Exceptions\TabulationException;
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Event;
|
||||
use App\Models\Student;
|
||||
use App\Services\DoublerService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
use function PHPUnit\Framework\assertArrayNotHasKey;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
beforeEach(function () {
|
||||
$this->doublerService = App::make(DoublerService::class);
|
||||
});
|
||||
|
||||
it('throws an error if an invalid event is provided', function () {
|
||||
$event = Event::factory()->make();
|
||||
$this->doublerService->doublersForEvent($event);
|
||||
})->throws(TabulationException::class, 'Invalid event provided');
|
||||
it('returns doublers for an event', function () {
|
||||
$concertEvent = Event::factory()->create(['name' => 'Concert Band', 'id' => 1000]);
|
||||
$jazzEvent = Event::factory()->create(['name' => 'Jazz Band', 'id' => 1001]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1000, 'name' => 'Alto Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1000,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1000, 'name' => 'Tenor Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1001,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1000, 'name' => 'Baritone Sax', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1002,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1000, 'name' => 'Clarinet', 'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1003,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1000, 'name' => 'Bass Clarinet',
|
||||
'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1004,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1001, 'name' => 'Jazz Alto', 'minimum_grade' => 7,
|
||||
'maximum_grade' => 12, 'id' => 1005,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1001, 'name' => 'Jazz Tenor', 'minimum_grade' => 7,
|
||||
'maximum_grade' => 12, 'id' => 1006,
|
||||
]);
|
||||
Audition::factory()->create([
|
||||
'event_id' => 1001, 'name' => 'Jazz Baritone',
|
||||
'minimum_grade' => 7, 'maximum_grade' => 12, 'id' => 1007,
|
||||
]);
|
||||
$allSaxDude = Student::factory()->create(['grade' => 11, 'id' => 1000]);
|
||||
Student::factory()->create(['grade' => 9, 'id' => 1001]);
|
||||
Student::factory()->create(['grade' => 9, 'id' => 1002]);
|
||||
Entry::create(['student_id' => 1000, 'audition_id' => 1000]);
|
||||
Entry::create(['student_id' => 1000, 'audition_id' => 1001]);
|
||||
Entry::create(['student_id' => 1000, 'audition_id' => 1002]);
|
||||
Entry::create(['student_id' => 1000, 'audition_id' => 1005]);
|
||||
Entry::create(['student_id' => 1000, 'audition_id' => 1006]);
|
||||
Entry::create(['student_id' => 1000, 'audition_id' => 1007]);
|
||||
Entry::create(['student_id' => 1001, 'audition_id' => 1003]);
|
||||
Entry::create(['student_id' => 1001, 'audition_id' => 1004]);
|
||||
Entry::create(['student_id' => 1002, 'audition_id' => 1000]);
|
||||
Entry::create(['student_id' => 1002, 'audition_id' => 1005]);
|
||||
|
||||
$return = $this->doublerService->doublersForEvent($concertEvent);
|
||||
expect(count($return))->toBe(2)
|
||||
->and($return[1000]['student_id'])->toBe($allSaxDude->id)
|
||||
->and($return[1000]['entries']->count())->toBe(3)
|
||||
->and($return[1001]['entries']->count())->toBe(2);
|
||||
assertArrayNotHasKey(1002, $return);
|
||||
$return = $this->doublerService->doublersForEvent($jazzEvent);
|
||||
expect(count($return))->toBe(1)
|
||||
->and($return[1000]['student_id'])->toBe($allSaxDude->id)
|
||||
->and($return[1000]['entries']->count())->toBe(3);
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Services\EntryService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function() {
|
||||
$this->entryService = App::make(EntryService::class);
|
||||
});
|
||||
|
||||
it('checks if an entry is late', function() {
|
||||
$openAudition = Audition::factory()->create(['entry_deadline' => Carbon::tomorrow()]);
|
||||
$closedAudition = Audition::factory()->create(['entry_deadline' => Carbon::yesterday()]);
|
||||
|
||||
$onTime = Entry::factory()->create(['audition_id' => $openAudition->id]);
|
||||
$late = Entry::factory()->create(['audition_id' => $closedAudition]);
|
||||
expect($this->entryService->isEntryLate($onTime))->toBeFalse();
|
||||
expect($this->entryService->isEntryLate($late))->toBeTrue();
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Audition;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Room;
|
||||
use App\Models\ScoreSheet;
|
||||
use App\Models\User;
|
||||
use App\Services\ScoreService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
#$this->scoreService = new ScoreService();
|
||||
$this->scoreService = App::make(ScoreService::class);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\UserService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function() {
|
||||
$this->userService = App::make(UserService::class);
|
||||
});
|
||||
|
||||
it('checks if a user exists', function() {
|
||||
$realUser = User::factory()->create();
|
||||
$fakeUser = User::factory()->make();
|
||||
expect ($this->userService->userExists($realUser))->toBeTrue();
|
||||
expect ($this->userService->userExists($fakeUser))->toBeFalse();
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue