diff --git a/app/Service/MemberService.php b/app/Service/MemberService.php index 27e339e8..1b8d3687 100644 --- a/app/Service/MemberService.php +++ b/app/Service/MemberService.php @@ -159,6 +159,13 @@ class MemberService public function assignOrganizationEntitiesToDifferentMember(Organization $organization, Member $fromMember, Member $toMember): void { + // Running time entries are deleted instead of moved, since $toMember could already have a running time entry + TimeEntry::query() + ->whereBelongsTo($organization, 'organization') + ->whereBelongsTo($fromMember, 'member') + ->whereNull('end') + ->delete(); + // Time entries TimeEntry::query() ->whereBelongsTo($organization, 'organization') diff --git a/tests/Unit/Endpoint/Api/V1/MemberEndpointTest.php b/tests/Unit/Endpoint/Api/V1/MemberEndpointTest.php index 7ee0bfec..80c4e29c 100644 --- a/tests/Unit/Endpoint/Api/V1/MemberEndpointTest.php +++ b/tests/Unit/Endpoint/Api/V1/MemberEndpointTest.php @@ -390,6 +390,40 @@ class MemberEndpointTest extends ApiEndpointTestAbstract ]); } + public function test_merge_into_deletes_running_time_entries_of_source_member(): void + { + // Arrange + $data = $this->createUserWithPermission([ + 'members:merge-into', + ]); + $userSource = User::factory()->placeholder()->create(); + $memberSource = Member::factory()->forUser($userSource)->forOrganization($data->organization)->role(Role::Placeholder)->create(); + $runningTimeEntrySource = TimeEntry::factory()->forOrganization($data->organization)->forMember($memberSource)->active()->create(); + TimeEntry::factory()->forOrganization($data->organization)->forMember($memberSource)->createMany(2); + + $userDestination = User::factory()->create(); + $memberDestination = Member::factory()->forUser($userDestination)->forOrganization($data->organization)->role(Role::Admin)->create(); + $runningTimeEntryDestination = TimeEntry::factory()->forOrganization($data->organization)->forMember($memberDestination)->active()->create(); + Passport::actingAs($data->user); + + // Act + $response = $this->postJson(route('api.v1.members.merge-into', [$data->organization->getKey(), $memberSource->getKey()]), [ + 'member_id' => $memberDestination->getKey(), + ]); + + // Assert + $response->assertStatus(204); + $this->assertDatabaseMissing(Member::class, [ + 'id' => $memberSource->getKey(), + ]); + $this->assertDatabaseMissing(TimeEntry::class, [ + 'id' => $runningTimeEntrySource->getKey(), + ]); + $memberDestination->refresh(); + $this->assertCount(3, $memberDestination->timeEntries); + $this->assertSame([$runningTimeEntryDestination->getKey()], TimeEntry::query()->whereBelongsTo($userDestination, 'user')->whereNull('end')->pluck('id')->all()); + } + public function test_merge_into_assigns_resources_of_source_member_to_destination_member_and_deletes_member_with_existing_destination_resources(): void { // Arrange diff --git a/tests/Unit/Service/MemberServiceTest.php b/tests/Unit/Service/MemberServiceTest.php index 830e101e..8d487742 100644 --- a/tests/Unit/Service/MemberServiceTest.php +++ b/tests/Unit/Service/MemberServiceTest.php @@ -228,6 +228,30 @@ class MemberServiceTest extends TestCaseWithDatabase $this->assertSame(0, ProjectMember::query()->whereBelongsTo($fromUserMember, 'member')->count()); } + public function test_assign_organization_entities_to_different_member_deletes_running_time_entries_of_the_member(): void + { + // Arrange + $organization = Organization::factory()->create(); + $fromUser = User::factory()->create(); + $toUser = User::factory()->create(); + $fromUserMember = Member::factory()->forOrganization($organization)->forUser($fromUser)->create(); + $toUserMember = Member::factory()->forOrganization($organization)->forUser($toUser)->create(); + $runningTimeEntryOfFromMember = TimeEntry::factory()->forOrganization($organization)->forMember($fromUserMember)->active()->create(); + TimeEntry::factory()->forOrganization($organization)->forMember($fromUserMember)->createMany(2); + $runningTimeEntryOfToMember = TimeEntry::factory()->forOrganization($organization)->forMember($toUserMember)->active()->create(); + + // Act + $this->memberService->assignOrganizationEntitiesToDifferentMember($organization, $fromUserMember, $toUserMember); + + // Assert + $this->assertDatabaseMissing(TimeEntry::class, [ + 'id' => $runningTimeEntryOfFromMember->getKey(), + ]); + $this->assertSame(3, TimeEntry::query()->whereBelongsTo($toUserMember, 'member')->count()); + $this->assertSame([$runningTimeEntryOfToMember->getKey()], TimeEntry::query()->whereBelongsTo($toUser, 'user')->whereNull('end')->pluck('id')->all()); + $this->assertSame(0, TimeEntry::query()->whereBelongsTo($fromUserMember, 'member')->count()); + } + public function test_assign_organization_entities_to_different_member_moves_all_goals_of_the_member(): void { // Arrange