diff --git a/CHANGELOG.md b/CHANGELOG.md index 7185ca588..57372ed35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to * [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506) * [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505) * [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486) +* [parcours] List of parcours for a specific user so they can be reassigned in case of absence (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/509) * [thirdparty] Thirdparty view page, english text translated (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/534) * [social_action] Translation changed in evaluation section (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/512) diff --git a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php new file mode 100644 index 000000000..ac5b6dfab --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php @@ -0,0 +1,115 @@ +accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; + $this->engine = $engine; + $this->formFactory = $formFactory; + $this->paginatorFactory = $paginatorFactory; + $this->security = $security; + $this->userRepository = $userRepository; + $this->userRender = $userRender; + } + + /** + * @Route("/{_locale}/person/accompanying-periods/reassign", name="chill_course_list_reassign") + */ + public function listAction(Request $request): Response + { + if (!$this->security->isGranted('ROLE_USER') || !$this->security->getUser() instanceof User) { + throw new AccessDeniedException(); + } + + $form = $this->buildFilterForm(); + + $form->handleRequest($request); + + $total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod( + $form['user']->getData() + ); + $paginator = $this->paginatorFactory->create($total); + $periods = $this->accompanyingPeriodACLAwareRepository + ->findByUserOpenedAccompanyingPeriod( + $form['user']->getData(), + ['openingDate' => 'ASC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return new Response( + $this->engine->render('@ChillPerson/AccompanyingPeriod/reassign_list.html.twig', [ + 'paginator' => $paginator, + 'periods' => $periods, + 'form' => $form->createView(), + ]) + ); + } + + private function buildFilterForm(): FormInterface + { + $data = [ + 'user' => null, + ]; + $builder = $this->formFactory->createBuilder(FormType::class, $data, [ + 'method' => 'get', 'csrf_protection' => false, ]); + + $builder + ->add('user', EntityType::class, [ + 'class' => User::class, + 'choices' => $this->userRepository->findByActive(['username' => 'ASC']), + 'choice_label' => function (User $u) { + return $this->userRender->renderString($u, []); + }, + 'multiple' => false, + 'label' => 'User', + 'required' => false, + ]); + + return $builder->getForm(); + } +} diff --git a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php index 04f989d84..4635eb4fc 100644 --- a/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/SectionMenuBuilder.php @@ -22,15 +22,9 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class SectionMenuBuilder implements LocalMenuBuilderInterface { - /** - * @var AuthorizationCheckerInterface - */ - protected $authorizationChecker; + protected AuthorizationCheckerInterface $authorizationChecker; - /** - * @var TranslatorInterface - */ - protected $translator; + protected TranslatorInterface $translator; /** * SectionMenuBuilder constructor. @@ -63,6 +57,14 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface 'order' => 11, 'icons' => ['plus'], ]); + + $menu->addChild($this->translator->trans('Accompanying courses of users'), [ + 'route' => 'chill_course_list_reassign', + ]) + ->setExtras([ + 'order' => 12, + 'icons' => ['task'], + ]); } public static function getMenuIds(): array diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index 6e6c3ca44..f4c9a52a3 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; @@ -41,6 +42,37 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC $this->centerResolverDispatcher = $centerResolverDispatcher; } + public function buildQueryOpenedAccompanyingCourseByUser(?User $user) + { + $qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap'); + + $qb->where($qb->expr()->eq('ap.user', ':user')) + ->andWhere( + $qb->expr()->neq('ap.step', ':draft'), + $qb->expr()->orX( + $qb->expr()->isNull('ap.closingDate'), + $qb->expr()->gt('ap.closingDate', ':now') + ) + ) + ->setParameter('user', $user) + ->setParameter('now', new \DateTime('now')) + ->setParameter('draft', AccompanyingPeriod::STEP_DRAFT); + + return $qb; + } + + public function countByUserOpenedAccompanyingPeriod(?User $user): int + { + if (null === $user) { + return 0; + } + + return $this->buildQueryOpenedAccompanyingCourseByUser($user) + ->select('COUNT(ap)') + ->getQuery() + ->getSingleScalarResult(); + } + public function findByPerson( Person $person, string $role, @@ -92,4 +124,25 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC return $qb->getQuery()->getResult(); } + + /** + * @return array|AccompanyingPeriod[] + */ + public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array + { + if (null === $user) { + return []; + } + + $qb = $this->buildQueryOpenedAccompanyingCourseByUser($user); + + $qb->setFirstResult($offset) + ->setMaxResults($limit); + + foreach ($orderBy as $field => $direction) { + $qb->addOrderBy('ap.'.$field, $direction); + } + + return $qb->getQuery()->getResult(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php index 778e8d8fe..0c5af217f 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; interface AccompanyingPeriodACLAwareRepositoryInterface @@ -22,4 +23,8 @@ interface AccompanyingPeriodACLAwareRepositoryInterface ?int $limit = null, ?int $offset = null ): array; + + public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array; + + public function countByUserOpenedAccompanyingPeriod(?User $user): int; } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index 122b658bd..ab7006fd8 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -78,6 +78,16 @@ final class AccompanyingPeriodRepository implements ObjectRepository ->getResult(); } + public function findConfirmedByUser(User $user) + { + $qb = $this->createQueryBuilder('ap'); + $qb->where($qb->expr()->eq('ap.user', ':user')) + ->andWhere('ap.step', 'CONFIRMED') + ->setParameter('user', $user); + + return $qb; + } + public function findOneBy(array $criteria): ?AccompanyingPeriod { return $this->findOneBy($criteria); diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig new file mode 100644 index 000000000..87166d990 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig @@ -0,0 +1,82 @@ +{% extends 'ChillMainBundle::layout.html.twig' %} + +{% block title 'period_by_user_list.Period by user'|trans %} + +{% block js %} + {{ encore_entry_script_tags('mod_set_referrer') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_set_referrer') }} +{% endblock %} + +{% macro period_meta(period) %} + {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', period) %} +
+ {% set job_id = null %} + {% if period.job is defined %} + {% set job_id = period.job.id %} + {% endif %} + +
+ {% endif %} +{% endmacro %} + + +{% macro period_actions(period) %} + {% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', period) %} +
  • + +
  • + {% endif %} +{% endmacro %} + +{% import _self as m %} + +{% block content %} +
    +

    {{ block('title') }}

    + + {{ form_start(form) }} +
    +
    + {{ form_label(form.user ) }} + {{ form_widget(form.user, {'attr': {'class': 'select2'}}) }} +
    +
    + + + + {{ form_end(form) }} + + {% if form.user.vars.value is empty %} +

    {{ 'period_by_user_list.Pick a user'|trans }}

    + {% elseif periods|length == 0 and form.user.vars.value is not empty %} +

    {{ 'period_by_user_list.Any course or no authorization to see them'|trans }}

    + + {% else %} +

    {{ paginator.totalItems }} parcours à réassigner (calculé ce jour à {{ null|format_time('medium') }})

    + +
    + {% for period in periods %} + {% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period, + 'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period) } %} + {% endfor %} +
    + {% endif %} + + {{ chill_pagination(paginator) }} + +
    +{% endblock %} + diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml index 22d58ccbe..67c724093 100644 --- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml @@ -41,6 +41,11 @@ services: autowire: true tags: ['controller.service_arguments'] + Chill\PersonBundle\Controller\ReassignAccompanyingPeriodController: + autoconfigure: true + autowire: true + tags: ['controller.service_arguments'] + Chill\PersonBundle\Controller\PersonApiController: arguments: $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index cd9dce294..d6e739632 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -194,6 +194,7 @@ No accompanying user: Aucun accompagnant No data given: Pas d'information Participants: Personnes impliquées Create an accompanying course: Créer un parcours +Accompanying courses of users: Parcours des utilisateurs This accompanying course is still a draft: Ce parcours est encore à l'état brouillon. Associated peoples: Usagers concernés Resources: Interlocuteurs privilégiés @@ -578,3 +579,8 @@ My accompanying periods in draft: Mes parcours brouillons workflow: Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval% + +period_by_user_list: + Period by user: Parcours d'accompagnement par utilisateur + Pick a user: Choisissez un utilisateur pour obtenir la liste de ses parcours + Any course or no authorization to see them: Aucun parcours pour ce référent, ou aucun droit pour visualiser les parcours de ce référent.