From 69cd679b975eba4f2285119c620e683fc39ca049 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 9 Aug 2024 11:50:52 -0500 Subject: [PATCH 1/5] Beginning work on doubler preference page Work on #63 --- app/Models/DoublerRequest.php | 22 +++++++++++++ app/Models/Student.php | 6 ++++ ...08_200522_create_doubler_request_table.php | 32 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 app/Models/DoublerRequest.php create mode 100644 database/migrations/2024_08_08_200522_create_doubler_request_table.php diff --git a/app/Models/DoublerRequest.php b/app/Models/DoublerRequest.php new file mode 100644 index 0000000..6c9d774 --- /dev/null +++ b/app/Models/DoublerRequest.php @@ -0,0 +1,22 @@ +belongsTo(Student::class); + } + + public function event(): BelongsTo + { + return $this->belongsTo(Event::class); + } +} diff --git a/app/Models/Student.php b/app/Models/Student.php index f4c9667..d81a6f2 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Services\DoublerService; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -49,4 +50,9 @@ class Student extends Model return $this->first_name.' '.$this->last_name; } + + public function doublerRequests(): HasMany + { + return $this->hasMany(DoublerService::class); + } } diff --git a/database/migrations/2024_08_08_200522_create_doubler_request_table.php b/database/migrations/2024_08_08_200522_create_doubler_request_table.php new file mode 100644 index 0000000..2dfb323 --- /dev/null +++ b/database/migrations/2024_08_08_200522_create_doubler_request_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignIdFor(Event::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate(); + $table->foreignIdFor(Student::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate(); + $table->string('request'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('doubler_request'); + } +}; -- 2.39.5 From 0a2a02bd5b18ac433aa2272823ea25b2a6ca57d3 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 9 Aug 2024 11:51:49 -0500 Subject: [PATCH 2/5] Update PdfInvoiceController.php --- app/Http/Controllers/PdfInvoiceController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/PdfInvoiceController.php b/app/Http/Controllers/PdfInvoiceController.php index 8a0321f..8badc7c 100644 --- a/app/Http/Controllers/PdfInvoiceController.php +++ b/app/Http/Controllers/PdfInvoiceController.php @@ -133,7 +133,7 @@ class PdfInvoiceController extends Controller $this->pdf->Cell(2, .3, '$'.number_format($this->invoiceData['grandTotal'], 2), 1); } - public function output($dest = 'D', $name = null) + public function output($dest = 'I', $name = null) { if (! $name) { $name = auditionSetting('auditionAbbreviation').' Invoice for '.$this->school->name.'.pdf'; -- 2.39.5 From 60b7b9fc153b9258187aebb553985c2d599b265c Mon Sep 17 00:00:00 2001 From: Matt Young Date: Fri, 9 Aug 2024 12:04:05 -0500 Subject: [PATCH 3/5] Fix issue with downloading PDF invoices --- app/Http/Controllers/PdfInvoiceController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/PdfInvoiceController.php b/app/Http/Controllers/PdfInvoiceController.php index 8badc7c..e5a1ffc 100644 --- a/app/Http/Controllers/PdfInvoiceController.php +++ b/app/Http/Controllers/PdfInvoiceController.php @@ -59,7 +59,7 @@ class PdfInvoiceController extends Controller } $this->grandTotal(); $this->output(); - return redirect()->back(); + exit(); } protected function newInvoicePage() @@ -133,7 +133,7 @@ class PdfInvoiceController extends Controller $this->pdf->Cell(2, .3, '$'.number_format($this->invoiceData['grandTotal'], 2), 1); } - public function output($dest = 'I', $name = null) + public function output($dest = 'D', $name = null) { if (! $name) { $name = auditionSetting('auditionAbbreviation').' Invoice for '.$this->school->name.'.pdf'; -- 2.39.5 From 8395a560a5a5ab4292402a110835bb2bebad1979 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 10 Aug 2024 16:23:11 -0500 Subject: [PATCH 4/5] User management of doubler preferences working Work on #63 --- .../Controllers/DoublerRequestController.php | 89 +++++++++++++++ app/Models/DoublerRequest.php | 2 + app/Policies/DoublerRequestPolicy.php | 68 +++++++++++ bootstrap/providers.php | 1 + ...08_200522_create_doubler_request_table.php | 5 +- .../views/doubler_request/index.blade.php | 44 +++++++ routes/user.php | 17 ++- tests/Feature/Pages/DoublerRequestsTest.php | 107 ++++++++++++++++++ 8 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 app/Http/Controllers/DoublerRequestController.php create mode 100644 app/Policies/DoublerRequestPolicy.php create mode 100644 resources/views/doubler_request/index.blade.php create mode 100644 tests/Feature/Pages/DoublerRequestsTest.php diff --git a/app/Http/Controllers/DoublerRequestController.php b/app/Http/Controllers/DoublerRequestController.php new file mode 100644 index 0000000..a79b7e0 --- /dev/null +++ b/app/Http/Controllers/DoublerRequestController.php @@ -0,0 +1,89 @@ +user()->school->students; + $studentIds = $students->pluck('id'); + $existingRequests = DoublerRequest::whereIn('student_id', $studentIds)->get(); + $doublers = []; + foreach ($events as $event) { + $event_doublers = $doublerService->doublersForEvent($event); + $doublers[$event->id] = $event_doublers; + } + + return view('doubler_request.index', compact('events', 'doublers', 'students', 'existingRequests')); + } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function makeRequest() + { + foreach (request()->get('doubler_requests') as $event_id => $requests) { + if (! Event::find($event_id)->exists()) { + return to_route('doubler_request.index')->with('error', 'Invalid event id specified'); + } + $thisEvent = Event::find($event_id); + foreach ($requests as $student_id => $request) { + if (! Student::find($student_id)->exists()) { + return to_route('doubler_request.index')->with('error', 'Invalid student id specified'); + } + $thisStudent = Student::find($student_id); + if (! $request) { + $oldRequest = DoublerRequest::where('student_id', $student_id) + ->where('event_id', $event_id) + ->first(); + if ($oldRequest) { + Debugbar::info('hit'); + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => 'Removed doubler request for '.$thisStudent->full_name().' in '.$thisEvent->name, + 'affected' => ['students' => [$student_id]], + ]); + $oldRequest->delete(); + } + + continue; + } + DoublerRequest::upsert([ + 'event_id' => $event_id, + 'student_id' => $student_id, + 'request' => $request, + ], + uniqueBy: ['event_id', 'student_id'], + update: ['request'] + ); + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => 'Made doubler request for '.$thisStudent->full_name().' in '.$thisEvent->name.'
Request: '.$request, + 'affected' => ['students' => [$student_id]], + ]); + } + } + echo 'hi'; + + return to_route('doubler_request.index')->with('success', 'Recorded doubler requests'); + } +} diff --git a/app/Models/DoublerRequest.php b/app/Models/DoublerRequest.php index 6c9d774..7181e36 100644 --- a/app/Models/DoublerRequest.php +++ b/app/Models/DoublerRequest.php @@ -10,6 +10,8 @@ class DoublerRequest extends Model { use HasFactory; + protected $guarded = []; + public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Policies/DoublerRequestPolicy.php b/app/Policies/DoublerRequestPolicy.php new file mode 100644 index 0000000..4e75131 --- /dev/null +++ b/app/Policies/DoublerRequestPolicy.php @@ -0,0 +1,68 @@ +school_id === null + ? Response::denyWithStatus(405) + : Response::allow(); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, DoublerRequest $doublerRequest): bool + { + // + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + // + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, DoublerRequest $doublerRequest): bool + { + // + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, DoublerRequest $doublerRequest): bool + { + // + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, DoublerRequest $doublerRequest): bool + { + // + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, DoublerRequest $doublerRequest): bool + { + // + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 9edefb0..6b20b30 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -4,4 +4,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\FortifyServiceProvider::class, App\Providers\InvoiceDataServiceProvider::class, + Barryvdh\Debugbar\ServiceProvider::class, ]; diff --git a/database/migrations/2024_08_08_200522_create_doubler_request_table.php b/database/migrations/2024_08_08_200522_create_doubler_request_table.php index 2dfb323..be22dc2 100644 --- a/database/migrations/2024_08_08_200522_create_doubler_request_table.php +++ b/database/migrations/2024_08_08_200522_create_doubler_request_table.php @@ -13,11 +13,12 @@ return new class extends Migration */ public function up(): void { - Schema::create('doubler_request', function (Blueprint $table) { + Schema::create('doubler_requests', function (Blueprint $table) { $table->id(); $table->foreignIdFor(Event::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate(); $table->foreignIdFor(Student::class)->constrained()->cascadeOnDelete()->cascadeOnUpdate(); $table->string('request'); + $table->unique(['student_id', 'event_id']); $table->timestamps(); }); } @@ -27,6 +28,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('doubler_request'); + Schema::dropIfExists('doubler_requests'); } }; diff --git a/resources/views/doubler_request/index.blade.php b/resources/views/doubler_request/index.blade.php new file mode 100644 index 0000000..7d2d6fa --- /dev/null +++ b/resources/views/doubler_request/index.blade.php @@ -0,0 +1,44 @@ + + Doubler Requests + + @foreach($events as $event) + + {{ $event->name }} + + + + Student Name + Entries + Request + + + + @foreach($students as $student) + @continue(! array_key_exists($student->id, $doublers[$event->id])) + @php + $existingRequest = $existingRequests + ->where('student_id',$student->id) + ->where('event_id',$event->id); + $value = $existingRequest?->first()?->request ?? ''; + @endphp + + {{ $student->full_name() }} + + @foreach($doublers[$event->id][$student->id]['entries'] as $entry) +

