diff --git a/app/Http/Controllers/Admin/EntryController.php b/app/Http/Controllers/Admin/EntryController.php
index 9898526..c7b8cdb 100644
--- a/app/Http/Controllers/Admin/EntryController.php
+++ b/app/Http/Controllers/Admin/EntryController.php
@@ -4,17 +4,13 @@ namespace App\Http\Controllers\Admin;
use App\Actions\Entries\CreateEntry;
use App\Actions\Entries\UpdateEntry;
-use App\Exceptions\AuditionAdminException;
-use App\Exceptions\ManageEntryException;
use App\Http\Controllers\Controller;
use App\Http\Requests\EntryStoreRequest;
use App\Models\Audition;
-use App\Models\AuditLogEntry;
use App\Models\Entry;
use App\Models\School;
use App\Models\Seat;
use App\Models\Student;
-use App\Services\ScoreService;
use Illuminate\Http\Request;
use function auditionSetting;
@@ -68,7 +64,6 @@ class EntryController extends Controller
if (isset($filters['entry_type']) && $filters['entry_type']) {
// TODO define actions for each possible type filter from index.blade.php of the admin entry
match ($filters['entry_type']) {
- 'all' => null,
'seats' => $entries->where('for_seating', true),
'advancement' => $entries->where('for_advancement', true),
'seatsOnly' => $entries->where('for_seating', true)->where('for_advancement', false),
@@ -110,11 +105,16 @@ class EntryController extends Controller
public function store(EntryStoreRequest $request, CreateEntry $creator)
{
$validData = $request->validatedWithEnterFor();
- try {
- $entry = $creator($validData['student_id'], $validData['audition_id'], $enter_for);
- } catch (ManageEntryException $ex) {
- return redirect()->route('admin.entries.index')->with('error', $ex->getMessage());
- }
+
+ /** @noinspection PhpUnhandledExceptionInspection */
+ $entry = $creator(
+ student: $validData['student_id'],
+ audition: $validData['audition_id'],
+ for_seating: $validData['for_seating'],
+ for_advancement: $validData['for_advancement'],
+ late_fee_waived: $validData['late_fee_waived'],
+ );
+
if ($validData['late_fee_waived']) {
$entry->addFlag('late_fee_waived');
}
@@ -122,7 +122,7 @@ class EntryController extends Controller
return redirect(route('admin.entries.index'))->with('success', 'The entry has been added.');
}
- public function edit(Entry $entry, ScoreService $scoreService)
+ public function edit(Entry $entry)
{
if ($entry->audition->hasFlag('seats_published')) {
return to_route('admin.entries.index')->with('error',
@@ -136,31 +136,33 @@ class EntryController extends Controller
$students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get();
$auditions = Audition::orderBy('score_order')->get();
- $scores = $entry->scoreSheets()->with('audition', 'judge')->get();
- foreach ($scores as $score) {
- $score->entry = $entry;
- $score->valid = $scoreService->isScoreSheetValid($score);
- $score->seating_total_score = $score->seating_total ?? 0;
- $score->advancement_total_score = $score->advancement_total ?? 0;
- }
+ // TODO: When updating Laravel, can we use the chaperone method I heard about ot load the entry back into the score
+ $scores = $entry->scoreSheets()->with('audition', 'judge', 'entry')->get();
return view('admin.entries.edit', compact('entry', 'students', 'auditions', 'scores'));
}
public function update(Request $request, Entry $entry, UpdateEntry $updater)
{
- if ($entry->audition->hasFlag('seats_published')) {
+ // If the entry's current audition is published, we can't change it
+ if ($entry->audition->hasFlag('seats_published') || $entry->audition->hasFlag('advancement_published')) {
return to_route('admin.entries.index')->with('error',
- 'Entries in auditions with seats published cannot be modified');
+ 'Entries in published auditions cannot be modified');
}
- if ($entry->audition->hasFlag('advancement_published')) {
- return to_route('admin.entries.index')->with('error',
- 'Entries in auditions with advancement results published cannot be modified');
- }
$validData = request()->validate([
'audition_id' => ['required', 'exists:auditions,id'],
+ 'late_fee_waived' => ['sometimes'],
+ 'for_seating' => ['sometimes'],
+ 'for_advancement' => ['sometimes'],
]);
+ $proposedAudition = Audition::find($validData['audition_id']);
+
+ // If the entry's new audition is published, we can't change it
+ if ($proposedAudition->hasFlag('seats_published') || $proposedAudition->hasFlag('advancement_published')) {
+ return to_route('admin.entries.index')->with('error',
+ 'Entries cannot be moved to published auditions');
+ }
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
@@ -170,11 +172,10 @@ class EntryController extends Controller
if (! auditionSetting('advanceTo')) {
$validData['for_seating'] = 1;
}
- try {
- $updater($entry, $validData);
- } catch (AuditionAdminException $e) {
- return redirect()->route('admin.entries.index')->with('error', $e->getMessage());
- }
+
+ /** @noinspection PhpUnhandledExceptionInspection */
+ $updater($entry, $validData);
+
if ($validData['late_fee_waived']) {
$entry->addFlag('late_fee_waived');
} else {
@@ -184,17 +185,13 @@ class EntryController extends Controller
return to_route('admin.entries.index')->with('success', 'Entry updated successfully');
}
- public function destroy(Request $request, Entry $entry)
+ public function destroy(Entry $entry)
{
- if ($entry->audition->hasFlag('seats_published')) {
+ if ($entry->audition->hasFlag('seats_published') || $entry->audition->hasFlag('advancement_published')) {
return to_route('admin.entries.index')->with('error',
- 'Entries in auditions with seats published cannot be deleted');
+ 'Entries in published auditions cannot be deleted');
}
- if ($entry->audition->hasFlag('advancement_published')) {
- return to_route('admin.entries.index')->with('error',
- 'Entries in auditions with advancement results published cannot be deleted');
- }
if (Seat::where('entry_id', $entry->id)->exists()) {
return redirect()->route('admin.entries.index')->with('error', 'Cannot delete an entry that is seated');
}
@@ -203,21 +200,7 @@ class EntryController extends Controller
return redirect()->route('admin.entries.index')->with('error',
'Cannot delete an entry that has been scored');
}
- if (auth()->user()) {
- $message = 'Deleted entry '.$entry->id;
- $affected = [
- 'entries' => [$entry->id],
- 'auditions' => [$entry->audition_id],
- 'schools' => [$entry->student->school_id],
- 'students' => [$entry->student_id],
- ];
- AuditLogEntry::create([
- 'user' => auth()->user()->email,
- 'ip_address' => request()->ip(),
- 'message' => $message,
- 'affected' => $affected,
- ]);
- }
+
$entry->delete();
return redirect()->route('admin.entries.index')->with('success', 'Entry Deleted');
diff --git a/app/Models/ScoreSheet.php b/app/Models/ScoreSheet.php
index 5d14979..304a0ba 100644
--- a/app/Models/ScoreSheet.php
+++ b/app/Models/ScoreSheet.php
@@ -49,7 +49,7 @@ class ScoreSheet extends Model
// this function is used at resources/views/tabulation/entry_score_sheet.blade.php
}
- public function testValidity()
+ public function testValidity(): bool
{
return $this->audition->judges->contains('id', $this->user_id);
}
diff --git a/resources/views/admin/entries/edit.blade.php b/resources/views/admin/entries/edit.blade.php
index f4fdd6c..a2b6912 100644
--- a/resources/views/admin/entries/edit.blade.php
+++ b/resources/views/admin/entries/edit.blade.php
@@ -88,12 +88,14 @@
{{ $score->judge->full_name() }}
-
+ @if(! $score->entry->audition->hasFlag('seats_published') && ! $score->entry->audition->hasFlag('advancement_published'))
+
Confirm you would like to delete the {{ $score->entry->audition->name }} score for {{ $score->entry->student->full_name() }} by {{ $score->judge->full_name() }}.
+ @endif
@foreach($score->subscores as $subscore)
@@ -105,16 +107,16 @@
{{ auditionSetting('auditionAbbreviation') }} Total
- {{ $score->seating_total_score }}
+ {{ $score->seating_total }}
@if( auditionSetting('advanceTo'))
{{ auditionSetting('advanceTo') }} Total
- {{ $score->advancement_total_score }}
+ {{ $score->advancement_total }}
@endif
- @if(! $score->valid))
+ @if(! $score->testValidity())
diff --git a/tests/Feature/app/Http/Controllers/Admin/EntryControllerTest.php b/tests/Feature/app/Http/Controllers/Admin/EntryControllerTest.php
index 327735d..f5af9ec 100644
--- a/tests/Feature/app/Http/Controllers/Admin/EntryControllerTest.php
+++ b/tests/Feature/app/Http/Controllers/Admin/EntryControllerTest.php
@@ -1,11 +1,16 @@
$this->entry4->id,
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$response->assertSee($this->entry4->student->full_name())
->assertDontSee($this->entry2->student->full_name());
});
@@ -68,7 +72,6 @@ describe('EntryController::index', function () {
'first_name' => $this->entry4->student->first_name,
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$response->assertSee($this->entry4->student->full_name())
->assertDontSee($this->entry2->student->full_name());
});
@@ -79,7 +82,6 @@ describe('EntryController::index', function () {
'last_name' => $this->entry4->student->last_name,
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$response->assertSee($this->entry4->student->full_name())
->assertDontSee($this->entry2->student->full_name());
});
@@ -90,7 +92,6 @@ describe('EntryController::index', function () {
'audition' => $this->entry1->audition_id,
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeFalse()
@@ -104,7 +105,6 @@ describe('EntryController::index', function () {
'school' => $this->entry1->student->school_id,
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeFalse()
@@ -118,7 +118,6 @@ describe('EntryController::index', function () {
'grade' => 9,
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeFalse()
@@ -132,7 +131,6 @@ describe('EntryController::index', function () {
'entry_type' => 'seats',
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeTrue()
@@ -146,7 +144,6 @@ describe('EntryController::index', function () {
'entry_type' => 'advancement',
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeFalse()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeTrue()
@@ -160,7 +157,6 @@ describe('EntryController::index', function () {
'entry_type' => 'seatsOnly',
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeFalse()
@@ -174,7 +170,6 @@ describe('EntryController::index', function () {
'entry_type' => 'advancementOnly',
],
])->get(route('admin.entries.index'))->assertOk();
- saveContentLocally($response->getContent());
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->contains('id', $this->entry1->id))->toBeFalse()
->and($returnedEntries->contains('id', $this->entry2->id))->toBeFalse()
@@ -254,7 +249,163 @@ describe('EntryController::store', function () {
$this->post(route('admin.entries.store'), $this->testSubmitData)->assertRedirect(route('dashboard'));
});
it('creates an entry', function () {
+ $startingEntryCount = Entry::count();
actAsAdmin();
$this->post(route('admin.entries.store'), $this->testSubmitData);
+ expect(Entry::count())->toEqual($startingEntryCount + 1);
+ });
+});
+
+describe('EntryController::edit', function () {
+ it('denies access to non-admins', function () {
+ $this->get(route('admin.entries.edit', $this->entry1))->assertRedirect(route('home'));
+ actAsNormal();
+ $this->get(route('admin.entries.edit', $this->entry1))->assertRedirect(route('dashboard'));
+ actAsTab();
+ $this->get(route('admin.entries.edit', $this->entry1))->assertRedirect(route('dashboard'));
+ });
+ it('will not edit a published audition', function () {
+ actAsAdmin();
+ $this->entry1->audition->addFlag('seats_published');
+ $this->entry1->refresh();
+ $this->get(route('admin.entries.edit', $this->entry1))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('error', 'Entries in auditions with seats published cannot be modified');
+ $this->entry2->audition->addFlag('advancement_published');
+ $this->entry2->refresh();
+ $this->get(route('admin.entries.edit', $this->entry2))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('error', 'Entries in auditions with advancement results published cannot be modified');
+ });
+ it('presents a form to edit an entry', function () {
+ actAsAdmin();
+ $this->get(route('admin.entries.edit', $this->entry1))->assertOk()
+ ->assertViewIs('admin.entries.edit');
+ });
+});
+
+describe('EntryController::update', function () {
+ it('denies access to non-admins', function () {
+ $this->patch(route('admin.entries.update', $this->entry1))->assertRedirect(route('home'));
+ actAsNormal();
+ $this->patch(route('admin.entries.update', $this->entry1))->assertRedirect(route('dashboard'));
+ actAsTab();
+ $this->patch(route('admin.entries.update', $this->entry1))->assertRedirect(route('dashboard'));
+ });
+ it('will not update an entry whose current audition has published seats', function () {
+ actAsAdmin();
+ $this->auditions[0]->addFlag('seats_published');
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[1]->id,
+ ]);
+ $response->assertRedirect(route('admin.entries.index'))->assertSessionHas('error',
+ 'Entries in published auditions cannot be modified');
+ });
+ it('will not update an entry whose current audition has published advancement', function () {
+ actAsAdmin();
+ $this->auditions[0]->addFlag('advancement_published');
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[1]->id,
+ ]);
+ $response->assertRedirect(route('admin.entries.index'))->assertSessionHas('error',
+ 'Entries in published auditions cannot be modified');
+ });
+ it('will not update an entry whose proposed audition has published seats', function () {
+ actAsAdmin();
+ $this->auditions[1]->addFlag('seats_published');
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[1]->id,
+ ]);
+ $response->assertRedirect(route('admin.entries.index'))->assertSessionHas('error',
+ 'Entries cannot be moved to published auditions');
+ });
+ it('will not update an entry whose proposed audition has published advancement', function () {
+ actAsAdmin();
+ $this->auditions[1]->addFlag('advancement_published');
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[1]->id,
+ ]);
+ $response->assertRedirect(route('admin.entries.index'))->assertSessionHas('error',
+ 'Entries cannot be moved to published auditions');
+ });
+ it('chan change entry type', function () {
+ actAsAdmin();
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[0]->id,
+ 'for_advancement' => 'on',
+ ]);
+ $response->assertRedirect(route('admin.entries.index'));
+ $this->entry1->refresh();
+ expect($this->entry1->for_seating)->toBeFalsy()
+ ->and($this->entry1->for_advancement)->toBeTruthy();
+ });
+ it('can waive late fees', function () {
+ actAsAdmin();
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[0]->id,
+ 'for_advancement' => 'on',
+ 'late_fee_waived' => 'on',
+ ]);
+ $response->assertRedirect(route('admin.entries.index'));
+ $this->entry1->refresh();
+ expect($this->entry1->hasFlag('late_fee_waived'))->toBeTruthy();
+ });
+ it('if we dont have advancement, for_seating must be true', function () {
+ actAsAdmin();
+ Settings::set('advanceTo', '');
+ $response = $this->patch(route('admin.entries.update', $this->entry1), [
+ 'audition_id' => $this->auditions[0]->id,
+ ]);
+ $response->assertRedirect(route('admin.entries.index'));
+ $this->entry1->refresh();
+ expect($this->entry1->for_seating)->toBeTruthy()
+ ->and($this->entry1->for_advancement)->toBeFalsy();
+ });
+});
+
+describe('EntryController::destroy', function () {
+ it('denies access to non-admins', function () {
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('home'));
+ actAsNormal();
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('dashboard'));
+ actAsTab();
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('dashboard'));
+ });
+ it('will not delete an entry with a published audition', function () {
+ actAsAdmin();
+ $this->auditions[0]->addFlag('seats_published');
+ $this->auditions[1]->addFlag('advancement_published');
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('error', 'Entries in published auditions cannot be deleted');
+ $this->delete(route('admin.entries.destroy', $this->entry2))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('error', 'Entries in published auditions cannot be deleted');
+ });
+ it('will not delete an entry that is seated', function () {
+ actAsAdmin();
+ $ensemble = Ensemble::factory()->create();
+ DB::table('seats')->insert([
+ 'ensemble_id' => $ensemble->id,
+ 'audition_id' => $this->entry1->audition_id,
+ 'seat' => 1,
+ 'entry_id' => $this->entry1->id,
+ ]);
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('error', 'Cannot delete an entry that is seated');
+ });
+ it('will not delete an entry that is scored', function () {
+ actAsAdmin();
+ DB::table('score_sheets')->insert([
+ 'user_id' => User::factory()->create()->id,
+ 'entry_id' => $this->entry1->id,
+ 'subscores' => json_encode([3, 5, 6]),
+ 'seating_total' => 44,
+ 'advancement_total' => 55,
+ ]);
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('error', 'Cannot delete an entry that has been scored');
+ });
+ it('can delete an entry', function () {
+ actAsAdmin();
+ $this->delete(route('admin.entries.destroy', $this->entry1))->assertRedirect(route('admin.entries.index'))
+ ->assertSessionHas('success', 'Entry Deleted');
+ assertDatabaseMissing('entries', ['id' => $this->entry1->id]);
});
});
diff --git a/tests/Feature/app/Models/ScoreSheetTest.php b/tests/Feature/app/Models/ScoreSheetTest.php
index 829853e..afbbd87 100644
--- a/tests/Feature/app/Models/ScoreSheetTest.php
+++ b/tests/Feature/app/Models/ScoreSheetTest.php
@@ -1,5 +1,7 @@