diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 2d9de61a..879302fc 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -16,7 +16,9 @@ use App\Export\Spreadsheet\UserExporter; use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter; use App\Export\Spreadsheet\Writer\XlsxWriter; use App\Form\Toolbar\UserToolbarForm; +use App\Form\Type\UserType; use App\Form\UserCreateType; +use App\Repository\Query\UserFormTypeQuery; use App\Repository\Query\UserQuery; use App\Repository\TimesheetRepository; use App\Repository\UserRepository; @@ -167,6 +169,16 @@ final class UserController extends AbstractController 'data-msg-error' => 'action.delete.error', ] ]) + ->add('user', UserType::class, [ + 'query_builder' => function (UserRepository $repo) use ($userToDelete) { + $query = new UserFormTypeQuery(); + $query->addUserToIgnore($userToDelete); + $query->setUser($this->getUser()); + + return $repo->getQueryBuilderForFormType($query); + }, + 'required' => false, + ]) ->setAction($this->generateUrl('admin_user_delete', ['id' => $userToDelete->getId()])) ->setMethod('POST') ->getForm(); @@ -174,23 +186,21 @@ final class UserController extends AbstractController $deleteForm->handleRequest($request); if ($deleteForm->isSubmitted() && $deleteForm->isValid()) { - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->remove($userToDelete); - $entityManager->flush(); - - $this->flashSuccess('action.delete.success'); + try { + $this->getRepository()->deleteUser($userToDelete, $deleteForm->get('user')->getData()); + $this->flashSuccess('action.delete.success'); + } catch (\Exception $ex) { + $this->flashDeleteException($ex); + } return $this->redirectToRoute('admin_user'); } - return $this->render( - 'user/delete.html.twig', - [ - 'user' => $userToDelete, - 'stats' => $stats, - 'form' => $deleteForm->createView(), - ] - ); + return $this->render('user/delete.html.twig', [ + 'user' => $userToDelete, + 'stats' => $stats, + 'form' => $deleteForm->createView(), + ]); } /** diff --git a/src/Repository/Query/UserFormTypeQuery.php b/src/Repository/Query/UserFormTypeQuery.php index 8c0e3441..f558c890 100644 --- a/src/Repository/Query/UserFormTypeQuery.php +++ b/src/Repository/Query/UserFormTypeQuery.php @@ -22,6 +22,10 @@ final class UserFormTypeQuery extends BaseFormTypeQuery * @var User[] */ private $includeUsers = []; + /** + * @var User[] + */ + private $ignoredUsers = []; /** * Sets a list of users which must be included in the result always. @@ -45,4 +49,27 @@ final class UserFormTypeQuery extends BaseFormTypeQuery { return $this->includeUsers; } + + /** + * Given user will be excluded from the result set. + * + * @param User $user + * @return $this + */ + public function addUserToIgnore(User $user): UserFormTypeQuery + { + $this->ignoredUsers[] = $user; + + return $this; + } + + /** + * Returns the list of users that should not be loaded. + * + * @return User[] + */ + public function getUsersToIgnore(): array + { + return $this->ignoredUsers; + } } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 129945c2..1f3ab900 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -9,7 +9,9 @@ namespace App\Repository; +use App\Entity\Invoice; use App\Entity\Role; +use App\Entity\Timesheet; use App\Entity\User; use App\Repository\Loader\UserIdLoader; use App\Repository\Loader\UserLoader; @@ -165,6 +167,14 @@ class UserRepository extends EntityRepository implements UserLoaderInterface $qb->andWhere($or); } + if (\count($query->getUsersToIgnore()) > 0) { + $ids = array_map(function (User $user) { + return $user->getId(); + }, $query->getUsersToIgnore()); + + $qb->andWhere($qb->expr()->notIn('u.id', $ids)); + } + $qb->orderBy('u.username', 'ASC'); $this->addPermissionCriteria($qb, $query->getUser(), $query->getTeams()); @@ -353,4 +363,41 @@ class UserRepository extends EntityRepository implements UserLoaderInterface return $results; } + + public function deleteUser(User $delete, ?User $replace = null) + { + $em = $this->getEntityManager(); + $em->beginTransaction(); + + try { + if (null !== $replace) { + $qb = $em->createQueryBuilder(); + $qb + ->update(Timesheet::class, 't') + ->set('t.user', ':replace') + ->where('t.user = :delete') + ->setParameter('delete', $delete) + ->setParameter('replace', $replace) + ->getQuery() + ->execute(); + + $qb = $em->createQueryBuilder(); + $qb + ->update(Invoice::class, 'i') + ->set('i.user', ':replace') + ->where('i.user = :delete') + ->setParameter('delete', $delete) + ->setParameter('replace', $replace) + ->getQuery() + ->execute(); + } + + $em->remove($delete); + $em->flush(); + $em->commit(); + } catch (ORMException $ex) { + $em->rollback(); + throw $ex; + } + } } diff --git a/templates/user/delete.html.twig b/templates/user/delete.html.twig index f4b59a0d..7991b5a4 100644 --- a/templates/user/delete.html.twig +++ b/templates/user/delete.html.twig @@ -14,7 +14,7 @@ } %} {{ include(app.request.xmlHttpRequest ? 'default/_form_delete_modal.html.twig' : 'default/_form_delete.html.twig', { - 'message': "admin_user.short_stats"|trans(params), + 'message': ("admin_user.short_stats"|trans(params) ~ "admin_entity.delete_confirm"|trans), 'form': form, 'used': inUse, 'back': path('admin_user') diff --git a/tests/Controller/UserControllerTest.php b/tests/Controller/UserControllerTest.php index 904bdc7b..ae0d5f6f 100644 --- a/tests/Controller/UserControllerTest.php +++ b/tests/Controller/UserControllerTest.php @@ -209,6 +209,53 @@ class UserControllerTest extends ControllerBaseTest $this->assertFalse($client->getResponse()->isSuccessful()); } + public function testDeleteActionWithUserReplacementAndTimesheetEntries() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN); + + $em = $this->getEntityManager(); + $user = $this->getUserByRole(User::ROLE_USER); + $userNew = $this->getUserByRole(User::ROLE_TEAMLEAD); + + $this->assertNotEquals($userNew->getId(), $user->getId()); + + $fixture = new TimesheetFixtures(); + $fixture->setUser($user); + $fixture->setAmount(10); + $this->importFixture($fixture); + + $timesheets = $em->getRepository(Timesheet::class)->findAll(); + $this->assertEquals(10, \count($timesheets)); + foreach ($timesheets as $timesheet) { + $this->assertEquals($user->getId(), $timesheet->getUser()->getId()); + } + + $this->request($client, '/admin/user/' . $user->getId() . '/delete'); + $this->assertTrue($client->getResponse()->isSuccessful()); + + $form = $client->getCrawler()->filter('form[name=form]')->form(); + $this->assertStringEndsWith($this->createUrl('/admin/user/' . $user->getId() . '/delete'), $form->getUri()); + $client->submit($form, [ + 'form' => [ + 'user' => $userNew->getId() + ] + ]); + + $this->assertIsRedirect($client, $this->createUrl('/admin/user/')); + $client->followRedirect(); + $this->assertHasFlashDeleteSuccess($client); + + $em->clear(); + $timesheets = $em->getRepository(Timesheet::class)->findAll(); + $this->assertEquals(10, \count($timesheets)); + foreach ($timesheets as $timesheet) { + $this->assertEquals($userNew->getId(), $timesheet->getUser()->getId()); + } + + $this->request($client, '/admin/user/' . $user->getId() . '/edit'); + $this->assertFalse($client->getResponse()->isSuccessful()); + } + /** * @dataProvider getValidationTestData */ diff --git a/tests/Repository/Query/UserFormTypeQueryTest.php b/tests/Repository/Query/UserFormTypeQueryTest.php index d69390a6..3ba33113 100644 --- a/tests/Repository/Query/UserFormTypeQueryTest.php +++ b/tests/Repository/Query/UserFormTypeQueryTest.php @@ -34,5 +34,9 @@ class UserFormTypeQueryTest extends BaseFormTypeQueryTest self::assertEquals([], $sut->getUsersAlwaysIncluded()); self::assertInstanceOf(UserFormTypeQuery::class, $sut->setUsersAlwaysIncluded($users)); self::assertSame($users, $sut->getUsersAlwaysIncluded()); + + self::assertEquals([], $sut->getUsersToIgnore()); + self::assertInstanceOf(UserFormTypeQuery::class, $sut->addUserToIgnore($users[0])); + self::assertSame([$users[0]], $sut->getUsersToIgnore()); } }