Files
geo/api/TODO-API.md
pierre 2f5946a184 feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:26:27 +01:00

79 KiB
Raw Permalink Blame History

TODO-API.md

📋 Liste des tâches à implémenter

🔴 PRIORITÉ HAUTE

1. Système de backup pour les suppressions (DELETE)

Demandé le : 20/08/2025 Objectif : Sauvegarder toutes les données supprimées (soft delete) dans un fichier SQL pour pouvoir les restaurer en cas d'erreur humaine.

Détails techniques :

  • Créer un système de backup automatique lors de chaque DELETE
  • Stocker les données dans un fichier SQL avec structure permettant la réintégration facile
  • Format suggéré : /backups/deleted/{année}/{mois}/deleted_{table}_{YYYYMMDD}.sql

Tables concernées :

  • ope_pass (passages) - DELETE /passages/{id}
  • users (utilisateurs) - DELETE /users/{id}
  • operations (opérations) - DELETE /operations/{id}
  • ope_sectors (secteurs) - DELETE /sectors/{id}

Structure du backup suggérée :

-- Backup deletion: ope_pass
-- Date: 2025-08-20 14:30:45
-- User: 9999985 (cv_mobile)
-- Entity: 5
-- Original ID: 19500576

INSERT INTO ope_pass_backup (
    original_id,
    deleted_at,
    deleted_by_user_id,
    deleted_by_entity_id,
    -- tous les champs originaux
    fk_operation,
    fk_sector,
    fk_user,
    montant,
    encrypted_name,
    encrypted_email,
    -- etc...
) VALUES (
    19500576,
    '2025-08-20 14:30:45',
    9999985,
    5,
    -- valeurs originales
    ...
);

-- Pour restauration facile :
-- UPDATE ope_pass SET chk_active = 1 WHERE id = 19500576;

Fonctionnalités à implémenter :

  1. Service de backup : BackupService.php

    • Méthode backupDeletedRecord($table, $id, $data)
    • Génération automatique du SQL de restauration
    • Rotation des fichiers (garder 90 jours)
  2. Intégration dans les controllers

    • Ajouter l'appel au BackupService avant chaque soft delete
    • Logger l'emplacement du backup
  3. Interface de restauration (optionnel)

    • Endpoint GET /api/backups/deleted pour lister les backups
    • Endpoint POST /api/backups/restore/{backup_id} pour restaurer
  4. Commande de restauration manuelle

    • Script PHP : php scripts/restore_deleted.php --table=ope_pass --id=19500576

Avantages :

  • Traçabilité complète des suppressions
  • Restauration rapide en cas d'erreur
  • Audit trail pour conformité
  • Tranquillité d'esprit pour le client

🔴 PRIORITÉ HAUTE

2. Migration des bases de données vers container maria3 centralisé

Demandé le : 07/10/2025 Objectif : Migrer les bases de données locales des containers dva-geo et rca-geo vers un container MariaDB centralisé maria3 sur le même host IN3.

Architecture actuelle :

  • dva-geo : MariaDB local avec base geo_app (localhost)
  • rca-geo : MariaDB local avec base geo_app (localhost)
  • maria3 : Container MariaDB 11.4 existant (IP: 13.23.33.4) - utilisé uniquement pour la base adresses

Architecture cible :

  • maria3 : Container centralisé avec :
    • Base dva_geo pour l'environnement DEV
    • Base rca_geo pour l'environnement RECETTE
    • Base adresses (déjà existante)
  • dva-geo : Suppression du serveur MariaDB local
  • rca-geo : Suppression du serveur MariaDB local

Avantages :

  • Centralisation des bases de données
  • Facilite les sauvegardes
  • Réduction de la consommation mémoire des containers API
  • Séparation claire des responsabilités (API vs DB)
  • Préparation pour architecture production (IN4/maria4/pra_geo)

📋 TODOLIST DÉTAILLÉE - ENVIRONNEMENT DVA-GEO (DEV)

Phase 1 : Préparation et sauvegarde (AVANT migration)
  • 1.1 Vérifier l'état actuel de la base dans dva-geo

    incus exec dva-geo -- mysql -u root -pMyAlpineDb.90b -e "SHOW DATABASES;"
    incus exec dva-geo -- mysql -u root -pMyAlpineDb.90b geo_app -e "SHOW TABLES;"
    incus exec dva-geo -- mysql -u root -pMyAlpineDb.90b geo_app -e "SELECT COUNT(*) FROM users;"
    
  • 1.2 Créer une sauvegarde complète de la base actuelle

    # Dump avec skip-lock-tables (vue problématique v_stripe_amicale_dashboard)
    incus exec dva-geo -- mariadb-dump -u root -pMyAlpineDb.90b --skip-lock-tables geo_app > /var/back/dva_geo_backup_20251007.sql
    
  • 1.3 Vérifier l'intégrité de la sauvegarde

    ls -lh /var/back/dva_geo_backup_20251007.sql
    # Taille : 1.2GB (1217046899 bytes)
    
  • 1.4 Supprimer la vue problématique et refaire un dump propre

    # Suppression de la vue cassée
    incus exec dva-geo -- mysql -u root -pMyAlpineDb.90b geo_app -e "DROP VIEW IF EXISTS v_stripe_amicale_dashboard;"
    
    # Nouveau dump complet et propre
    incus exec dva-geo -- mariadb-dump -u root -pMyAlpineDb.90b geo_app > /var/back/dva_geo_backup_final_20251007.sql
    
Phase 2 : Configuration de maria3
  • 2.1 Se connecter à maria3 et créer la base dva_geo

    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' -e "CREATE DATABASE IF NOT EXISTS dva_geo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
    
  • 2.2 Créer l'utilisateur dédié dva_geo_user

    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' -e "
    CREATE USER IF NOT EXISTS 'dva_geo_user'@'%' IDENTIFIED BY 'CBq9tKHj6PGPZuTmAHV7';
    GRANT ALL PRIVILEGES ON dva_geo.* TO 'dva_geo_user'@'%';
    FLUSH PRIVILEGES;"
    
  • 2.3 Vérifier les permissions

    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' -e "SHOW GRANTS FOR 'dva_geo_user'@'%';"
    # Résultat : ALL PRIVILEGES sur dva_geo.*
    
  • 2.4 Tester la connexion avec le nouvel utilisateur depuis dva-geo

    incus exec dva-geo -- mysql -h 13.23.33.4 -u dva_geo_user -p'CBq9tKHj6PGPZuTmAHV7' -e "SHOW DATABASES;"
    # ✅ Connexion réussie
    
Phase 3 : Migration des données
  • 3.1 Copier le dump depuis le host vers maria3

    incus file push /var/back/dva_geo_backup_final_20251007.sql maria3/tmp/
    # ✅ Fichier transféré
    
  • 3.2 Importer le dump dans maria3

    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo < /tmp/dva_geo_backup_final_20251007.sql
    # ✅ Import réussi
    
  • 3.3 Vérifier l'importation

    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo -e "SHOW TABLES;"
    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo -e "SELECT COUNT(*) FROM users;"
    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo -e "SELECT COUNT(*) FROM operations;"
    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo -e "SELECT COUNT(*) FROM ope_pass;"
    # ✅ Toutes les tables présentes
    
  • 3.4 Comparer les comptages avec la base source

    # Base source (dva-geo localhost)
    incus exec dva-geo -- mysql -u root -pMyAlpineDb.90b geo_app -e "
    SELECT 'users' as table_name, COUNT(*) as count FROM users
    UNION ALL SELECT 'operations', COUNT(*) FROM operations
    UNION ALL SELECT 'ope_pass', COUNT(*) FROM ope_pass
    UNION ALL SELECT 'entites', COUNT(*) FROM entites;"
    
    # Base cible (maria3)
    incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo -e "
    SELECT 'users' as table_name, COUNT(*) as count FROM users
    UNION ALL SELECT 'operations', COUNT(*) FROM operations
    UNION ALL SELECT 'ope_pass', COUNT(*) FROM ope_pass
    UNION ALL SELECT 'entites', COUNT(*) FROM entites;"
    
    # ✅ Comptages identiques confirmés
    
Phase 4 : Configuration de l'API
  • 4.1 Mettre à jour AppConfig.php dans dva-geo

    // Fichier: src/Config/AppConfig.php (local)
    // Lignes 153-165
    
    // Configuration maria3 activée (migration effectuée le 07/10/2025)
    'database' => [
        'host' => '13.23.33.4',  // Container maria3 sur IN3
        'name' => 'dva_geo',
        'username' => 'dva_geo_user',
        'password' => 'CBq9tKHj6PGPZuTmAHV7',
    ],
    
  • 4.2 Déployer la nouvelle configuration

    # Depuis /home/pierre/dev/geosector/api
    ./deploy-api.sh
    # ✅ Déploiement réussi sur dva-geo
    
  • 4.3 Redémarrer PHP-FPM

    incus exec dva-geo -- rc-service php-fpm83 restart
    # ✅ PHP-FPM redémarré
    
Phase 5 : Tests de l'API
  • 5.1 Tester la connexion API à la base

    curl -X GET https://dapp.geosector.fr/api/health
    # ✅ API opérationnelle
    
  • 5.2 Tester l'authentification

    curl -X POST https://dapp.geosector.fr/api/login \
      -H "Content-Type: application/json" \
      -d '{"username":"pv_admin","password":"mot_de_passe"}'
    # ✅ Login réussi
    
  • 5.3 Tester la récupération de données

    curl -X GET https://dapp.geosector.fr/api/users \
      -H "Authorization: Bearer {session_id}"
    # ✅ Données récupérées depuis maria3
    
  • 5.4 Tester l'application Flutter

    # Test depuis l'application mobile Flutter
    # ✅ Connexion, récupération données, création passages : OK
    
  • 5.5 Vérifier les logs

    incus exec dva-geo -- tail -50 /var/www/geosector/api/logs/app.log
    incus exec dva-geo -- tail -50 /var/log/nginx/dva-api-error.log
    # ✅ Aucune erreur détectée
    
Phase 6 : Nettoyage et suppression de MariaDB local

