# 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 maria3/maria4)
   - **DVA** : maria3 (13.23.33.4) - base `adresses`
     - User : `adr_geo_user` / `d66,AdrGeoDev.User`
   - **RCA** : maria3 (13.23.33.4) - base `adresses`
     - User : `adr_geo_user` / `d66,AdrGeoRec.User`
   - **PROD** : maria4 (13.23.33.4) - base `adresses`
     - User : `adr_geo_user` / `d66,AdrGeoPrd.User`
   - Tables par département : `cp22`, `cp23`, etc.

3. **Base bâtiments** (dans conteneurs maria3/maria4)
   - **DVA** : maria3 (13.23.33.4) - base `batiments`
     - User : `adr_geo_user` / `d66,AdrGeoDev.User`
   - **RCA** : maria3 (13.23.33.4) - base `batiments`
     - User : `adr_geo_user` / `d66,AdrGeoRec.User`
   - **PROD** : maria4 (13.23.33.4) - base `batiments`
     - User : `adr_geo_user` / `d66,AdrGeoPrd.User`
   - Tables par département : `bat22`, `bat23`, etc.
   - Colonnes principales : `batiment_groupe_id`, `cle_interop_adr`, `nb_log`, `nb_niveau`, `residence`, `altitude_sol_mean`
   - Lien avec adresses : `bat{dept}.cle_interop_adr = cp{dept}.id`

### Configuration

Dans `src/Config/AppConfig.php` :

```php
// DÉVELOPPEMENT
'addresses_database' => [
    'host' => '13.23.33.4',  // Container maria3 sur IN3
    'name' => 'adresses',
    'username' => 'adr_geo_user',
    'password' => 'd66,AdrGeoDev.User',
],

// RECETTE
'addresses_database' => [
    'host' => '13.23.33.4',  // Container maria3 sur IN3
    'name' => 'adresses',
    'username' => 'adr_geo_user',
    'password' => 'd66,AdrGeoRec.User',
],

// PRODUCTION
'addresses_database' => [
    'host' => '13.23.33.4',  // Container maria4 sur IN4
    'name' => 'adresses',
    'username' => 'adr_geo_user',
    'password' => 'd66,AdrGeoPrd.User',
],

// DÉVELOPPEMENT - Bâtiments
'buildings_database' => [
    'host' => '13.23.33.4',  // Container maria3 sur IN3
    'name' => 'batiments',
    'username' => 'adr_geo_user',
    'password' => 'd66,AdrGeoDev.User',
],

// RECETTE - Bâtiments
'buildings_database' => [
    'host' => '13.23.33.4',  // Container maria3 sur IN3
    'name' => 'batiments',
    'username' => 'adr_geo_user',
    'password' => 'd66,AdrGeoRec.User',
],

// PRODUCTION - Bâtiments
'buildings_database' => [
    'host' => '13.23.33.4',  // Container maria4 sur IN4
    'name' => 'batiments',
    'username' => 'adr_geo_user',
    'password' => 'd66,AdrGeoPrd.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]
    ]
]
```

### BuildingService

Enrichit les adresses avec les données bâtiments :

```php
namespace App\Services;

class BuildingService {
    // Enrichit une liste d'adresses avec les métadonnées des bâtiments
    public function enrichAddresses(array $addresses): array
}
```

**Fonctionnement** :
- Connexion à la base `batiments` externe
- Interrogation des tables `bat{dept}` par département
- JOIN sur `bat{dept}.cle_interop_adr = cp{dept}.id`
- Ajout des métadonnées : `fk_batiment`, `fk_habitat`, `nb_niveau`, `nb_log`, `residence`, `alt_sol`
- Fallback : `fk_habitat=1` (maison individuelle) si pas de bâtiment trouvé

**Données retournées** :
```php
[
    'id' => 'cp22.123456',
    'numero' => '10',
    'voie' => 'Rue Victor Hugo',
    'code_postal' => '22000',
    'commune' => 'Saint-Brieuc',
    'latitude' => 48.5149,
    'longitude' => -2.7658,
    // Données bâtiment enrichies :
    'fk_batiment' => 'BAT_123456',      // null si maison
    'fk_habitat' => 2,                   // 1=individuel, 2=collectif
    'nb_niveau' => 4,                    // null si maison
    'nb_log' => 12,                      // null si maison
    'residence' => 'Résidence Les Pins', // '' si maison
    'alt_sol' => 25.5                    // null si maison
]
```

