diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php index a887eb201..ad7585d71 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UserScopeFilter.php @@ -13,17 +13,25 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Form\Type\PickRollingDateType; +use Chill\MainBundle\Service\RollingDate\RollingDate; +use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Doctrine\ORM\Query\Expr\Andx; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class UserScopeFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + private const PREFIX = 'acp_act_filter_user_scope'; // creator ? cfr translation + + public function __construct( + private readonly RollingDateConverter $rollingDateConverter, + private readonly TranslatableStringHelper $translatableStringHelper + ) {} public function addRole(): ?string { @@ -32,22 +40,32 @@ class UserScopeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('actuser', $qb->getAllAliases(), true)) { - $qb->join('activity.user', 'actuser'); - } + $p = self::PREFIX; - $where = $qb->getDQLPart('where'); - - $clause = $qb->expr()->in('actuser.mainScope', ':userscope'); - - if ($where instanceof Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } - - $qb->add('where', $where); - $qb->setParameter('userscope', $data['accepted_userscope']); + $qb + ->leftJoin("activity.user", "{$p}_user") // createdBy ? cfr translation + ->leftJoin( + UserScopeHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", ":{$p}_at"), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", ":{$p}_at") + ) + ) + ) + ->andWhere( + $qb->expr()->in("{$p}_history.scope", ":{$p}_scope") + ) + ->setParameters([ + "{$p}_scope" => $data["scopes"], + "{$p}_at" => $this->rollingDateConverter->convert($data["scope_at"]) + ]); } public function applyOn(): string @@ -57,37 +75,46 @@ class UserScopeFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - $builder->add('accepted_userscope', EntityType::class, [ - 'class' => Scope::class, - 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( - $s->getName() - ), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize( + $s->getName() + ), + 'multiple' => true, + 'expanded' => true, + ]) + ->add('scope_at', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.course.by_user_scope.Calc date', + 'required' => true, + ]); } public function describeAction($data, $format = 'string'): array { $scopes = []; - foreach ($data['accepted_userscope'] as $s) { + foreach ($data['scopes'] as $s) { $scopes[] = $this->translatableStringHelper->localize( $s->getName() ); } - return ['Filtered activity by userscope: only %scopes%', [ + return ['export.filter.activity.course.by_user_scope.Filtered activity by userscope: only %scopes%', [ '%scopes%' => implode(', ', $scopes), ]]; } + public function getFormDefaultData(): array + { + return [ + 'scopes' => [], + 'scope_at' => new RollingDate(RollingDate::T_TODAY) + ]; + } + public function getTitle(): string { - return 'Filter activity by userscope'; + return 'export.filter.activity.course.by_user_scope.Filter activity by userscope'; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php index 69f743987..4185ad4e9 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -13,8 +13,12 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Form\Type\PickRollingDateType; +use Chill\MainBundle\Service\RollingDate\RollingDate; +use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -22,7 +26,12 @@ use Symfony\Component\Form\FormBuilderInterface; class UsersJobFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper) {} + private const PREFIX = 'act_filter_user_job'; + + public function __construct( + private readonly RollingDateConverter $rollingDateConverter, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function addRole(): ?string { @@ -31,14 +40,24 @@ class UsersJobFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { + $p = self::PREFIX; + $qb ->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . Activity::class . ' activity_users_job_filter_act - JOIN activity_users_job_filter_act.users users WHERE users.userJob IN (:activity_users_job_filter_jobs) AND activity_users_job_filter_act = activity ' + "SELECT 1 FROM " . Activity::class . " {$p}_act " + . "JOIN {$p}_act.users {$p}_user " + . "JOIN " . UserJobHistory::class . " {$p}_history WITH {$p}_history.user = {$p}_user " + . "WHERE {$p}_act = activity " + . "AND {$p}_history.startDate <= :{$p}_at " + . "AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > :{$p}_at) " + . "AND {$p}_history.job IN ( :{$p}_jobs )" ) ) - ->setParameter('activity_users_job_filter_jobs', $data['jobs']); + ->setParameters([ + "{$p}_jobs" => $data["jobs"], + "{$p}_at" => $this->rollingDateConverter->convert($data["job_at"]) + ]); } public function applyOn() @@ -48,21 +67,22 @@ class UsersJobFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - $builder->add('jobs', EntityType::class, [ - 'class' => UserJob::class, - 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), + 'multiple' => true, + 'expanded' => true, + ]) + ->add('job_at', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.by_users_job.Calc date', + 'required' => true + ]); } public function describeAction($data, $format = 'string') { - return ['export.filter.activity.by_usersjob.Filtered activity by users job: only %jobs%', [ + return ['export.filter.activity.by_users_job.Filtered activity by users job: only %jobs%', [ '%jobs%' => implode( ', ', array_map( @@ -73,8 +93,16 @@ class UsersJobFilter implements FilterInterface ]]; } + public function getFormDefaultData(): array + { + return [ + 'jobs' => [], + 'job_at' => new RollingDate(RollingDate::T_TODAY) + ]; + } + public function getTitle() { - return 'export.filter.activity.by_usersjob.Filter by users job'; + return 'export.filter.activity.by_users_job.Filter by users job'; } } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php index 02c3229a7..e2c4ca1e0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -14,8 +14,12 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\ScopeRepositoryInterface; +use Chill\MainBundle\Service\RollingDate\RollingDate; +use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -23,7 +27,13 @@ use Symfony\Component\Form\FormBuilderInterface; class UsersScopeFilter implements FilterInterface { - public function __construct(private readonly ScopeRepositoryInterface $scopeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} + private const PREFIX = 'act_filter_user_scope'; + + public function __construct( + private readonly RollingDateConverter $rollingDateConverter, + private readonly ScopeRepositoryInterface $scopeRepository, + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function addRole(): ?string { @@ -32,39 +42,50 @@ class UsersScopeFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { + $p = self::PREFIX; + $qb ->andWhere( $qb->expr()->exists( - 'SELECT 1 FROM ' . Activity::class . ' activity_users_scope_filter_act - JOIN activity_users_scope_filter_act.users users WHERE users.mainScope IN (:activity_users_scope_filter_scopes) AND activity_users_scope_filter_act = activity ' + "SELECT 1 FROM " . Activity::class . " {$p}_act " + . "JOIN {$p}_act.users {$p}_user " + . "JOIN " . UserScopeHistory::class . " {$p}_history WITH {$p}_history.user = {$p}_user " + . "WHERE {$p}_act = activity " + . "AND {$p}_history.startDate <= :{$p}_at " + . "AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > :{$p}_at) " + . "AND {$p}_history.scope IN ( :{$p}_scopes )" ) ) - ->setParameter('activity_users_scope_filter_scopes', $data['scopes']); + ->setParameters([ + "{$p}_scopes" => $data["scopes"], + "{$p}_at" => $this->rollingDateConverter->convert($data["scope_at"]) + ]); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) { - $builder->add('scopes', EntityType::class, [ - 'class' => Scope::class, - 'choices' => $this->scopeRepository->findAllActive(), - 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), - 'multiple' => true, - 'expanded' => true, - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + 'multiple' => true, + 'expanded' => true, + ]) + ->add('scope_at', PickRollingDateType::class, [ + 'label' => 'export.filter.activity.by_users_scope.Calc date', + 'required' => true, + ]); } - public function describeAction($data, $format = 'string') + public function describeAction($data, $format = 'string'): array { - return ['export.filter.activity.by_usersscope.Filtered activity by users scope: only %scopes%', [ + return ['export.filter.activity.by_users_scope.Filtered activity by users scope: only %scopes%', [ '%scopes%' => implode( ', ', array_map( @@ -75,8 +96,16 @@ class UsersScopeFilter implements FilterInterface ]]; } - public function getTitle() + public function getFormDefaultData(): array { - return 'export.filter.activity.by_usersscope.Filter by users scope'; + return [ + 'scopes' => [], + 'scope_at' => new RollingDate(RollingDate::T_TODAY) + ]; + } + + public function getTitle(): string + { + return 'export.filter.activity.by_users_scope.Filter by users scope'; } } diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index ef9a1b45e..2576b9bf4 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -287,8 +287,6 @@ Filter activity by creator: Filtrer les échanges par créateur de l'échange 'Filtered activity by users: only %users%': "Filtré par utilisateurs participants: uniquement %users%" 'Filtered activity by creator: only %users%': "Filtré par créateur: uniquement %users%" Creators: Créateurs -Filter activity by userscope: Filtrer les échanges par service du créateur -'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" Accepted userscope: Services Filter acp which has no activity: Filtrer les parcours qui n’ont pas d’échange @@ -362,18 +360,25 @@ export: filter: activity: - by_usersjob: + by_users_job: Filter by users job: Filtrer les échanges par métier d'au moins un utilisateur participant 'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%' - by_usersscope: + Calc date: Date de calcul du métier de l'utilisateur + by_users_scope: Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant 'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%' + Calc date: Date de calcul du service de l'utilisateur course_having_activity_between_date: Title: Filtre les parcours ayant reçu un échange entre deux dates Receiving an activity after: Ayant reçu un échange après le Receiving an activity before: Ayant reçu un échange avant le acp_by_activity_type: 'acp_containing_at_least_one_%activitytypes%': 'Parcours filtrés: uniquement ceux qui contiennent au moins un échange d''un des types suivants: %activitytypes%' + course: + by_user_scope: + Filter activity by userscope: Filtrer les échanges par service du créateur + 'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" + Calc date: Date de calcul du service du créateur aggregator: diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php index eca8a299a..f9b652eeb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php @@ -11,19 +11,29 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\UserJobRepositoryInterface; +use Chill\MainBundle\Service\RollingDate\RollingDate; +use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Export\Declarations; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use function in_array; class CreatorJobFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly UserJobRepositoryInterface $userJobRepository) {} + private const PREFIX = 'acp_filter_creator_job'; + + public function __construct( + private readonly RollingDateConverter $rollingDateConverter, + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly UserJobRepositoryInterface $userJobRepository + ) {} public function addRole(): ?string { @@ -32,13 +42,30 @@ class CreatorJobFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('acp_creator', $qb->getAllAliases(), true)) { - $qb->join('acp.createdBy', 'acp_creator'); - } + $p = self::PREFIX; $qb - ->andWhere($qb->expr()->in('acp_creator.userJob', ':creator_job')) - ->setParameter('creator_job', $data['creator_job']); + ->join("acp.createdBy", "{$p}_user") + ->leftJoin( + UserJobHistory::class, + "{$p}_history", + Join::WITH, + $qb->expr()->eq("{$p}_history.user", "{$p}_user") + ) + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte("{$p}_history.startDate", ":{$p}_at"), + $qb->expr()->orX( + $qb->expr()->isNull("{$p}_history.endDate"), + $qb->expr()->gt("{$p}_history.endDate", ":{$p}_at") + ) + ) + ) + ->andWhere($qb->expr()->in("{$p}_history.job", ":{$p}_jobs")) + ->setParameters([ + "{$p}_jobs" => $data["creator_job"], + "{$p}_at" => $this->rollingDateConverter->convert($data["job_at"]) + ]); } public function applyOn(): string @@ -48,20 +75,21 @@ class CreatorJobFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { - $builder->add('creator_job', EntityType::class, [ - 'class' => UserJob::class, - 'choices' => $this->userJobRepository->findAllActive(), - 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize( - $j->getLabel() - ), - 'multiple' => true, - 'expanded' => true, - 'label' => 'Job', - ]); - } - public function getFormDefaultData(): array - { - return []; + $builder + ->add('creator_job', EntityType::class, [ + 'class' => UserJob::class, + 'choices' => $this->userJobRepository->findAllActive(), + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize( + $j->getLabel() + ), + 'multiple' => true, + 'expanded' => true, + 'label' => 'Job', + ]) + ->add('job_at', PickRollingDateType::class, [ + 'label' => 'export.filter.course.creator_job.Calc date', + 'required' => true + ]); } public function describeAction($data, $format = 'string'): array @@ -79,8 +107,16 @@ class CreatorJobFilter implements FilterInterface ]]; } + public function getFormDefaultData(): array + { + return [ + 'creator_job' => [], + 'job_at' => new RollingDate(RollingDate::T_TODAY) + ]; + } + public function getTitle(): string { - return 'Filter by creator job'; + return 'export.filter.course.creator_job.Filter by creator job'; } } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index f1a2ccdf0..08027db7e 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -571,8 +571,6 @@ Filtered acp which has no actions: 'Filtré: uniquement les parcours qui n''ont Group by number of actions: Grouper les parcours par nombre d’actions Filter by creator: Filtrer les parcours par créateur 'Filtered by creator: only %creators%': 'Filtré par créateur: uniquement %creators%' -Filter by creator job: Filtrer les parcours par métier du créateur -'Filtered by creator job: only %jobs%': 'Filtré par métier du créateur: uniquement %jobs%' Filter actions without end date: Filtre les actions sans date de fin (ouvertes) Filtered actions without end date: 'Filtré: uniquement les actions sans date de fin (ouvertes)' @@ -1135,7 +1133,9 @@ export: Having a person's location: Ayant une localisation auprès d'un usager Calculation date: Date de la localisation creator_job: - 'Filtered by creator job: only %jobs%': 'Filtré par métier du créateur: seulement %jobs%' + Filter by creator job: Filtrer les parcours par métier du créateur + 'Filtered by creator job: only %jobs%': "Filtré par métier du créateur: uniquement %jobs%" + Calc date: Date de calcul du métier du créateur by_user_job: Filter by user job: Filtrer les parcours par métier du référent "Filtered by user job: only %job%": "Filtré par métier du référent: uniquement %job%"