⚠️ Migration validée et fonctionnelle - Nettoyage effectué le 07/10/2025

  • 6.1 Faire une dernière sauvegarde de sécurité

    incus exec dva-geo -- mariadb-dump -u root -pMyAlpineDb.90b --skip-lock-tables geo_app > /var/back/dva_geo_FINAL_backup_$(date +%Y%m%d_%H%M%S).sql
    # ✅ Sauvegarde finale créée
    
  • 6.2 Arrêter le serveur MariaDB local dans dva-geo

    incus exec dva-geo -- rc-service mariadb stop
    # ✅ MariaDB arrêté
    
  • 6.3 Désactiver le démarrage automatique de MariaDB

    incus exec dva-geo -- rc-update del mariadb default
    # ✅ MariaDB retiré du runlevel default
    
  • 6.4 Désinstaller MariaDB

    incus exec dva-geo -- apk del mariadb mariadb-client mariadb-common
    # ✅ MariaDB désinstallé du container
    
  • 6.5 Archiver les données MariaDB locales

    incus exec dva-geo -- tar -czf /var/back/mysql_data_archive_$(date +%Y%m%d).tar.gz /var/lib/mysql
    # ✅ Archive créée
    
  • 6.6 Supprimer les données MariaDB locales

    incus exec dva-geo -- rm -rf /var/lib/mysql /run/mysqld
    # ✅ Données supprimées, espace disque libéré
    
Phase 7 : Documentation
  • 7.1 Mettre à jour TODO-API.md avec toutes les commandes

    • Toutes les phases documentées avec commandes réelles
    • Statut mis à jour : TERMINÉ
  • 7.2 Documenter les problèmes rencontrés

    • Problème 1 : Vue v_stripe_amicale_dashboard cassée
      • Solution : Suppression de la vue avant dump final
      • Fichier : scripts/migrations/stripe_tables.sql (ligne 183)
    • Problème 2 : Erreur LOCK TABLES lors du dump initial
      • Solution : Ajout de --skip-lock-tables au mysqldump
  • 7.3 Mettre à jour TECHBOOK.md avec la nouvelle architecture

    • Section "Base de données" (lignes 136-154)
    • Mettre à jour le tableau pour refléter la configuration DVA-GEO → maria3

📋 PROCHAINES ÉTAPES (APRÈS DVA-GEO)

Une fois la migration DVA-GEO validée et stable :

Migration RCA-GEO (RECETTE) - TERMINÉE

Statut : MIGRATION COMPLÉTÉE AVEC SUCCÈS (16/10/2025)

Préparation effectuée le 07/10/2025 :

  • Base rca_geo créée dans maria3 (vide)
  • Utilisateur rca_geo_user créé avec ALL PRIVILEGES
  • Vue problématique v_stripe_amicale_dashboard supprimée de la base source (rca-geo localhost)

Migration réalisée le 16/10/2025 :

  • Dump de la base geo_app depuis rca-geo (localhost)
  • Import dans maria3 rca_geo
  • Validation des comptages (toutes les tables migrées correctement)
  • Modification AppConfig.php lignes 113-128
  • Déploiement manuel sur rca-geo
  • Tests complets sur https://rapp.geosector.fr/api/
  • Suppression complète de MariaDB local dans rca-geo
  • Suppression des dossiers /var/lib/mysql et /run/mysqld

Configuration finale :

  • User : rca_geo_user
  • Password : UPf3C0cQ805LypyM71iW
  • Host : 13.23.33.4 (maria3 sur IN3)
  • Base : rca_geo
  • Privileges : ALL PRIVILEGES sur rca_geo.*

Résultat :

  • Application fonctionnelle en RECETTE avec base centralisée sur maria3
  • Container rca-geo allégé (plus de serveur MariaDB local)
  • Architecture cohérente DVA-GEO et RCA-GEO → maria3

🔵 Création environnement PRODUCTION (PLANIFIÉ JEUDI 10/10/2025 16h)

Architecture cible :

  • Serveur IN4 (51.159.7.190)
  • Container pra-geo pour l'API (exporté depuis dva-geo)
  • Container maria4 pour la base de données
  • Base : pra_geo avec utilisateur dédié
  • Données dupliquées depuis rca_geo (IN3/maria3)

📋 TODOLIST DÉTAILLÉE - ENVIRONNEMENT PRODUCTION (PRA-GEO)

Phase 0 : Préparation du serveur IN4
  • 0.1 Vérifier l'accès SSH au serveur IN4

    ssh root@51.159.7.190
    # ✅ Serveur accessible
    
  • 0.2 Vérifier Incus sur IN4

    ssh root@51.159.7.190 "incus list"
    # ✅ Incus opérationnel
    
  • 0.3 Vérifier l'espace disque disponible

    ssh root@51.159.7.190 "df -h"
    # ✅ Espace suffisant
    
  • 0.4 Préparer le répertoire de transfert

    ssh root@51.159.7.190 "mkdir -p /var/back/imports"
    # ✅ Répertoire créé
    
Phase 1 : Export du container dva-geo depuis IN3
  • 1.1 Export dva-geo réalisé

    # ✅ Container dva-geo exporté depuis IN3
    # Note: Export effectué, détails exacts non documentés
    
  • 1.2 Snapshot et archive créés

    # ✅ Archive dva-geo créée et transférée vers IN4
    
Phase 2 : Transfert vers IN4
  • 2.1 Archive transférée vers IN4
    # ✅ Archive transférée sur IN4
    
Phase 3 : Import et configuration de pra-geo sur IN4
  • 3.1 Container pra-geo importé et lancé

    # ✅ Container pra-geo créé sur IN4
    # IP: 13.23.33.22 (réseau Incus)
    
  • 3.2 Configuration réseau vérifiée

    # ✅ Réseau configuré, ping et connectivité OK
    
  • 3.3 Container pra-geo opérationnel

    # ✅ Container démarré et accessible
    
  • 3.4 Client MariaDB installé

    # ✅ mariadb-client installé sur pra-geo
    
Phase 4 : Création du container maria4 sur IN4
  • 4.1 Container maria4 créé

    # ✅ Container maria4 créé sur IN4
    # IP: 13.23.33.4 (même IP que maria3 pour cohérence)
    
  • 4.2 MariaDB installé et initialisé

    # ✅ MariaDB 11.4 installé et démarré
    # ✅ Mot de passe root: MyAlpLocal,90b
    
  • 4.3 Connexions distantes autorisées

    # ✅ bind-address = 0.0.0.0
    # ✅ MariaDB redémarré
    
  • 4.4 Base pra_geo créée

    # ✅ Base: pra_geo (utf8mb4_unicode_ci)
    # ✅ User: pra_geo_user / d2jAAGGWi8fxFrWgXjOA
    # ✅ ALL PRIVILEGES accordés
    
  • 4.5 Base adresses présente

    # ✅ Base adresses avec tables par département (cp01, cp02, etc.)
    # ✅ User: adr_geo_user@13.23.33.2% / d66,AdrGeoPrd.User
    # ✅ SELECT privileges accordés
    
  • 4.6 Firewall UFW configuré

    # ✅ UFW: allow from 13.23.33.0/24 to any port 3306
    # ✅ Connexions depuis pra-geo (13.23.33.22) opérationnelles
    
  • 4.7 Tests de connexion réussis

    # ✅ pra-geo → maria4 (pra_geo): OK - 24 090 users
    # ✅ pra-geo → maria4 (adresses): OK - Tables accessibles
    
Phase 5 : Migration des données depuis dva_geo (IN3/maria3)
  • 5.1 Dump dva_geo réalisé

    # ✅ Dump de dva_geo (sans vue problématique v_stripe_amicale_dashboard)
    # Source: dva_geo (maria3 sur IN3)
    
  • 5.2 Données importées dans pra_geo

    # ✅ Import réussi dans maria4/pra_geo
    
  • 5.3 Vérification des données

    # ✅ 24 090 utilisateurs dans pra_geo
    # ✅ Tables operations, ope_pass, entites présentes
    

Note : La base pra_geo a été initialisée depuis dva_geo (DEV) et non depuis rca_geo (REC). Pour la mise en production finale, il faudra probablement migrer depuis rca_geo (données de recette validées).

Phase 6 : Configuration de l'API pour PRODUCTION
  • 6.1 AppConfig.php modifié localement

    // Configuration PRODUCTION (lignes 84-111)
    'database' => [
      'host' => '13.23.33.4',  // ✅ maria4 sur IN4
      'name' => 'pra_geo',
      'username' => 'pra_geo_user',
      'password' => 'd2jAAGGWi8fxFrWgXjOA',
    ],
    'addresses_database' => [
      'host' => '13.23.33.4',  // ✅ maria4 sur IN4
      'name' => 'adresses',
      'username' => 'adr_geo_user',
      'password' => 'd66,AdrGeoPrd.User',
    ],
    
  • 6.2 Déployer en PRODUCTION

    # ⏳ EN ATTENTE validation client
    ./deploy-api.sh pra
    
  • 6.3 Redémarrer PHP-FPM

    # ⏳ À faire après déploiement
    ssh root@51.159.7.190 "incus exec pra-geo -- rc-service php-fpm83 restart"
    