## 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::getAddressesInPolygon()`
8. **Enrichissement** avec données bâtiments via `AddressService::enrichAddressesWithBuildings()`
9. **Stockage** des adresses dans `sectors_adresses` avec colonnes bâtiment :
   - `fk_batiment`, `fk_habitat`, `nb_niveau`, `nb_log`, `residence`, `alt_sol`
10. **Création** des passages dans `ope_pass` :
   - **Maisons individuelles** (fk_habitat=1) : 1 passage par adresse
   - **Immeubles** (fk_habitat=2) : nb_log passages par adresse (1 par appartement)
   - Champs ajoutés : `residence`, `appt` (numéro 1 à nb_log), `fk_habitat`
   - 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
11. **Commit** de la transaction ou **rollback** en cas d'erreur

## Processus de modification de secteur

### 1. Structure du payload UPDATE

```json
{
    "libelle": "Secteur Centre-Ville Modifié",
    "color": "#00FF00",
    "sector": "48.117266/-1.6777926#48.118500/-1.6750000#...",
    "users": [12, 34],
    "chk_adresses_change": 1
}
```

### 2. Paramètre chk_adresses_change

**Valeurs** :
- `0` : Ne pas recalculer les adresses et passages (modification simple)
- `1` : Recalculer les adresses et passages (défaut)

**Cas d'usage** :

#### chk_adresses_change = 0
Modification rapide sans toucher aux adresses/passages :
- ✅ Modification du libellé
- ✅ Modification de la couleur
- ✅ Modification des coordonnées du polygone (visuel uniquement)
- ✅ Modification des membres affectés
- ❌ Pas de recalcul des adresses dans sectors_adresses
- ❌ Pas de mise à jour des passages (orphelins, créés, supprimés)
- ❌ **Réponse sans passages_sector** (tableau vide)

**Utilité** : Permet aux admins de corriger rapidement un libellé, une couleur, ou d'ajuster légèrement le périmètre visuel sans déclencher un recalcul complet qui pourrait prendre plusieurs secondes.

**Réponse API** :
```json
{
    "status": "success",
    "message": "Secteur modifié avec succès",
    "sector": { "id": 123, "libelle": "...", "color": "...", "sector": "..." },
    "passages_sector": [],  // Vide car chk_adresses_change = 0
    "passages_orphaned": 0,
    "passages_deleted": 0,
    "passages_updated": 0,
    "passages_created": 0,
    "passages_total": 0,
    "users_sectors": [...]
}
```

#### chk_adresses_change = 1 (défaut)
Modification complète avec recalcul :
- ✅ Modification du libellé/couleur/polygone
- ✅ Modification des membres
- ✅ Suppression et recréation de sectors_adresses
- ✅ Application des règles de gestion des bâtiments
- ✅ Mise en orphelin des passages hors périmètre
- ✅ Création de nouveaux passages pour nouvelles adresses

### 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_adresse` : ID de l'adresse dans la base externe
- `numero`, `rue`, `rue_bis`, `cp`, `ville`
- `gps_lat`, `gps_lng`
- **Colonnes bâtiment** :
  - `fk_batiment` : ID bâtiment (VARCHAR 50, null si maison)
  - `fk_habitat` : 1=individuel, 2=collectif (TINYINT UNSIGNED)
  - `nb_niveau` : Nombre d'étages (INT, null)
  - `nb_log` : Nombre de logements (INT, null)
  - `residence` : Nom résidence/copropriété (VARCHAR 75)
  - `alt_sol` : Altitude sol en mètres (DECIMAL 10,2, null)

### ope_pass (passages)
- `fk_operation`, `fk_sector`, `fk_user`, `fk_adresse`
- `numero`, `rue`, `rue_bis`, `ville`
- `gps_lat`, `gps_lng`
- **Colonnes bâtiment** :
  - `residence` : Nom résidence (VARCHAR 75)
  - `appt` : Numéro appartement (VARCHAR 10, saisie libre)
  - `niveau` : Étage (VARCHAR 10, saisie libre)
  - `fk_habitat` : 1=individuel, 2=collectif (TINYINT UNSIGNED)
