From bed9ab48e774731eccd3bc95494df51cab09cdad Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 2 Jul 2026 19:54:03 -0400 Subject: [PATCH 1/3] Fix manual time entries storing with wrong timezone --- app/Http/Requests/TimeEntryStoreRequest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/TimeEntryStoreRequest.php b/app/Http/Requests/TimeEntryStoreRequest.php index 83b759d..ad055c4 100644 --- a/app/Http/Requests/TimeEntryStoreRequest.php +++ b/app/Http/Requests/TimeEntryStoreRequest.php @@ -43,8 +43,13 @@ public function rules(): array { */ public function passedValidation(): void { $this->merge([ - 'start' => $this->date('start')?->timezone(config('app.timezone')) ?? now(), - 'stop' => $this->date('stop')?->timezone(config('app.timezone')), + 'start' => ($this->date('start')?->timezone(config('app.timezone')) ?? now())->toISOString(true), + 'stop' => $this->date('stop')?->timezone(config('app.timezone'))?->toISOString(true), ]); + + $this->validator->setData(array_merge($this->validator->getData(), [ + 'start' => $this->input('start'), + 'stop' => $this->input('stop'), + ])); } } From a08bbda499db7a3f74d1007059b4f5a90f413503 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 28 Sep 2026 15:16:24 -0400 Subject: [PATCH 2/3] Fix check for ongoing entries during manual creation --- app/Http/Controllers/VolunteerController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Http/Controllers/VolunteerController.php b/app/Http/Controllers/VolunteerController.php index 9b49b49..23083b4 100644 --- a/app/Http/Controllers/VolunteerController.php +++ b/app/Http/Controllers/VolunteerController.php @@ -93,8 +93,7 @@ public function checkOut(Request $request, TimeEntry $timeEntry): JsonResponse|R */ public function storeTimeEntry(TimeEntryStoreRequest $request, Event $event, User $user): JsonResponse { // Don't allow an ongoing entry to be created if there already is one - $input = $request->safe(); - if (!isset($input['stop'])) { + if (!$request->filled('stop')) { if ($user->timeEntries()->ongoing()->forEvent($event)->exists()) { return response()->json(['error' => 'User already has an ongoing time entry.'], 409); } From 2f4ae0e43669d9a2b32320d5e63bd91e6d4461d8 Mon Sep 17 00:00:00 2001 From: tmolick Date: Wed, 30 Sep 2026 12:52:21 -0700 Subject: [PATCH 3/3] Add tests for ongoing time entry check on manual creation --- tests/Feature/VolunteerTimeEntryStoreTest.php | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/Feature/VolunteerTimeEntryStoreTest.php diff --git a/tests/Feature/VolunteerTimeEntryStoreTest.php b/tests/Feature/VolunteerTimeEntryStoreTest.php new file mode 100644 index 0000000..bc52b11 --- /dev/null +++ b/tests/Feature/VolunteerTimeEntryStoreTest.php @@ -0,0 +1,59 @@ +create(['role' => Role::Admin]); + $volunteer = User::factory()->create(); + $event = Event::factory()->create(); + $department = Department::factory()->create(['event_id' => $event->id]); + + $this->actingAs($manager); + + // Mirrors the real clock-in UI, which always sends an explicit 'start' and omits 'stop'. + $this->putJson(route('volunteer.time.store', [$event, $volunteer]), [ + 'department_id' => $department->id, + 'start' => now()->subSecond()->toISOString(), + ])->assertOk(); + + expect(TimeEntry::where('user_id', $volunteer->id)->whereNull('stop')->count())->toBe(1); + + $this->putJson(route('volunteer.time.store', [$event, $volunteer]), [ + 'department_id' => $department->id, + 'start' => now()->subSecond()->toISOString(), + ]) + ->assertStatus(409) + ->assertJson(['error' => 'User already has an ongoing time entry.']); + + expect(TimeEntry::where('user_id', $volunteer->id)->whereNull('stop')->count())->toBe(1); +}); + +it('allows creating a time entry with an explicit stop while another is ongoing', function () { + $manager = User::factory()->create(['role' => Role::Admin]); + $volunteer = User::factory()->create(); + $event = Event::factory()->create(); + $department = Department::factory()->create(['event_id' => $event->id]); + + $this->actingAs($manager); + + $this->putJson(route('volunteer.time.store', [$event, $volunteer]), [ + 'department_id' => $department->id, + 'start' => now()->subSecond()->toISOString(), + ])->assertOk(); + + $this->putJson(route('volunteer.time.store', [$event, $volunteer]), [ + 'department_id' => $department->id, + 'start' => now()->subHour()->toISOString(), + 'stop' => now()->toISOString(), + ])->assertOk(); + + expect(TimeEntry::where('user_id', $volunteer->id)->count())->toBe(2); + expect(TimeEntry::where('user_id', $volunteer->id)->whereNull('stop')->count())->toBe(1); +}); \ No newline at end of file