'Company Admin', 'email' => 'admin@company.com', 'password' => bcrypt('password'), 'type' => 'company', ]); $this->branch = Branch::create([ 'name' => 'Main Branch', 'created_by' => $companyUser->id, ]); $this->user = User::create([ 'name' => 'Test Employee', 'email' => 'testemployee@example.com', 'password' => bcrypt('password'), 'type' => 'employee', 'created_by' => $companyUser->id, 'branch_id' => $this->branch->id, ]); // Shift 09:00:00 to 18:00:00 with 15 mins grace period $this->shift = Shift::create([ 'name' => 'Morning Shift', 'start_time' => '09:00:00', 'end_time' => '18:00:00', 'grace_period' => 15, 'status' => 'active', 'created_by' => 1, ]); $this->employee = Employee::create([ 'user_id' => $this->user->id, 'employee_id' => 'EMPTEST001', 'branch_id' => $this->branch->id, 'shift_id' => $this->shift->id, 'rest_days' => ['sunday'], 'date_of_joining' => '2026-08-01', 'created_by' => 1, ]); } public function test_clock_in_within_grace_period_is_not_late() { $service = app(AttendanceService::class); $result = $service->processClock($this->user, $this->employee, [ 'action' => 'clock_in', 'latitude' => 14.5995, 'longitude' => 120.9842, ]); $this->assertTrue($result['success']); $record = AttendanceRecord::where('employee_id', $this->user->id)->first(); $this->assertNotNull($record); $this->assertEquals($this->shift->id, $record->shift_id); } public function test_late_clock_in_sets_is_late_flag() { // Simulate late clock in past grace period $record = AttendanceRecord::create([ 'employee_id' => $this->user->id, 'branch_id' => $this->branch->id, 'shift_id' => $this->shift->id, 'date' => '2026-08-10', 'clock_in' => '09:30:00', // Shift 09:00 + 15m grace = 09:15. 09:30 is late 'is_late' => true, 'late_hours' => 0.50, 'status' => 'present', 'created_by' => $this->user->id, ]); $this->assertTrue((bool)$record->is_late); $this->assertEquals(0.50, $record->late_hours); } public function test_break_in_and_break_out_recorded_on_attendance_record() { $record = AttendanceRecord::create([ 'employee_id' => $this->user->id, 'branch_id' => $this->branch->id, 'shift_id' => $this->shift->id, 'date' => '2026-08-10', 'clock_in' => '09:00:00', 'status' => 'present', 'created_by' => $this->user->id, ]); $service = app(AttendanceService::class); // Break In $service->processClock($this->user, $this->employee, [ 'action' => 'break_in', ]); $record->refresh(); $this->assertNotNull($record->break_in); // Break Out $service->processClock($this->user, $this->employee, [ 'action' => 'break_out', ]); $record->refresh(); $this->assertNotNull($record->break_out); } public function test_today_api_returns_all_db_metrics_breakdown() { AttendanceRecord::create([ 'employee_id' => $this->user->id, 'branch_id' => $this->branch->id, 'shift_id' => $this->shift->id, 'date' => \Carbon\Carbon::now('Asia/Manila')->format('Y-m-d'), 'clock_in' => '09:30:00', 'clock_out' => '17:30:00', 'break_in' => '12:00:00', 'break_out' => '13:00:00', 'total_hours' => 8.00, 'late_hours' => 0.50, 'early_hours' => 0.50, 'break_hours' => 1.00, 'overtime_hours' => 1.50, 'status' => 'present', 'created_by' => $this->user->id, ]); $response = $this->actingAs($this->user)->getJson('/api/attendance/today'); $response->assertStatus(200) ->assertJsonPath('success', true) ->assertJsonPath('data.total_hours', 8) ->assertJsonPath('data.late_hours', 0.5) ->assertJsonPath('data.early_hours', 0.5) ->assertJsonPath('data.break_hours', 1) ->assertJsonPath('data.overtime_hours', 1.5); } }