- `fk_type` : Type passage (2=à faire, autres valeurs pour fait/refus)
- `encrypted_name`, `encrypted_email`, `encrypted_phone` : Données cryptées
- `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`

## Règles de gestion des bâtiments lors de l'UPDATE

### Principe général

Lors de la mise à jour d'un secteur, le système applique une logique intelligente pour gérer les passages en fonction du type d'habitat (maison/immeuble) et du nombre de logements.

### Clé d'identification unique

**Tous les passages** sont identifiés par la clé : `numero|rue|rue_bis|ville`

Cette clé ne contient **pas** `residence` ni `appt` car ces champs sont en **saisie libre** par l'utilisateur.

### Cas 1 : Maison individuelle (fk_habitat=1)

#### Si 0 passage existant :
```
→ INSERT 1 nouveau passage
  - fk_habitat = 1
  - residence = ''
  - appt = ''
```

#### Si 1+ passages existants :
```
→ UPDATE le premier passage
  - fk_habitat = 1
  - residence = ''
→ Les autres passages restent INTACTS
  (peuvent correspondre à plusieurs habitants saisis manuellement)
```

### Cas 2 : Immeuble (fk_habitat=2)

#### Étape 1 : UPDATE systématique
```
→ UPDATE TOUS les passages existants à cette adresse
  - fk_habitat = 2
  - residence = sectors_adresses.residence (si non vide)
```

#### Étape 2a : Si nb_existants < nb_log (ex: 3 passages, nb_log=6)
```
→ INSERT (nb_log - nb_existants) nouveaux passages
  - fk_habitat = 2
  - residence = sectors_adresses.residence
  - appt = '' (pas de numéro prédéfini)
  - fk_type = 2 (à faire)

Résultat : 6 passages total (3 conservés + 3 créés)
```

#### Étape 2b : Si nb_existants > nb_log (ex: 10 passages, nb_log=6)
```
→ DELETE max (nb_existants - nb_log) passages
  Conditions de suppression :
    - fk_type = 2 (à faire)
    - ET encrypted_name vide (non visité)
    - Tri par created_at ASC (les plus anciens d'abord)

Résultat : Entre 6 et 10 passages (selon combien sont visités)
```

### Points importants

✅ **Préservation des données utilisateur** :
- `appt` et `niveau` ne sont **JAMAIS modifiés** (saisie libre conservée)
- Les passages visités (encrypted_name rempli) ne sont **JAMAIS supprimés**

✅ **Mise à jour conditionnelle** :
- `residence` est mis à jour **uniquement si non vide** dans sectors_adresses
- Permet de conserver une saisie manuelle si la base bâtiments n'a pas l'info

✅ **Gestion des transitions** :
- Une adresse peut passer de maison (fk_habitat=1) à immeuble (fk_habitat=2) ou inversement
- La logique s'adapte automatiquement au nouveau type d'habitat

✅ **Uniformisation GPS** :
- **Tous les passages d'une même adresse partagent les mêmes coordonnées GPS** (gps_lat, gps_lng)
- Ces coordonnées proviennent de `sectors_adresses` (enrichies depuis la base externe `adresses`)
- Cette règle s'applique lors de la **création** et de la **mise à jour** avec `chk_adresses_change=1`
- Garantit la cohérence géographique pour tous les passages d'un même immeuble

### Exemple concret

**Situation initiale** :
- Adresse : "10 rue Victor Hugo, 22000 Saint-Brieuc"
- 8 passages existants (dont 3 visités)
- nb_log passe de 8 à 5

**Actions** :
1. UPDATE les 8 passages → fk_habitat=2, residence="Les Chênes"
2. Tentative suppression de (8-5) = 3 passages
3. Recherche des passages avec fk_type=2 ET encrypted_name vide
4. Suppose 5 passages non visités trouvés
5. Suppression des 3 plus anciens non visités
6. **Résultat** : 5 passages restants (3 visités + 2 non visités)

## 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`