From 432acc0ace569cc249313700f16c02ff4b957940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 4 Oct 2022 22:17:16 +0200 Subject: [PATCH] [export][person] Feature: add filter and aggregator by geographical unit on person --- .../Entity/GeographicalUnit.php | 7 + .../Repository/GeographicalUnitRepository.php | 4 +- .../GeographicalUnitAggregator.php | 163 ++++++++++++++++++ .../PersonFilters/GeographicalUnitFilter.php | 134 ++++++++++++++ .../config/services/exports_person.yaml | 13 ++ .../translations/messages+intl-icu.fr.yaml | 5 + .../translations/messages.fr.yml | 2 + 7 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php create mode 100644 src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php index 8c704a166..49414db27 100644 --- a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit.php @@ -53,6 +53,13 @@ class GeographicalUnit return $this->id; } + protected function setId(int $id): self + { + $this->id = $id; + + return $this; + } + public function getUnitName(): ?string { return $this->unitName; diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php index ed259840b..3f6c09ee6 100644 --- a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php @@ -3,6 +3,8 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\GeographicalUnit; +use Chill\MainBundle\Entity\GeographicalUnitDTO; +use Chill\MainBundle\Entity\GeographicalUnitLayer; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; @@ -33,7 +35,7 @@ class GeographicalUnitRepository implements GeographicalUnitRepositoryInterface { return $this->repository ->createQueryBuilder('gu') - ->addSelect('PARTIAL gu.{id,unitName,unitRefId,layer}') + ->select('PARTIAL gu.{id,unitName,unitRefId,layer}') ->addOrderBy('IDENTITY(gu.layer)') ->addOrderBy(('gu.unitName')) ->getQuery() diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php new file mode 100644 index 000000000..816666e2f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php @@ -0,0 +1,163 @@ +geographicalUnitLayerRepository = $geographicalUnitLayerRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + /** + * @inheritDoc + */ + public function getLabels($key, array $values, $data) + { + switch ($key) { + case 'geog_unit_name': + return function ($value): string { + if ('_header' === $value) { + return 'acp_geog_agg_unitname'; + } + + if (null === $value) { + return ''; + } + + return $value; + }; + + case 'geog_unit_key': + return function ($value): string { + if ('_header' === $value) { + return 'acp_geog_agg_unitrefid'; + } + + if (null === $value) { + return ''; + } + + return $value; + }; + + default: + throw new \LogicException('key not supported'); + } + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data) + { + return ['geog_unit_name', 'geog_unit_key']; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('date_calc', ChillDateType::class, [ + 'label' => 'Address valid at this date', + 'required' => true, + 'data' => new \DateTimeImmutable('today'), + 'input' => 'datetime_immutable', + ]) + ->add('level', EntityType::class, [ + 'label' => 'Geographical layer', + 'placeholder' => 'Select a geographical layer', + 'class' => GeographicalUnitLayer::class, + 'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(), + 'choice_label' => function(GeographicalUnitLayer $item) { + return $this->translatableStringHelper->localize($item->getName()); + }, + ]); + } + + /** + * @inheritDoc + */ + public function getTitle() + { + return 'Group people by geographical unit based on his address'; + } + + /** + * @inheritDoc + */ + public function addRole(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data): void + { + $qb + ->leftJoin('person.householdAddresses', 'person_geog_agg_current_household_address') + ->leftJoin('person_geog_agg_current_household_address.address', 'person_geog_agg_address') + ->leftJoin(GeographicalUnit::class, 'person_geog_agg_geog_unit', Join::WITH, 'ST_CONTAINS(person_geog_agg_geog_unit.geom, person_geog_agg_address.point) = TRUE') + ->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('person_geog_agg_current_household_address'), + $qb->expr()->andX( + $qb->expr()->lte('person_geog_agg_current_household_address.validFrom', ':person_geog_agg_date'), + $qb->expr()->orX( + $qb->expr()->isNull('person_geog_agg_current_household_address.validTo'), + $qb->expr()->gt('person_geog_agg_current_household_address.validTo', ':person_geog_agg_date') + ) + ) + ) + ) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('person_geog_agg_geog_unit'), + $qb->expr()->in('person_geog_agg_geog_unit.layer', ':person_geog_agg_layers') + ) + ) + ->setParameter('person_geog_agg_date', $data['date_calc']) + ->setParameter('person_geog_agg_layers', $data['level']) + ->addSelect('person_geog_agg_geog_unit.unitName AS geog_unit_name') + ->addSelect('person_geog_agg_geog_unit.unitRefId AS geog_unit_key') + ->addGroupBy('geog_unit_name') + ->addGroupBy('geog_unit_key') + ; + } + + /** + * @inheritDoc + */ + public function applyOn() + { + return Declarations::PERSON_TYPE; + } + + public static function getDefaultAlias(): string + { + return 'person_geog_agg'; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php new file mode 100644 index 000000000..e95aff798 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php @@ -0,0 +1,134 @@ +geographicalUnitRepository = $geographicalUnitRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + /** + * @inheritDoc + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('date_calc', ChillDateType::class, [ + 'label' => 'Compute geographical location at date', + 'required' => true, + 'data' => new \DateTimeImmutable('today'), + 'input' => 'datetime_immutable', + ]) + ->add('units', EntityType::class, [ + 'label' => 'Geographical unit', + 'placeholder' => 'Select a geographical unit', + 'class' => GeographicalUnit::class, + 'choices' => $this->geographicalUnitRepository->findAll(), + 'choice_label' => function(GeographicalUnit $item) { + return $this->translatableStringHelper->localize($item->getLayer()->getName()) . ' > ' . $item->getUnitName(); + }, + 'attr' => [ + 'class' => 'select2', + ], + 'multiple' => true, + ]); + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'Filter by person\'s geographical unit (based on address)'; + } + + /** + * @inheritDoc + */ + public function describeAction($data, $format = 'string') + { + return [ + 'exports.by_person.Filtered by person\'s geographical unit (based on address) computed at datecalc, only units', + [ + 'datecalc' => $data['date_calc']->format('Y-m-d'), + 'units' => implode( + ', ', + array_map( + function (GeographicalUnit $item) { + return $this->translatableStringHelper->localize($item->getLayer()->getName()) . ' > ' . $item->getUnitName(); + }, + $data['units']->toArray() + ) + ) + ] + ]; + } + + /** + * @inheritDoc + */ + public function addRole(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data) + { + $subQuery = + 'SELECT 1 + FROM '.PersonHouseholdAddress::class.' person_filter_geog_person_household_address + JOIN person_filter_geog_person_household_address.address person_filter_geog_address + JOIN '.GeographicalUnit::class.' person_filter_geog_unit + WITH ST_CONTAINS(person_filter_geog_unit.geom, person_filter_geog_address.point) = TRUE + WHERE + person_filter_geog_person_household_address.validFrom <= :person_filter_geog_date + AND + (person_filter_geog_person_household_address.validTo IS NULL + OR person_filter_geog_person_household_address.validTo > :person_filter_geog_date) + AND + person_filter_geog_unit IN (:person_filter_geog_units) + AND + person_filter_geog_person_household_address.person = person + '; + + $qb + ->andWhere( + $qb->expr()->exists($subQuery) + ) + ->setParameter('person_filter_geog_date', $data['date_calc']) + ->setParameter('person_filter_geog_units', $data['units']) + ; + } + + /** + * @inheritDoc + */ + public function applyOn() + { + return Declarations::PERSON_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml index fa1c8df8d..ffffaf175 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml @@ -90,6 +90,12 @@ services: tags: - { name: chill.export_filter, alias: person_marital_status_filter } + Chill\PersonBundle\Export\Filter\PersonFilters\GeographicalUnitFilter: + autowire: true + autoconfigure: true + tags: + - { name: chill.export_filter, alias: person_geog_filter } + ## Aggregators chill.person.export.aggregator_nationality: class: Chill\PersonBundle\Export\Aggregator\PersonAggregators\NationalityAggregator @@ -132,3 +138,10 @@ services: autoconfigure: true tags: - { name: chill.export_aggregator, alias: person_household_position_aggregator } + + Chill\PersonBundle\Export\Aggregator\PersonAggregators\GeographicalUnitAggregator: + autowire: true + autoconfigure: true + tags: + - { name: chill.export_aggregator, alias: person_geog_aggregator } + diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index 50cb9f0e6..091b9232f 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -130,3 +130,8 @@ periods: many {Masquer # parcours clôturés ou anciens parcours} other {Masquer # parcours clôturés ou anciens parcours} } + +exports: + by_person: + Filtered by person\'s geographical unit (based on address) computed at date, only units: + "Filtré par zone géographique sur base de l'adresse, calculé à {datecalc, date, short}, seulement les zones suivantes: {units}" diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 9e3ddf6a5..12a193ac2 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -462,6 +462,8 @@ acp_geog_agg_unitname: Zone géographique acp_geog_agg_unitrefid: Clé de la zone géographique Geographical layer: Couche géographique Select a geographical layer: Choisir une couche géographique +Group people by geographical unit based on his address: Grouper les personnes par zone géographique (sur base de l'adresse) +Filter by person's geographical unit (based on address): Filter les personnes par zone géographique (sur base de l'adresse) Filter by socialaction: Filtrer les parcours par action d'accompagnement Accepted socialactions: Actions d'accompagnement