From b8110e222a066c84cf2060e1ee0aef376c3c683e Mon Sep 17 00:00:00 2001 From: Constantin Graf Date: Wed, 30 Apr 2025 13:36:21 +0200 Subject: [PATCH] Fixed descriptions and billable in shared reports --- app/Service/TimeEntryAggregationService.php | 14 ++ .../TimeEntryAggregationServiceTest.php | 194 ++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/app/Service/TimeEntryAggregationService.php b/app/Service/TimeEntryAggregationService.php index 55aa1f82..a662011a 100644 --- a/app/Service/TimeEntryAggregationService.php +++ b/app/Service/TimeEntryAggregationService.php @@ -280,6 +280,20 @@ class TimeEntryAggregationService 'color' => null, ]; } + } elseif ($type === TimeEntryAggregationType::Description) { + foreach ($keys as $key) { + $descriptorMap[$key] = [ + 'description' => $key, + 'color' => null, + ]; + } + } elseif ($type === TimeEntryAggregationType::Billable) { + foreach ($keys as $key) { + $descriptorMap[$key] = [ + 'description' => $key === '0' ? 'Non-billable' : 'Billable', + 'color' => null, + ]; + } } return $descriptorMap; diff --git a/tests/Unit/Service/TimeEntryAggregationServiceTest.php b/tests/Unit/Service/TimeEntryAggregationServiceTest.php index b0925909..02542490 100644 --- a/tests/Unit/Service/TimeEntryAggregationServiceTest.php +++ b/tests/Unit/Service/TimeEntryAggregationServiceTest.php @@ -498,4 +498,198 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase ], ], $result); } + + public function test_aggregated_time_entries_with_descriptions_by_description_and_billable(): void + { + // Arrange + TimeEntry::factory()->startWithDuration(now(), 10)->create([ + 'description' => 'TEST 1', + 'billable' => true, + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->create([ + 'description' => '', + 'billable' => false, + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->create([ + 'description' => 'TEST 1', + 'billable' => false, + ]); + TimeEntry::factory()->startWithDuration(now(), 10)->create([ + 'description' => '', + 'billable' => false, + ]); + $query = TimeEntry::query(); + + // Act + $result = $this->service->getAggregatedTimeEntriesWithDescriptions( + $query, + TimeEntryAggregationType::Description, + TimeEntryAggregationType::Billable, + 'Europe/Vienna', + Weekday::Monday, + false, + null, + null, + true + ); + + // Assert + $this->assertSame([ + 'seconds' => 40, + 'cost' => 0, + 'grouped_type' => 'description', + 'grouped_data' => [ + [ + 'key' => null, + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => 'billable', + 'grouped_data' => [ + [ + 'key' => '0', + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => 'Non-billable', + 'color' => null, + ], + ], + 'description' => null, + 'color' => null, + ], + [ + 'key' => 'TEST 1', + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => 'billable', + 'grouped_data' => [ + [ + 'key' => '0', + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => 'Non-billable', + 'color' => null, + ], + [ + 'key' => '1', + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => 'Billable', + 'color' => null, + ], + ], + 'description' => 'TEST 1', + 'color' => null, + ], + ], + ], $result); + } + + public function test_aggregated_time_entries_with_descriptions_by_client_and_project(): void + { + // Arrange + $client1 = Client::factory()->create(); + $client2 = Client::factory()->create(); + $project1 = Project::factory()->forClient($client1)->create(); + $project2 = Project::factory()->forClient($client2)->create(); + $project3 = Project::factory()->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 + $result = $this->service->getAggregatedTimeEntriesWithDescriptions( + $query, + TimeEntryAggregationType::Client, + TimeEntryAggregationType::Project, + 'Europe/Vienna', + Weekday::Monday, + false, + null, + null, + true + ); + + // Assert + $this->assertEqualsCanonicalizing([ + 'seconds' => 40, + 'cost' => 0, + 'grouped_type' => 'client', + 'grouped_data' => [ + [ + 'key' => null, + 'seconds' => 20, + 'cost' => 0, + 'grouped_type' => 'project', + 'grouped_data' => [ + [ + 'key' => null, + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => null, + 'color' => null, + ], + [ + 'key' => $project3->getKey(), + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => $project3->name, + 'color' => $project3->color, + ], + ], + 'description' => null, + 'color' => null, + ], + [ + 'key' => $client1->getKey(), + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => 'project', + 'grouped_data' => [ + [ + 'key' => $project1->getKey(), + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => $project1->name, + 'color' => $project1->color, + ], + ], + 'description' => $client1->name, + 'color' => null, + ], + [ + 'key' => $client2->getKey(), + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => 'project', + 'grouped_data' => [ + [ + 'key' => $project2->getKey(), + 'seconds' => 10, + 'cost' => 0, + 'grouped_type' => null, + 'grouped_data' => null, + 'description' => $project2->name, + 'color' => $project2->color, + ], + ], + 'description' => $client2->name, + 'color' => null, + ], + ], + ], $result); + } }