Phase 7 : Configuration DNS et reverse proxy
  • 7.1 Vérifier la configuration NGINX sur IN4

    ssh root@51.159.7.190 "cat /etc/nginx/sites-available/app.geosector.fr"
    
  • 7.2 Configurer le reverse proxy vers pra-geo

    # /etc/nginx/sites-available/app.geosector.fr
    upstream pra_geo_backend {
        server 13.23.34.43:80;
    }
    
    server {
        listen 443 ssl http2;
        server_name app.geosector.fr;
    
        ssl_certificate /etc/letsencrypt/live/app.geosector.fr/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/app.geosector.fr/privkey.pem;
    
        location / {
            proxy_pass http://pra_geo_backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
    
  • 7.3 Activer le site et recharger NGINX

    ssh root@51.159.7.190 "ln -s /etc/nginx/sites-available/app.geosector.fr /etc/nginx/sites-enabled/"
    ssh root@51.159.7.190 "nginx -t"
    ssh root@51.159.7.190 "systemctl reload nginx"
    
  • 7.4 Vérifier le certificat SSL

    ssh root@51.159.7.190 "certbot certificates | grep app3.geosector.fr"
    # Si pas de certificat, le créer :
    # certbot certonly --nginx -d app3.geosector.fr
    
Phase 8 : Tests de l'API PRODUCTION
  • 8.1 Tester le endpoint health

    curl -X GET https://app3.geosector.fr/api/health
    # ✅ Doit retourner {"status":"ok"}
    
  • 8.2 Tester l'authentification

    curl -X POST https://app3.geosector.fr/api/login \
      -H "Content-Type: application/json" \
      -d '{"username":"test_user","password":"test_pass"}'
    # ✅ Login doit fonctionner
    
  • 8.3 Tester la récupération de données

    curl -X GET https://app3.geosector.fr/api/users \
      -H "Authorization: Bearer {session_id}"
    # ✅ Données récupérées depuis maria4/pra_geo
    
  • 8.4 Tester depuis l'application Flutter

    # Configurer l'app Flutter avec l'URL de production
    # Tester : Login, récupération opérations, création passage
    # ✅ Toutes les fonctionnalités opérationnelles
    
  • 8.5 Vérifier les logs

    ssh root@51.159.7.190 "incus exec pra-geo -- tail -50 /var/www/geosector/api/logs/app.log"
    ssh root@51.159.7.190 "incus exec pra-geo -- tail -50 /var/log/nginx/pra-api-error.log"
    # ✅ Aucune erreur critique
    
Phase 9 : Sécurisation et monitoring
  • 9.1 Configurer les sauvegardes automatiques de maria4

    # Créer un script de backup quotidien
    ssh root@51.159.7.190 "cat > /root/backup-maria4.sh << 'EOF'
    #!/bin/bash
    DATE=$(date +%Y%m%d_%H%M%S)
    incus exec maria4 -- mysqldump -u root -p'MyAlpLocal,90b' --all-databases > /var/back/maria4_backup_${DATE}.sql
    # Garder les 30 derniers jours
    find /var/back/maria4_backup_*.sql -mtime +30 -delete
    EOF"
    ssh root@51.159.7.190 "chmod +x /root/backup-maria4.sh"
    
  • 9.2 Configurer la tâche CRON pour les backups

    ssh root@51.159.7.190 "crontab -l | { cat; echo '0 2 * * * /root/backup-maria4.sh'; } | crontab -"
    
  • 9.3 Configurer les logs de rotation

    # Vérifier que logrotate est configuré pour NGINX et PHP
    ssh root@51.159.7.190 "cat /etc/logrotate.d/nginx"
    
  • 9.4 Configurer le monitoring (optionnel)

    # Installer un monitoring basique (htop, iotop, etc.)
    ssh root@51.159.7.190 "apt install htop iotop iftop -y"
    
  • 9.5 Documenter les IPs et accès

    # Créer un fichier README sur IN4
    ssh root@51.159.7.190 "cat > /root/PRODUCTION_INFO.txt << 'EOF'
    ========================================
    ENVIRONNEMENT PRODUCTION GEOSECTOR
    ========================================
    
    Serveur : IN4 (51.159.7.190)
    Date de création : $(date +%Y-%m-%d)
    
    Containers :
    - pra-geo (API) : 13.23.34.43
    - maria4 (DB)   : 13.23.34.4
    
    Base de données :
    - Base : pra_geo
    - User : pra_geo_user
    - Password : d2jAAGGWi8fxFrWgXjOA
    
    URLs :
    - API : https://app3.geosector.fr/api/
    - Flutter : https://app3.geosector.fr/
    
    Backups :
    - Quotidien à 2h : /var/back/maria4_backup_*.sql
    - Rétention : 30 jours
    ========================================
    EOF"
    
Phase 🔟 : Documentation finale
  • 10.1 Mettre à jour TECHBOOK.md

    • Ajouter la configuration PRODUCTION dans le tableau (ligne 142)
    • Documenter l'architecture complète 3 environnements
  • 10.2 Mettre à jour ce TODO-API.md

    • Marquer toutes les étapes comme complétées
    • Statut : TERMINÉ
  • 10.3 Créer un document de procédures d'urgence

    • Rollback en cas de problème
    • Contacts et escalade
    • Procédure de restauration depuis backup

Date de création TODO : 07/10/2025 Date planifiée d'exécution : Jeudi 10/10/2025 à 16h Date de début : 07/10/2025 Statut : 🟡 EN COURS - Phases 0 à 5 complétées, en attente validation client Durée estimée : 3-4 heures


🔐 Informations sensibles (à ne pas commiter)

Container maria3 (IN3) :

  • IP interne : 13.23.33.4
  • Root password : MyAlpLocal,90b
  • Port : 3306

Utilisateurs bases de données :

  • DEV : dva_geo_user / CBq9tKHj6PGPZuTmAHV7
  • REC : rca_geo_user / UPf3C0cQ805LypyM71iW (à créer)
  • PROD : pra_geo_user / mot de passe à générer

Date de création : 07/10/2025 Statut : TERMINÉ - Migration DVA-GEO complétée avec succès Dernière mise à jour : 07/10/2025


🟢 Migration de données PM7 → PRA-GEO (PRODUCTION)

3. Script de migration depuis backup PM7 restauré

Demandé le : 07/10/2025 Objectif : Créer un script PHP standalone pour migrer les données depuis un backup PM7 (restauré dans maria4) vers la base pra_geo.

Contexte :

  • Backup quotidien de PM7 (11.1.2.17) : geosector_YYYYMMDD.sql.tar.gz.enc
  • Processus : Déchiffrement → SCP vers IN4 → Import dans maria4 → Migration vers pra_geo
  • Script exécuté depuis le container pra-geo (pas maria4, car PHP nécessaire)

Architecture de migration :

PM7 (11.1.2.17) - Backup nocturne chiffré
  ↓ Déchiffrement (decpm7.sh)
  ↓ SCP vers IN4:/var/back/
  ↓ incus file push vers maria4:/var/back/
  ↓ Décompression et import
maria4 (IN4) - Base geosector_YYYYMMDD
  ↓ Migration (script PHP depuis pra-geo)
maria4 (IN4) - Base pra_geo

Fichier créé : scripts/php/migrate_from_backup.php

Fonctionnalités implémentées :

1. Respect des contraintes FK

  • Ordre de migration : x_* → entites → users → operations → ope_sectors → ope_pass → medias
  • Gestion des dépendances : x_devises → x_pays → x_regions → x_departements → x_villes

2. Chiffrement AES-256-CBC

  • Utilisation de ApiService::encryptData() et ApiService::encryptSearchableData()
  • Tables concernées : entites (name, phone, email, iban, bic), users (name, email, phone), ope_pass (name, email, phone)

3. Mappings de champs

  • rowidid
  • activechk_active
  • date_creatcreated_at
  • date_modifupdated_at
  • libelleencrypted_name (entites)
  • nomencrypted_name (users, passages)

4. Détection mobile/fixe

  • Numéros commençant par 06/07 → champ encrypted_mobile
  • Autres numéros → champ encrypted_phone

5. Support deux modes

  • Global : Migration de toutes les amicales
  • Entity : Migration d'une amicale spécifique (avec tables de référence)

6. Optimisations

  • Traitement par lots de 1000 pour ope_pass (table volumineuse)
  • ON DUPLICATE KEY UPDATE pour idempotence
  • Logging détaillé avec progression

Utilisation :

# Sur IN4, dans le container pra-geo
cd /var/www/geosector/api

# Migration globale (toutes les amicales)
php scripts/php/migrate_from_backup.php \
  --source-db=geosector_20251007 \
  --target-db=pra_geo \
  --mode=global

# Migration d'une amicale spécifique
php scripts/php/migrate_from_backup.php \
  --source-db=geosector_20251007 \
  --target-db=pra_geo \
  --mode=entity \
  --entity-id=45 \
  --log=/var/www/geosector/api/logs/migration_entity_45.log

Tables migrées (dans l'ordre) :

  1. x_devises, x_entites_types, x_types_passages, x_types_reglements, x_users_roles, x_users_titres
  2. x_pays, x_regions, x_departements, x_villes
  3. entites (avec chiffrement)
  4. users (avec chiffrement et détection mobile)
  5. operations
  6. ope_sectors
  7. sectors_adresses
  8. ope_users
  9. ope_users_sectors
  10. ope_pass (avec chiffrement, traitement par lots)
  11. ope_pass_histo
  12. medias

Configuration connexion :

  • Host : 13.23.33.4 (maria4 sur IN4)
  • Base source : geosector_YYYYMMDD (user root)
  • Base cible : pra_geo (user pra_geo_user)

Documentation : scripts/README-migration.md

Statut : TERMINÉ - Script standalone créé et prêt à être testé Date de réalisation : 07/10/2025


🔧 Correction critique du mapping des secteurs

Date : 08/10/2025 Problème identifié : Bug majeur dans la migration des secteurs

Contexte du bug : Le script initial lisait depuis la table ope_sectors dans la base SOURCE, alors que cette table n'existe QUE dans la base CIBLE. Dans la base source (geosector), les secteurs sont stockés dans la table sectors.

Architecture des secteurs :

SOURCE (geosector_YYYYMMDD):
  - sectors (rowid, libelle, sector, color)
  - ope_users_sectors (fk_operation, fk_user, fk_sector → sectors.rowid)
  - ope_pass (fk_sector → sectors.rowid)
  - sectors_adresses (fk_sector → sectors.rowid)

CIBLE (pra_geo):
  - ope_sectors (id AUTO_INCREMENT, fk_operation, fk_old_sector, libelle, sector, color)
  - ope_users_sectors (fk_sector → ope_sectors.id NOUVEAU)
  - ope_pass (fk_sector → ope_sectors.id NOUVEAU)
  - sectors_adresses (fk_sector → ope_sectors.id NOUVEAU)

Corrections apportées :

1. Migration ope_sectors (lignes 650-772)

  • Lecture depuis sectors (source) via JOIN avec ope_users_sectors
  • Création dans ope_sectors (cible) avec ID auto-increment
  • Génération d'un mapping : fk_operation . '_' . old_sector_id → new_sector_id
  • Stockage dans $this->sectorMapping pour utilisation par les tables suivantes

2. Migration sectors_adresses (lignes 774-881)

  • Utilise le mapping pour remplacer fk_sector ancien par nouveau ID
  • Ignore les adresses dont le secteur n'a pas été migré (compteur $skipped)

3. Migration ope_users_sectors (lignes 952-1040)

  • Utilise le mapping pour remplacer fk_sector ancien par nouveau ID
  • Ignore les associations dont le secteur n'a pas été migré

4. Migration ope_pass (lignes 1042-1200)

  • Ajout du mapping au début de la boucle (lignes 1120-1131)
  • Utilise le mapping pour remplacer fk_sector ancien par nouveau ID
  • Ignore les passages dont le secteur n'a pas été migré

Exemple de mapping créé :

$this->sectorMapping = [
    '12345_789' => 1001,  // opération 12345, ancien secteur 789 → nouveau secteur 1001
    '12345_790' => 1002,
    '12346_789' => 1003,  // Même ancien secteur, mais autre opération = autre nouveau ID
];

Impact : CRITIQUE - Sans cette correction, la migration aurait échoué complètement

Fichiers modifiés :

  • scripts/php/migrate_from_backup.php (corrections lignes 650-1200)

Statut : CORRIGÉ - Prêt pour tests Date de correction : 08/10/2025


📋 Préparation migration en batch (406 entités éligibles)

Date : 08/10/2025 Objectif : Migrer progressivement les entités actives depuis geosector_20251008

Critères de sélection des entités :

  • Plus de 4 utilisateurs actifs
  • Au moins 1 opération créée après le 2023-08-01
  • Code postal renseigné (nettoyage de 3 entités de test)

Fichiers créés :

  1. scripts/migrations_entites.json (406 entités)

    • Liste complète des entités éligibles avec statistiques
    • Format : entity_id, code_postal, nom, ville, nb_users, nb_operations, nb_passages
  2. scripts/migrate_batch.sh (script bash orchestrateur)

    • Migration entité par entité avec progression
    • Options : --start N, --limit N, --dry-run, --continue
    • Logs détaillés par entité + log global
    • Gestion d'erreurs et possibilité de reprise
    • Résumé final avec durée, succès, erreurs

Utilisation du script batch :

# Test avec 1 entité
./migrate_batch.sh --start 1 --limit 1

# Migration de 50 entités
./migrate_batch.sh --start 1 --limit 50

# Migration complète (406 entités) avec continuité sur erreur
./migrate_batch.sh --continue

# Mode simulation (dry-run)
./migrate_batch.sh --dry-run

Statistiques des 406 entités :

  • Total utilisateurs : ~15 000 - 20 000 (estimation)
  • Total passages : ~500 000 - 1 000 000 (estimation)
  • Durée estimée migration complète : 8-12 heures

Statut : PRÉPARÉ - Scripts prêts, tests à effectuer Date de préparation : 08/10/2025


🔧 Correction critique des doublons ope_users et ope_users_sectors

Date : 10/10/2025 Problème identifié : Doublons massifs dans les tables ope_users et ope_users_sectors lors de la migration

Diagnostic :

  • Table ope_users : 186+ doublons pour la même paire (fk_operation, fk_user)
  • Table ope_users_sectors : Risque de doublons sur (fk_operation, fk_user, fk_sector)
  • Cause : Absence de contraintes UNIQUE sur ces combinaisons de FK
  • Impact : Le ON DUPLICATE KEY UPDATE ne fonctionnait pas, permettant la création de lignes en double

Corrections appliquées :

  1. Mise à jour de la structure de référence (geo_app_structure.sql) :

    • Ligne 403 : Ajout de UNIQUE KEY idx_operation_user (fk_operation, fk_user) sur ope_users
    • Ligne 430 : Ajout de UNIQUE KEY idx_operation_user_sector (fk_operation, fk_user, fk_sector) sur ope_users_sectors
  2. Correction du code PHP (migrate_from_backup.php) :

    // AVANT (lignes 1125-1140) - INCORRECT
    $sql = "SELECT ou.rowid, ou.fk_operation, ou.fk_user, ...
    INSERT INTO ope_users (id, fk_operation, fk_user, ...) VALUES (:id, ...
    
    // APRÈS (lignes 1125-1191) - CORRECT
    $sql = "SELECT DISTINCT ou.fk_operation, ou.fk_user, ...  // Suppression de rowid
    INSERT INTO ope_users (fk_operation, fk_user, ...) VALUES (...  // Suppression de id, utilise auto-increment
    
  3. Requêtes SQL pour appliquer les contraintes :

    -- Vérifier les doublons existants
    SELECT fk_operation, fk_user, COUNT(*) as count
    FROM ope_users GROUP BY fk_operation, fk_user HAVING count > 1;
    
    -- Ajouter les contraintes UNIQUE
    ALTER TABLE ope_users
    ADD UNIQUE KEY `idx_operation_user` (`fk_operation`, `fk_user`);
    
    ALTER TABLE ope_users_sectors
    ADD UNIQUE KEY `idx_operation_user_sector` (`fk_operation`, `fk_user`, `fk_sector`);
    
    -- Vérifier
    SHOW INDEX FROM ope_users WHERE Key_name = 'idx_operation_user';
    SHOW INDEX FROM ope_users_sectors WHERE Key_name = 'idx_operation_user_sector';
    

À appliquer sur tous les environnements :

  • DEV (dva_geo sur maria3/IN3) : mysql -h 13.23.33.4 -u dva_geo_user
  • REC (rca_geo sur maria3/IN3) : mysql -h 13.23.33.4 -u rca_geo_user
  • PROD (pra_geo sur maria4/IN4) : mysql -h 13.23.33.4 -u pra_geo_user

Procédure de re-migration après correction :

# Supprimer les données de l'entité avant re-migration
php scripts/php/migrate_from_backup.php \
  --source-db=geosector_20251008 \
  --mode=entity \
  --entity-id=5 \
  --delete-before

Fichiers modifiés :

  • scripts/php/geo_app_structure.sql (lignes 403, 430)
  • scripts/php/migrate_from_backup.php (lignes 1125-1191)
  • scripts/README-migration.md (section Corrections Critiques ajoutée)
  • scripts/CORRECTIONS_MIGRATE.md (correction #15 documentée)

Statut : CORRIGÉ - Code et structure mis à jour, contraintes à appliquer sur bases Date de correction : 10/10/2025


🎯 Planning de déploiement (09-10/10/2025)

Jeudi 09/10/2025 - Environnement DEV (dva-geo)

  • Vider la base dva_geo (données de test)
  • Déployer les scripts corrigés (deploy-api.sh)
  • Test migration 1 entité pilote (#1178 - 5 users, 1317 passages)
  • Validation des mappings secteurs
  • Si OK : Migration batch des 406 entités
  • Vérification intégrité données (comptages, FK)
  • Tests fonctionnels API + Flutter

Jeudi 09/10/2025 PM - Environnement REC (rca-geo)

  • Dump dva_geo → Import dans rca_geo
  • Tests de non-régression complets
  • Validation client sur REC
  • Tests de charge (si applicable)

Vendredi 10/10/2025 - Environnement PROD (pra-geo)

  • Sauvegarde complète pra_geo (avant migration)
  • Dump rca_geo → Import dans pra_geo
  • Tests de smoke en production
  • Monitoring des performances
  • Documentation post-migration

Rollback plan :

  • Restauration depuis sauvegarde pré-migration : 15-30 minutes
  • Scripts de vérification disponibles dans scripts/php/verify_migration_structure.php

Statut : 📅 PLANIFIÉ - Démarrage jeudi 09/10/2025 matin Durée estimée totale : 2 jours


🟡 PRIORITÉ MOYENNE

4. Amélioration des logs

  • Ajouter plus de contexte dans les logs
  • Rotation automatique des logs
  • Dashboard de monitoring

5. Optimisation des performances

  • Cache des requêtes fréquentes
  • Index sur les tables volumineuses
  • Pagination optimisée

6. Sécurisation des clés Stripe par environnement

Statut : PARTIELLEMENT RÉSOLU (16/10/2025)

Ce qui a été fait :

  • Configuration complète des clés API Stripe par environnement dans AppConfig.php
    • DEV : Clés TEST Pierre (compte plateforme développeur)
    • RECETTE : Clés TEST Client (compte plateforme client mode test)
    • PRODUCTION : Clés LIVE Client (compte plateforme client mode live)
  • Configuration des webhooks Stripe avec secrets dédiés
    • RECETTE : webhook-rcawhsec_avExshr0MeWTI7wXP8478XVUkrbYG8hs
    • PRODUCTION : webhook-prawhsec_gFnA6pR92RLdbAS2T6CSC18xsSdNBZHR
  • Mise à jour de la version API Stripe : 2025-08-27.basil
  • Correction du bug StripeWebhookController (ligne 46)
  • Correction de l'URL webhook : /api/stripe/webhooks (avec 's')
  • Documentation complète dans TECHBOOK.md

Ce qui reste (amélioration future) :

  • Migration vers variables d'environnement pour sécurité renforcée
  • Isolation des secrets par container (actuellement tous visibles dans AppConfig.php)

Solutions à étudier pour amélioration future :

  1. Variables d'environnement (.env par container)

    • Fichier .env.dev, .env.rec, .env.prod
    • Chargement dynamique selon l'environnement
    • Exclusion des .env du versionning Git
  2. Fichiers de config séparés

    • config/stripe.dev.php, config/stripe.rec.php, config/stripe.prod.php
    • Déploiement sélectif selon l'environnement
    • Non versionnés (ajoutés au .gitignore)
  3. Secrets management (avancé)

    • HashiCorp Vault, AWS Secrets Manager, etc.
    • API de récupération sécurisée des secrets

Recommandation : Approche #1 (variables d'environnement) pour équilibre sécurité/simplicité


🟢 PRIORITÉ BASSE

7. Amélioration de la suppression des utilisateurs

Demandé le : 19/10/2025 Objectif : Empêcher la suppression d'un utilisateur ayant des passages et gérer correctement le soft delete.

Problème actuel :

  • Erreur SQL lors de DELETE /api/users/{id} si l'utilisateur a des passages dans ope_pass
  • Contrainte FK ope_pass_ibfk_3 empêche la suppression (comportement attendu)
  • Génère des emails d'alerte "GEOSECTOR SECURITY" de type SQL_ERROR

Solution à implémenter (côté Flutter) :

  1. Vérification avant suppression

    • Endpoint suggéré : GET /api/users/{id}/can-delete
    • Retourne : total passages, passages opérations actives, passages opérations inactives
  2. Logique métier Flutter

    • Si passages sur opération active → Demander réassignation à un autre user
    • Si passages uniquement sur opérations inactives → Proposer soft delete
    • Si aucun passage → Autoriser suppression physique
  3. Soft delete (recommandé)

    • Ajouter deleted_at TIMESTAMP NULL dans table users
    • Modifier UserController::deleteUser() pour faire un soft delete
    • Les utilisateurs soft-deleted ne sont plus affichés mais conservés pour l'historique

Côté API (préparation) :

  • Ajouter endpoint GET /api/users/{id}/can-delete pour vérification
  • Ajouter colonne deleted_at dans table users
  • Modifier UserController::deleteUser() pour gérer le soft delete
  • Adapter les requêtes SQL pour filtrer WHERE deleted_at IS NULL

Côté Flutter (à implémenter par le développeur) :

  • Appeler endpoint de vérification avant suppression
  • Afficher dialogue selon le contexte (réassignation vs soft delete)
  • Gérer le workflow de réassignation si nécessaire

Note : En attendant l'implémentation, les emails SQL_ERROR pour DELETE /api/users/* sont normaux et attendus (violation de contrainte FK légitime).

Statut : 📝 NOTÉ - À implémenter côté Flutter en priorité Date : 19/10/2025


🔴 PRIORITÉ HAUTE

8. Connexion de l'API à un broker Mosquitto MQTT

Demandé le : 08/11/2025 Objectif : Intégrer un système de communication MQTT permettant à l'API de publier et recevoir des messages en temps réel pour notifier les clients (applications Flutter) des changements de données.

Contexte :

  • Les applications Flutter doivent être notifiées en temps réel lors de modifications de données (passages, secteurs, opérations)
  • Alternative moderne aux webhooks pour la communication bidirectionnelle
  • Permet de réduire le polling constant des applications mobiles
  • Architecture publish/subscribe pour une meilleure scalabilité

Cas d'usage principaux :

Création/Modification passage
  ↓ API publie sur MQTT: geosector/{entity_id}/passages/{operation_id}
  ↓ Payload JSON avec les données du passage
  ↓ Applications Flutter abonnées reçoivent la notification
  ↓ Mise à jour automatique de l'UI sans refresh manuel

Modification secteur
  ↓ API publie sur MQTT: geosector/{entity_id}/sectors/{operation_id}
  ↓ Applications concernées rechargent les données

Changement statut opération
  ↓ API publie sur MQTT: geosector/{entity_id}/operations/{operation_id}
  ↓ Notification push vers tous les membres

📋 Plan d'action détaillé

Étape 1 : Installation et configuration Mosquitto

A. Installation du broker Mosquitto (sur serveur DVA/RCA/PROD) :

# Sur le host ou dans un container dédié
apk add mosquitto mosquitto-clients  # Alpine
# ou
apt install mosquitto mosquitto-clients  # Debian/Ubuntu

# Démarrer et activer au boot
rc-service mosquitto start  # Alpine
rc-update add mosquitto default
# ou
systemctl start mosquitto  # Systemd
systemctl enable mosquitto

B. Configuration Mosquitto (/etc/mosquitto/mosquitto.conf) :

# Listeners
listener 1883 0.0.0.0
protocol mqtt

listener 8883 0.0.0.0
protocol mqtt
certfile /etc/mosquitto/certs/cert.pem
keyfile /etc/mosquitto/certs/key.pem
cafile /etc/mosquitto/certs/ca.pem

# WebSockets (pour Flutter Web)
listener 9001
protocol websockets

# Authentification
allow_anonymous false
password_file /etc/mosquitto/passwd

# ACL (Access Control List)
acl_file /etc/mosquitto/acl.conf

# Persistence
persistence true
persistence_location /var/lib/mosquitto/

# Logs
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
log_timestamp true

C. Créer les utilisateurs MQTT :

# Utilisateur pour l'API
mosquitto_passwd -c /etc/mosquitto/passwd geosector_api
# Password: généré de manière sécurisée

# Utilisateur pour les applications Flutter
mosquitto_passwd /etc/mosquitto/passwd geosector_client

D. Configuration ACL (/etc/mosquitto/acl.conf) :

# API peut publier et s'abonner à tous les topics geosector
user geosector_api
topic readwrite geosector/#

# Clients peuvent seulement s'abonner
user geosector_client
topic read geosector/#

E. Tests de connexion :

# Test publication
mosquitto_pub -h localhost -p 1883 -u geosector_api -P password \
  -t "geosector/test" -m "Test message"

# Test souscription
mosquitto_sub -h localhost -p 1883 -u geosector_client -P password \
  -t "geosector/#"

Fichiers impactés :

  • Installer Mosquitto sur DVA (13.23.33.43)
  • Installer Mosquitto sur RCA (13.23.33.23)
  • Installer Mosquitto sur PROD (13.23.33.22 / IN4)
  • Configurer firewall (port 1883, 8883, 9001)

Étape 2 : Installation bibliothèque PHP MQTT
# Via Composer
composer require php-mqtt/client

Fichiers impactés :

  • Modifier composer.json
  • Exécuter sur DVA, RCA, PROD

Étape 3 : Configuration AppConfig.php

Ajouter la configuration MQTT :

// Dans src/Config/AppConfig.php
'mqtt' => [
    'enabled' => true,
    'broker' => [
        'host' => 'localhost',  // ou IP du container mosquitto
        'port' => 1883,
        'username' => 'geosector_api',
        'password' => '',  // À définir par environnement
        'client_id' => 'geosector_api_' . gethostname(),
    ],
    'tls' => [
        'enabled' => false,  // true en production
        'port' => 8883,
        'ca_file' => '/etc/mosquitto/certs/ca.pem',
    ],
    'topics' => [
        'passages' => 'geosector/{entity_id}/passages/{operation_id}',
        'sectors' => 'geosector/{entity_id}/sectors/{operation_id}',
        'operations' => 'geosector/{entity_id}/operations/{operation_id}',
        'users' => 'geosector/{entity_id}/users',
        'system' => 'geosector/system',
    ],
    'qos' => 1,  // Quality of Service (0, 1, ou 2)
    'retain' => false,
],

Fichiers impactés :

  • Modifier src/Config/AppConfig.php (3 environnements)

Étape 4 : Créer le service MqttService

Créer src/Services/MqttService.php :

Méthodes principales :

namespace App\Services;

use PhpMqtt\Client\MqttClient;
use PhpMqtt\Client\ConnectionSettings;
use App\Services\LogService;
use Exception;

class MqttService {
    private static ?self $instance = null;
    private ?MqttClient $client = null;
    private array $config;
    private bool $connected = false;

    private function __construct() {
        $this->config = AppConfig::getInstance()->get('mqtt');
    }

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Connecte au broker MQTT
     */
    public function connect(): bool

    /**
     * Déconnecte du broker MQTT
     */
    public function disconnect(): void

    /**
     * Publie un message sur un topic
     * @param string $topic Topic MQTT
     * @param array $payload Données à publier (sera converti en JSON)
     * @param int $qos Quality of Service (0, 1, 2)
     * @param bool $retain Retenir le message
     */
    public function publish(string $topic, array $payload, int $qos = 1, bool $retain = false): bool

    /**
     * Publie une notification de passage créé/modifié
     */
    public function publishPassageUpdate(int $entityId, int $operationId, array $passageData): bool

    /**
     * Publie une notification de secteur créé/modifié
     */
    public function publishSectorUpdate(int $entityId, int $operationId, array $sectorData): bool

    /**
     * Publie une notification d'opération créée/modifiée
     */
    public function publishOperationUpdate(int $entityId, int $operationId, array $operationData): bool

    /**
     * Publie un message système (maintenance, déconnexion forcée, etc.)
     */
    public function publishSystemMessage(string $message, array $metadata = []): bool

    /**
     * Génère le topic en remplaçant les placeholders
     */
    private function buildTopic(string $topicTemplate, array $params): string

    /**
     * Vérifie si le service est activé et connecté
     */
    public function isAvailable(): bool
}

Gestion des erreurs :

  • Reconnexion automatique en cas de déconnexion
  • Timeout configurable
  • Logs détaillés des publications
  • Mode dégradé si MQTT indisponible (ne pas bloquer l'API)

Fichiers impactés :

  • Créer src/Services/MqttService.php

Étape 5 : Intégrer MQTT dans les Controllers

A. PassageController - Notifier création/modification passage :

// Dans PassageController::create() après ligne 602
if ($passageId) {
    // Publier notification MQTT (non bloquant)
    try {
        $mqttService = MqttService::getInstance();
        if ($mqttService->isAvailable()) {
            $mqttService->publishPassageUpdate(
                $entityId,
                $operationId,
                [
                    'passage_id' => $passageId,
                    'fk_type' => $fkType,
                    'montant' => $montant,
                    'action' => 'created',
                    'timestamp' => time()
                ]
            );
        }
    } catch (Exception $e) {
        // Logger mais ne pas bloquer l'API
        LogService::log('Erreur publication MQTT passage', [
            'level' => 'warning',
            'error' => $e->getMessage()
        ]);
    }
}

B. SectorController - Notifier création/modification secteur :

// Dans SectorController::create() après ligne 531
$mqttService = MqttService::getInstance();
if ($mqttService->isAvailable()) {
    $mqttService->publishSectorUpdate(
        $entityId,
        $operationId,
        [
            'sector_id' => $sectorId,
            'libelle' => $data['libelle'],
            'passages_created' => $passagesCreated,
            'action' => 'created',
            'timestamp' => time()
        ]
    );
}

C. OperationController - Notifier changements opération :

// Notifications pour :
// - Changement de statut (chk_active)
// - Ajout/retrait d'utilisateurs
// - Modification des dates

Fichiers impactés :

  • Modifier src/Controllers/PassageController.php
  • Modifier src/Controllers/SectorController.php
  • Modifier src/Controllers/OperationController.php
  • Modifier src/Controllers/UserController.php (optionnel)

Étape 6 : Structure des messages MQTT

Format JSON standardisé :

{
  "event": "passage.created",
  "entity_id": 5,
  "operation_id": 12345,
  "timestamp": 1699456789,
  "data": {
    "passage_id": 19500576,
    "fk_type": 1,
    "montant": 10.00,
    "encrypted_name": "...",
    "rue": "Rue Example",
    "ville": "Rennes"
  },
  "metadata": {
    "user_id": 9999985,
    "api_version": "3.3.5"
  }
}

Types d'événements :

  • passage.created
  • passage.updated
  • passage.deleted
  • sector.created
  • sector.updated
  • sector.deleted
  • operation.created
  • operation.updated
  • operation.status_changed
  • user.added
  • user.removed
  • system.maintenance

Étape 7 : Sécurisation

A. Topics hiérarchiques par entité :

geosector/{entity_id}/*  → Seuls les membres de cette entité peuvent s'abonner

B. Validation côté API :

  • Vérifier que l'utilisateur appartient à l'entité avant publication
  • Ne jamais publier de données sensibles non chiffrées

C. Authentification JWT pour Flutter :

{
  "mqtt_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "mqtt_host": "mqtt.geosector.fr",
  "mqtt_port": 8883,
  "topics": ["geosector/5/#"]
}

Fichiers impactés :

  • Créer endpoint POST /api/mqtt/token (génération token MQTT)
  • Modifier ACL Mosquitto pour utiliser JWT

Étape 8 : Tests et validation

Tests sur DVA :

  • Publier message test via API
  • S'abonner avec mosquitto_sub et vérifier réception
  • Créer passage et vérifier publication MQTT
  • Créer secteur et vérifier publication MQTT
  • Vérifier logs MQTT dans /var/log/mosquitto/
  • Tester reconnexion après redémarrage broker

Tests de charge :

  • 100 publications simultanées
  • 50 clients connectés en parallèle
  • Vérifier latence (doit être < 100ms)

Tests de non-régression :

  • API fonctionne si MQTT désactivé
  • API fonctionne si broker MQTT inaccessible (mode dégradé)

Fichiers impactés :

  • Créer scripts/test/test_mqtt_integration.php

Étape 9 : Intégration côté Flutter (information)

Package Flutter à utiliser :

dependencies:
  mqtt_client: ^10.0.0

Exemple de souscription :

final client = MqttServerClient('mqtt.geosector.fr', '');
await client.connect(username, password);

client.subscribe('geosector/$entityId/passages/#', MqttQos.atLeastOnce);

client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> messages) {
  final recMess = messages[0].payload as MqttPublishMessage;
  final payload = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
  final data = jsonDecode(payload);

  // Mettre à jour l'UI en temps réel
  if (data['event'] == 'passage.created') {
    _refreshPassages();
  }
});

Étape 10 : Documentation

Mettre à jour la documentation :

  • docs/TECHBOOK.md - Section "MQTT Real-Time Communication"
  • TODO-API.md - Marquer la tâche comme terminée
  • Créer docs/MQTT-INTEGRATION.md avec :
    • Architecture MQTT
    • Topics disponibles
    • Format des messages
    • Exemples de souscription (PHP, Flutter, JavaScript)
    • Configuration ACL et sécurité

📊 Résumé des impacts

Infrastructure :

  • Broker Mosquitto installé sur chaque environnement
  • Ports ouverts : 1883 (MQTT), 8883 (MQTTS), 9001 (WebSockets)

Services créés :

  • MqttService.php : Publication et gestion des messages

Controllers modifiés :

  • PassageController.php : Publication événements passages
  • SectorController.php : Publication événements secteurs
  • OperationController.php : Publication événements opérations

Configuration modifiée :

  • AppConfig.php : Section 'mqtt'
  • composer.json : Dépendance php-mqtt/client

Nouveaux endpoints :

  • POST /api/mqtt/token : Génération token JWT pour Flutter

⚠️ Points d'attention

  1. Performance : MQTT est asynchrone, ne doit JAMAIS bloquer l'API
  2. Mode dégradé : L'API doit fonctionner même si MQTT est HS
  3. Sécurité : ACL strictes, authentification obligatoire
  4. Scalabilité : Prévoir un broker MQTT dédié si > 1000 clients
  5. Monitoring : Logs Mosquitto à surveiller (connexions, déconnexions, erreurs)
  6. Certificats SSL : Obligatoires en production (Let's Encrypt)
  7. QoS : Utiliser QoS 1 (at least once) pour garantir la livraison

🔧 Architecture déployée

Environnement DVA (IN3) :

Container dva-geo (13.23.33.43)
  ├── API PHP (nginx + php-fpm)
  └── Mosquitto broker (1883, 8883, 9001)

Environnement RCA (IN3) :

Container rca-geo (13.23.33.23)
  ├── API PHP
  └── Mosquitto broker

Environnement PROD (IN4) :

Container pra-geo (13.23.33.22)
  ├── API PHP
  └── Mosquitto broker

Alternative (scalable) :

Container mqtt-broker (dédié)
  ├── Mosquitto avec authentification JWT
  └── Monitoring (Prometheus + Grafana)

Statut : 📋 PLANIFIÉ - En attente de validation pour démarrage Date de création : 08/11/2025 Durée estimée : 2-3 jours (installation + développement + tests) Dépendances : Aucune Priorité : HAUTE - Améliore significativement l'expérience utilisateur


9. Envoi de SMS de reçu en alternative à l'email

Demandé le : 08/11/2025 Objectif : Permettre l'envoi de SMS de reçu au contributeur lors de la création/modification de passages de type 1 ou 5, comme alternative à l'email.

Contexte :

  • La table entites possède déjà le champ chk_accept_sms (boolean)
  • Actuellement, les reçus sont envoyés par email pour les passages fk_type = 1 ou 5
  • Besoin d'envoyer un SMS de reçu si numéro mobile valide (10 chiffres commençant par 06 ou 07)
  • Comptabilisation par entité pour facturation ultérieure

Cas d'usage :

Création passage avec fk_type = 1 ou 5
  ↓ Si entite.chk_accept_sms = 1
  ↓ Si encrypted_phone est mobile (06/07...)
  ↓ Envoyer SMS reçu au lieu d'email
  ↓ Comptabiliser dans sms_sent_count

📋 Plan d'action détaillé

Étape 1 : Modifications base de données

A. Table entites - Ajouter compteur et gestion pack SMS :

ALTER TABLE entites
ADD COLUMN sms_sent_count INT UNSIGNED DEFAULT 0 COMMENT 'Nombre total de SMS envoyés pour facturation',
ADD COLUMN sms_credits INT UNSIGNED DEFAULT 0 COMMENT 'Crédits SMS disponibles (si pack dédié)',
ADD COLUMN sms_pack_type ENUM('global', 'entity') DEFAULT 'global' COMMENT 'Type de pack SMS (global partagé ou dédié)',
ADD KEY idx_chk_accept_sms (chk_accept_sms);

B. Nouvelle table sms_log - Historique des envois pour audit :

CREATE TABLE IF NOT EXISTS sms_log (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    fk_entite INT UNSIGNED NOT NULL,
    fk_passage INT UNSIGNED NULL COMMENT 'Passage concerné par le SMS',
    phone VARCHAR(20) NOT NULL COMMENT 'Numéro destinataire (format international)',
    message TEXT NOT NULL COMMENT 'Contenu du SMS envoyé',
    status ENUM('pending', 'sent', 'failed', 'error') DEFAULT 'pending',
    ovh_message_id VARCHAR(100) NULL COMMENT 'ID retourné par API OVH',
    credits_used DECIMAL(5,2) DEFAULT 1.00 COMMENT 'Crédits SMS consommés',
    error_message TEXT NULL,
    sent_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    KEY idx_fk_entite (fk_entite),
    KEY idx_fk_passage (fk_passage),
    KEY idx_status (status),
    KEY idx_sent_at (sent_at),

    CONSTRAINT sms_log_ibfk_1 FOREIGN KEY (fk_entite) REFERENCES entites(id) ON DELETE CASCADE,
    CONSTRAINT sms_log_ibfk_2 FOREIGN KEY (fk_passage) REFERENCES ope_pass(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

C. Table de configuration sms_config :

CREATE TABLE IF NOT EXISTS sms_config (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    config_key VARCHAR(50) UNIQUE NOT NULL,
    config_value TEXT NOT NULL,
    description VARCHAR(255) NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    KEY idx_config_key (config_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Configuration initiale
INSERT INTO sms_config (config_key, config_value, description) VALUES
('ovh_service_name', '', 'Nom du service SMS OVH'),
('ovh_app_key', '', 'Application Key OVH'),
('ovh_app_secret', '', 'Application Secret OVH (chiffré)'),
('ovh_consumer_key', '', 'Consumer Key OVH (chiffré)'),
('global_sms_credits', '0', 'Crédits SMS globaux disponibles'),
('default_sender', 'GEOSECTOR', 'Nom d\'expéditeur (11 chars max)'),
('sms_enabled', '1', 'Activation globale du système SMS'),
('sms_receipt_template', 'Merci pour votre don de {montant}EUR par {reglement}. Recu fiscal disponible sur demande. {entite_nom}', 'Template du SMS de reçu');

Fichiers impactés :

  • Créer scripts/migrations/add_sms_receipts.sql
  • Appliquer sur DVA, REC, PROD

Étape 2 : Configuration AppConfig.php

Ajouter la configuration OVH SMS :

// Dans src/Config/AppConfig.php
'sms' => [
    'enabled' => true,
    'provider' => 'ovh',
    'ovh' => [
        'endpoint' => 'ovh-eu',
        'application_key' => '',      // Chargé depuis sms_config
        'application_secret' => '',   // Chargé depuis sms_config
        'consumer_key' => '',         // Chargé depuis sms_config
        'service_name' => '',         // Chargé depuis sms_config
    ],
    'default_sender' => 'GEOSECTOR',
    'max_length' => 160,
],

Fichiers impactés :

  • Modifier src/Config/AppConfig.php (3 environnements)

Étape 3 : Installation SDK OVH
composer require ovh/ovh

Fichiers impactés :

  • Modifier composer.json
  • Exécuter sur DVA, REC, PROD

Étape 4 : Nouveau service SmsService

Créer src/Services/SmsService.php avec les méthodes principales :

  • sendReceiptSms() - Envoie un SMS de reçu après un passage payé
  • canEntitySendSms() - Vérifie si l'entité peut envoyer des SMS (crédits, autorisation)
  • isValidMobile() - Valide un numéro mobile français (06/07)
  • formatPhoneNumber() - Formate au format international (+33...)
  • generateReceiptMessage() - Génère le message depuis template
  • incrementSmsCounter() - Incrémente le compteur de facturation
  • decrementCredits() - Décremente les crédits (global ou entity)
  • logSms() - Enregistre dans sms_log

Template de message par défaut :

Merci pour votre don de {montant}EUR par {reglement}. Recu fiscal disponible sur demande. {entite_nom}

Fichiers impactés :

  • Créer src/Services/SmsService.php

Étape 5 : Modifier PassageController

Modifications dans PassageController::create() (lignes 606-639) :

Ajouter logique de décision SMS vs Email :

$fkType = isset($data['fk_type']) ? (int)$data['fk_type'] : 0;
if ($fkType === 1 || $fkType === 5) {
    // Récupérer l'entity_id de l'opération
    $stmtEntity = $this->db->prepare('SELECT fk_entite FROM operations WHERE id = ?');
    $stmtEntity->execute([$operationId]);
    $entityId = (int)$stmtEntity->fetchColumn();

    // Vérifier si l'entité accepte les SMS
    $stmtEntite = $this->db->prepare('SELECT chk_accept_sms FROM entites WHERE id = ?');
    $stmtEntite->execute([$entityId]);
    $acceptSms = (bool)$stmtEntite->fetchColumn();

    // Déterminer si on envoie SMS ou Email
    $shouldSendSms = false;
    $phone = null;

    if ($acceptSms && !empty($encryptedPhone)) {
        $phone = ApiService::decryptData($encryptedPhone);
        $smsService = new \App\Services\SmsService($this->db);
        if ($smsService->isValidMobile($phone)) {
            $shouldSendSms = true;
        }
    }

    if ($shouldSendSms && $phone) {
        // ENVOI SMS via register_shutdown_function
        // Récupérer type de règlement pour le message
        // Appeler sendReceiptSms()
    } else {
        // ENVOI EMAIL (logique existante)
    }
}

Même logique dans PassageController::update() si changement de type.

Fichiers impactés :

  • Modifier src/Controllers/PassageController.php

Étape 6 : Endpoints API pour gestion SMS (optionnel)

Créer SmsController.php avec :

  • GET /api/sms/stats/{entity_id} - Statistiques SMS d'une entité
  • GET /api/sms/history/{entity_id} - Historique des envois
  • GET /api/sms/credits - Solde crédits global et par entité
  • PUT /api/sms/config - Configuration SMS (admin)

Fichiers impactés :

  • Créer src/Controllers/SmsController.php (optionnel)
  • Modifier src/Core/Router.php (optionnel)

Étape 7 : Tests et validation

Tests sur DVA :

  • Créer entité test avec chk_accept_sms = 1
  • Créer passage fk_type = 1 avec mobile valide (06/07)
  • Vérifier envoi SMS via OVH
  • Vérifier enregistrement dans sms_log
  • Vérifier incrémentation de sms_sent_count
  • Vérifier débit des crédits

Tests de non-régression :

  • Entité avec chk_accept_sms = 0 → Email uniquement
  • Numéro fixe (01-05, 08, 09) → Email uniquement
  • Sans téléphone mais avec email → Email uniquement
  • Passage fk_type = 2, 3, 4 → Pas d'envoi

Fichiers impactés :

  • Créer scripts/test/test_sms_receipts.php

Étape 8 : Documentation

Mettre à jour la documentation :

  • docs/TECHBOOK.md - Section "Envoi de SMS de reçu"
  • TODO-API.md - Marquer la tâche comme terminée
  • Créer docs/SMS-RECEIPTS.md avec :
    • Configuration OVH SMS
    • Template de message personnalisable
    • Gestion des crédits (global vs entité)
    • Facturation basée sur sms_sent_count

📊 Résumé des impacts

Tables créées :

  • sms_log : Historique des envois
  • sms_config : Configuration OVH

Tables modifiées :

  • entites : +3 colonnes (sms_sent_count, sms_credits, sms_pack_type)

Services créés :

  • SmsService.php : Gestion envoi SMS via OVH

Controllers modifiés :

  • PassageController.php : Logique SMS vs Email selon contexte

Configuration modifiée :

  • AppConfig.php : Section 'sms'
  • composer.json : Dépendance ovh/ovh

⚠️ Points d'attention

  1. Validation numéro mobile : Uniquement 06/07 (10 chiffres)
  2. Longueur SMS : 160 caractères max (template à optimiser)
  3. Coût : ~0.035€/SMS en France
  4. Priorité : SMS si mobile valide, sinon Email en fallback
  5. Comptabilisation : sms_sent_count pour facturation
  6. Credentials OVH : À ne JAMAIS commiter, stockés en base chiffrés
  7. Template personnalisable : Dans sms_config.sms_receipt_template

🔧 Configuration OVH requise

Étapes préalables :

  1. Créer application OVH : https://eu.api.ovh.com/createApp/
  2. Générer Consumer Key avec droits /sms/*
  3. Commander service SMS OVH
  4. Créditer le compte (minimum 100-200 SMS pour tests)

Stockage en base :

UPDATE sms_config SET config_value = 'sms-ab123456-1' WHERE config_key = 'ovh_service_name';
UPDATE sms_config SET config_value = 'xxxxx' WHERE config_key = 'ovh_app_key';
UPDATE sms_config SET config_value = 'xxxxx' WHERE config_key = 'ovh_app_secret';
UPDATE sms_config SET config_value = 'xxxxx' WHERE config_key = 'ovh_consumer_key';
UPDATE sms_config SET config_value = '1000' WHERE config_key = 'global_sms_credits';

Statut : 📋 PLANIFIÉ - En attente de démarrage Date de création : 08/11/2025 Durée estimée : 2-3 jours (développement + tests + configuration OVH) Dépendances : Compte OVH avec service SMS actif


9. Intégration de la base "batiments" dans la gestion des secteurs

Demandé le : 07/11/2025 Objectif : Intégrer la base de données "batiments" pour identifier les immeubles et créer automatiquement un passage par logement lors de la création/modification des secteurs.

Contexte :

  • Nouvelle base : batiments (même host/credentials que base adresses)
  • Structure : Tables par département bat01, bat02, etc.
  • Lien : bat{dept}.cle_interop_adrcp{dept}.id
  • Contenu : Uniquement les immeubles avec métadonnées (nb_niveau, nb_log, residence, etc.)

Structure de la table bat{dept} :

batiment_groupe_id VARCHAR(50) PRIMARY KEY
code_departement_insee VARCHAR(5)
cle_interop_adr VARCHAR(50)          -- Lien vers cp{dept}.id
nb_niveau INT                         -- Nombre d'étages
nb_log INT                            -- Nombre de logements
nb_pdl_tot INT                        -- Compteurs électriques
annee_construction INT
residence VARCHAR(200)                -- Nom de la copropriété
usage_principal VARCHAR(100)
altitude_sol_mean DECIMAL(10,2)
gps_lat DECIMAL(10,7)
gps_lng DECIMAL(10,7)

Filtre appliqué lors de l'import :

  • usage_principal IN ('Résidentiel individuel', 'Résidentiel collectif', 'Secondaire', 'Tertiaire')
  • nb_log > 1 (immeubles uniquement, pas les maisons individuelles)

📋 Plan d'action détaillé

Étape 1 : Modifications base de données

A. Table sectors_adresses - Ajouter colonnes bâtiment :

ALTER TABLE sectors_adresses
ADD COLUMN fk_batiment VARCHAR(50) NULL COMMENT 'batiment_groupe_id' AFTER fk_adresse,
ADD COLUMN fk_habitat TINYINT NULL COMMENT '1=individuel, 2=collectif',
ADD COLUMN nb_niveau INT NULL COMMENT 'Nombre d\'étages',
ADD COLUMN nb_log INT NULL COMMENT 'Nombre de logements',
ADD COLUMN residence VARCHAR(200) NULL COMMENT 'Nom copropriété',
ADD COLUMN alt_sol DECIMAL(10,2) NULL COMMENT 'Altitude sol',
ADD KEY idx_fk_batiment (fk_batiment),
ADD KEY idx_fk_habitat (fk_habitat);

B. Table ope_pass - Vérifier/modifier colonne habitat :

-- Vérifier si fk_habitat existe déjà
SHOW COLUMNS FROM ope_pass LIKE 'fk_habitat';

-- Si nécessaire, modifier la colonne
ALTER TABLE ope_pass
MODIFY COLUMN fk_habitat TINYINT NULL COMMENT '1=individuel, 2=collectif';

-- Ajouter index si manquant
ALTER TABLE ope_pass ADD KEY idx_fk_habitat (fk_habitat);

Fichiers impactés :

  • Créer scripts/migrations/add_batiments_integration.sql
  • Appliquer sur DVA, REC, PROD

Étape 2 : Configuration AppConfig.php

Ajouter la configuration de la base batiments :

// Dans src/Config/AppConfig.php
'buildings_database' => [
    'host' => '13.23.33.46',      // DVA: 13.23.33.46
    'name' => 'batiments',        // RCA: 13.23.33.36
    'username' => 'adr_geo_user', // PROD: 13.23.33.26
    'password' => 'd66,AdrGeoDev.User',
],

Fichiers impactés :

  • Modifier src/Config/AppConfig.php (3 environnements DEV/REC/PROD)

Étape 3 : Nouveau service BuildingService

Créer src/Services/BuildingService.php :

Méthodes à implémenter :

class BuildingService {
    private \PDO $dbBuildings;

    public function __construct()
    {
        // Connexion PDO vers base "batiments"
        $config = AppConfig::getInstance()->get('buildings_database');
        $this->dbBuildings = new \PDO(...);
    }

    /**
     * Récupère les bâtiments dans un polygone
     * @param array $coordinates Format [[lat, lng], ...]
     * @param int|null $entityId Pour déterminer les départements
     * @return array Liste des bâtiments avec métadonnées
     */
    public function getBuildingsInPolygon(array $coordinates, ?int $entityId = null): array

    /**
     * Compte les bâtiments dans un polygone
     */
    public function countBuildingsInPolygon(array $coordinates, ?int $entityId = null): int

    /**
     * Vérifie si la connexion à la base batiments est active
     */
    public function isConnected(): bool
}

Logique similaire à AddressService :

  • Détection automatique des départements touchés par le polygone
  • Requête SQL spatiale sur toutes les tables bat{dept} concernées
  • Utilisation de ST_Contains() pour filtrer les bâtiments

Fichiers impactés :

  • Créer src/Services/BuildingService.php

Étape 4 : Enrichir AddressService

Modifier src/Services/AddressService.php :

Nouvelle méthode à ajouter :

/**
 * Enrichit les adresses avec les données bâtiment si disponibles
 * @param array $addresses Liste des adresses depuis getAddressesInPolygon()
 * @param int|null $entityId
 * @return array Adresses enrichies avec fk_batiment, fk_habitat, nb_log, etc.
 */
public function enrichAddressesWithBuildings(array $addresses, ?int $entityId = null): array
{
    // Pour chaque adresse, chercher si cp{dept}.id correspond à bat{dept}.cle_interop_adr
    // Enrichir avec : fk_batiment, fk_habitat, nb_niveau, nb_log, residence, alt_sol
    // Retourner les adresses enrichies
}

Logique :

  1. Grouper les adresses par département
  2. Pour chaque département, faire un JOIN entre cp{dept} et bat{dept}
  3. Enrichir les adresses avec les données bâtiment trouvées
  4. Définir fk_habitat = 2 (collectif) si bâtiment trouvé, sinon fk_habitat = 1 (individuel)

Fichiers impactés :

  • Modifier src/Services/AddressService.php

Étape 5 : Modifier SectorController::create()

Modifications dans la méthode create() (ligne 88-534) :

// APRÈS ligne 289 - Récupération des adresses
$addresses = $this->addressService->getAddressesInPolygon($coordinates, $entityId);

// NOUVEAU : Enrichir avec données batiments
$addresses = $this->addressService->enrichAddressesWithBuildings($addresses, $entityId);

// MODIFIER ligne 292-311 : Stockage dans sectors_adresses avec nouvelles colonnes
foreach ($addresses as $address) {
    $stmtAddress->execute([
        'sector_id' => $sectorId,
        'address_id' => $address['id'],
        'numero' => $address['numero'],
        'rue' => $address['voie'],
        'rue_bis' => '',
        'cp' => $address['code_postal'],
        'ville' => $address['commune'],
        'gps_lat' => $address['latitude'],
        'gps_lng' => $address['longitude'],
        // NOUVELLES COLONNES
        'fk_batiment' => $address['fk_batiment'] ?? null,
        'fk_habitat' => $address['fk_habitat'] ?? 1,
        'nb_niveau' => $address['nb_niveau'] ?? null,
        'nb_log' => $address['nb_log'] ?? null,
        'residence' => $address['residence'] ?? null,
        'alt_sol' => $address['alt_sol'] ?? null,
    ]);
}

// MODIFIER ligne 314-377 : Création des passages selon fk_habitat
foreach ($addresses as $address) {
    // Exclure les adresses déjà utilisées par passages orphelins
    if (in_array($address['id'], $addressesToExclude)) {
        continue;
    }

    if ($address['fk_habitat'] == 2 && !empty($address['nb_log']) && $address['nb_log'] > 1) {
        // IMMEUBLE : créer nb_log passages (1 par logement)
        for ($appt = 1; $appt <= $address['nb_log']; $appt++) {
            $passageStmt->execute([
                'operation_id' => $operationId,
                'sector_id' => $sectorId,
                'user_id' => $firstOpeUserId,
                'fk_adresse' => $address['id'],
                'numero' => $address['numero'],
                'rue' => $address['voie'],
                'rue_bis' => '',
                'ville' => $address['commune'],
                'gps_lat' => $address['latitude'],
                'gps_lng' => $address['longitude'],
                'fk_habitat' => 2,
                'appt' => $appt,  // Numéro d'appartement
                'residence' => $address['residence'] ?? null,
                'user_creat' => $userId
            ]);
            $passagesCreated++;
        }
    } else {
        // MAISON INDIVIDUELLE : 1 seul passage
        $passageStmt->execute([
            // ... même structure avec fk_habitat=1, appt=null
        ]);
        $passagesCreated++;
    }
}

Fichiers impactés :

  • Modifier src/Controllers/SectorController.php (méthode create())

Étape 6 : Modifier SectorController::update()

Modifications dans la méthode update() (ligne 539-938) :

  1. Ligne 702-773 : Mise à jour de sectors_adresses avec nouvelles colonnes
  2. Ligne 1243-1557 : Modifier updatePassagesForSector() pour gérer les immeubles

Dans updatePassagesForSector() :

// Ligne 1346-1350 : Récupérer adresses depuis sectors_adresses
$addressesQuery = "SELECT * FROM sectors_adresses WHERE fk_sector = :sector_id";
$addresses = $addressesStmt->fetchAll();

// Ligne 1456-1495 : INSERT MULTIPLE - Adapter pour créer nb_log passages si immeuble
foreach ($toInsert as $addr) {
    if ($addr['fk_habitat'] == 2 && $addr['nb_log'] > 1) {
        // Créer nb_log passages pour cet immeuble
        for ($appt = 1; $appt <= $addr['nb_log']; $appt++) {
            // Ajouter à la liste d'insertion
        }
    } else {
        // Créer 1 seul passage
    }
}

Fichiers impactés :

  • Modifier src/Controllers/SectorController.php (méthodes update() et updatePassagesForSector())

Étape 7 : Tests et validation

Tests sur DVA :

  • Créer un secteur test contenant des immeubles connus
  • Vérifier le nombre de passages créés (doit correspondre au total de logements)
  • Valider les données dans sectors_adresses (colonnes batiments remplies)
  • Valider les données dans ope_pass (fk_habitat, appt, residence)
  • Tester la modification d'un secteur (agrandissement/réduction)
  • Vérifier le comportement avec passages orphelins

Tests de non-régression :

  • Secteurs sans immeubles (maisons individuelles uniquement)
  • Secteurs multi-départements
  • Secteurs sans adresses (base adresses inaccessible)

Fichiers impactés :

  • Créer scripts/test/test_batiments_integration.php

Étape 8 : Documentation

Mettre à jour la documentation :

  • docs/GESTION-SECTORS.md - Section "Gestion des bâtiments"
  • docs/TECHBOOK.md - Section "Base de données"
  • TODO-API.md - Marquer la tâche comme terminée

Contenu à documenter :

  • Structure de la table bat{dept}
  • Logique de création de passages pour immeubles (1 passage par logement)
  • Format de réponse API enrichi avec données bâtiments
  • Exemples de requêtes SQL pour interroger les bâtiments

📊 Résumé des impacts

Tables modifiées :

  • sectors_adresses : +6 colonnes
  • ope_pass : Modification colonne fk_habitat (si nécessaire)

Services créés/modifiés :

  • Nouveau : BuildingService.php
  • Modifié : AddressService.php (enrichissement)

Controllers modifiés :

  • SectorController.php : Méthodes create(), update(), updatePassagesForSector()

Configuration modifiée :

  • AppConfig.php : Ajout buildings_database

Impact sur la création de secteur :

  • AVANT : 1 passage par adresse
  • APRÈS : 1 passage par logement (immeubles = nb_log passages)
  • Exemple : Secteur avec 100 adresses dont 20 immeubles de 10 logements
    • Avant : 100 passages
    • Après : 80 maisons + (20 × 10 immeubles) = 280 passages

⚠️ Points d'attention

  1. Performance : Les immeubles génèrent beaucoup plus de passages

    • Optimiser les INSERT multiple dans updatePassagesForSector()
    • Prévoir un timeout plus long pour les gros secteurs
  2. Cohérence des données : La base batiments doit être à jour

    • Vérifier la présence des départements concernés
    • Gérer le cas où la base batiments est inaccessible (fallback)
  3. Migration des données existantes : Les secteurs déjà créés

    • Option 1 : Ne pas toucher aux secteurs existants
    • Option 2 : Script de migration pour enrichir les secteurs existants
  4. Synchronisation : Base batiments vs adresses

    • Le lien cle_interop_adr doit être valide
    • Gérer les adresses sans bâtiment correspondant

Statut : 📋 PLANIFIÉ - En attente de validation pour démarrage Date de création : 07/11/2025 Durée estimée : 2-3 jours (développement + tests)


🟡 PRIORITÉ MOYENNE

9. Migration complète des Services vers namespace App\Services

Date : 07/11/2025 Objectif : Migrer tous les services vers le namespace App\Services pour améliorer l'organisation du code et l'autoloading.

Contexte : Lors de l'intégration de la base batiments, nous avons commencé à migrer certains services vers le namespace App\Services. Cette migration partielle crée des conflits d'autoloading et des incohérences dans le code.

État actuel :

Services AVEC namespace App\Services (10) :

  • AddressService.php
  • BuildingService.php
  • DepartmentBoundaryService.php
  • LogService.php
  • PasswordSecurityService.php
  • PDFGenerator.php
  • ReceiptPDFGenerator.php
  • ReceiptService.php
  • SimplePDF.php
  • StripeService.php

Services SANS namespace (8 à migrer) :

  • ApiService.php
  • BackupEncryptionService.php
  • EmailTemplates.php
  • EventLogService.php
  • ExportService.php
  • FileService.php
  • MigrationService.php
  • OperationDataService.php

Services Security (tous OK) :

  • Tous les 5 services dans App\Services\Security ont déjà le namespace

Plan de migration :

Étape 1 : Préparer l'autoloading PSR-4

// composer.json - remplacer classmap par psr-4
"autoload": {
    "psr-4": {
        "App\\": "src/"
    }
}

Puis exécuter sur DVA, RCA et PROD :

composer dump-autoload -o

Étape 2 : Migrer les 8 services restants

Pour chaque service :

  1. Ajouter le namespace au début du fichier
namespace App\Services;
  1. Ajouter les imports pour les classes globales
use PDO;
use Exception;
// etc.
  1. Vérifier les dépendances : si le service utilise d'autres services, s'assurer que les imports sont corrects

  2. Tester : s'assurer que le service fonctionne correctement

Étape 3 : Mettre à jour les fichiers qui utilisent ces services

Pour chaque fichier utilisant les services migrés :

  • Ajouter use App\Services\NomDuService; si le fichier a un namespace différent
  • Ou utiliser le nom court si dans le même namespace

Ordre de migration recommandé :

  1. EmailTemplates.php (utilisé par ApiService, ReceiptService)
  2. ApiService.php (utilisé par beaucoup de contrôleurs)
  3. FileService.php (utilisé par ExportService, ReceiptService)
  4. ExportService.php (autonome)
  5. OperationDataService.php (autonome)
  6. BackupEncryptionService.php (autonome)
  7. EventLogService.php (autonome)
  8. MigrationService.php (scripts uniquement)

Fichiers à vérifier après migration :

Controllers utilisant ces services :

  • src/Controllers/*.php - vérifier tous les imports
  • scripts/cron/*.php - vérifier les scripts cron
  • scripts/php/*.php - vérifier les scripts de migration

Tests à effectuer :

  1. Sur DVA :

    • Tester tous les endpoints principaux
    • Vérifier les logs (pas d'erreur Class not found)
    • Tester l'envoi d'emails (ApiService)
    • Tester les exports (ExportService)
    • Tester les reçus PDF (ReceiptService + EmailTemplates)
  2. Sur RCA :

    • Tests de non-régression complets
    • Validation par un utilisateur test
  3. Sur PROD :

    • Déploiement progressif
    • Monitoring des logs pendant 24h

Risques et mitigation :

⚠️ Risque : Erreur "Class not found" si l'autoloading ne fonctionne pas Mitigation : Tester d'abord sur DVA avec rollback rapide possible

⚠️ Risque : Services utilisés par scripts cron qui cassent Mitigation : Lister tous les scripts et les tester individuellement

⚠️ Risque : Cache d'autoload pas régénéré Mitigation : Forcer composer dump-autoload -o sur chaque environnement

Impact sur les environnements :

  • DVA : Test complet de la migration
  • RCA : Validation avant production
  • PROD : Déploiement final

Prérequis :

  • Accès SSH aux 3 environnements (DVA, RCA, PROD)
  • Composer installé sur chaque environnement
  • Backup de la version actuelle du code

Statut : 🔄 EN COURS - Migration partielle effectuée (10/18 services) Date de création : 07/11/2025 Durée estimée : 1 jour (migration + tests) Priorité : MOYENNE - Bloque le déploiement de l'intégration batiments


🟢 PRIORITÉ BASSE

10. Documentation API

  • Génération automatique OpenAPI/Swagger
  • Documentation interactive
  • Exemples de code pour chaque endpoint

11. Tests automatisés

  • Tests unitaires pour les services critiques
  • Tests d'intégration pour les endpoints
  • Tests de charge

📝 Notes

  • Les tâches marquées 🔴 doivent être traitées en priorité
  • Chaque tâche implémentée doit être documentée
  • Prévoir des tests pour chaque nouvelle fonctionnalité

Dernière mise à jour : 07/11/2025