diff --git a/.changes/unreleased/Feature-20251210-032045.yaml b/.changes/unreleased/Feature-20251210-032045.yaml new file mode 100644 index 000000000..67a648b15 --- /dev/null +++ b/.changes/unreleased/Feature-20251210-032045.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: 'Add filtering to admin lists: social actions, social issues, goals, results, and evaluations' +time: 2025-12-10T03:20:45.135973502+01:00 +custom: + Issue: "478" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/EvaluationController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/EvaluationController.php index 13a4aca99..b52dba8b2 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/EvaluationController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/EvaluationController.php @@ -13,14 +13,63 @@ namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository; use Symfony\Component\HttpFoundation\Request; class EvaluationController extends CRUDController { + public function __construct(private readonly EvaluationRepository $repository) {} + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { $query->addOrderBy('e.id', 'ASC'); return parent::orderQuery($action, $query, $request, $paginator); } + + protected function getQueryResult( + string $action, + Request $request, + int $totalItems, + PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null, + ) { + if (0 === $totalItems) { + return []; + } + + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); + } + + $queryString = $filterOrder->getQueryString(); + $activeFilter = $filterOrder->getCheckboxData('activeFilter'); + $nb = $this->repository->countFilteredEvaluations($queryString, $activeFilter); + + $paginator = $this->getPaginatorFactory()->create($nb); + + return $this->repository->findFilteredEvaluations($queryString, $activeFilter, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage()); + } + + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int + { + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::countEntities($action, $request, $filterOrder); + } + + return $this->repository->countFilteredEvaluations( + $filterOrder->getQueryString(), + $filterOrder->getCheckboxData('activeFilter') + ); + } + + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return $this->getFilterOrderHelperFactory() + ->create(self::class) + ->addSearchBox(['label']) + ->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active']) + ->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/GoalController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/GoalController.php index df57b012a..a746a89f1 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/GoalController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/GoalController.php @@ -13,14 +13,68 @@ namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\PersonBundle\Repository\SocialWork\GoalRepository; use Symfony\Component\HttpFoundation\Request; class GoalController extends CRUDController { + public function __construct(private readonly GoalRepository $repository) {} + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { $query->addOrderBy('e.id', 'ASC'); return parent::orderQuery($action, $query, $request, $paginator); } + + protected function getQueryResult( + string $action, + Request $request, + int $totalItems, + PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null, + ) { + if (0 === $totalItems) { + return []; + } + + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); + } + + $queryString = $filterOrder->getQueryString(); + $activeFilter = $filterOrder->getCheckboxData('activeFilter'); + $nb = $this->repository->countFilteredGoals($queryString, $activeFilter); + + $paginator = $this->getPaginatorFactory()->create($nb); + + return $this->repository->findFilteredGoals( + $queryString, + $activeFilter, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + } + + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int + { + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::countEntities($action, $request, $filterOrder); + } + + return $this->repository->countFilteredGoals( + $filterOrder->getQueryString(), + $filterOrder->getCheckboxData('activeFilter') + ); + } + + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return $this->getFilterOrderHelperFactory() + ->create(self::class) + ->addSearchBox(['label']) + ->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active']) + ->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/ResultController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/ResultController.php index 274f7136e..9cc8c38b3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/ResultController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/ResultController.php @@ -13,14 +13,68 @@ namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\PersonBundle\Repository\SocialWork\ResultRepository; use Symfony\Component\HttpFoundation\Request; class ResultController extends CRUDController { + public function __construct(private readonly ResultRepository $repository) {} + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { $query->addOrderBy('e.id', 'ASC'); return parent::orderQuery($action, $query, $request, $paginator); } + + protected function getQueryResult( + string $action, + Request $request, + int $totalItems, + PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null, + ) { + if (0 === $totalItems) { + return []; + } + + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); + } + + $queryString = $filterOrder->getQueryString(); + $activeFilter = $filterOrder->getCheckboxData('activeFilter'); + $nb = $this->repository->countFilteredResults($queryString, $activeFilter); + + $paginator = $this->getPaginatorFactory()->create($nb); + + return $this->repository->findFilteredResults( + $queryString, + $activeFilter, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + } + + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int + { + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::countEntities($action, $request, $filterOrder); + } + + return $this->repository->countFilteredResults( + $filterOrder->getQueryString(), + $filterOrder->getCheckboxData('activeFilter') + ); + } + + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return $this->getFilterOrderHelperFactory() + ->create(self::class) + ->addSearchBox(['label']) + ->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active']) + ->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialActionController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialActionController.php index 69bbf7ace..9bdccda3a 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialActionController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialActionController.php @@ -13,14 +13,68 @@ namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Symfony\Component\HttpFoundation\Request; class SocialActionController extends CRUDController { + public function __construct(private readonly SocialActionRepository $repository) {} + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) { $query->addOrderBy('e.ordering', 'ASC'); return parent::orderQuery($action, $query, $request, $paginator); } + + protected function getQueryResult( + string $action, + Request $request, + int $totalItems, + PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null, + ) { + if (0 === $totalItems) { + return []; + } + + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); + } + + $queryString = $filterOrder->getQueryString(); + $activeFilter = $filterOrder->getCheckboxData('activeFilter'); + $nb = $this->repository->countFilteredSocialActions($queryString, $activeFilter); + + $paginator = $this->getPaginatorFactory()->create($nb); + + return $this->repository->findFilteredSocialActions( + $queryString, + $activeFilter, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + } + + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int + { + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::countEntities($action, $request, $filterOrder); + } + + return $this->repository->countFilteredSocialActions( + $filterOrder->getQueryString(), + $filterOrder->getCheckboxData('activeFilter') + ); + } + + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return $this->getFilterOrderHelperFactory() + ->create(self::class) + ->addSearchBox(['label']) + ->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active']) + ->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialIssueController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialIssueController.php index 316417583..df76b60f2 100644 --- a/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialIssueController.php +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWork/SocialIssueController.php @@ -13,11 +13,15 @@ namespace Chill\PersonBundle\Controller\SocialWork; use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\Templating\Listing\FilterOrderHelper; +use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; class SocialIssueController extends CRUDController { + public function __construct(private readonly SocialIssueRepository $repository) {} + protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface { if ('new' === $action) { @@ -37,4 +41,54 @@ class SocialIssueController extends CRUDController return parent::orderQuery($action, $query, $request, $paginator); } + + protected function getQueryResult( + string $action, + Request $request, + int $totalItems, + PaginatorInterface $paginator, + ?FilterOrderHelper $filterOrder = null, + ) { + if (0 === $totalItems) { + return []; + } + + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::getQueryResult($action, $request, $totalItems, $paginator, $filterOrder); + } + + $queryString = $filterOrder->getQueryString(); + $activeFilter = $filterOrder->getCheckboxData('activeFilter'); + $nb = $this->repository->countFilteredSocialIssues($queryString, $activeFilter); + + $paginator = $this->getPaginatorFactory()->create($nb); + + return $this->repository->findFilteredSocialIssues( + $queryString, + $activeFilter, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); + } + + protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int + { + if (!$filterOrder instanceof FilterOrderHelper) { + return parent::countEntities($action, $request, $filterOrder); + } + + return $this->repository->countFilteredSocialIssues( + $filterOrder->getQueryString(), + $filterOrder->getCheckboxData('activeFilter') + ); + } + + protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper + { + return $this->getFilterOrderHelperFactory() + ->create(self::class) + ->addSearchBox(['label']) + ->addCheckbox('activeFilter', [true => 'Active', false => 'Inactive'], ['Active']) + ->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php index 1c89a98f6..e78fdb590 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php @@ -14,12 +14,16 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Evaluation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\HttpFoundation\RequestStack; final readonly class EvaluationRepository implements EvaluationRepositoryInterface { private EntityRepository $repository; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(private EntityManagerInterface $entityManager, private RequestStack $requestStack) { $this->repository = $entityManager->getRepository(Evaluation::class); } @@ -65,4 +69,86 @@ final readonly class EvaluationRepository implements EvaluationRepositoryInterfa { return Evaluation::class; } + + private function getLang(): string + { + return $this->requestStack->getCurrentRequest()?->getLocale() ?? 'fr'; + } + + public function getResult( + QueryBuilder $qb, + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = [], + ): array { + $qb->select('e'); + + $qb + ->setFirstResult($start) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('e.'.$field, $direction); + } + + return $qb->getQuery()->getResult(); + } + + private function queryByTitle(string $pattern): QueryBuilder + { + $qb = $this->entityManager->createQueryBuilder()->from(Evaluation::class, 'e'); + + // Extract the current locale's value from the JSON `title` and search on it + $qb + ->where($qb->expr()->like('LOWER(UNACCENT(JSON_EXTRACT(e.title, :lang)))', "CONCAT('%', LOWER(UNACCENT(:pattern)), '%')")) + ->setParameter('pattern', $pattern) + ->setParameter('lang', $this->getLang()); + + return $qb; + } + + public function buildFilterBaseQuery(?string $queryString, array $isActive): QueryBuilder + { + if (null !== $queryString) { + $qb = $this->queryByTitle($queryString); + } else { + $qb = $this->entityManager->createQueryBuilder()->from(Evaluation::class, 'e'); + } + + // Add condition based on active/inactive status + if (in_array('Active', $isActive, true) && !in_array('Inactive', $isActive, true)) { + $qb->andWhere('e.active = true'); + } elseif (in_array('Inactive', $isActive, true) && !in_array('Active', $isActive, true)) { + $qb->andWhere('e.active = false'); + } + + return $qb; + } + + public function findFilteredEvaluations( + ?string $queryString = null, + array $isActive = ['active'], + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = ['title' => 'ASC'], + ): array { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + return $this->getResult($qb, $start, $limit, $orderBy); + } + + public function countFilteredEvaluations( + ?string $queryString = null, + array $isActive = ['active'], + ): int { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + try { + return $qb + ->select('COUNT(e)') + ->getQuery()->getSingleScalarResult(); + } catch (NoResultException|NonUniqueResultException $e) { + throw new \LogicException('a count query should return one result', previous: $e); + } + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php index ec89c597d..ac25445f4 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php @@ -15,14 +15,17 @@ use Chill\PersonBundle\Entity\SocialWork\Goal; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use Symfony\Component\HttpFoundation\RequestStack; final readonly class GoalRepository implements ObjectRepository { private EntityRepository $repository; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(private EntityManagerInterface $entityManager, private RequestStack $requestStack) { $this->repository = $entityManager->getRepository(Goal::class); } @@ -101,6 +104,102 @@ final readonly class GoalRepository implements ObjectRepository return Goal::class; } + private function getLang(): string + { + return $this->requestStack->getCurrentRequest()?->getLocale() ?? 'fr'; + } + + public function getResult( + QueryBuilder $qb, + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = [], + ): array { + $qb->select('g'); + + $qb + ->setFirstResult($start) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('g.'.$field, $direction); + } + + return $qb->getQuery()->getResult(); + } + + private function queryByTitle(string $pattern): QueryBuilder + { + $qb = $this->entityManager->createQueryBuilder()->from(Goal::class, 'g'); + + // search across locales by extracting the localized value + $qb + ->where($qb->expr()->like('LOWER(UNACCENT(JSON_EXTRACT(g.title, :lang)))', "CONCAT('%', LOWER(UNACCENT(:pattern)), '%')")) + ->setParameter('pattern', $pattern) + ->setParameter('lang', $this->getLang()); + + return $qb; + } + + public function buildFilterBaseQuery(?string $queryString, array $isActive): QueryBuilder + { + if (null !== $queryString) { + $qb = $this->queryByTitle($queryString); + } else { + $qb = $this->entityManager->createQueryBuilder()->from(Goal::class, 'g'); + } + + // Active when desactivationDate is null or in the future + $now = new \DateTime('now'); + if (in_array('Active', $isActive, true) && !in_array('Inactive', $isActive, true)) { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('g.desactivationDate'), + $qb->expr()->gt('g.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } elseif (in_array('Inactive', $isActive, true) && !in_array('Active', $isActive, true)) { + $qb->andWhere( + $qb->expr()->andX( + $qb->expr()->isNotNull('g.desactivationDate'), + $qb->expr()->lte('g.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } + + return $qb; + } + + /** + * @return array + */ + public function findFilteredGoals( + ?string $queryString = null, + array $isActive = ['active'], + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = ['id' => 'ASC'], + ): array { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + return $this->getResult($qb, $start, $limit, $orderBy); + } + + public function countFilteredGoals( + ?string $queryString = null, + array $isActive = ['active'], + ): int { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + try { + return $qb + ->select('COUNT(g)') + ->getQuery()->getSingleScalarResult(); + } catch (NoResultException|NonUniqueResultException $e) { + throw new \LogicException('a count query should return one result', previous: $e); + } + } + private function buildQueryBySocialActionWithDescendants(SocialAction $action): QueryBuilder { $actions = $action->getDescendantsWithThis(); diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php index 0d470d2d7..9e80ec491 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php @@ -16,14 +16,17 @@ use Chill\PersonBundle\Entity\SocialWork\Result; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use Symfony\Component\HttpFoundation\RequestStack; final readonly class ResultRepository implements ObjectRepository { private EntityRepository $repository; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(private EntityManagerInterface $entityManager, private RequestStack $requestStack) { $this->repository = $entityManager->getRepository(Result::class); } @@ -125,6 +128,100 @@ final readonly class ResultRepository implements ObjectRepository return Result::class; } + private function getLang(): string + { + return $this->requestStack->getCurrentRequest()?->getLocale() ?? 'fr'; + } + + public function getResult( + QueryBuilder $qb, + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = [], + ): array { + $qb->select('r'); + + $qb + ->setFirstResult($start) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('r.'.$field, $direction); + } + + return $qb->getQuery()->getResult(); + } + + private function queryByTitle(string $pattern): QueryBuilder + { + $qb = $this->entityManager->createQueryBuilder()->from(Result::class, 'r'); + + $qb + ->where($qb->expr()->like('LOWER(UNACCENT(JSON_EXTRACT(r.title, :lang)))', "CONCAT('%', LOWER(UNACCENT(:pattern)), '%')")) + ->setParameter('pattern', $pattern) + ->setParameter('lang', $this->getLang()); + + return $qb; + } + + public function buildFilterBaseQuery(?string $queryString, array $isActive): QueryBuilder + { + if (null !== $queryString) { + $qb = $this->queryByTitle($queryString); + } else { + $qb = $this->entityManager->createQueryBuilder()->from(Result::class, 'r'); + } + + $now = new \DateTime('now'); + if (in_array('Active', $isActive, true) && !in_array('Inactive', $isActive, true)) { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('r.desactivationDate'), + $qb->expr()->gt('r.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } elseif (in_array('Inactive', $isActive, true) && !in_array('Active', $isActive, true)) { + $qb->andWhere( + $qb->expr()->andX( + $qb->expr()->isNotNull('r.desactivationDate'), + $qb->expr()->lte('r.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } + + return $qb; + } + + /** + * @return array + */ + public function findFilteredResults( + ?string $queryString = null, + array $isActive = ['active'], + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = ['id' => 'ASC'], + ): array { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + return $this->getResult($qb, $start, $limit, $orderBy); + } + + public function countFilteredResults( + ?string $queryString = null, + array $isActive = ['active'], + ): int { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + try { + return $qb + ->select('COUNT(r)') + ->getQuery()->getSingleScalarResult(); + } catch (NoResultException|NonUniqueResultException $e) { + throw new \LogicException('a count query should return one result', previous: $e); + } + } + private function buildQueryByGoal(Goal $goal): QueryBuilder { $qb = $this->repository->createQueryBuilder('r'); diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php index 4d37cb3d4..81da2c60c 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php @@ -14,14 +14,17 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use Symfony\Component\HttpFoundation\RequestStack; final readonly class SocialActionRepository implements ObjectRepository { private EntityRepository $repository; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(private EntityManagerInterface $entityManager, private RequestStack $requestStack) { $this->repository = $entityManager->getRepository(SocialAction::class); } @@ -84,6 +87,100 @@ final readonly class SocialActionRepository implements ObjectRepository return SocialAction::class; } + private function getLang(): string + { + return $this->requestStack->getCurrentRequest()?->getLocale() ?? 'fr'; + } + + public function getResult( + QueryBuilder $qb, + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = [], + ): array { + $qb->select('sa'); + + $qb + ->setFirstResult($start) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('sa.'.$field, $direction); + } + + return $qb->getQuery()->getResult(); + } + + private function queryByTitle(string $pattern): QueryBuilder + { + $qb = $this->entityManager->createQueryBuilder()->from(SocialAction::class, 'sa'); + + $qb + ->where($qb->expr()->like('LOWER(UNACCENT(JSON_EXTRACT(sa.title, :lang)))', "CONCAT('%', LOWER(UNACCENT(:pattern)), '%')")) + ->setParameter('pattern', $pattern) + ->setParameter('lang', $this->getLang()); + + return $qb; + } + + public function buildFilterBaseQuery(?string $queryString, array $isActive): QueryBuilder + { + if (null !== $queryString) { + $qb = $this->queryByTitle($queryString); + } else { + $qb = $this->entityManager->createQueryBuilder()->from(SocialAction::class, 'sa'); + } + + $now = new \DateTime('now'); + if (in_array('Active', $isActive, true) && !in_array('Inactive', $isActive, true)) { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('sa.desactivationDate'), + $qb->expr()->gt('sa.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } elseif (in_array('Inactive', $isActive, true) && !in_array('Active', $isActive, true)) { + $qb->andWhere( + $qb->expr()->andX( + $qb->expr()->isNotNull('sa.desactivationDate'), + $qb->expr()->lte('sa.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } + + return $qb; + } + + /** + * @return array + */ + public function findFilteredSocialActions( + ?string $queryString = null, + array $isActive = ['active'], + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = ['ordering' => 'ASC'], + ): array { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + return $this->getResult($qb, $start, $limit, $orderBy); + } + + public function countFilteredSocialActions( + ?string $queryString = null, + array $isActive = ['active'], + ): int { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + try { + return $qb + ->select('COUNT(sa)') + ->getQuery()->getSingleScalarResult(); + } catch (NoResultException|NonUniqueResultException $e) { + throw new \LogicException('a count query should return one result', previous: $e); + } + } + private function buildQueryWithDesactivatedDateCriteria(): QueryBuilder { $qb = $this->repository->createQueryBuilder('sa'); diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php index 40ec35855..638e1a869 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php @@ -14,14 +14,17 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use Symfony\Component\HttpFoundation\RequestStack; final readonly class SocialIssueRepository implements ObjectRepository { private EntityRepository $repository; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(private EntityManagerInterface $entityManager, private RequestStack $requestStack) { $this->repository = $entityManager->getRepository(SocialIssue::class); } @@ -79,6 +82,100 @@ final readonly class SocialIssueRepository implements ObjectRepository return SocialIssue::class; } + public function getResult( + QueryBuilder $qb, + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = [], + ): array { + $qb->select('si'); + + $qb + ->setFirstResult($start) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('si.'.$field, $direction); + } + + return $qb->getQuery()->getResult(); + } + + private function getLang(): string + { + return $this->requestStack->getCurrentRequest()?->getLocale() ?? 'fr'; + } + + private function queryByTitle(string $pattern): QueryBuilder + { + $qb = $this->entityManager->createQueryBuilder()->from(SocialIssue::class, 'si'); + + $qb + ->where($qb->expr()->like('LOWER(UNACCENT(JSON_EXTRACT(si.title, :lang)))', "CONCAT('%', LOWER(UNACCENT(:pattern)), '%')")) + ->setParameter('pattern', $pattern) + ->setParameter('lang', $this->getLang()); + + return $qb; + } + + public function buildFilterBaseQuery(?string $queryString, array $isActive): QueryBuilder + { + if (null !== $queryString) { + $qb = $this->queryByTitle($queryString); + } else { + $qb = $this->entityManager->createQueryBuilder()->from(SocialIssue::class, 'si'); + } + + $now = new \DateTime('now'); + if (in_array('Active', $isActive, true) && !in_array('Inactive', $isActive, true)) { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('si.desactivationDate'), + $qb->expr()->gt('si.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } elseif (in_array('Inactive', $isActive, true) && !in_array('Active', $isActive, true)) { + $qb->andWhere( + $qb->expr()->andX( + $qb->expr()->isNotNull('si.desactivationDate'), + $qb->expr()->lte('si.desactivationDate', ':now') + ) + )->setParameter('now', $now); + } + + return $qb; + } + + /** + * @return array + */ + public function findFilteredSocialIssues( + ?string $queryString = null, + array $isActive = ['active'], + ?int $start = 0, + ?int $limit = 50, + ?array $orderBy = ['ordering' => 'ASC'], + ): array { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + return $this->getResult($qb, $start, $limit, $orderBy); + } + + public function countFilteredSocialIssues( + ?string $queryString = null, + array $isActive = ['active'], + ): int { + $qb = $this->buildFilterBaseQuery($queryString, $isActive); + + try { + return $qb + ->select('COUNT(si)') + ->getQuery()->getSingleScalarResult(); + } catch (NoResultException|NonUniqueResultException $e) { + throw new \LogicException('a count query should return one result', previous: $e); + } + } + private function buildQueryWithDesactivatedDateCriteria(): QueryBuilder { $qb = $this->repository->createQueryBuilder('si'); diff --git a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Evaluation/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Evaluation/index.html.twig index 556065e42..af7989b8f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Evaluation/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Evaluation/index.html.twig @@ -2,6 +2,9 @@ {% block admin_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} + + {% block filter_order %}{{ filter_order|chill_render_filter_order_helper }}{% endblock %} + {% block table_entities_thead_tr %} {{ 'Id'|trans }} {{ 'Title'|trans }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Goal/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Goal/index.html.twig index 14e9bab73..eac9d4bfc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Goal/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Goal/index.html.twig @@ -2,6 +2,9 @@ {% block admin_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} + + {% block filter_order %}{{ filter_order|chill_render_filter_order_helper }}{% endblock %} + {% block table_entities_thead_tr %} {{ 'Id'|trans }} {{ 'Title'|trans }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Result/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Result/index.html.twig index da9e71bc5..85286ab5b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Result/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/Result/index.html.twig @@ -2,6 +2,9 @@ {% block admin_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} + + {% block filter_order %}{{ filter_order|chill_render_filter_order_helper }}{% endblock %} + {% block table_entities_thead_tr %} {{ 'Id'|trans }} {{ 'Title'|trans }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialAction/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialAction/index.html.twig index 152b8981c..003965667 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialAction/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialAction/index.html.twig @@ -2,6 +2,9 @@ {% block admin_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} + + {% block filter_order %}{{ filter_order|chill_render_filter_order_helper }}{% endblock %} + {% block table_entities_thead_tr %} {{ 'Id' }} {{ 'Title'|trans }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialIssue/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialIssue/index.html.twig index 7ca3c6636..9eb786b22 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialIssue/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/SocialWork/SocialIssue/index.html.twig @@ -2,6 +2,9 @@ {% block admin_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} + + {% block filter_order %}{{ filter_order|chill_render_filter_order_helper }}{% endblock %} + {% block table_entities_thead_tr %} {{ 'Id'|trans }} {{ 'Title'|trans }}