Compare commits

...

11 Commits

Author SHA1 Message Date
0b7651f519 FIX changes to repository: using ACLAware instead of deprecated method 2023-03-07 10:56:03 +01:00
033e1f9aa1 FEATURE [filters][parameters] filter working, but still using deprecated method from ActivityRepository 2023-03-03 13:06:43 +01:00
08df1c4ac8 FEATURE [repository] add parameters for filter data - typing error still for userjob 2023-03-03 12:23:07 +01:00
8d6cd0cf63 FEATURE [activity][filter] filter form created to list activities within the parcours and person context 2023-03-03 09:44:18 +01:00
f762f35386 FEATURE [repository] method added to repository to alter query using filters - not done 2023-03-03 09:44:18 +01:00
3cc56e7431 FEATURE [activity][filter] filter form created to list activities within the parcours and person context 2023-03-03 09:44:18 +01:00
243e148c00 Merge branch 'issue719_filter_activities' of gitlab.com:Chill-Projet/chill-bundles into issue719_filter_activities 2023-02-23 15:25:11 +01:00
382d3ddd42 FEATURE [activity][filter] filter form created to list activities within the parcours and person context 2023-02-23 15:22:10 +01:00
7fd823f1ee FEATURE [repository] method added to repository to alter query using filters - not done 2023-02-16 14:14:27 +01:00
1c80e0b5f5 FEATURE [activity][filter] filter form created to list activities within the parcours and person context 2023-02-16 14:14:27 +01:00
3923a13b30 FEATURE [activity][filter] filter form created to list activities within the parcours and person context 2023-02-15 16:27:34 +01:00
8 changed files with 359 additions and 22 deletions

View File

@@ -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,

View 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([
]);
}
}

View File

@@ -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);

View File

@@ -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).

View File

@@ -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);
}

View File

@@ -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'} %}

View File

@@ -1,5 +1,8 @@
---
services:
Chill\ActivityBundle\Form\ActivityFilterType:
autowire: true
autoconfigure: true
Chill\ActivityBundle\Form\Type\TranslatableActivityReasonCategoryType:
autowire: true
autoconfigure: true

View File

@@ -284,6 +284,8 @@ Filter acp which has no activity: Filtrer les parcours qui nont pas dactiv
Filtered acp which has no activities: Filtrer les parcours sans activité associée
Group acp by activity number: Grouper les parcours par nombre dactivité
My activities only: Mes échanges uniquement
#aggregators
Activity type: Type d'activité
Activity user: Utilisateur lié à l'activité