mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 08:12:17 +01:00
add tag grouping
This commit is contained in:
@@ -9,6 +9,7 @@ use App\Enums\TimeEntryRoundingType;
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\Client;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\TimeEntryAggregationService;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -1007,4 +1008,201 @@ class TimeEntryAggregationServiceTest extends TestCaseWithDatabase
|
||||
],
|
||||
], $result);
|
||||
}
|
||||
|
||||
public function test_aggregate_time_entries_group_by_tag_includes_no_tag_and_avoids_double_counting_overall(): void
|
||||
{
|
||||
// Arrange
|
||||
$tag1 = Tag::factory()->create();
|
||||
$tag2 = Tag::factory()->create();
|
||||
$start = Carbon::now();
|
||||
|
||||
// One entry with two tags (100s)
|
||||
TimeEntry::factory()->startWithDuration($start, 100)->create([
|
||||
'tags' => [$tag1->getKey(), $tag2->getKey()],
|
||||
]);
|
||||
// One entry with one tag (50s)
|
||||
TimeEntry::factory()->startWithDuration($start, 50)->create([
|
||||
'tags' => [$tag1->getKey()],
|
||||
]);
|
||||
// One entry with no tags (25s)
|
||||
TimeEntry::factory()->startWithDuration($start, 25)->create([
|
||||
'tags' => [],
|
||||
]);
|
||||
|
||||
$query = TimeEntry::query();
|
||||
|
||||
// Act
|
||||
$result = $this->service->getAggregatedTimeEntries(
|
||||
$query,
|
||||
TimeEntryAggregationType::Tag,
|
||||
null,
|
||||
'Europe/Vienna',
|
||||
Weekday::Monday,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// Assert - overall total should be 175 and groups: null=25, tag1=150, tag2=100
|
||||
$expected = [
|
||||
'seconds' => 175,
|
||||
'cost' => 0,
|
||||
'grouped_type' => 'tag',
|
||||
'grouped_data' => [
|
||||
[
|
||||
'key' => null,
|
||||
'seconds' => 25,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
[
|
||||
'key' => $tag1->getKey(),
|
||||
'seconds' => 150,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
[
|
||||
'key' => $tag2->getKey(),
|
||||
'seconds' => 100,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $result);
|
||||
}
|
||||
|
||||
public function test_aggregate_time_entries_group_by_project_and_subgroup_tag(): void
|
||||
{
|
||||
// Arrange
|
||||
$project = Project::factory()->create();
|
||||
$tag1 = Tag::factory()->create();
|
||||
$tag2 = Tag::factory()->create();
|
||||
$start = Carbon::now();
|
||||
|
||||
TimeEntry::factory()->startWithDuration($start, 120)->forProject($project)->create([
|
||||
'tags' => [$tag1->getKey()],
|
||||
]);
|
||||
TimeEntry::factory()->startWithDuration($start, 60)->forProject($project)->create([
|
||||
'tags' => [$tag2->getKey()],
|
||||
]);
|
||||
|
||||
$query = TimeEntry::query();
|
||||
|
||||
// Act
|
||||
$result = $this->service->getAggregatedTimeEntries(
|
||||
$query,
|
||||
TimeEntryAggregationType::Project,
|
||||
TimeEntryAggregationType::Tag,
|
||||
'Europe/Vienna',
|
||||
Weekday::Monday,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// Assert
|
||||
$expected = [
|
||||
'seconds' => 180,
|
||||
'cost' => 0,
|
||||
'grouped_type' => 'project',
|
||||
'grouped_data' => [
|
||||
[
|
||||
'key' => $project->getKey(),
|
||||
'seconds' => 180,
|
||||
'cost' => 0,
|
||||
'grouped_type' => 'tag',
|
||||
'grouped_data' => [
|
||||
[
|
||||
'key' => $tag1->getKey(),
|
||||
'seconds' => 120,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
[
|
||||
'key' => $tag2->getKey(),
|
||||
'seconds' => 60,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $result);
|
||||
}
|
||||
|
||||
public function test_aggregate_time_entries_group_by_project_and_subgroup_tag_avoids_double_counting(): void
|
||||
{
|
||||
// Arrange
|
||||
$project = Project::factory()->create();
|
||||
$tag1 = Tag::factory()->create();
|
||||
$tag2 = Tag::factory()->create();
|
||||
$start = Carbon::now();
|
||||
|
||||
// One entry with two tags => subgroup rows show both tags, but project total should equal entry duration
|
||||
TimeEntry::factory()->startWithDuration($start, 100)->forProject($project)->create([
|
||||
'tags' => [$tag1->getKey(), $tag2->getKey()],
|
||||
]);
|
||||
|
||||
$query = TimeEntry::query();
|
||||
|
||||
// Act
|
||||
$result = $this->service->getAggregatedTimeEntries(
|
||||
$query,
|
||||
TimeEntryAggregationType::Project,
|
||||
TimeEntryAggregationType::Tag,
|
||||
'Europe/Vienna',
|
||||
Weekday::Monday,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// Assert
|
||||
$expected = [
|
||||
'seconds' => 100,
|
||||
'cost' => 0,
|
||||
'grouped_type' => 'project',
|
||||
'grouped_data' => [
|
||||
[
|
||||
'key' => $project->getKey(),
|
||||
'seconds' => 100,
|
||||
'cost' => 0,
|
||||
'grouped_type' => 'tag',
|
||||
'grouped_data' => [
|
||||
[
|
||||
'key' => $tag1->getKey(),
|
||||
'seconds' => 100,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
[
|
||||
'key' => $tag2->getKey(),
|
||||
'seconds' => 100,
|
||||
'cost' => 0,
|
||||
'grouped_type' => null,
|
||||
'grouped_data' => null,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user