From 8fabfdd5c023571429ba5b2efa13d20b27c9fd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 14 Jun 2023 22:54:18 +0200 Subject: [PATCH 1/3] Feature: [export] filter on accompanying period step: allow to check multiple steps --- .../unreleased/Feature-20230614-224720.yaml | 6 +++++ .../AccompanyingCourseFilters/StepFilter.php | 27 ++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 .changes/unreleased/Feature-20230614-224720.yaml diff --git a/.changes/unreleased/Feature-20230614-224720.yaml b/.changes/unreleased/Feature-20230614-224720.yaml new file mode 100644 index 000000000..3f86b022f --- /dev/null +++ b/.changes/unreleased/Feature-20230614-224720.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[Export] Filter accompanying period by step at date: allow to pick multiple + steps' +time: 2023-06-14T22:47:20.577100599+02:00 +custom: + Issue: "113" diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php index 8ee798c07..539aa9940 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php @@ -27,7 +27,11 @@ class StepFilter implements FilterInterface { private const A = 'acp_filter_bystep_stephistories'; - private const DEFAULT_CHOICE = AccompanyingPeriod::STEP_CONFIRMED; + private const DEFAULT_CHOICE = [ + AccompanyingPeriod::STEP_CONFIRMED, + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, + ]; private const P = 'acp_step_filter_date'; @@ -79,7 +83,7 @@ class StepFilter implements FilterInterface $qb->expr()->in(self::A . '.step', ':acp_filter_by_step_steps') ) ->setParameter(self::P, $this->rollingDateConverter->convert($data['calc_date'])) - ->setParameter('acp_filter_by_step_steps', $data['accepted_steps']); + ->setParameter('acp_filter_by_step_steps', $data['accepted_steps_multi']); } public function applyOn() @@ -90,25 +94,30 @@ class StepFilter implements FilterInterface public function buildForm(FormBuilderInterface $builder) { $builder - ->add('accepted_steps', ChoiceType::class, [ + ->add('accepted_steps_multi', ChoiceType::class, [ 'choices' => self::STEPS, - 'multiple' => false, + 'multiple' => true, 'expanded' => true, - 'empty_data' => self::DEFAULT_CHOICE, - 'data' => self::DEFAULT_CHOICE, ]) ->add('calc_date', PickRollingDateType::class, [ 'label' => 'export.filter.course.by_step.date_calc', - 'data' => new RollingDate(RollingDate::T_TODAY), ]); } + public function getFormDefaultData(): array + { + return [ + 'accepted_steps_multi' => self::DEFAULT_CHOICE, + 'calc_date' => new RollingDate(RollingDate::T_TODAY), + ]; + } + public function describeAction($data, $format = 'string') { - $step = array_flip(self::STEPS)[$data['accepted_steps']]; + $steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']); return ['Filtered by steps: only %step%', [ - '%step%' => $this->translator->trans($step), + '%step%' => implode(', ', $steps), ]]; } From 398b63386393cd11a0a154e9a469ffc483d61aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 14 Jun 2023 23:29:18 +0200 Subject: [PATCH 2/3] DX: simplify overlapsI expression in postgresql --- .changes/unreleased/DX-20230614-233000.yaml | 5 +++++ src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 .changes/unreleased/DX-20230614-233000.yaml diff --git a/.changes/unreleased/DX-20230614-233000.yaml b/.changes/unreleased/DX-20230614-233000.yaml new file mode 100644 index 000000000..ca4a3171b --- /dev/null +++ b/.changes/unreleased/DX-20230614-233000.yaml @@ -0,0 +1,5 @@ +kind: DX +body: 'DQL function OVERLAPSI: simplify expression in postgresql' +time: 2023-06-14T23:30:00.217427234+02:00 +custom: + Issue: "" diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php index 29642dc15..d39c1451c 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/OverlapsI.php @@ -90,16 +90,14 @@ class OverlapsI extends FunctionNode if ($part instanceof PathExpression) { return sprintf( - "CASE WHEN %s IS NOT NULL THEN %s ELSE '%s'::date END", - $part->dispatch($sqlWalker), + "COALESCE(%s, '%s'::date)", $part->dispatch($sqlWalker), $p ); } return sprintf( - "CASE WHEN %s::date IS NOT NULL THEN %s::date ELSE '%s'::date END", - $part->dispatch($sqlWalker), + "COALESCE(%s::date, '%s'::date)", $part->dispatch($sqlWalker), $p ); From fe936ac0f2aab826149a226efb5d631efc0d76b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 14 Jun 2023 23:31:43 +0200 Subject: [PATCH 3/3] Feature: [export][course] filter course by step, between two dates --- .../unreleased/Feature-20230614-233107.yaml | 5 + .../StepFilterBetweenDates.php | 125 ++++++++++++++++++ .../{StepFilter.php => StepFilterOnDate.php} | 5 +- .../StepFilterTest.php | 4 +- .../services/exports_accompanying_course.yaml | 7 +- .../translations/messages.fr.yml | 7 +- 6 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 .changes/unreleased/Feature-20230614-233107.yaml create mode 100644 src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php rename src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/{StepFilter.php => StepFilterOnDate.php} (95%) diff --git a/.changes/unreleased/Feature-20230614-233107.yaml b/.changes/unreleased/Feature-20230614-233107.yaml new file mode 100644 index 000000000..2d0e502f2 --- /dev/null +++ b/.changes/unreleased/Feature-20230614-233107.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: '[export] add a filter on accompanying period: filter by step between two dates' +time: 2023-06-14T23:31:07.979389911+02:00 +custom: + Issue: "113" diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php new file mode 100644 index 000000000..9033c64b3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php @@ -0,0 +1,125 @@ + AccompanyingPeriod::STEP_DRAFT, + 'course.confirmed' => AccompanyingPeriod::STEP_CONFIRMED, + 'course.closed' => AccompanyingPeriod::STEP_CLOSED, + 'course.inactive_short' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, + 'course.inactive_long' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, + ]; + + private RollingDateConverterInterface $rollingDateConverter; + + private TranslatorInterface $translator; + + public function __construct( + RollingDateConverterInterface $rollingDateConverter, + TranslatorInterface $translator + ) { + $this->rollingDateConverter = $rollingDateConverter; + $this->translator = $translator; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $alias = 'acp_filter_by_step_between_dat_alias'; + $steps = 'acp_filter_by_step_between_dat_steps'; + $from = 'acp_filter_by_step_between_dat_from'; + $to = 'acp_filter_by_step_between_dat_to'; + + $qb + ->andWhere( + $qb->expr()->exists( + "SELECT 1 FROM " . AccompanyingPeriod\AccompanyingPeriodStepHistory::class . " {$alias} " . + "WHERE {$alias}.step IN (:{$steps}) AND OVERLAPSI ({$alias}.startDate, {$alias}.endDate),(:{$from}, :{$to}) = TRUE " . + "AND {$alias}.period = acp" + ) + ) + ->setParameter($from, $this->rollingDateConverter->convert($data['date_from'])) + ->setParameter($to, $this->rollingDateConverter->convert($data['date_to'])) + ->setParameter($steps, $data['accepted_steps_multi']); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('accepted_steps_multi', ChoiceType::class, [ + 'label' => 'export.filter.course.by_step.steps', + 'choices' => self::STEPS, + 'multiple' => true, + 'expanded' => true, + ]) + ->add('date_from', PickRollingDateType::class, [ + 'label' => 'export.filter.course.by_step.date_from', + ]) + ->add('date_to', PickRollingDateType::class, [ + 'label' => 'export.filter.course.by_step.date_to', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'accepted_steps_multi' => self::DEFAULT_CHOICE, + 'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'date_to' => new RollingDate(RollingDate::T_TODAY), + ]; + } + + public function describeAction($data, $format = 'string') + { + $steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']); + + return ['export.filter.course.by_step.Filtered by steps: only %step% and between %date_from% and %date_to%', [ + '%step%' => implode(', ', $steps), + '%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), + '%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'), + ]]; + } + + public function getTitle() + { + return 'export.filter.course.by_step.Filter by step between dates'; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php similarity index 95% rename from src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php rename to src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php index 539aa9940..357ed6f30 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php @@ -23,7 +23,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function in_array; -class StepFilter implements FilterInterface +class StepFilterOnDate implements FilterInterface { private const A = 'acp_filter_bystep_stephistories'; @@ -95,6 +95,7 @@ class StepFilter implements FilterInterface { $builder ->add('accepted_steps_multi', ChoiceType::class, [ + 'label' => 'export.filter.course.by_step.steps', 'choices' => self::STEPS, 'multiple' => true, 'expanded' => true, @@ -123,6 +124,6 @@ class StepFilter implements FilterInterface public function getTitle() { - return 'Filter by step'; + return 'export.filter.course.by_step.Filter by step'; } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php index 42eec5823..1b6224487 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php @@ -13,7 +13,7 @@ namespace Chill\PersonBundle\Tests\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilter; +use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilterOnDate; /** * @internal @@ -21,7 +21,7 @@ use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilter; */ final class StepFilterTest extends AbstractFilterTest { - private StepFilter $filter; + private StepFilterOnDate $filter; protected function setUp(): void { diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml index adde8d336..c299bbaaa 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml @@ -28,11 +28,14 @@ services: tags: - { name: chill.export_filter, alias: accompanyingcourse_socialissue_filter } - chill.person.export.filter_step: - class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilter + Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilterOnDate: tags: - { name: chill.export_filter, alias: accompanyingcourse_step_filter } + Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\StepFilterBetweenDates: + tags: + - { name: chill.export_filter, alias: accompanyingcourse_step_filter_between_dates } + chill.person.export.filter_geographicalunitstat: class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\GeographicalUnitStatFilter tags: diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 5cc9ee1f1..bdb93afc0 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -473,7 +473,6 @@ Accepted socialissues: Problématiques sociales "Filtered by socialissues: only %socialissues%": "Filtré par problématique sociale: uniquement %socialissues%" Group by social issue: Grouper les parcours par problématiques sociales -Filter by step: Filtrer les parcours par statut du parcours Accepted steps: Statuts Step: Statut "Filtered by steps: only %step%": "Filtré par statut du parcours: uniquement %step%" @@ -1085,7 +1084,13 @@ export: title: Filter les parcours par intervenant 'Filtered by user working on course: only %users%': 'Filtré par intervenants sur le parcours: seulement %users%' by_step: + Filter by step: Filtrer les parcours par statut du parcours + Filter by step between dates: Filtrer les parcours par statut du parcours entre deux dates + steps: Statuts retenus date_calc: Date de prise en compte du statut + date_from: Statuts acquis après cette date + date_to: Statuts acquis avant cette date + 'Filtered by steps: only %step% and between %date_from% and %date_to%': 'Filtré par statut: seulement %step%, entre %date_from% et %date_to%' by_user_scope: Computation date for referrer: Date à laquelle le référent était actif by_referrer: