From e7a8403bd5bf7fb3813f8c39a270f9fd4f51cdff Mon Sep 17 00:00:00 2001 From: Saidi Date: Fri, 5 Apr 2024 15:38:30 +0200 Subject: [PATCH] load epci #2423 --- migrations/Version20240405172355.php | 32 ++++++ src/Command/LoadEpciCommand.php | 98 +++++++++++++++++ .../Signalement/SignalementSearchQuery.php | 17 ++- src/Entity/Commune.php | 15 +++ src/Entity/Epci.php | 92 ++++++++++++++++ src/Repository/CommuneRepository.php | 9 ++ src/Repository/EpciRepository.php | 23 ++++ src/Service/Signalement/SearchFilter.php | 2 + .../Command/LoadEpciCommandTest.php | 103 ++++++++++++++++++ .../Back/SignalementListControllerTest.php | 9 +- .../SignalementSearchQueryTest.php | 2 +- 11 files changed, 390 insertions(+), 12 deletions(-) create mode 100644 migrations/Version20240405172355.php create mode 100644 src/Command/LoadEpciCommand.php create mode 100644 src/Entity/Epci.php create mode 100644 src/Repository/EpciRepository.php create mode 100644 tests/Functional/Command/LoadEpciCommandTest.php diff --git a/migrations/Version20240405172355.php b/migrations/Version20240405172355.php new file mode 100644 index 0000000000..e86e47229d --- /dev/null +++ b/migrations/Version20240405172355.php @@ -0,0 +1,32 @@ +addSql('CREATE TABLE epci (id INT AUTO_INCREMENT NOT NULL, nom VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, UNIQUE INDEX slug_unique (slug), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE commune ADD epci_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE commune ADD CONSTRAINT FK_E2E2D1EE4E7C18CB FOREIGN KEY (epci_id) REFERENCES epci (id)'); + $this->addSql('CREATE INDEX IDX_E2E2D1EE4E7C18CB ON commune (epci_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE commune DROP FOREIGN KEY FK_E2E2D1EE4E7C18CB'); + $this->addSql('DROP TABLE epci'); + $this->addSql('DROP INDEX IDX_E2E2D1EE4E7C18CB ON commune'); + $this->addSql('ALTER TABLE commune DROP epci_id'); + } +} diff --git a/src/Command/LoadEpciCommand.php b/src/Command/LoadEpciCommand.php new file mode 100644 index 0000000000..29ee1afdb1 --- /dev/null +++ b/src/Command/LoadEpciCommand.php @@ -0,0 +1,98 @@ +httpClient->request('GET', self::API_EPCI_ALL_URL); + if (Response::HTTP_OK !== $response->getStatusCode()) { + $io->error('API failed'); + + return Command::FAILURE; + } + + $epciList = json_decode($response->getContent(), true); + $progressBar = new ProgressBar($output, \count($epciList)); + $progressBar->start(); + foreach ($epciList as $epciItem) { + $epci = $this->epciRepository->findOneBy(['nom' => $epciNom = $epciItem['nom']]); + if (!$epci) { + $epci = (new Epci()) + ->setNom($epciNom) + ->setSlug($this->slugger->slug($epciNom)); + } + $response = $this->httpClient->request( + 'GET', + $epciCommunesUrl = sprintf(self::API_EPCI_COMMUNE_URL, $epciItem['code']) + ); + + if (Response::HTTP_OK === $response->getStatusCode()) { + $communeList = json_decode($response->getContent(), true); + foreach ($communeList as $communeItem) { + $communes = $this->communeRepository->findByCodesPostaux($communeItem['codesPostaux']); + foreach ($communes as $commune) { + $epci->addCommune($commune); + } + } + $this->entityManager->flush(); + } else { + $io->error(sprintf('API failed for: %s', $epciCommunesUrl)); + } + $progressBar->advance(); + } + + $progressBar->finish(); + $this->entityManager->flush(); + $nbCommunesWithECPI = $this->communeRepository->count([]); + $nbCommunesWithoutECPI = $this->communeRepository->count(['epci' => null]); + $io->success(sprintf( + 'EPCI loaded with %d communes that belong to EPCI', + $nbCommunesWithECPI - $nbCommunesWithoutECPI + )); + if ($nbCommunesWithoutECPI > 0) { + $io->warning(sprintf( + '%d communes do not belong to EPCI, the code postal might be obsolete.', + $nbCommunesWithoutECPI + )); + } + + return Command::SUCCESS; + } +} diff --git a/src/Dto/Request/Signalement/SignalementSearchQuery.php b/src/Dto/Request/Signalement/SignalementSearchQuery.php index 49d752dab3..f356629d28 100644 --- a/src/Dto/Request/Signalement/SignalementSearchQuery.php +++ b/src/Dto/Request/Signalement/SignalementSearchQuery.php @@ -3,6 +3,7 @@ namespace App\Dto\Request\Signalement; use App\Entity\Enum\SignalementStatus; +use App\Service\Signalement\SearchFilter; use Symfony\Component\Validator\Constraints as Assert; class SignalementSearchQuery @@ -14,7 +15,7 @@ public function __construct( private readonly ?string $searchTerms = null, #[Assert\Choice(['nouveau', 'en cours', 'ferme', 'refuse'])] private readonly ?string $status = null, - private readonly ?string $commune = null, + private readonly ?array $communes = null, private readonly ?array $etiquettes = null, #[Assert\Date(message: 'La date de début n\'est pas une date valide')] private readonly ?string $dateDepotDebut = null, @@ -29,7 +30,7 @@ public function __construct( private readonly ?string $dateDernierSuiviDebut = null, #[Assert\Date(message: 'La date de fin n\'est pas une date valide')] private readonly ?string $dateDernierSuiviFin = null, - #[Assert\Choice(['accepte', 'en_attente', 'refuse', 'cloture_un_partnaire', 'cloture_tous_partenaire'])] + #[Assert\Choice(['accepte', 'en_attente', 'refuse', 'cloture_un_partenaire', 'cloture_tous_partenaire'])] private readonly ?string $statusAffectation = null, #[Assert\GreaterThanOrEqual(0)] private readonly ?float $criticiteScoreMin = null, @@ -69,9 +70,9 @@ public function getStatus(): ?string return $this->status; } - public function getCommune(): ?string + public function getCommunes(): ?array { - return $this->commune; + return $this->communes; } public function getEtiquettes(): ?array @@ -174,6 +175,10 @@ public function getOrderBy(): string return $this->orderBy; } + /** + * Permet de s'appuyer sur le système pour le système de filtre. + * Aprés la MEP s'appuyer exclusivement sur le DTO au lieu du tableau de filtres @see SearchFilter::buildFilters(). + */ public function getFilters(): array { $filters = []; @@ -182,7 +187,7 @@ public function getFilters(): array $filters['statuses'] = null !== $this->getStatus() ? [SignalementStatus::mapFilterStatus($this->getStatus())] : null; - $filters['cities'] = null !== $this->getCommune() ? [$this->getCommune()] : null; + $filters['cities'] = $this->getCommunes() ?? null; $filters['partners'] = $this->getPartenaires() ?? null; $filters['allocs'] = null !== $this->getAllocataire() ? [$this->getAllocataire()] : null; $filters['housetypes'] = match ($this->getNatureParc()) { @@ -224,7 +229,7 @@ public function getFilters(): array } $filters['statusAffectation'] = $this->getStatusAffectation(); $filters['closed_affectation'] = match ($filters['statusAffectation']) { - 'cloture_un_partnaire' => ['ONE_CLOSED'], + 'cloture_un_partenaire' => ['ONE_CLOSED'], 'cloture_tous_partenaire' => ['ALL_CLOSED'], default => null }; diff --git a/src/Entity/Commune.php b/src/Entity/Commune.php index 54a504ffb4..e26744a653 100755 --- a/src/Entity/Commune.php +++ b/src/Entity/Commune.php @@ -30,6 +30,9 @@ class Commune #[ORM\Column(type: 'boolean')] private $isZonePermisLouer; + #[ORM\ManyToOne(inversedBy: 'communes', cascade: ['persist'])] + private ?Epci $epci = null; + public function getId(): ?int { return $this->id; @@ -83,6 +86,18 @@ public function setTerritory(?Territory $territory): self return $this; } + public function getEpci(): ?Epci + { + return $this->epci; + } + + public function setEpci(?Epci $epci): static + { + $this->epci = $epci; + + return $this; + } + public function __toString(): string { return $this->nom; diff --git a/src/Entity/Epci.php b/src/Entity/Epci.php new file mode 100644 index 0000000000..230310d88b --- /dev/null +++ b/src/Entity/Epci.php @@ -0,0 +1,92 @@ +communes = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getNom(): ?string + { + return $this->nom; + } + + public function setNom(string $nom): static + { + $this->nom = $nom; + + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(string $slug): static + { + $this->slug = strtolower($slug); + + return $this; + } + + /** + * @return Collection + */ + public function getCommunes(): Collection + { + return $this->communes; + } + + public function addCommune(Commune $commune): static + { + if (!$this->communes->contains($commune)) { + $this->communes->add($commune); + $commune->setEpci($this); + } + + return $this; + } + + public function removeCommune(Commune $commune): static + { + if ($this->communes->removeElement($commune)) { + // set the owning side to null (unless already changed) + if ($commune->getEpci() === $this) { + $commune->setEpci(null); + } + } + + return $this; + } +} diff --git a/src/Repository/CommuneRepository.php b/src/Repository/CommuneRepository.php index 3be497ca55..7b46b5c13f 100755 --- a/src/Repository/CommuneRepository.php +++ b/src/Repository/CommuneRepository.php @@ -33,4 +33,13 @@ public function remove(Commune $entity, bool $flush = false): void $this->getEntityManager()->flush(); } } + + public function findByCodesPostaux(array $codesPostaux): array + { + return $this->createQueryBuilder('c') + ->where('c.codePostal IN (:codePostaux)') + ->setParameter('codePostaux', $codesPostaux) + ->getQuery() + ->getResult(); + } } diff --git a/src/Repository/EpciRepository.php b/src/Repository/EpciRepository.php new file mode 100644 index 0000000000..a5413472e1 --- /dev/null +++ b/src/Repository/EpciRepository.php @@ -0,0 +1,23 @@ + + * + * @method Epci|null find($id, $lockMode = null, $lockVersion = null) + * @method Epci|null findOneBy(array $criteria, array $orderBy = null) + * @method Epci[] findAll() + * @method Epci[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class EpciRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Epci::class); + } +} diff --git a/src/Service/Signalement/SearchFilter.php b/src/Service/Signalement/SearchFilter.php index 53d1a870de..9a7216dd41 100755 --- a/src/Service/Signalement/SearchFilter.php +++ b/src/Service/Signalement/SearchFilter.php @@ -14,6 +14,7 @@ use App\Entity\Suivi; use App\Entity\Territory; use App\Entity\User; +use App\Repository\CommuneRepository; use App\Repository\NotificationRepository; use App\Repository\SignalementQualificationRepository; use App\Repository\SuiviRepository; @@ -66,6 +67,7 @@ public function __construct( private TerritoryRepository $territoryRepository, private EntityManagerInterface $entityManager, private SignalementQualificationRepository $signalementQualificationRepository, + private CommuneRepository $communeRepository, ) { } diff --git a/tests/Functional/Command/LoadEpciCommandTest.php b/tests/Functional/Command/LoadEpciCommandTest.php new file mode 100644 index 0000000000..581248a933 --- /dev/null +++ b/tests/Functional/Command/LoadEpciCommandTest.php @@ -0,0 +1,103 @@ +getEpciAllResponse()), + new MockResponse($this->getEpciCommunes()), + ]; + $mockHttpClient = new MockHttpClient($responses); + /** @var EntityManagerInterface $entityManager */ + $entityManager = self::getContainer()->get('doctrine')->getManager(); + + $command = new LoadEpciCommand( + $mockHttpClient, + self::getContainer()->get(TerritoryRepository::class), + self::getContainer()->get(CommuneRepository::class), + self::getContainer()->get(EpciRepository::class), + $entityManager, + self::getContainer()->get(SluggerInterface::class) + ); + + $commandTester = new CommandTester($command); + $commandTester->execute([]); + $output = $commandTester->getDisplay(); + $this->assertStringContainsString('EPCI loaded with 8 communes that belong to EPCI', $output, $output); + $this->assertStringContainsString('35720 communes do not belong to EPCI', $output, $output); + } + + private function getEpciAllResponse(): string + { + return json_encode([ + [ + 'nom' => 'CC Faucigny - Glières', + 'code' => '200000172', + 'codesDepartements' => [ + '74', + ], + 'codesRegions' => [ + '84', + ], + 'population' => 27764, + ], + ]); + } + + private function getEpciCommunes(): string + { + return json_encode([ + [ + 'nom' => 'Ayse', + 'code' => '74024', + 'codeDepartement' => '74', + 'siren' => '217400241', + 'codeEpci' => '200000172', + 'codeRegion' => '84', + 'codesPostaux' => [ + '74130', + ], + 'population' => 2274, + ], + [ + 'nom' => 'Bonneville', + 'code' => '74042', + 'codeDepartement' => '74', + 'siren' => '217400423', + 'codeEpci' => '200000172', + 'codeRegion' => '84', + 'codesPostaux' => [ + '74130', + ], + 'population' => 12895, + ], + [ + 'nom' => 'Brizon', + 'code' => '74049', + 'codeDepartement' => '74', + 'siren' => '217400498', + 'codeEpci' => '200000172', + 'codeRegion' => '84', + 'codesPostaux' => [ + '74130', + ], + 'population' => 473, + ], + ]); + } +} diff --git a/tests/Functional/Controller/Back/SignalementListControllerTest.php b/tests/Functional/Controller/Back/SignalementListControllerTest.php index b2871efd99..535d5481dd 100644 --- a/tests/Functional/Controller/Back/SignalementListControllerTest.php +++ b/tests/Functional/Controller/Back/SignalementListControllerTest.php @@ -234,16 +234,16 @@ public function provideNewFilterSearch(): \Generator yield 'Search Terms with Lastname Occupant' => [['searchTerms' => 'Nawell'], 2]; yield 'Search Terms with Email Occupant' => [['searchTerms' => 'nawell.mapaire@yopmail.com'], 1]; yield 'Search by Territory' => [['territory' => '13'], 25]; - yield 'Search by Commune' => [['commune' => 'gex'], 5]; - yield 'Search by Commune code postal' => [['commune' => '13002'], 1]; + yield 'Search by Commune' => [['communes' => ['gex', 'marseille']], 30]; + yield 'Search by Commune code postal' => [['communes' => ['13002']], 1]; yield 'Search by Partner' => [['partenaires' => ['5']], 2]; yield 'Search by Etiquettes' => [['etiquettes' => ['3']], 4]; yield 'Search by Parc public' => [['natureParc' => 'public'], 5]; yield 'Search by Parc public/prive non renseigné' => [['natureParc' => 'non_renseigne'], 1]; - yield 'Search by Enfant moins de 6ans' => [['enfantsM6' => '0'], 4]; + yield 'Search by Enfant moins de 6ans' => [['enfantsM6' => 'non'], 4]; yield 'Search by Date de depot' => [['dateDepotDebut' => '2023-03-01', 'dateDepotFin' => '2023-04-01'], 2]; yield 'Search by Prcedure estimé' => [['procedure' => 'rsd'], 3]; - yield 'Search by Partenaires affectés' => [['partenaires' => ['10']], 2]; + yield 'Search by Partenaires affectés' => [['partenaires' => ['5']], 2]; yield 'Search by Statut de la visite' => [['visiteStatus' => 'Planifiée'], 5]; yield 'Search by Type de dernier suivi' => [['typeDernierSuivi' => 'automatique'], 16]; yield 'Search by Date de dernier suivi' => [['dateDernierSuiviDebut' => '2023-03-01', 'dateDernierSuiviFin' => '2023-12-31'], 3]; @@ -252,7 +252,6 @@ public function provideNewFilterSearch(): \Generator yield 'Search by Declarant' => [['typeDeclarant' => 'locataire'], 3]; yield 'Search by Nature du parc' => [['natureParc' => 'public'], 5]; yield 'Search by Allocataire' => [['allocataire' => 'caf'], 13]; - yield 'Search by Enfants de moins de 6 ans' => [['enfantsM6' => 0], 4]; yield 'Search by Situation Bail en cours' => [['situation' => 'bail_en_cours'], 3]; yield 'Search by Situation Prévis de départ' => [['situation' => 'preavis_de_depart'], 1]; yield 'Search by Situation Attente de relogement' => [['situation' => 'attente_relogement'], 2]; diff --git a/tests/Unit/Dto/Request/Signalement/SignalementSearchQueryTest.php b/tests/Unit/Dto/Request/Signalement/SignalementSearchQueryTest.php index eb2e5518d4..829308bd95 100644 --- a/tests/Unit/Dto/Request/Signalement/SignalementSearchQueryTest.php +++ b/tests/Unit/Dto/Request/Signalement/SignalementSearchQueryTest.php @@ -14,7 +14,7 @@ public function testGetFilters(): void '13', 'John', 'nouveau', - 'Marseille', + ['Marseille'], ['1', '5'], '2022-01-01', '2022-12-31',