# GESTION-SECTORS.md ## Vue d'ensemble Ce document décrit le système de gestion des secteurs dans l'API Geosector, incluant la connexion aux bases de données d'adresses externes, la validation des limites départementales, et le processus complet de création de secteurs avec génération automatique des passages. ## Évolutions récentes ### Gestion des sessions - La session stocke maintenant `entity_id` depuis `fk_entite` lors du login - Méthode `Session::getEntityId()` disponible pour récupérer l'ID de l'entité - Utilisation cohérente de l'entity_id dans toutes les opérations ### Gestion des passages orphelins - Les passages avec `fk_sector = 0` sont automatiquement intégrés au nouveau secteur - Évite les doublons pour les passages ayant déjà une `fk_adresse` - Mise à jour atomique dans la transaction de création du secteur ## Architecture multi-bases ### Bases de données principales 1. **Base principale** (`geosector_app`) - Contient toutes les tables de l'application - Tables concernées : `ope_sectors`, `sectors_adresses`, `ope_pass`, `ope_users_sectors`, `x_departements_contours` 2. **Base adresses** (dans conteneurs Incus séparés) - DVA : `dva-maria` (13.23.33.46) - base `adresses` - RCA : `rca-maria` (13.23.33.36) - base `adresses` - PRA : `pra-maria` (13.23.33.26) - base `adresses` - Credentials : `adr_geo_user` / `d66,AdrGeoDev.User` - Tables par département : `cp22`, `cp23`, etc. ### Configuration Dans `src/Config/AppConfig.php` : ```php 'addresses_database' => [ 'host' => '13.23.33.46', // Varie selon l'environnement 'name' => 'adresses', 'username' => 'adr_geo_user', 'password' => 'd66,AdrGeoDev.User', ], ``` ## Gestion des contours départementaux ### Table x_departements_contours Création manuelle de la table (sans DROP permissions) : ```sql CREATE TABLE IF NOT EXISTS `x_departements_contours` ( `id` int(11) NOT NULL AUTO_INCREMENT, `code_dept` varchar(3) NOT NULL, `nom_dept` varchar(100) NOT NULL, `contour` GEOMETRY NOT NULL, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `idx_code_dept` (`code_dept`), SPATIAL KEY `idx_contour` (`contour`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Contours géographiques des départements français'; ``` ### Import des contours 1. **Fichier source** : `docs/contour-des-departements.geojson` (depuis data.gouv.fr) 2. **Import automatique** : Uniquement lors de la connexion de l'admin `d6soft` 3. **Script** : `scripts/init_departements_contours.php` 4. **Résultat** : 96 départements importés avec support Polygon et MultiPolygon ## Services principaux ### AddressService Gère la récupération des adresses depuis la base externe : ```php class AddressService { // Récupère toutes les adresses dans un polygone public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array // Compte les adresses dans un polygone public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int } ``` **Caractéristiques** : - Détection automatique des départements touchés par le secteur - Interrogation de toutes les tables cp{dept} concernées - Gestion des secteurs multi-départements ### DepartmentBoundaryService Vérifie les limites départementales des secteurs : ```php class DepartmentBoundaryService { // Vérifie si un secteur est contenu dans un département public function checkSectorInDepartment(array $sectorCoordinates, string $departmentCode): array // Liste tous les départements touchés par un secteur public function getDepartmentsForSector(array $sectorCoordinates): array } ``` **Retour type** : ```php [ 'is_contained' => bool, 'message' => string, 'intersecting_departments' => [ ['code_dept' => '22', 'nom_dept' => 'Côtes-d\'Armor', 'percentage_overlap' => 75.5], ['code_dept' => '29', 'nom_dept' => 'Finistère', 'percentage_overlap' => 24.5] ] ] ``` ## Processus de création de secteur ### 1. Structure du payload ```json { "user_id": 123, "fk_entite": 45, "operation_id": 789, "sector": { "id": 0, "libelle": "Secteur Centre-Ville", "color": "#FF5733", "sector": "48.117266/-1.6777926#48.118500/-1.6750000#..." }, "users": [12, 34, 56, 78] } ``` ### 2. Étapes de création 1. **Validation** des données et de l'opération 2. **Vérification** des limites départementales (warning si débordement) 3. **Début de transaction** pour garantir la cohérence des données 4. **Insertion** du secteur dans `ope_sectors` 5. **Affectation** des utilisateurs dans `ope_users_sectors` avec : - `fk_operation`, `fk_user`, `fk_sector` - `created_at`, `fk_user_creat`, `chk_active = 1` 6. **Intégration des passages orphelins** : - Recherche des passages avec `fk_sector = 0` dans le polygone - Mise à jour de leur `fk_sector` vers le nouveau secteur - Exclusion des passages ayant déjà une `fk_adresse` 7. **Récupération** des adresses via `AddressService` 8. **Stockage** des adresses dans `sectors_adresses` 9. **Création** des passages dans `ope_pass` pour chaque adresse : - Affectés au premier utilisateur de la liste - Avec toutes les FK nécessaires (entité, opération, secteur, user) - Données d'adresse complètes 10. **Commit** de la transaction ou **rollback** en cas d'erreur ### 3. Réponse API pour CREATE **Format standardisé** : Les données sont placées à la racine, sans groupe "data" intermédiaire. ```json { "status": "success", "message": "Secteur créé avec succès", "sector": { "id": 123, "libelle": "Secteur Centre-Ville", "color": "#FF5733", "sector": "48.117266/-1.6777926#48.118500/-1.6750000#..." }, "passages_sector": [ { "id": 456, "fk_operation": 789, "fk_sector": 123, "fk_user": 12, "fk_type": 2, "fk_adresse": "cp22.12345", "passed_at": null, "numero": "10", "rue": "Rue de la Paix", "rue_bis": "", "ville": "Saint-Brieuc", "residence": null, "fk_habitat": null, "appt": null, "niveau": null, "gps_lat": "48.117266", "gps_lng": "-1.6777926", "nom_recu": null, "name": "", // Décrypté depuis encrypted_name "remarque": null, "email": "", // Décrypté depuis encrypted_email "phone": "", // Décrypté depuis encrypted_phone "montant": null, "fk_type_reglement": null, "email_erreur": null, "nb_passages": null } ], "passages_integrated": 5, // Passages orphelins intégrés "passages_created": 10, // Nouveaux passages créés "users_sectors": [ { "id": 12, "first_name": "Jean", "sect_name": "JDU", "fk_sector": 123, "name": "Dupont" // Décrypté depuis encrypted_name } ] } ``` ### 4. Réponse API pour UPDATE La réponse est identique à CREATE avec des compteurs supplémentaires : ```json { "status": "success", "message": "Secteur modifié avec succès", "sector": { "id": 123, "libelle": "Secteur Centre-Ville Modifié", "color": "#FF5733", "sector": "48.117266/-1.6777926#48.118500/-1.6750000#..." }, "passages_sector": [ // Liste complète de TOUS les passages actuels du secteur ], "passages_orphaned": 3, // Passages mis en orphelin (hors polygone) "passages_updated": 5, // Passages mis à jour avec fk_adresse "passages_created": 10, // Nouveaux passages créés "passages_total": 25, // Nombre total de passages dans le secteur "users_sectors": [ // Liste des utilisateurs affectés ] } ``` **Notes importantes** : - Les champs sensibles (name, email, phone) sont stockés cryptés et décryptés à la volée - La structure est identique entre CREATE et UPDATE pour faciliter l'intégration - Tous les champs sont retournés, même s'ils sont null - Code HTTP : 201 pour CREATE, 200 pour UPDATE ## Gestion des secteurs multi-départements ### Détection automatique Le système détecte automatiquement quand un secteur touche plusieurs départements : 1. **Analyse spatiale** : Utilisation de `ST_Intersects` pour identifier tous les départements touchés 2. **Calcul de pourcentage** : `ST_Area(ST_Intersection)` pour calculer le % de recouvrement 3. **Interrogation multi-tables** : Requête sur toutes les tables cp{dept} concernées ### Exemple de secteur multi-départements ```php // Secteur à cheval sur 22 (Côtes-d'Armor) et 29 (Finistère) $coordinates = [ [48.5778, -3.8280], // Morlaix (29) [48.5778, -3.7280], // Vers l'est (22) [48.4778, -3.7280], [48.4778, -3.8280] ]; // Le système va automatiquement : // 1. Détecter que le secteur touche 22 et 29 // 2. Interroger cp22 et cp29 pour les adresses // 3. Créer les passages pour toutes les adresses trouvées ``` ## Tables de données ### ope_sectors - `id` : Identifiant unique - `libelle` : Nom du secteur - `color` : Couleur d'affichage - `sector` : Coordonnées (format lat/lng#lat/lng#...) - `fk_entite` : Lien vers l'entité ### sectors_adresses - `fk_sector` : Lien vers le secteur - `fk_address` : ID de l'adresse dans la base externe - `numero`, `voie`, `code_postal`, `commune` - `latitude`, `longitude` ### ope_pass (passages) - `fk_entite`, `fk_operation`, `fk_sector`, `fk_user` - `numero`, `voie`, `code_postal`, `commune` - `latitude`, `longitude` - `created_at`, `fk_user_creat`, `chk_active` ### ope_users_sectors - `fk_operation` : Lien vers l'opération - `fk_user` : Lien vers l'utilisateur (ope_users) - `fk_sector` : Lien vers le secteur - `created_at`, `fk_user_creat`, `chk_active` ## Logs et monitoring Le système génère des logs détaillés pour : - Nombre d'adresses trouvées par département - Secteurs hors limites départementales - Passages créés avec succès - Erreurs de connexion aux bases d'adresses - Performance des requêtes spatiales ## Scripts de test - `test_sector_departments.php` : Test des limites départementales - `test_addresses_connection.php` : Test de connexion à la base d'adresses ## Notes importantes 1. **Fail-safe** : La création de secteur continue même si la base d'adresses est inaccessible 2. **Transactions** : - Toute la création est dans une transaction pour garantir la cohérence - Toujours vérifier `inTransaction()` avant d'appeler `rollBack()` - Gestion correcte des erreurs PDO avec try/catch 3. **Performance** : Les requêtes spatiales utilisent des index spatiaux pour optimiser les performances 4. **Modification de secteur** : Plus complexe car nécessite de gérer les passages existants (non implémenté) 5. **Paramètres SQL** : Utiliser des noms uniques pour éviter l'erreur "Invalid parameter number" 6. **Jointures** : Les données utilisateur viennent de la table `users`, pas `ope_users` (qui n'a pas nom/prenom) ## Bilan de la gestion des adresses et passages ### Vue d'ensemble du cycle de vie ``` Base Adresses (cp22, cp23...) → sectors_adresses → ope_pass ``` ### 1. CRÉATION D'UN SECTEUR #### Flux des données : 1. **Récupération des adresses** depuis la base externe (`AddressService`) 2. **Intégration des passages orphelins** (`fk_sector = NULL`) situés dans le polygone 3. **Stockage dans `sectors_adresses`** de toutes les adresses du polygone 4. **Création automatique de passages** (`ope_pass`) pour chaque adresse SAUF celles déjà utilisées par les passages orphelins #### Détails : - **Passages créés** : `fk_type = 2`, `encrypted_name = ''` (vide), affectés au premier utilisateur - **Passages orphelins** : mis à jour avec le nouveau `fk_sector` - **Évite les doublons** : les adresses déjà utilisées par des passages orphelins ne génèrent pas de nouveau passage ### 2. MISE À JOUR D'UN SECTEUR #### Processus de mise à jour : 1. **Mise à jour des attributs** (libelle, color, sector) 2. **Mise à jour des membres affectés** 3. **Suppression/recréation des adresses** dans `sectors_adresses` 4. **Gestion intelligente des passages** via `updatePassagesForSector` : #### Gestion des passages lors de l'UPDATE : ##### a) Vérification géographique des passages existants - Pour chaque passage du secteur, vérification si ses coordonnées GPS sont dans le nouveau polygone - **Si DANS le polygone** : Conservation du passage - **Si HORS du polygone** : Mise en orphelin (`fk_sector = NULL`) ##### b) Traitement des nouvelles adresses Pour chaque adresse dans `sectors_adresses` : 1. **Vérification primaire** : Recherche par `fk_adresse` 2. **Vérification secondaire** : Si pas trouvé, recherche par `numero`, `rue_bis`, `rue`, `ville` - Si trouvé → Mise à jour du `fk_adresse` dans le(s) passage(s) 3. **Création** : Si aucun passage existant, création avec : - `fk_type = 2`, `encrypted_name = ''` - Affecté au premier utilisateur du secteur - Toutes les données de l'adresse ### 3. SUPPRESSION D'UN SECTEUR #### Traitement différencié des passages : 1. **Passages "non visités"** (`fk_type = 2` ET `encrypted_name` vide) : - Suppression définitive de la base - Ces passages correspondent aux adresses non visitées 2. **Passages "visités"** (tous les autres) : - Mise à jour : `fk_sector = NULL` - Deviennent des passages orphelins - Conservent toutes leurs données (contact, montant, etc.) #### Autres suppressions : - Suppression des affectations membres (`ope_users_sectors`) - Suppression des adresses (`sectors_adresses`) - Suppression du secteur lui-même ### Tableau récapitulatif | Action | sectors_adresses | ope_pass dans polygone | ope_pass hors polygone | Nouvelles adresses | | ------ | ----------------------------- | ------------------------------------------------ | ------------------------ | ----------------------------------- | | CREATE | Insertion depuis base externe | - | Intégration si orphelins | Création automatique de passages | | UPDATE | Suppression/recréation | Conservation | Mise en orphelin | Création si pas de passage existant | | DELETE | Suppression totale | Suppression si non visités / Orphelin si visités | - | - | ### Points d'attention 1. **Cohérence géographique** : Lors d'un UPDATE, le système vérifie automatiquement et met en orphelin les passages hors du nouveau périmètre 2. **Passages orphelins** : Peuvent être réintégrés lors de la création d'un nouveau secteur englobant 3. **Mise à jour du fk_adresse** : Lors d'un UPDATE, les passages existants peuvent recevoir leur `fk_adresse` s'ils correspondent à une adresse 4. **Performance** : La création/mise à jour génère potentiellement des milliers de passages selon la densité d'adresses ## Erreurs communes et solutions ### "There is no active transaction" - **Cause** : Appel à `rollBack()` sans transaction active - **Solution** : Vérifier `$db->inTransaction()` avant rollback ### "Column not found: fk_address" - **Cause** : La colonne s'appelle `fk_adresse` (avec 'e') - **Solution** : Corriger les noms de colonnes dans les requêtes ### "Invalid parameter number" - **Cause** : Réutilisation du même nom de paramètre dans une requête - **Solution** : Utiliser des noms uniques (`:param1`, `:param2`, etc.) ### "Unknown column 'ou.nom'" - **Cause** : La table `ope_users` n'a pas de colonnes nom/prenom - **Solution** : Joindre avec la table `users` qui contient `encrypted_name` et `first_name` ### "Class 'ApiService' not found" - **Cause** : Import manquant dans le controller - **Solution** : Ajouter `use App\Services\ApiService;` et `require_once`