diff --git a/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php index 3a2b1be6e..30608072a 100644 --- a/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php +++ b/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php @@ -11,46 +11,97 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\Translation\TranslatorInterface; class UserAccompanyingPeriodController extends AbstractController { - public function __construct(private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, private readonly PaginatorFactory $paginatorFactory) {} + public function __construct( + private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, + private readonly PaginatorFactory $paginatorFactory, + private readonly AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository, + private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory, + private readonly TranslatorInterface $translator, + ) {} + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ #[Route(path: '/{_locale}/person/accompanying-periods/my', name: 'chill_person_accompanying_period_user')] public function listAction(Request $request): Response { - $active = $request->query->getBoolean('active', true); - $steps = match ($active) { - true => [ + $filter = (int) $request->query->get('filter', 2); + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new \LogicException('Expected an instance of Chill\MainBundle\Entity\User.'); + } + + $activeTab = match ($filter) { + 2 => 'referrer', + 4 => 'referrer_to_works', + 6 => 'both', + 8 => 'intervening', + default => 'referrer', + }; + + $statusAndDateFilter = $this->buildStatusAndDateFilter($filter); + + $status = $statusAndDateFilter->getCheckboxData('statusFilter'); + $from = null; + $to = null; + + if ('intervening' === $activeTab) { + $interventionBetweenDates = $statusAndDateFilter->getDateRangeData('interventionBetweenDates'); + $from = $interventionBetweenDates['from']; + $to = $interventionBetweenDates['to']; + } + + $steps = []; + + if (in_array('is_open', $status, true)) { + $steps[] = [ + ...$steps, AccompanyingPeriod::STEP_CONFIRMED, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, - ], - false => [ - AccompanyingPeriod::STEP_CLOSED, - ], - }; + ]; + } - $total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => $steps]); - $pagination = $this->paginatorFactory->create($total); - $accompanyingPeriods = $this->accompanyingPeriodRepository->findBy( - ['user' => $this->getUser(), 'step' => $steps], - ['openingDate' => 'DESC'], - $pagination->getItemsPerPage(), - $pagination->getCurrentPageFirstItemNumber() + if (in_array('is_closed', $status, true)) { + $steps[] = AccompanyingPeriod::STEP_CLOSED; + } + + $total = $this->accompanyingPeriodACLAwareRepository->countByUserAssociation($user, $steps, $from, $to, $filter); + $paginator = $this->paginatorFactory->create($total); + $accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository->findByUserAssociation( + $user, + $steps, + $from, + $to, + $filter, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage(), ); return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [ 'accompanyingPeriods' => $accompanyingPeriods, - 'pagination' => $pagination, - 'active' => $active, + 'pagination' => $paginator, + 'activeTab' => $activeTab, + 'filter' => $filter, + 'statusFilter' => $statusAndDateFilter, ]); } @@ -71,4 +122,29 @@ class UserAccompanyingPeriodController extends AbstractController 'pagination' => $pagination, ]); } + + public function buildStatusAndDateFilter(int $filter) + { + $filterBuilder = $this->filterOrderHelperFactory + ->create(self::class) + ->addCheckbox( + 'statusFilter', + ['is_open', 'is_closed'], + ['is_open'], + array_map( + static fn (string $s) => 'my_parcours_filters.'.$s, + ['is_open', 'is_closed'] + ) + ); + + if (8 === $filter) { + $filterBuilder->addDateRange( + 'interventionBetweenDates', + $this->translator->trans('Since'), + new \DateTimeImmutable('-6 months'), + ); + } + + return $filterBuilder->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index ebd72e0b4..4ba50e912 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -365,4 +365,90 @@ final readonly class AccompanyingPeriodACLAwareRepository implements Accompanyin return $qb; } + + public function findByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter, ?int $start = 0, ?int $limit = 1000): array + { + $qb = $this->buildQueryByUserAssociation($user, $steps, $from, $to, $filter); + + $qb->addOrderBy('acp.openingDate', 'DESC'); + + if (null !== $start) { + $qb->setFirstResult($start); + } + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + return $qb->getQuery()->getResult(); + } + + public function countByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter): int + { + $qb = $this->buildQueryByUserAssociation($user, $steps, $from, $to, $filter); + + $qb->select('COUNT(DISTINCT acp.id)'); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function buildQueryByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter): QueryBuilder + { + $qb = $this->accompanyingPeriodRepository->createQueryBuilder('acp'); + + // Create an andX expression to hold the user association conditions + $whereUserAssociation = $qb->expr()->andX(); + + if (($filter & self::USER_IS_REFERRER) > 0) { + $whereUserAssociation->add($qb->expr()->eq('acp.user', ':user')); + } + + if (($filter & self::USER_IS_WORK_REFERRER) > 0) { + $whereUserAssociation->add( + $qb->expr()->exists( + 'SELECT 1 + FROM '.AccompanyingPeriod\AccompanyingPeriodWork::class.' subw + JOIN subw.referrersHistory subw_ref_history + WHERE subw.id = acpw.id + AND subw_ref_history.user = :user + AND subw_ref_history.endDate IS NULL' + ) + ); + + $qb->innerJoin('acp.works', 'acpw'); + } + + if (($filter & self::USER_IS_INTERVENING) > 0) { + + $expr = 'SELECT 1 + FROM '.AccompanyingPeriod\AccompanyingPeriodInfo::class.' info + WHERE info.accompanyingPeriod = acp + AND info.user = :user'; + + if (null !== $from) { + $expr .= ' AND info.infoDate >= :from'; + $qb->setParameter('from', $from); + } + + if (null !== $to) { + $expr .= ' AND info.infoDate <= :to'; + $qb->setParameter('to', $to); + } + + $whereUserAssociation->add( + $qb->expr()->exists($expr) + ); + } + + // Apply the compound condition to the query builder + $qb->andWhere($whereUserAssociation); + + // Apply the steps condition + $qb->andWhere($qb->expr()->in('acp.step', ':steps')); + + // Set the remaining parameters + $qb->setParameter('user', $user) + ->setParameter('steps', $steps); + + return $qb; + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php index 560917ac8..bb4887a85 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php @@ -21,6 +21,22 @@ use Chill\PersonBundle\Entity\Person; interface AccompanyingPeriodACLAwareRepositoryInterface { + public const USER_IS_REFERRER = 0b0010; // 2 in decimal + public const USER_IS_WORK_REFERRER = 0b0100; // 4 in decimal + public const USER_IS_INTERVENING = 0b1000; // 8 in decimal + + /** + * Finds associations for a given user within a specific date range and step filters. + * + * @param \DateTimeImmutable|null $from the start date for filtering when intervention in accompanying period took place + * @param \DateTimeImmutable|null $to the end date for filtering when intervention in accompanying period took place + * + * @return array the list of user associations matching the given criteria + */ + public function findByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter, ?int $start = 0, ?int $limit = 1000): array; + + public function countByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter): int; + /** * @param array|UserJob[] $jobs * @param array|Scope[] $services diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index 73f1beecb..a3e49c657 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -62,6 +62,18 @@ final readonly class AccompanyingPeriodRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } + public function findOneBy(array $criteria): ?AccompanyingPeriod + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return AccompanyingPeriod::class; + } + + // CUSTOM FIND BY METHODS + /** * @return array|AccompanyingPeriod[] */ @@ -87,16 +99,6 @@ final readonly class AccompanyingPeriodRepository implements ObjectRepository return $qb; } - public function findOneBy(array $criteria): ?AccompanyingPeriod - { - return $this->findOneBy($criteria); - } - - public function getClassName() - { - return AccompanyingPeriod::class; - } - private function buildQueryByRecentUserHistory(User $user, \DateTimeImmutable $since): QueryBuilder { $qb = $this->repository->createQueryBuilder('a'); diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig index 38e055378..078a445bd 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig @@ -19,14 +19,38 @@
-{{ 'Number of periods'|trans }}: {{ pagination.totalItems }}
+ {{ statusFilter|chill_render_filter_order_helper }} + +{{ 'Number of periods'|trans }}: {{ pagination.totalItems }}