From 748e566c7e46e72a0bfb25bde1f4bbb2d273f003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 20 May 2023 00:03:25 +0200 Subject: [PATCH] Feature: [admin] list of users on csv format --- .../Controller/UserExportController.php | 97 +++++++++++++++++++ .../Repository/UserRepository.php | 47 +++++++++ .../Repository/UserRepositoryInterface.php | 22 ++--- .../Resources/views/User/index.html.twig | 11 +++ .../config/services/controller.yaml | 3 + .../translations/messages.fr.yml | 24 +++++ 6 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Controller/UserExportController.php diff --git a/src/Bundle/ChillMainBundle/Controller/UserExportController.php b/src/Bundle/ChillMainBundle/Controller/UserExportController.php new file mode 100644 index 000000000..2fe199ee6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/UserExportController.php @@ -0,0 +1,97 @@ +security->isGranted('ROLE_ADMIN')) { + throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list'); + } + + $users = $this->userRepository->findAllAsArray($request->getLocale()); + + $csv = Writer::createFromPath('php://temp', 'r+'); + $csv->insertOne( + array_map( + fn (string $e) => $this->translator->trans('admin.users.export.' . $e), + [ + 'id', + 'username', + 'email', + 'enabled', + 'civility_id', + 'civility_abbreviation', + 'civility_name', + 'label', + 'mainCenter_id' , + 'mainCenter_name', + 'mainScope_id', + 'mainScope_name', + 'userJob_id', + 'userJob_name', + 'currentLocation_id', + 'currentLocation_name', + 'mainLocation_id', + 'mainLocation_name', + 'absenceStart' + ] + ) + ); + $csv->addFormatter(fn (array $row) => null !== ($row['absenceStart'] ?? null) ? array_merge($row, ['absenceStart' => $row['absenceStart']->format('Y-m-d')]) : $row); + $csv->insertAll($users); + + return new StreamedResponse( + function () use ($csv) { + foreach ($csv->chunk(1024) as $chunk) { + echo $chunk; + flush(); + } + }, + Response::HTTP_OK, + [ + 'Content-Encoding' => 'none', + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Content-Disposition' => 'attachment; users.csv', + ] + ); + } + +} diff --git a/src/Bundle/ChillMainBundle/Repository/UserRepository.php b/src/Bundle/ChillMainBundle/Repository/UserRepository.php index a26ae04ad..ac6128042 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/UserRepository.php @@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\GroupCenter; use Chill\MainBundle\Entity\User; +use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\NoResultException; @@ -76,6 +77,52 @@ final class UserRepository implements UserRepositoryInterface return $this->repository->findAll(); } + /** + * @param string $lang + */ + public function findAllAsArray(string $lang): iterable + { + $dql = sprintf(<<<'DQL' + SELECT + u.id AS id, + u.username AS username, + u.email, + u.enabled, + IDENTITY(u.civility) AS civility_id, + JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation, + JSON_EXTRACT(civility.name, :lang) AS civility_name, + u.label, + mainCenter.id AS mainCenter_id, + mainCenter.name AS mainCenter_name, + IDENTITY(u.mainScope) AS mainScope_id, + JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name, + IDENTITY(u.userJob) AS userJob_id, + JSON_EXTRACT(userJob.label, :lang) AS userJob_name, + currentLocation.id AS currentLocation_id, + currentLocation.name AS currentLocation_name, + mainLocation.id AS mainLocation_id, + mainLocation.name AS mainLocation_name, + u.absenceStart + FROM Chill\MainBundle\Entity\User u + LEFT JOIN u.civility civility + LEFT JOIN u.currentLocation currentLocation + LEFT JOIN u.mainLocation mainLocation + LEFT JOIN u.mainCenter mainCenter + LEFT JOIN u.mainScope mainScope + LEFT JOIN u.userJob userJob + ORDER BY u.label + DQL); + + $query = $this->entityManager->createQuery($dql) + ->setHydrationMode(AbstractQuery::HYDRATE_ARRAY) + ->setParameter('lang', $lang) + ; + + foreach ($query->toIterable() as $u) { + yield $u; + } + } + /** * @param mixed|null $limit * @param mixed|null $offset diff --git a/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php index c869e1b82..99234dbec 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php @@ -14,6 +14,9 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\User; use Doctrine\Persistence\ObjectRepository; +/** + * @template ObjectRepository + */ interface UserRepositoryInterface extends ObjectRepository { public function countBy(array $criteria): int; @@ -24,20 +27,11 @@ interface UserRepositoryInterface extends ObjectRepository public function countByUsernameOrEmail(string $pattern): int; - public function find($id, $lockMode = null, $lockVersion = null): ?User; - /** - * @return User[] + * @param string $lang The lang to display all the translatable string (no fallback if not present) + * @return iterable */ - public function findAll(): array; - - /** - * @param mixed|null $limit - * @param mixed|null $offset - * - * @return User[] - */ - public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array; + public function findAllAsArray(string $lang): iterable; /** * @return array|User[] @@ -53,8 +47,6 @@ interface UserRepositoryInterface extends ObjectRepository public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; - public function findOneBy(array $criteria, ?array $orderBy = null): ?User; - public function findOneByUsernameOrEmail(string $pattern): ?User; /** @@ -68,6 +60,4 @@ interface UserRepositoryInterface extends ObjectRepository * @param mixed $flag */ public function findUsersHavingFlags($flag, array $amongstUsers = []): array; - - public function getClassName(): string; } diff --git a/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig index e4e7d70d8..446607a45 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig @@ -98,6 +98,17 @@
  • {{'Back to the admin'|trans}}
  • + +
  • + +
  • {{ 'Create'|trans }}
  • diff --git a/src/Bundle/ChillMainBundle/config/services/controller.yaml b/src/Bundle/ChillMainBundle/config/services/controller.yaml index 16c545a81..428147961 100644 --- a/src/Bundle/ChillMainBundle/config/services/controller.yaml +++ b/src/Bundle/ChillMainBundle/config/services/controller.yaml @@ -37,3 +37,6 @@ services: Chill\MainBundle\Controller\RegroupmentController: autowire: true autoconfigure: true + + Chill\MainBundle\Controller\UserExportController: + tags: ['controller.service_arguments'] diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 14de6dbf0..d65cc7fab 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -610,3 +610,27 @@ absence: You are listed as absent, as of: Votre absence est indiquée à partir du No absence listed: Aucune absence indiquée. Is absent: Absent? + +admin: + users: + export_list_csv: Liste des utilisateurs (format CSV) + export: + id: Identifiant + username: Nom d'utilisateur + email: Courriel + enabled: Activé + civility_id: Identifiant civilité + civility_abbreviation: Abbréviation civilité + civility_name: Civilité + label: Label + mainCenter_id: Identifiant centre principal + mainCenter_name: Centre principal + mainScope_id: Identifiant service principal + mainScope_name: Service principal + userJob_id: Identifiant métier + userJob_name: Métier + currentLocation_id: Identifiant localisation actuelle + currentLocation_name: Localisation actuelle + mainLocation_id: Identifiant localisation principale + mainLocation_name: Localisation principale + absenceStart: Absent à partir du