updates to entry action

This commit is contained in:
Matt Young 2025-07-08 16:27:12 -05:00
parent 66fe859f06
commit b09f1b13ca
6 changed files with 331 additions and 55 deletions

View File

@ -17,37 +17,51 @@ class CreateEntry
/**
* @throws ManageEntryException
*/
public function __invoke(Student|int $student, Audition|int $audition, string|array|null $entry_for = null)
{
return $this->createEntry($student, $audition, $entry_for);
public function __invoke(
Student|int $student,
Audition|int $audition,
$for_seating = false,
$for_advancement = false,
$late_fee_waived = false
) {
return $this->createEntry($student, $audition, $for_seating, $for_advancement, $late_fee_waived);
}
/**
* @throws ManageEntryException
*/
public function createEntry(Student|int $student, Audition|int $audition, string|array|null $entry_for = null): Entry
{
public function createEntry(
Student|int $student,
Audition|int $audition,
$for_seating = false,
$for_advancement = false,
$late_fee_waived = false
): Entry {
if (is_int($student)) {
$student = Student::find($student);
}
if (is_int($audition)) {
$audition = Audition::find($audition);
}
if (! $entry_for) {
$entry_for = ['seating', 'advancement'];
}
$entry_for = collect($entry_for);
$this->verifySubmission($student, $audition);
if (! $for_advancement && ! $for_seating) {
$for_seating = true;
$for_advancement = true;
}
$entry = Entry::make([
'student_id' => $student->id,
'audition_id' => $audition->id,
'draw_number' => $this->checkDraw($audition),
'for_seating' => $entry_for->contains('seating'),
'for_advancement' => $entry_for->contains('advancement'),
'for_seating' => $for_seating,
'for_advancement' => $for_advancement,
]);
$entry->save();
if ($late_fee_waived) {
$entry->addFlag('late_fee_waived');
$entry->refresh();
}
return $entry;
}

View File

@ -7,6 +7,7 @@ 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;
@ -15,7 +16,6 @@ use App\Models\Seat;
use App\Models\Student;
use App\Services\ScoreService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use function auditionSetting;
use function compact;
@ -25,9 +25,6 @@ class EntryController extends Controller
{
public function index()
{
if (! Auth::user()->is_admin) {
abort(403);
}
$perPage = 25;
$filters = session('adminEntryFilters') ?? null;
$minGrade = Audition::min('minimum_grade');
@ -38,31 +35,31 @@ class EntryController extends Controller
$entries = Entry::with(['student.school', 'audition']);
$entries->orderBy('id', 'DESC');
if ($filters) {
if ($filters['id']) {
if ($filters['id'] ?? false) {
$entries->where('id', $filters['id']);
}
if ($filters['audition']) {
if ($filters['audition'] ?? false) {
$entries->where('audition_id', $filters['audition']);
}
if ($filters['school']) {
if ($filters['school'] ?? false) {
$entries->whereHas('student', function ($query) use ($filters) {
$query->where('school_id', '=', $filters['school']);
});
}
if ($filters['grade']) {
if ($filters['grade'] ?? false) {
$entries->whereHas('student', function ($query) use ($filters) {
$query->where('grade', $filters['grade']);
});
}
if ($filters['first_name']) {
if ($filters['first_name'] ?? false) {
$entries->whereHas('student', function ($query) use ($filters) {
$query->where('first_name', 'like', '%'.$filters['first_name'].'%');
});
}
if ($filters['last_name']) {
if ($filters['last_name'] ?? false) {
$entries->whereHas('student', function ($query) use ($filters) {
$query->where('last_name', 'like', '%'.$filters['last_name'].'%');
});
@ -110,27 +107,9 @@ class EntryController extends Controller
return view('admin.entries.create', ['students' => $students, 'auditions' => $auditions]);
}
public function store(Request $request, CreateEntry $creator)
public function store(EntryStoreRequest $request, CreateEntry $creator)
{
if (! Auth::user()->is_admin) {
abort(403);
}
$validData = request()->validate([
'student_id' => ['required', 'exists:students,id'],
'audition_id' => ['required', 'exists:auditions,id'],
]);
$validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
$validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
$validData['late_fee_waived'] = $request->get('late_fee_waived') ? 1 : 0;
$enter_for = [];
if ($validData['for_seating']) {
$enter_for[] = 'seating';
}
if ($validData['for_advancement']) {
$enter_for[] = 'advancement';
}
$validData = $request->validatedWithEnterFor();
try {
$entry = $creator($validData['student_id'], $validData['audition_id'], $enter_for);
} catch (ManageEntryException $ex) {

View File

@ -38,8 +38,12 @@ class EntryController extends Controller
public function store(EntryStoreRequest $request, CreateEntry $creator)
{
$validData = $request->validatedWithEnterFor();
$creator($validData['student_id'], $validData['audition_id'], $validData['enter_for'] ?? []);
$creator(
$validData['student_id'],
$validData['audition_id'],
for_seating: $validData['for_seating'],
for_advancement: $validData['for_advancement'],
);
return redirect()->route('entries.index')->with('success', 'The entry has been added.');
}

View File

@ -3,12 +3,13 @@
namespace App\Http\Requests;
use App\Models\Audition;
use Auth;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
class EntryStoreRequest extends FormRequest
{
public function authorize()
public function authorize(): bool
{
if (auth()->user()->is_admin) {
return true;
@ -20,17 +21,25 @@ class EntryStoreRequest extends FormRequest
return false;
}
public function rules()
public function rules(): array
{
return [
$rules = [
'student_id' => ['required', 'exists:students,id'],
'audition_id' => ['required', 'exists:auditions,id'],
'for_seating' => ['sometimes', 'boolean'],
'for_advancement' => ['sometimes', 'boolean'],
];
// Add late_fee_waived validation only for admin users
if (auth()->user()->is_admin) {
$rules['late_fee_waived'] = ['sometimes', 'boolean'];
}
return $rules;
}
public function withValidator($validator)
public function withValidator($validator): void
{
$validator->after(function ($validator) {
$auditionId = $this->input('audition_id');
@ -42,10 +51,12 @@ class EntryStoreRequest extends FormRequest
return;
}
$currentDate = Carbon::now('America/Chicago')->format('Y-m-d');
if (! Auth::user()->is_admin) { //Admins don't care about deadlines
$currentDate = Carbon::now('America/Chicago')->format('Y-m-d');
if ($audition->entry_deadline < $currentDate) {
$validator->errors()->add('entry_deadline', 'The entry deadline for that audition has passed.');
if ($audition->entry_deadline < $currentDate) {
$validator->errors()->add('entry_deadline', 'The entry deadline for that audition has passed.');
}
}
});
}
@ -53,13 +64,21 @@ class EntryStoreRequest extends FormRequest
/**
* Prepare the data for validation.
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
// Normalize the boolean inputs to 1 or 0
$this->merge([
$data = [
'for_seating' => $this->boolean('for_seating'),
'for_advancement' => $this->boolean('for_advancement'),
]);
];
// Only include late_fee_waived in the data if the user is admin
if (auth()->user()->is_admin) {
$data['late_fee_waived'] = $this->boolean('late_fee_waived');
}
$this->merge($data);
}
/**

View File

@ -64,7 +64,7 @@ it('allows setting only seating', function () {
test('allows setting only advancement', function () {
$student = Student::factory()->create(['grade' => 9]);
$audition = Audition::factory()->create(['minimum_grade' => 9, 'maximum_grade' => 12]);
$this->scribe->createEntry($student, $audition, 'advancement');
$this->scribe->createEntry($student, $audition, for_advancement: true);
$thisEntry = Entry::where('student_id', $student->id)->first();
expect($thisEntry->for_seating)->toBeFalsy()

View File

@ -0,0 +1,260 @@
<?php
use App\Models\Audition;
use App\Models\Entry;
use App\Models\Event;
use App\Models\Student;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->event = Event::factory()->create();
$this->auditions = Audition::factory()->count(2)->create(['event_id' => $this->event->id]);
$this->students = Student::factory()->count(2)->create();
$this->entry1 = Entry::factory()->create([
'audition_id' => $this->auditions[0]->id, 'student_id' => $this->students[0]->id, 'for_seating' => 1,
'for_advancement' => 0,
]);
$this->entry2 = Entry::factory()->create([
'audition_id' => $this->auditions[1]->id, 'student_id' => $this->students[1]->id, 'for_seating' => 1,
'for_advancement' => 1,
]);
$this->entry3 = Entry::factory()->create([
'audition_id' => $this->auditions[0]->id, 'student_id' => $this->students[1]->id, 'for_seating' => 0,
'for_advancement' => 1,
]);
$this->entry4 = Entry::factory()->create([
'audition_id' => $this->auditions[1]->id, 'student_id' => $this->students[0]->id, 'for_seating' => 1,
'for_advancement' => 1,
]);
$this->entry1->student->update(['grade' => 9]);
$this->entry2->student->update(['grade' => 10]);
});
describe('EntryController::index', function () {
it('denies access to non-admins', function () {
$this->get(route('admin.entries.index'))->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.entries.index'))->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.entries.index'))->assertRedirect(route('dashboard'));
});
it('provides a list of entries', function () {
actAsAdmin();
$response = $this->get(route('admin.entries.index'))->assertOk()
->assertViewIs('admin.entries.index');
foreach (Entry::all() as $entry) {
$response->assertSee($entry->student->full_name());
$response->assertSee($entry->audition->name);
}
});
describe('test filters', function () {
it('can filter by ID', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'id' => $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());
});
it('can filter by first_name', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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());
});
it('can filter by last_name', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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());
});
it('can filter by audition', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeFalse();
});
it('can filter by school', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeFalse()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeTrue();
});
it('can filter by grade', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeFalse()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeTrue();
});
it('can show auditions entered in seating', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeFalse()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeTrue();
});
it('can show auditions entered in advancement', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeTrue();
});
it('can show auditions entered only in seating', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeFalse()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeFalse();
});
it('can show auditions entered only in advancement', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'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()
->and($returnedEntries->contains('id', $this->entry3->id))->toBeTrue()
->and($returnedEntries->contains('id', $this->entry4->id))->toBeFalse();
});
it('can limit the number of results per page', function () {
actAsAdmin();
$response = $this->withSession([
'adminEntryFilters' => [
'entries_per_page' => 1,
],
])->get(route('admin.entries.index'))->assertOk();
$returnedEntries = $response->viewData('entries');
expect($returnedEntries->count())->toEqual(1);
});
});
});
describe('EntryController::create', function () {
it('denies access to non-admins', function () {
$this->get(route('admin.entries.create'))->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.entries.create'))->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.entries.create'))->assertRedirect(route('dashboard'));
});
it('provides a form to make an entry', function () {
actAsAdmin();
$response = $this->get(route('admin.entries.create'));
$response->assertOk();
});
it('provides auditions to the form', function () {
actAsAdmin();
$response = $this->get(route('admin.entries.create'));
$response->assertOk();
$response->assertViewHas('auditions');
$returnedAuditions = $response->viewData('auditions');
foreach (Audition::all() as $audition) {
expect($returnedAuditions->contains('id', $audition->id))->toBeTrue();
}
});
it('does not provide published auditions to the form', function () {
actAsAdmin();
$unavailableAudition[0] = Audition::factory()->create();
$unavailableAudition[0]->addFlag('seats_published');
$unavailableAudition[1] = Audition::factory()->create();
$unavailableAudition[1]->addFlag('advancement_published');
$response = $this->get(route('admin.entries.create'));
$response->assertOk();
$response->assertViewHas('auditions');
$returnedAuditions = $response->viewData('auditions');
foreach ($unavailableAudition as $audition) {
expect($returnedAuditions->contains('id', $audition->id))->toBeFalse();
}
});
});
describe('EntryController::store', function () {
beforeEach(function () {
$this->testAudition = Audition::factory()->create();
$this->testStudent = Student::factory()->create();
$this->testSubmitData = [
'student_id' => $this->testStudent->id,
'audition_id' => $this->testAudition->id,
'for_seating' => 'on',
'for_advancement' => 'on',
'late_fee_waived' => 'on',
];
});
it('denies access to non-admins', function () {
$this->post(route('admin.entries.store'), $this->testSubmitData)->assertRedirect(route('home'));
actAsNormal();
$this->post(route('admin.entries.store'), $this->testSubmitData)->assertRedirect(route('dashboard'));
actAsTab();
$this->post(route('admin.entries.store'), $this->testSubmitData)->assertRedirect(route('dashboard'));
});
it('creates an entry', function () {
actAsAdmin();
$this->post(route('admin.entries.store'), $this->testSubmitData);
});
});