mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	Compare commits
	
		
			11 Commits
		
	
	
		
			2.12.1
			...
			issue719_f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0b7651f519 | |||
| 033e1f9aa1 | |||
| 08df1c4ac8 | |||
| 8d6cd0cf63 | |||
| f762f35386 | |||
| 3cc56e7431 | |||
| 243e148c00 | |||
| 382d3ddd42 | |||
| 7fd823f1ee | |||
| 1c80e0b5f5 | |||
| 3923a13b30 | 
| @@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Controller; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Entity\ActivityReason; | ||||
| use Chill\ActivityBundle\Form\ActivityFilterType; | ||||
| use Chill\ActivityBundle\Form\ActivityType; | ||||
| use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface; | ||||
| use Chill\ActivityBundle\Repository\ActivityRepository; | ||||
| @@ -294,6 +295,10 @@ final class ActivityController extends AbstractController | ||||
|  | ||||
|         [$person, $accompanyingPeriod] = $this->getEntity($request); | ||||
|  | ||||
|         $form = $this->createForm(ActivityFilterType::class); | ||||
|  | ||||
|         $form->handleRequest($request); | ||||
|  | ||||
|         if ($person instanceof Person) { | ||||
|             $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); | ||||
|             $activities = $this->activityACLAwareRepository | ||||
| @@ -309,15 +314,43 @@ final class ActivityController extends AbstractController | ||||
|         } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { | ||||
|             $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); | ||||
|  | ||||
|             $activities = $this->activityACLAwareRepository | ||||
|                 ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); | ||||
|  | ||||
|             $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; | ||||
|             if ($form->isSubmitted() && $form->isValid()) { | ||||
|                 $data = $form->getData(); | ||||
|                 $activities = $this->activityACLAwareRepository | ||||
|                     ->findByAccompanyingPeriod( | ||||
|                         $accompanyingPeriod, | ||||
|                         ActivityVoter::SEE, | ||||
|                         0, | ||||
|                         $data['dateTo'], | ||||
|                         $data['dateFrom'], | ||||
|                         $data['jobs']->getValues(), | ||||
|                         $data['types']->getValues(), | ||||
|                         $data['onlyMe'], | ||||
|                         null, | ||||
|                         ['date' => 'DESC', 'id' => 'DESC'] | ||||
|                     ); | ||||
|             } else { | ||||
|                 $activities = $this->activityACLAwareRepository | ||||
|                     ->findByAccompanyingPeriod( | ||||
|                         $accompanyingPeriod, | ||||
|                         ActivityVoter::SEE, | ||||
|                         0, | ||||
|                         null, | ||||
|                         null, | ||||
|                         [], | ||||
|                         [], | ||||
|                         false, | ||||
|                         null, | ||||
|                         ['date' => 'DESC', 'id' => 'DESC'] | ||||
|                     ); | ||||
|             } | ||||
|         } | ||||
|         $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; | ||||
|  | ||||
|         return $this->render( | ||||
|             $view, | ||||
|             [ | ||||
|                 'form' => $form->createView(), | ||||
|                 'activities' => $activities, | ||||
|                 'person' => $person, | ||||
|                 'accompanyingCourse' => $accompanyingPeriod, | ||||
|   | ||||
							
								
								
									
										107
									
								
								src/Bundle/ChillActivityBundle/Form/ActivityFilterType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/Bundle/ChillActivityBundle/Form/ActivityFilterType.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\ActivityBundle\Form; | ||||
|  | ||||
| use Chill\MainBundle\Entity\UserJob; | ||||
| use Chill\MainBundle\Form\Type\ChillDateType; | ||||
| use Chill\MainBundle\Templating\TranslatableStringHelperInterface; | ||||
| use Doctrine\ORM\EntityRepository; | ||||
| use Symfony\Bridge\Doctrine\Form\Type\EntityType; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| use Symfony\Component\Form\Extension\Core\Type\CheckboxType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\Form\FormError; | ||||
| use Symfony\Component\Form\FormEvent; | ||||
| use Symfony\Component\Form\FormEvents; | ||||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||||
| use Symfony\Contracts\Translation\TranslatorInterface; | ||||
| use Chill\ActivityBundle\Entity\ActivityType; | ||||
|  | ||||
| class ActivityFilterType extends AbstractType | ||||
| { | ||||
|     private TranslatableStringHelperInterface $translatableString; | ||||
|  | ||||
|     protected TranslatorInterface $translator; | ||||
|  | ||||
|     public function __construct(TranslatableStringHelperInterface $translatableString, TranslatorInterface $translator) | ||||
|     { | ||||
|         $this->translatableString = $translatableString; | ||||
|  | ||||
|         $this->translator = $translator; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options): void | ||||
|     { | ||||
|  | ||||
|         $builder | ||||
|             ->add('types', EntityType::class, [ | ||||
|                 'class' => ActivityType::class, | ||||
|                 'label' => 'Activity type', | ||||
|                 'multiple' => true, | ||||
|                 'query_builder' => static function (EntityRepository $er) { | ||||
|                     $qb = $er->createQueryBuilder('t'); | ||||
|                     $qb->andWhere($qb->expr()->eq('t.active', "'TRUE'")); | ||||
|  | ||||
|                     return $qb; | ||||
|                 }, | ||||
|                 'choice_label' => function (ActivityType $t) { | ||||
|                     return $this->translatableString->localize($t->getName()); | ||||
|                 }, | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('jobs', EntityType::class, [ | ||||
|                 'class' => UserJob::class, | ||||
|                 'label' => 'user job', | ||||
|                 'multiple' => true, | ||||
|                 'query_builder' => static function (EntityRepository $er) { | ||||
|                     $qb = $er->createQueryBuilder('j'); | ||||
|                     $qb->andWhere($qb->expr()->eq('j.active', "'TRUE'")); | ||||
|  | ||||
|                     return $qb; | ||||
|                 }, | ||||
|                 'choice_label' => function (UserJob $j) { | ||||
|                     return $this->translatableString->localize($j->getLabel()); | ||||
|                 }, | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('dateFrom', ChillDateType::class, [ | ||||
|                 'label' => 'Activities after this date', | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('dateTo', ChillDateType::class, [ | ||||
|                 'label' => 'Activities before this date', | ||||
|                 'required' => false, | ||||
|             ]) | ||||
|             ->add('onlyMe', CheckboxType::class, [ | ||||
|                 'label' => 'My activities only', | ||||
|                 'required' => false, | ||||
|             ]); | ||||
|  | ||||
|         $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { | ||||
|  | ||||
|             $form = $event->getForm(); | ||||
|             $dateFrom = $form->get('dateFrom')->getData(); | ||||
|             $dateTo = $form->get('dateTo')->getData(); | ||||
|  | ||||
|             // check that date_from is before date_to | ||||
|             if ( | ||||
|                 (null !== $dateFrom && null !== $dateTo) | ||||
|                 && $dateFrom >= $dateTo | ||||
|             ) { | ||||
|                 $form->get('dateTo')->addError(new FormError( | ||||
|                     $this->translator->trans('This date should be after ' | ||||
|                         . 'the date given in "Implied in an activity after ' | ||||
|                         . 'this date" field') | ||||
|                 )); | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public function configureOptions(OptionsResolver $resolver): void | ||||
|     { | ||||
|         $resolver->setDefaults([ | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -22,14 +22,15 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper; | ||||
| use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use DateTime; | ||||
| use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\AbstractQuery; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\Query\ResultSetMappingBuilder; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||||
|  | ||||
| use Symfony\Component\Security\Core\Role\Role; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| use function count; | ||||
| use function in_array; | ||||
|  | ||||
| @@ -63,21 +64,31 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte | ||||
|         $this->security = $security; | ||||
|     } | ||||
|  | ||||
|     public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array | ||||
|     { | ||||
|         $user = $this->security->getUser(); | ||||
|         $center = $this->centerResolverDispatcher->resolveCenter($period); | ||||
|     /*    public function findByAccompanyingPeriod( | ||||
|             AccompanyingPeriod $period, | ||||
|             string $role, | ||||
|             ?int $start = 0, | ||||
|             ?DateTime $before = null, | ||||
|             ?DateTime $after = null, | ||||
|             ?array $userJob = [], | ||||
|             ?array $activityTypes = [], | ||||
|             bool $onlyMe = false, | ||||
|             ?int $limit = 1000, | ||||
|             ?array $orderBy = [] | ||||
|         ): array { | ||||
|             $user = $this->security->getUser(); | ||||
|             $center = $this->centerResolverDispatcher->resolveCenter($period); | ||||
|  | ||||
|         if (0 === count($orderBy)) { | ||||
|             $orderBy = ['date' => 'DESC']; | ||||
|         } | ||||
|             if (0 === count($orderBy)) { | ||||
|                 $orderBy = ['date' => 'DESC']; | ||||
|             } | ||||
|  | ||||
|         $scopes = $this->authorizationHelper | ||||
|             ->getReachableCircles($user, $role, $center); | ||||
|             $scopes = $this->authorizationHelper | ||||
|                 ->getReachableCircles($user, $role, $center); | ||||
|  | ||||
|         return $this->em->getRepository(Activity::class) | ||||
|             ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); | ||||
|     } | ||||
|             return $this->em->getRepository(Activity::class) | ||||
|                 ->findByAccompanyingPeriod($period, $scopes, true, $before, $after, $userJob, $activityTypes, $onlyMe, $limit, $start, $orderBy); | ||||
|         }*/ | ||||
|  | ||||
|     public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array | ||||
|     { | ||||
| @@ -199,6 +210,62 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return Activity[] | ||||
|      */ | ||||
|     private function findByAccompanyingPeriod( | ||||
|         AccompanyingPeriod $period, | ||||
|         string $role, | ||||
|         ?int $start = 0, | ||||
|         ?DateTime $before = null, | ||||
|         ?DateTime $after = null, | ||||
|         ?array $userJob = [], | ||||
|         ?array $activityTypes = [], | ||||
|         bool $onlyMe = false, | ||||
|         ?int $limit = 100, | ||||
|         ?int $offset = 0, | ||||
|         ?array $orderBy = ['date' => 'desc'] | ||||
|     ): array { | ||||
|         $qb = $this->createQueryBuilder('a'); | ||||
|         $qb->select('a'); | ||||
|  | ||||
|         $user = $this->security->getUser(); | ||||
|         $center = $this->centerResolverDispatcher->resolveCenter($period); | ||||
|  | ||||
|         if (0 === count($orderBy)) { | ||||
|             $orderBy = ['date' => 'DESC']; | ||||
|         } | ||||
|  | ||||
|         $scopes = $this->authorizationHelper | ||||
|             ->getReachableCircles($user, $role, $center); | ||||
|  | ||||
|         $qb | ||||
|             ->where( | ||||
|                 $qb->expr()->orX( | ||||
|                     $qb->expr()->in('a.scope', ':scopes'), | ||||
|                     $qb->expr()->isNull('a.scope') | ||||
|                 ) | ||||
|             ) | ||||
|             ->setParameter('scopes', $scopes); | ||||
|  | ||||
|         $qb | ||||
|             ->andWhere( | ||||
|                 $qb->expr()->eq('a.accompanyingPeriod', ':period') | ||||
|             ) | ||||
|             ->setParameter('period', $period); | ||||
|  | ||||
|         //Add filter queries | ||||
|         $this->repository->addQueryFilters($qb, $userJob, $activityTypes, $after, $before, $onlyMe); | ||||
|  | ||||
|         foreach ($orderBy as $k => $dir) { | ||||
|             $qb->addOrderBy('a.' . $k, $dir); | ||||
|         } | ||||
|  | ||||
|         $qb->setMaxResults($limit)->setFirstResult($offset); | ||||
|  | ||||
|         return $qb->getQuery()->getResult(); | ||||
|     } | ||||
|  | ||||
|     private function getFromClauseCenter(array $args): string | ||||
|     { | ||||
|         $metadataActivity = $this->em->getClassMetadata(Activity::class); | ||||
|   | ||||
| @@ -11,15 +11,29 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use DateTime; | ||||
|  | ||||
| interface ActivityACLAwareRepositoryInterface | ||||
| { | ||||
|     /** | ||||
|      * @return Activity[]|array | ||||
|      */ | ||||
|     public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; | ||||
|     public function findByAccompanyingPeriod( | ||||
|         AccompanyingPeriod $period, | ||||
|         string $role, | ||||
|         ?int $start = 0, | ||||
|         ?DateTime $before = null, | ||||
|         ?DateTime $after = null, | ||||
|         ?array $userJob = [], | ||||
|         ?array $activityTypes = [], | ||||
|         bool $onlyMe = false, | ||||
|         ?int $limit = 1000, | ||||
|         ?int $offset = 0, | ||||
|         ?array $orderBy = [] | ||||
|     ): array; | ||||
|  | ||||
|     /** | ||||
|      * Return a list of activities, simplified as array (not object). | ||||
|   | ||||
| @@ -12,10 +12,16 @@ declare(strict_types=1); | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Entity\ActivityType; | ||||
| use Chill\MainBundle\Entity\UserJob; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use DateTime; | ||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Doctrine\Persistence\ManagerRegistry; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
| use function count; | ||||
|  | ||||
| /** | ||||
|  * @method Activity|null find($id, $lockMode = null, $lockVersion = null) | ||||
| @@ -25,9 +31,58 @@ use Doctrine\Persistence\ManagerRegistry; | ||||
|  */ | ||||
| class ActivityRepository extends ServiceEntityRepository | ||||
| { | ||||
|     public function __construct(ManagerRegistry $registry) | ||||
|     private Security $security; | ||||
|  | ||||
|     public function __construct(ManagerRegistry $registry, Security $security) | ||||
|     { | ||||
|         parent::__construct($registry, Activity::class); | ||||
|         $this->security = $security; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param array|UserJob $jobs | ||||
|      * @param array|ActivityType $types | ||||
|      * @param DateTime $dateFrom | ||||
|      * @param DateTime $dateTo | ||||
|      */ | ||||
|     public function addQueryFilters(QueryBuilder $qb, array $jobs, array $types, ?DateTime $dateFrom, ?DateTime $dateTo, bool $onlyMe): QueryBuilder | ||||
|     { | ||||
|         if (0 < count($jobs)) { | ||||
|             //TODO check for jobs of all users involved | ||||
|             $qb->innerJoin('a.user', 'u'); | ||||
|             $qb->andWhere($qb->expr()->in('u.userJob', ':jobs')) | ||||
|                 ->setParameter('jobs', $jobs); | ||||
|         } | ||||
|  | ||||
|         if (0 < count($types)) { | ||||
|             $qb->andWhere($qb->expr()->in('a.activityType', ':types')) | ||||
|                 ->setParameter('types', $types); | ||||
|         } | ||||
|  | ||||
|         if (null !== $dateFrom && null !== $dateTo) { | ||||
|             $qb->andWhere($qb->expr()->between( | ||||
|                 'a.date', | ||||
|                 ':date_from', | ||||
|                 ':date_to' | ||||
|             )) | ||||
|                 ->setParameter('date_from', $dateFrom) | ||||
|                 ->setParameter('date_to', $dateTo); | ||||
|         } elseif (null !== $dateFrom && null === $dateTo) { | ||||
|             $qb->andWhere($qb->expr()->gt('a.date', ':date_from')) | ||||
|                 ->setParameter('date_from', $dateFrom); | ||||
|         } elseif (null === $dateFrom && null !== $dateTo) { | ||||
|             $qb->andWhere($qb->expr()->lt('a.date', ':date_to')) | ||||
|                 ->setParameter('date_to', $dateTo); | ||||
|         } | ||||
|  | ||||
|         if (true === $onlyMe) { | ||||
|             $currentUser = $this->security->getUser(); | ||||
|  | ||||
|             $qb->andWhere($qb->expr()->eq('a.user', ':currentUser')) | ||||
|                 ->setParameter('currentUser', $currentUser); | ||||
|         } | ||||
|  | ||||
|         return $qb; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -35,8 +90,19 @@ class ActivityRepository extends ServiceEntityRepository | ||||
|      * | ||||
|      * @return Activity[] | ||||
|      */ | ||||
|     public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array | ||||
|     { | ||||
|     public function findByAccompanyingPeriod( | ||||
|         AccompanyingPeriod $period, | ||||
|         array $scopes, | ||||
|         ?bool $allowNullScope = false, | ||||
|         ?DateTime $before = null, | ||||
|         ?DateTime $after = null, | ||||
|         ?array $userJob = [], | ||||
|         array $activityTypes = [], | ||||
|         bool $onlyMe = false, | ||||
|         ?int $limit = 100, | ||||
|         ?int $offset = 0, | ||||
|         array $orderBy = ['date' => 'desc'] | ||||
|     ): array { | ||||
|         $qb = $this->createQueryBuilder('a'); | ||||
|         $qb->select('a'); | ||||
|  | ||||
| @@ -61,6 +127,9 @@ class ActivityRepository extends ServiceEntityRepository | ||||
|             ) | ||||
|             ->setParameter('period', $period); | ||||
|  | ||||
|         //Add filter queries | ||||
|         $this->addQueryFilters($qb, $userJob, $activityTypes, $after, $before, $onlyMe); | ||||
|  | ||||
|         foreach ($orderBy as $k => $dir) { | ||||
|             $qb->addOrderBy('a.' . $k, $dir); | ||||
|         } | ||||
|   | ||||
| @@ -27,7 +27,49 @@ | ||||
|         {% set accompanying_course_id = accompanyingCourse.id %} | ||||
|     {% endif %} | ||||
|  | ||||
|     <h1>{{ 'Activity list' |trans }}</h1> | ||||
|     <h1>{{ 'Activity list'|trans }}</h1> | ||||
|  | ||||
| {# TODO: form error messages not displaying #} | ||||
|     <p>{{ form_errors(form) }}</p> | ||||
|  | ||||
|         {{ form_start(form) }} | ||||
|         <div class="row"> | ||||
|             <div class="col-md-4"> | ||||
|                 {{ form_label(form.types ) }} | ||||
|                 {{ form_widget(form.types, {'attr': {'class': 'select2'}}) }} | ||||
|             </div> | ||||
|             <div class="col-md-4"> | ||||
|                 {{ form_label(form.jobs) }} | ||||
|                 {{ form_widget(form.jobs, {'attr': {'class': 'select2'}}) }} | ||||
|             </div> | ||||
|             <div class="row"> | ||||
|                 <div class="col-md-4"> | ||||
|                     {{ form_label(form.dateFrom) }} | ||||
|                     {{ form_widget(form.dateFrom) }} | ||||
|                 </div> | ||||
|                 <div class="col-md-4"> | ||||
|                     {{ form_label(form.dateTo) }} | ||||
|                     {{ form_widget(form.dateTo) }} | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row"> | ||||
|                 <div class="col-md-4"> | ||||
|                     {{ form_label(form.onlyMe) }} | ||||
|                     {{ form_widget(form.onlyMe) }} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <ul class="record_actions"> | ||||
|             <li> | ||||
|                 <button type="submit" class="btn btn-save change-icon"> | ||||
|                     <i class="fa fa-filter"></i> Filtrer | ||||
|                 </button> | ||||
|             </li> | ||||
|         </ul> | ||||
|  | ||||
|         {{ form_end(form) }} | ||||
| {#    </div>#} | ||||
|  | ||||
|     {% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'accompanyingCourse'} %} | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| --- | ||||
| services: | ||||
|     Chill\ActivityBundle\Form\ActivityFilterType: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|     Chill\ActivityBundle\Form\Type\TranslatableActivityReasonCategoryType: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|   | ||||
| @@ -284,6 +284,8 @@ Filter acp which has no activity: Filtrer les parcours qui n’ont pas d’activ | ||||
| Filtered acp which has no activities: Filtrer les parcours sans activité associée | ||||
| Group acp by activity number: Grouper les parcours par nombre d’activité | ||||
|  | ||||
| My activities only: Mes échanges uniquement | ||||
|  | ||||
| #aggregators | ||||
| Activity type: Type d'activité | ||||
| Activity user: Utilisateur lié à l'activité | ||||
|   | ||||
		Reference in New Issue
	
	Block a user