diff --git a/app/Enums/TimeEntryAggregationType.php b/app/Enums/TimeEntryAggregationType.php index 97a331f2..2c053416 100644 --- a/app/Enums/TimeEntryAggregationType.php +++ b/app/Enums/TimeEntryAggregationType.php @@ -15,6 +15,7 @@ enum TimeEntryAggregationType: string case Task = 'task'; case Client = 'client'; case Billable = 'billable'; + case Description = 'description'; public function toInterval(): ?TimeEntryAggregationTypeInterval { diff --git a/app/Service/TimeEntryAggregationService.php b/app/Service/TimeEntryAggregationService.php index 68f97fde..56dfeb44 100644 --- a/app/Service/TimeEntryAggregationService.php +++ b/app/Service/TimeEntryAggregationService.php @@ -270,6 +270,8 @@ class TimeEntryAggregationService return 'client_id'; } elseif ($group === TimeEntryAggregationType::Billable) { return 'billable'; + } elseif ($group === TimeEntryAggregationType::Description) { + return 'description'; } } diff --git a/tests/Unit/Service/TimeEntryAggregationServiceTest.php b/tests/Unit/Service/TimeEntryAggregationServiceTest.php index cb7c96f9..c9974b41 100644 --- a/tests/Unit/Service/TimeEntryAggregationServiceTest.php +++ b/tests/Unit/Service/TimeEntryAggregationServiceTest.php @@ -53,6 +53,90 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase ], $result); } + public function test_aggregate_time_entries_by_project_and_description(): void + { + // Arrange + $project1 = Project::factory()->create([ + // Note: To ensure deterministic order + 'id' => '5de4e6df-9560-4675-95be-18d42c441bfc', + ]); + $project2 = Project::factory()->create([ + // Note: To ensure deterministic order + 'id' => '130bdf66-d370-4564-aec7-7171e9b415f7', + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project1)->create([ + 'description' => 'Test', + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project2)->create([ + 'description' => '', + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project1)->create([ + 'description' => 'Test', + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project2)->create([ + 'description' => 'Test', + ]); + $query = TimeEntry::query(); + + // Act + $result = $this->service->getAggregatedTimeEntries( + $query, + TimeEntryAggregationType::Project, + TimeEntryAggregationType::Description, + 'Europe/Vienna', + Weekday::Monday, + false, + Carbon::now()->subDays(2)->utc(), + Carbon::now()->subDay()->utc(), + ); + + // Assert + $this->assertSame([ + 'seconds' => 40, + 'cost' => 0, + 'grouped_type' => 'project', + 'grouped_data' => [ + [ + 'key' => $project2->getKey(), + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => 'description', + 'grouped_data' => [ + [ + 'key' => null, + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + ], + [ + 'key' => 'Test', + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + ], + ], + ], + [ + 'key' => $project1->getKey(), + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => 'description', + 'grouped_data' => [ + [ + 'key' => 'Test', + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + ], + ], + ], + ], + ], $result); + } + public function test_aggregate_time_entries_empty_state_by_day_and_project_with_filled_gaps(): void { // Arrange @@ -67,7 +151,7 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase Weekday::Monday, true, Carbon::now()->subDays(2)->utc(), - Carbon::now()->subDays(1)->utc(), + Carbon::now()->subDay()->utc(), ); // Assert @@ -84,7 +168,7 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase 'grouped_data' => [], ], [ - 'key' => Carbon::now()->subDays(1)->utc()->format('Y-m-d'), + 'key' => Carbon::now()->subDay()->utc()->format('Y-m-d'), 'seconds' => 0, 'cost' => 0, 'grouped_type' => 'project', @@ -108,7 +192,7 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase Weekday::Monday, true, Carbon::now()->subDays(2), - Carbon::now()->subDays(1), + Carbon::now()->subDay(), ); // Assert @@ -134,7 +218,7 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase Weekday::Monday, true, Carbon::now()->subDays(2), - Carbon::now()->subDays(1), + Carbon::now()->subDay(), ); // Assert @@ -154,10 +238,10 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase $project1 = Project::factory()->forClient($client1)->create(); $project2 = Project::factory()->forClient($client2)->create(); $project3 = Project::factory()->create(); - $timeEntry1 = TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project1)->create(); - $timeEntry2 = TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project2)->create(); - $timeEntry3 = TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project3)->create(); - $timeEntry4 = TimeEntry::factory()->startWithDuration(now(), 10)->create(); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project1)->create(); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project2)->create(); + TimeEntry::factory()->startWithDuration(now(), 10)->forProject($project3)->create(); + TimeEntry::factory()->startWithDuration(now(), 10)->create(); $query = TimeEntry::query(); // Act