diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f1e60ee..77df37924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,3 +19,9 @@ Version 1.5.2 Thanks to @matla :-) +Master branch +============= + +- add filtering on accompanying period +- fix problems in gender filter + diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index ff2a59acc..4034af894 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -64,6 +64,10 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/menu.yml'); $loader->load('services/privacyEvent.yml'); $loader->load('services/command.yml'); + + if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') { + $loader->load('services/exports_accompanying_period.yml'); + } } private function handlePersonFieldsParameters(ContainerBuilder $container, $config) diff --git a/Export/AbstractAccompanyingPeriodExportElement.php b/Export/AbstractAccompanyingPeriodExportElement.php new file mode 100644 index 000000000..9025b6aa2 --- /dev/null +++ b/Export/AbstractAccompanyingPeriodExportElement.php @@ -0,0 +1,47 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\PersonBundle\Export; + +use Doctrine\ORM\QueryBuilder; + +/** + * + * + */ +class AbstractAccompanyingPeriodExportElement +{ + protected function havingAccompanyingPeriodInJoin(QueryBuilder $query) + { + $joins = $query->getDQLPart('join'); + + foreach ($joins['person'] as $join) { + if ($join->getAlias() === 'accompanying_period') { + return true; + } + } + + return false; + } + + protected function addJoinAccompanyingPeriod(QueryBuilder $query) + { + if (FALSE === $this->havingAccompanyingPeriodInJoin($query)) { + $query->join('person.accompanyingPeriods', 'accompanying_period'); + } + } +} diff --git a/Export/Filter/AccompanyingPeriodClosingFilter.php b/Export/Filter/AccompanyingPeriodClosingFilter.php new file mode 100644 index 000000000..7179de4d3 --- /dev/null +++ b/Export/Filter/AccompanyingPeriodClosingFilter.php @@ -0,0 +1,85 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\PersonBundle\Export\Filter; + +use Chill\MainBundle\Export\FilterInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Form\Type\ChillDateType; +use Doctrine\DBAL\Types\Type; +use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; + +/** + * + * + */ +class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface +{ + public function addRole() + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $this->addJoinAccompanyingPeriod($qb); + + $clause = $qb->expr()->andX( + $qb->expr()->lte('accompanying_period.closingDate', ':date_to'), + $qb->expr()->gte('accompanying_period.closingDate', ':date_from')); + + $qb->andWhere($clause); + $qb->setParameter('date_from', $data['date_from'], Type::DATE); + $qb->setParameter('date_to', $data['date_to'], Type::DATE); + } + + public function applyOn(): string + { + return 'person'; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('date_from', ChillDateType::class, array( + 'label' => "Having an accompanying period closed after this date", + 'data' => new \DateTime("-1 month"), + )); + + $builder->add('date_to', ChillDateType::class, array( + 'label' => "Having an accompanying period closed before this date", + 'data' => new \DateTime(), + )); + } + + public function describeAction($data, $format = 'string') + { + return [ + "Filtered by accompanying period: persons having an accompanying period" + . " closed between the %date_from% and %date_to%", + [ + '%date_from%' => $data['date_from']->format('d-m-Y'), + '%date_to%' => $data['date_to']->format('d-m-Y') + ] + ]; + } + + public function getTitle(): string + { + return "Filter by accompanying period: closed between two dates"; + } +} diff --git a/Export/Filter/AccompanyingPeriodFilter.php b/Export/Filter/AccompanyingPeriodFilter.php new file mode 100644 index 000000000..40f7dd501 --- /dev/null +++ b/Export/Filter/AccompanyingPeriodFilter.php @@ -0,0 +1,95 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\PersonBundle\Export\Filter; + +use Chill\MainBundle\Export\FilterInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Form\Type\ChillDateType; +use Doctrine\DBAL\Types\Type; +use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; + +/** + * + * + */ +class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface +{ + public function addRole() + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $this->addJoinAccompanyingPeriod($qb); + + $clause = $qb->expr()->andX(); + + $clause->add( + $qb->expr()->lte('accompanying_period.openingDate', ':date_to') + ); + $clause->add( + $qb->expr()->orX( + $qb->expr()->gte('accompanying_period.closingDate', ':date_from'), + $qb->expr()->isNull('accompanying_period.closingDate') + ) + ); + + $qb->andWhere($clause); + $qb->setParameter('date_from', $data['date_from'], Type::DATE); + $qb->setParameter('date_to', $data['date_to'], Type::DATE); + } + + public function applyOn(): string + { + return 'person'; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('date_from', ChillDateType::class, array( + 'label' => "Having an accompanying period opened after this date", + 'data' => new \DateTime("-1 month"), + )); + + $builder->add('date_to', ChillDateType::class, array( + 'label' => "Having an accompanying period ending before this date, or " + . "still opened at this date", + 'data' => new \DateTime(), + )); + } + + public function describeAction($data, $format = 'string') + { + return [ + "Filtered by accompanying period: persons having an accompanying period" + . " opened after the %date_from% and closed before the %date_to% (or still opened " + . "at the %date_to%)", + [ + '%date_from%' => $data['date_from']->format('d-m-Y'), + '%date_to%' => $data['date_to']->format('d-m-Y') + ] + ]; + } + + public function getTitle(): string + { + return "Filter by accompanying period: active period"; + } +} diff --git a/Export/Filter/AccompanyingPeriodOpeningFilter.php b/Export/Filter/AccompanyingPeriodOpeningFilter.php new file mode 100644 index 000000000..26a8818df --- /dev/null +++ b/Export/Filter/AccompanyingPeriodOpeningFilter.php @@ -0,0 +1,85 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\PersonBundle\Export\Filter; + +use Chill\MainBundle\Export\FilterInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Form\Type\ChillDateType; +use Doctrine\DBAL\Types\Type; +use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; + +/** + * + * + */ +class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface +{ + public function addRole() + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $this->addJoinAccompanyingPeriod($qb); + + $clause = $qb->expr()->andX( + $qb->expr()->lte('accompanying_period.openingDate', ':date_to'), + $qb->expr()->gte('accompanying_period.openingDate', ':date_from')); + + $qb->andWhere($clause); + $qb->setParameter('date_from', $data['date_from'], Type::DATE); + $qb->setParameter('date_to', $data['date_to'], Type::DATE); + } + + public function applyOn(): string + { + return 'person'; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('date_from', ChillDateType::class, array( + 'label' => "Having an accompanying period opened after this date", + 'data' => new \DateTime("-1 month"), + )); + + $builder->add('date_to', ChillDateType::class, array( + 'label' => "Having an accompanying period opened before this date", + 'data' => new \DateTime(), + )); + } + + public function describeAction($data, $format = 'string') + { + return [ + "Filtered by accompanying period: persons having an accompanying period" + . " opened between the %date_from% and %date_to%", + [ + '%date_from%' => $data['date_from']->format('d-m-Y'), + '%date_to%' => $data['date_to']->format('d-m-Y') + ] + ]; + } + + public function getTitle(): string + { + return "Filter by accompanying period: starting between two dates"; + } +} diff --git a/Export/Filter/GenderFilter.php b/Export/Filter/GenderFilter.php index 9039e4315..43e6c62a5 100644 --- a/Export/Filter/GenderFilter.php +++ b/Export/Filter/GenderFilter.php @@ -28,6 +28,7 @@ use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Translation\TranslatorInterface; /** * @@ -37,8 +38,18 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; class GenderFilter implements FilterInterface, ExportElementValidatedInterface { + /** + * + * @var TranslatorInterface + */ + protected $translator; + + function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } - public function applyOn() + public function applyOn() { return 'person'; } @@ -111,11 +122,19 @@ class GenderFilter implements FilterInterface, public function describeAction($data, $format = 'string') { - switch($data['accepted_genders']) { - case Person::MALE_GENDER: - return 'Filtering by gender: male only'; - case Person::FEMALE_GENDER: - return array('Filtering by gender: female only'); + $genders = []; + + foreach ($data['accepted_genders'] as $g) { + if ('null' === $g) { + $genders[] = $this->translator->trans('Not given'); + } else { + $genders[] = $this->translator->trans($g); + } } + + return [ + "Filtering by genders: only %genders%", + [ "%genders%" => \implode(", ", $genders)] + ]; } } diff --git a/Resources/config/services/exports.yml b/Resources/config/services/exports.yml index fc6cf645f..9a20898e6 100644 --- a/Resources/config/services/exports.yml +++ b/Resources/config/services/exports.yml @@ -18,6 +18,8 @@ services: chill.person.export.filter_gender: class: Chill\PersonBundle\Export\Filter\GenderFilter + arguments: + $translator: '@translator' tags: - { name: chill.export_filter, alias: person_gender_filter } diff --git a/Resources/config/services/exports_accompanying_period.yml b/Resources/config/services/exports_accompanying_period.yml new file mode 100644 index 000000000..6126af6d6 --- /dev/null +++ b/Resources/config/services/exports_accompanying_period.yml @@ -0,0 +1,15 @@ +services: + chill.person.export.filter_accompanying_period: + class: Chill\PersonBundle\Export\Filter\AccompanyingPeriodFilter + tags: + - { name: chill.export_filter, alias: person_accc_period_filter } + + chill.person.export.filter_accompanying_period_opening: + class: Chill\PersonBundle\Export\Filter\AccompanyingPeriodOpeningFilter + tags: + - { name: chill.export_filter, alias: person_acc_pe_op_filter } + + chill.person.export.filter_accompanying_period_closing: + class: Chill\PersonBundle\Export\Filter\AccompanyingPeriodClosingFilter + tags: + - { name: chill.export_filter, alias: person_acc_pe_cl_filter } diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index ba6d770cf..c23dab7a8 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -174,8 +174,7 @@ Address valid at this date: Addresse valide à cette date ## filters Filter by person gender: Filtrer par genre de la personne Accepted genders: Genres acceptés -'Filtering by gender: male only': 'Filtré par genre: hommes seulement' -'Filtering by gender: female only': 'Filtré par genre: femmes seulement' +'Filtering by genders: only %genders%': 'Filtré par genre: seulement %genders%' Filter by person's nationality: Filtrer par nationalité Nationalities: Nationalités @@ -189,6 +188,20 @@ This field should not be empty: Ce champ ne peut pas être vide This date should be after the date given in "born after" field: Cette date doit être après la date donnée du le champ "nés après le" "Filtered by person's birtdate: between %date_from% and %date_to%": "Filtré par date de naissance de la personne: uniquement nés entre le %date_from% et %date_to%" +"Filter by accompanying period: active period": "Filtrer par période d'accompagnement: en file active" +Having an accompanying period opened after this date: Ayant une période d'accompagnement ouverte après cette date +Having an accompanying period ending before this date, or still opened at this date: Ayant une période d'accompagnement fermée après cette date, ou toujours ouverte à cette date +"Filtered by accompanying period: persons having an accompanying period opened after the %date_from% and closed before the %date_to% (or still opened at the %date_to%)": "Filtré par période d'accompagnement: personnes ayant une période d'accompagnement ouverte après le %date_from%, et cloturée le %date_to% (ou toujours ouverte le %date_to%)" + +"Filter by accompanying period: starting between two dates": "Filtrer par période d'accompagnement: début de la période entre deux dates" +"Having an accompanying period opened before this date": "Ayant une période d'accompagnement ouverte avant cette date" +"Filtered by accompanying period: persons having an accompanying period opened between the %date_from% and %date_to%": "Filtrer par période d'accompagnement: ayant une période ouverte entre le %date_from% et le %date_to%" + +"Filter by accompanying period: closed between two dates": "Filtrer par période d'accompagnement: période fermée entre deux dates" +Having an accompanying period closed after this date: Ayant une période d'accompagnement fermée après cette date +"Having an accompanying period closed before this date": "Ayant une période d'accompagnement fermée avant cette date" +"Filtered by accompanying period: persons having an accompanying period closed between the %date_from% and %date_to%": "Filtrer par période d'accompagnement: ayant une période fermée entre le %date_from% et le %date_to%" + ## aggregators Group people by nationality: Aggréger les personnes par nationalités Group by level: Grouper par niveau diff --git a/Tests/Export/Filter/AccompanyingPeriodFilterTest.php b/Tests/Export/Filter/AccompanyingPeriodFilterTest.php new file mode 100644 index 000000000..c6c0fe4de --- /dev/null +++ b/Tests/Export/Filter/AccompanyingPeriodFilterTest.php @@ -0,0 +1,95 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\PersonBundle\Tests\Export\Filter; + +use Chill\MainBundle\Test\Export\AbstractFilterTest; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * + * + */ +class AccompanyingPeriodFilterTest extends AbstractFilterTest +{ + /** + * + * @var \Chill\PersonBundle\Export\Filter\BirthdateFilter + */ + private $filter; + + public function setUp() + { + static::bootKernel(); + + $container = static::$kernel->getContainer(); + + try { + $this->filter = $container->get('chill.person.export.filter_accompanying_period'); + } catch (ServiceNotFoundException $e) { + $this->markTestSkipped("The current configuration does not use accompanying_periods"); + } + } + + + public function getFilter() + { + return $this->filter; + } + + public function getFormData() + { + return array( + array( + 'date_from' => \DateTime::createFromFormat('Y-m-d', '2000-01-01'), + 'date_to' => \DateTime::createFromFormat('Y-m-d', '2010-01-01') + ) + ); + } + + public function getQueryBuilders() + { + if (static::$kernel === null) { + static::bootKernel(); + } + + $em = static::$kernel->getContainer() + ->get('doctrine.orm.entity_manager'); + + return array( + $em->createQueryBuilder() + ->select('person.firstName') + ->from('ChillPersonBundle:Person', 'person'), + $em->createQueryBuilder() + ->select('person.firstName') + ->from('ChillPersonBundle:Person', 'person') + // add a dummy where clause + ->where('person.firstname IS NOT NULL'), + $em->createQueryBuilder() + ->select('count(IDENTITY(p))') + ->from('ChillPersonBundle:Person', 'person') + // add a dummy where clause + ->where('person.firstname IS NOT NULL'), + $em->createQueryBuilder() + ->select('count(IDENTITY(p))') + ->from('ChillPersonBundle:Person', 'person') + ->join('person.accompanyingPeriods', 'accompanying_period') + // add a dummy where clause + ->where('person.firstname IS NOT NULL') + ); + } +} diff --git a/Tests/Export/Filter/GenderFilterTest.php b/Tests/Export/Filter/GenderFilterTest.php index 5e678c88f..dce198344 100644 --- a/Tests/Export/Filter/GenderFilterTest.php +++ b/Tests/Export/Filter/GenderFilterTest.php @@ -37,6 +37,12 @@ class GenderFilterTest extends AbstractFilterTest { static::bootKernel(); + // add a fake request with a default locale (used in translatable string) + $prophet = new \Prophecy\Prophet; + $request = $prophet->prophesize(); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + $container = static::$kernel->getContainer(); $this->filter = $container->get('chill.person.export.filter_gender'); @@ -52,10 +58,13 @@ class GenderFilterTest extends AbstractFilterTest { return array( array( - 'accepted_genders' => Person::FEMALE_GENDER + 'accepted_genders' => [ Person::FEMALE_GENDER ] ), array( - 'accepted_genders' => Person::MALE_GENDER + 'accepted_genders' => [ Person::MALE_GENDER ] + ), + array( + 'accepted_genders' => [ Person::MALE_GENDER, Person::BOTH_GENDER ] ) ); }