{{$entry->audition->name}}

+ @endforeach +
+ + + + + @endforeach +
+
+
+ @endforeach + Save Doubler Requests +
+
diff --git a/routes/user.php b/routes/user.php index 2d84a45..a210d90 100644 --- a/routes/user.php +++ b/routes/user.php @@ -2,6 +2,7 @@ // Dashboard Related Routes use App\Http\Controllers\DashboardController; +use App\Http\Controllers\DoublerRequestController; use App\Http\Controllers\EntryController; use App\Http\Controllers\PdfInvoiceController; use App\Http\Controllers\SchoolController; @@ -18,7 +19,9 @@ Route::middleware(['auth', 'verified'])->group(function () { }); // Entry Related Routes -Route::middleware(['auth', 'verified', 'can:create,App\Models\Entry'])->controller(EntryController::class)->group(function () { +Route::middleware([ + 'auth', 'verified', 'can:create,App\Models\Entry', +])->controller(EntryController::class)->group(function () { Route::get('/entries', 'index')->name('entries.index'); Route::get('/entries/create', 'create')->name('entries.create'); Route::post('/entries', 'store')->name('entries.store'); @@ -32,7 +35,9 @@ Route::middleware(['auth', 'verified'])->controller(UserController::class)->grou }); // Student Related Routes -Route::middleware(['auth', 'verified', 'can:create,App\Models\Student'])->controller(StudentController::class)->group(function () { +Route::middleware([ + 'auth', 'verified', 'can:create,App\Models\Student', +])->controller(StudentController::class)->group(function () { Route::get('/students', 'index')->name('students.index'); Route::post('students', 'store')->name('students.store'); Route::get('/students/{student}/edit', 'edit')->name('students.edit'); @@ -48,3 +53,11 @@ Route::middleware(['auth', 'verified'])->controller(SchoolController::class)->gr Route::get('/schools/{school}', 'show')->name('schools.show'); Route::patch('/schools/{school}', 'update')->name('schools.update'); }); + +// Doubler Related Routes +Route::middleware([ + 'auth', 'verified', 'can:viewAny,App\Models\DoublerRequest', +])->controller(DoublerRequestController::class)->prefix('doubler_request')->group(function () { + Route::get('/', 'index')->name('doubler_request.index'); + Route::post('/', 'makeRequest')->name('doubler_request.make_request'); +}); diff --git a/tests/Feature/Pages/DoublerRequestsTest.php b/tests/Feature/Pages/DoublerRequestsTest.php new file mode 100644 index 0000000..db8576b --- /dev/null +++ b/tests/Feature/Pages/DoublerRequestsTest.php @@ -0,0 +1,107 @@ +assertRedirect(route('home')); +}); +it('denies access to a user with no school', function () { + // Arrange + actAsNormal(); + // Act & Assert + get(route('doubler_request.index')) + ->assertStatus(405); +}); +it('allows access to a user with a school', function () { + // Arrange + $school = School::factory()->create(); + $user = User::factory()->create(['school_id' => $school->id]); + actingAs($user); + // Act & Assert + get(route('doubler_request.index')) + ->assertOk() + ->assertSessionHasNoErrors(); +}); +it('includes a section for each event', function () { + // Arrange + $events = \App\Models\Event::factory()->count(3)->create(); + $school = School::factory()->create(); + $user = User::factory()->create(['school_id' => $school->id]); + actingAs($user); + // Act & Assert + $response = get(route('doubler_request.index')); + $response->assertOk() + ->assertViewHas('events', \App\Models\Event::all()); + foreach ($events as $event) { + $response->assertSee($event->name); + } +}); +it(/** + * @throws ManageEntryException + */ 'includes all doublers for the users school', function () { + // Arrange + $school = School::factory()->create(); + $user = User::factory()->create(['school_id' => $school->id]); + $concert_event = \App\Models\Event::create(['name' => 'Concert Band']); + $jazz_event = \App\Models\Event::create(['name' => 'Jazz Band']); + $jas_audition = Audition::factory()->create([ + 'name' => 'Jazz Alto Sax', + 'event_id' => $jazz_event->id, + 'minimum_grade' => 1, + 'maximum_grade' => 99, + ]); + $jts_audition = Audition::factory()->create([ + 'name' => 'Jazz Tenor Sax', + 'event_id' => $jazz_event->id, + 'minimum_grade' => 1, + 'maximum_grade' => 99, + ]); + $cas_audition = Audition::factory()->create([ + 'name' => 'Alto Sax', + 'event_id' => $concert_event->id, + 'minimum_grade' => 1, + 'maximum_grade' => 99, + ]); + $cts_audition = Audition::factory()->create([ + 'name' => 'Tenor Sax', + 'event_id' => $concert_event->id, + 'minimum_grade' => 1, + 'maximum_grade' => 99, + ]); + $concert_student = Student::factory()->create(['school_id' => $school->id]); + $jazz_student = Student::factory()->create(['school_id' => $school->id]); + $singleton_student = Student::factory()->create(['school_id' => $school->id]); + $other_school_student = Student::factory()->create(); + $entryCreator = new CreateEntry(); + $entryCreator->createEntry($concert_student, $cas_audition); + $entryCreator->createEntry($singleton_student, $cas_audition); + $entryCreator->createEntry($concert_student, $cts_audition); + $entryCreator->createEntry($jazz_student, $jas_audition); + $entryCreator->createEntry($jazz_student, $jts_audition); + $entryCreator->createEntry($singleton_student, $jts_audition); + $entryCreator->createEntry($other_school_student, $cas_audition); + $entryCreator->createEntry($other_school_student, $cts_audition); + $entryCreator->createEntry($other_school_student, $jts_audition); + $entryCreator->createEntry($other_school_student, $jas_audition); + actingAs($user); + // Act + $response = get(route('doubler_request.index')); + // Assert + $response->assertOk() + ->assertSee($concert_student->full_name()) + ->assertSee($jazz_student->full_name()) + ->assertDontSee($singleton_student->full_name()) + ->assertDontSee($other_school_student->full_name()); +}); -- 2.39.5 From d575f50164588eab3ed645860592e9754e2c41b3 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Sat, 10 Aug 2024 16:35:37 -0500 Subject: [PATCH 5/5] Correct typo Work on #63 --- app/Models/Student.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Models/Student.php b/app/Models/Student.php index d81a6f2..dece4dd 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Services\DoublerService; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -53,6 +52,6 @@ class Student extends Model public function doublerRequests(): HasMany { - return $this->hasMany(DoublerService::class); + return $this->hasMany(DoublerRequest::class); } } -- 2.39.5