diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 47cccbddc..9a0f8b7b3 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -15,6 +15,8 @@ use Chill\MainBundle\Doctrine\Model\Point; use Chill\ThirdPartyBundle\Entity\ThirdParty; use DateTime; use DateTimeInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -97,6 +99,23 @@ class Address */ private $floor; + /** + * List of geographical units and addresses. + * + * This list is computed by a materialized view. It won't be populated until a refresh is done + * on the materialized view. + * + * @var Collection|GeographicalUnit[] + * @readonly + * @ORM\ManyToMany(targetEntity=GeographicalUnit::class) + * @ORM\JoinTable( + * name="view_chill_main_address_geographical_unit", + * joinColumns={@ORM\JoinColumn(name="address_id")}, + * inverseJoinColumns={@ORM\JoinColumn(name="geographical_unit_id")} + * ) + */ + private Collection $geographicalUnits; + /** * @var int * @@ -104,8 +123,9 @@ class Address * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") * @Groups({"write"}) + * @readonly */ - private $id; + private ?int $id = null; /** * True if the address is a "no address", aka homeless person, ... @@ -190,6 +210,7 @@ class Address public function __construct() { $this->validFrom = new DateTime(); + $this->geographicalUnits = new ArrayCollection(); } public static function createFromAddress(Address $original): Address @@ -273,6 +294,14 @@ class Address return $this->floor; } + /** + * @return Collection|GeographicalUnit[] + */ + public function getGeographicalUnits(): Collection + { + return $this->geographicalUnits; + } + /** * Get id. * diff --git a/src/Bundle/ChillMainBundle/migrations/Version20221114105345.php b/src/Bundle/ChillMainBundle/migrations/Version20221114105345.php new file mode 100644 index 000000000..8bc75ac29 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20221114105345.php @@ -0,0 +1,42 @@ +addSql('DROP MATERIALIZED VIEW view_chill_main_address_geographical_unit'); + } + + public function getDescription(): string + { + return 'Create materialized view to store GeographicalUnitAddress'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE MATERIALIZED VIEW view_chill_main_address_geographical_unit (address_id, geographical_unit_id) AS + SELECT + address.id AS address_id, + geographical_unit.id AS geographical_unit_id + FROM chill_main_address AS address + JOIN chill_main_geographical_unit AS geographical_unit ON ST_CONTAINS(geographical_unit.geom, address.point) + '); + + $this->addSql('CREATE INDEX IDX_BD42692CF5B7AF75 ON view_chill_main_address_geographical_unit (address_id)'); + $this->addSql('CREATE INDEX IDX_BD42692CDAA4DAB8 ON view_chill_main_address_geographical_unit (geographical_unit_id)'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php index 891ce63e0..9dec0f116 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators; use Chill\MainBundle\Entity\Address; -use Chill\MainBundle\Entity\GeographicalUnit; use Chill\MainBundle\Entity\GeographicalUnitLayer; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Form\Type\ChillDateType; @@ -26,7 +25,6 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; use UnexpectedValueException; -use function in_array; final class GeographicalUnitStatAggregator implements AggregatorInterface { @@ -49,57 +47,49 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('acp_geog_agg_location_history', $qb->getAllAliases(), true)) { - $qb->leftJoin('acp.locationHistories', 'acp_geog_agg_location_history'); + $qb->leftJoin('acp.locationHistories', 'acp_geog_agg_location_history'); - $qb->andWhere( - $qb->expr()->andX( - 'acp_geog_agg_location_history.startDate <= :acp_geog_aggregator_date', - $qb->expr()->orX( + $qb->andWhere( + $qb->expr()->andX( + 'acp_geog_agg_location_history.startDate <= :acp_geog_aggregator_date', + $qb->expr()->orX( 'acp_geog_agg_location_history.endDate IS NULL', 'acp_geog_agg_location_history.endDate > :acp_geog_aggregator_date' ) - ) - ); + ) + ); - $qb->setParameter('acp_geog_aggregator_date', $data['date_calc']); - } + $qb->setParameter('acp_geog_aggregator_date', $data['date_calc']); // link between location history and person - if (!in_array('acp_geog_agg_address_person_location', $qb->getAllAliases(), true)) { - $qb->leftJoin( - PersonHouseholdAddress::class, - 'acp_geog_agg_address_person_location', - Join::WITH, - $qb->expr()->andX( - 'IDENTITY(acp_geog_agg_address_person_location.person) = IDENTITY(acp_geog_agg_location_history.personLocation)', - 'acp_geog_agg_address_person_location.validFrom < :acp_geog_aggregator_date', - $qb->expr()->orX( + $qb->leftJoin( + PersonHouseholdAddress::class, + 'acp_geog_agg_address_person_location', + Join::WITH, + $qb->expr()->andX( + 'IDENTITY(acp_geog_agg_address_person_location.person) = IDENTITY(acp_geog_agg_location_history.personLocation)', + 'acp_geog_agg_address_person_location.validFrom < :acp_geog_aggregator_date', + $qb->expr()->orX( 'acp_geog_agg_address_person_location.validTo > :acp_geog_aggregator_date', $qb->expr()->isNull('acp_geog_agg_address_person_location.validTo') ) - ) - ); + ) + ); - $qb->setParameter('acp_geog_aggregator_date', $data['date_calc']); - } + $qb->setParameter('acp_geog_aggregator_date', $data['date_calc']); // we finally find an address - if (!in_array('acp_geog_agg_address', $qb->getAllAliases(), true)) { - $qb->leftJoin( - Address::class, - 'acp_geog_agg_address', - Join::WITH, - 'COALESCE(IDENTITY(acp_geog_agg_address_person_location.address), IDENTITY(acp_geog_agg_location_history.addressLocation)) = acp_geog_agg_address.id' - ); - } + $qb->leftJoin( + Address::class, + 'acp_geog_agg_address', + Join::WITH, + 'COALESCE(IDENTITY(acp_geog_agg_address_person_location.address), IDENTITY(acp_geog_agg_location_history.addressLocation)) = acp_geog_agg_address.id' + ); // and we do a join with units $qb->leftJoin( - GeographicalUnit::class, - 'acp_geog_units', - Join::WITH, - 'ST_CONTAINS(acp_geog_units.geom, acp_geog_agg_address.point) = TRUE' + 'acp_geog_agg_address.geographicalUnits', + 'acp_geog_units' ); $qb->andWhere($qb->expr()->eq('acp_geog_units.layer', ':acp_geog_unit_layer')); diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php index 3513625e9..87fe6abb2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators; -use Chill\MainBundle\Entity\GeographicalUnit; use Chill\MainBundle\Entity\GeographicalUnitLayer; use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Form\Type\ChillDateType; @@ -19,7 +18,6 @@ use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Export\Declarations; use DateTimeImmutable; -use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use LogicException; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -49,7 +47,7 @@ class GeographicalUnitAggregator implements AggregatorInterface $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') + ->leftJoin('person_geog_agg_address.geographicalUnits', 'person_geog_agg_geog_unit') ->andWhere( $qb->expr()->orX( $qb->expr()->isNull('person_geog_agg_current_household_address'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php index ee420e63e..67355aeca 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Address; -use Chill\MainBundle\Entity\GeographicalUnit; use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\ChillDateType; @@ -23,7 +22,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; use Chill\PersonBundle\Export\Declarations; use DateTimeImmutable; -use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; @@ -33,8 +31,6 @@ use Symfony\Component\Form\FormBuilderInterface; */ class GeographicalUnitStatFilter implements FilterInterface { - private EntityManagerInterface $em; - private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository; private GeographicalUnitRepositoryInterface $geographicalUnitRepository; @@ -42,12 +38,10 @@ class GeographicalUnitStatFilter implements FilterInterface private TranslatableStringHelperInterface $translatableStringHelper; public function __construct( - EntityManagerInterface $em, GeographicalUnitRepositoryInterface $geographicalUnitRepository, GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, TranslatableStringHelperInterface $translatableStringHelper ) { - $this->em = $em; $this->geographicalUnitRepository = $geographicalUnitRepository; $this->geographicalUnitLayerRepository = $geographicalUnitLayerRepository; $this->translatableStringHelper = $translatableStringHelper; @@ -68,7 +62,7 @@ class GeographicalUnitStatFilter implements FilterInterface WITH IDENTITY(acp_geog_filter_location_history.personLocation) = IDENTITY(acp_geog_filter_address_person_location.person) LEFT JOIN ' . Address::class . ' acp_geog_filter_address WITH COALESCE(IDENTITY(acp_geog_filter_address_person_location.address), IDENTITY(acp_geog_filter_location_history.addressLocation)) = acp_geog_filter_address.id - LEFT JOIN ' . GeographicalUnit::class . ' acp_geog_filter_units WITH ST_CONTAINS(acp_geog_filter_units.geom, acp_geog_filter_address.point) = TRUE + LEFT JOIN acp_geog_filter_address.geographicalUnits acp_geog_filter_units WHERE (acp_geog_filter_location_history.startDate <= :acp_geog_filter_date AND ( acp_geog_filter_location_history.endDate IS NULL OR acp_geog_filter_location_history.endDate < :acp_geog_filter_date @@ -120,7 +114,7 @@ class GeographicalUnitStatFilter implements FilterInterface { return ['Filtered by geographic unit: computed at %date%, only in %units%', [ '%date%' => $data['date_calc']->format('d-m-Y'), - '%units' => implode( + '%units%' => implode( ', ', array_map( function (SimpleGeographicalUnitDTO $item) { diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php index 03d9ab568..3d539a442 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; -use Chill\MainBundle\Entity\GeographicalUnit; use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface; @@ -53,8 +52,7 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface '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 + JOIN person_filter_geog_address.geographicalUnits person_filter_geog_unit WHERE person_filter_geog_person_household_address.validFrom <= :person_filter_geog_date AND diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 0b53bd7a5..7c9afba48 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -586,6 +586,8 @@ end period date: Date de fin de la période Filter by current evaluations: Filtrer les évaluations en cours "Filtered by current evaluations": "Filtré: uniquement les évaluations en cours" +'Filtered by geographic unit: computed at %date%, only in %units%': 'Filtré par unité géographique: adresse le %date%, seulement les unités %units%' + ## social actions filters/aggr Filter by treating agent scope: Filtrer les actions par service de l'agent traitant "Filtered by treating agent scope: only %scopes%": "Filtré par service de l'agent traitant: uniquement %scopes%"