create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id]); $response = $this->getJson("/api/capacity?month=2026-02&team_member_id={$teamMember->id}", [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(200); $service = app(CapacityService::class); $capacity = $service->calculateIndividualCapacity($teamMember->id, '2026-02'); $expected = [ 'data' => [ 'team_member_id' => $teamMember->id, 'month' => '2026-02', 'working_days' => $service->calculateWorkingDays('2026-02'), 'person_days' => $capacity['person_days'], 'hours' => $capacity['hours'], 'details' => $capacity['details'], ], ]; $response->assertExactJson($expected); }); test('4.1.12 Capacity accounts for availability', function () { $token = loginAsManager($this); $role = Role::factory()->create(); $member = TeamMember::factory()->create(['role_id' => $role->id]); TeamMemberAvailability::factory()->forDate('2026-02-03')->availability(0.5)->create(['team_member_id' => $member->id]); TeamMemberAvailability::factory()->forDate('2026-02-04')->availability(0.0)->create(['team_member_id' => $member->id]); $response = $this->getJson("/api/capacity?month=2026-02&team_member_id={$member->id}", [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(200); $details = collect($response->json('data.details')); expect($details->firstWhere('date', '2026-02-03')['availability'])->toBe(0.5); expect($details->firstWhere('date', '2026-02-04')['availability'])->toBe(0); }); test('4.1.13 Capacity subtracts PTO', function () { $token = loginAsManager($this); $role = Role::factory()->create(); $member = TeamMember::factory()->create(['role_id' => $role->id]); Pto::create([ 'team_member_id' => $member->id, 'start_date' => '2026-02-10', 'end_date' => '2026-02-12', 'reason' => 'Vacation', 'status' => 'approved', ]); $response = $this->getJson("/api/capacity?month=2026-02&team_member_id={$member->id}", [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(200); $details = collect($response->json('data.details')); expect($details->where('is_pto', true)->count())->toBe(3); expect($details->firstWhere('date', '2026-02-11')['availability'])->toBe(0); }); test('4.1.14 Capacity subtracts holidays', function () { $token = loginAsManager($this); $role = Role::factory()->create(); $member = TeamMember::factory()->create(['role_id' => $role->id]); Holiday::create([ 'date' => '2026-02-17', 'name' => 'Presidents Day', 'description' => 'Company wide', ]); $response = $this->getJson("/api/capacity?month=2026-02&team_member_id={$member->id}", [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(200); $dates = collect($response->json('details'))->pluck('date'); expect($dates)->not->toContain('2026-02-17'); }); test('4.1.15 GET /api/capacity/team sums active members', function () { $token = loginAsManager($this); $role = Role::factory()->create(); $activeA = TeamMember::factory()->create(['role_id' => $role->id]); $activeB = TeamMember::factory()->create(['role_id' => $role->id]); TeamMember::factory()->inactive()->create(['role_id' => $role->id]); $expectedDays = 0; $expectedHours = 0; foreach ([$activeA, $activeB] as $member) { $capacity = app(CapacityService::class)->calculateIndividualCapacity($member->id, '2026-02'); $expectedDays += $capacity['person_days']; $expectedHours += $capacity['hours']; } $response = $this->getJson('/api/capacity/team?month=2026-02', [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(200); $response->assertJsonCount(2, 'data.members'); expect(round($response->json('data.person_days'), 2))->toBe(round($expectedDays, 2)); expect($response->json('data.hours'))->toBe($expectedHours); }); test('4.1.16 GET /api/capacity/revenue calculates possible revenue', function () { $token = loginAsManager($this); $role = Role::factory()->create(); TeamMember::factory()->create(['role_id' => $role->id, 'hourly_rate' => 150]); TeamMember::factory()->create(['role_id' => $role->id, 'hourly_rate' => 125]); $expectedRevenue = app(CapacityService::class)->calculatePossibleRevenue('2026-02'); $response = $this->getJson('/api/capacity/revenue?month=2026-02', [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(200); $response->assertJsonPath('data.possible_revenue', $expectedRevenue); }); test('4.1.17 POST /api/holidays creates holiday', function () { $token = loginAsManager($this); $response = $this->postJson('/api/holidays', [ 'date' => '2026-02-20', 'name' => 'Test Holiday', 'description' => 'Test description', ], [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(201); assertDatabaseHas('holidays', ['date' => '2026-02-20 00:00:00', 'name' => 'Test Holiday']); }); test('4.1.18 POST /api/ptos creates PTO request', function () { $token = loginAsManager($this); $role = Role::factory()->create(); $member = TeamMember::factory()->create(['role_id' => $role->id]); $response = $this->postJson('/api/ptos', [ 'team_member_id' => $member->id, 'start_date' => '2026-02-10', 'end_date' => '2026-02-11', 'reason' => 'Refresh', ], [ 'Authorization' => "Bearer {$token}", ]); $response->assertStatus(201); $response->assertJson(['status' => 'pending']); assertDatabaseHas('ptos', ['team_member_id' => $member->id, 'status' => 'pending']); }); function loginAsManager(TestCase $test): string { $user = User::factory()->create([ 'email' => 'manager@example.com', 'password' => bcrypt('password123'), 'role' => 'manager', 'active' => true, ]); $response = $test->postJson('/api/auth/login', [ 'email' => 'manager@example.com', 'password' => 'password123', ]); return $response->json('access_token'); }