- 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>
79 KiB
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 :
-
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)
- Méthode
-
Intégration dans les controllers
- Ajouter l'appel au BackupService avant chaque soft delete
- Logger l'emplacement du backup
-
Interface de restauration (optionnel)
- Endpoint GET /api/backups/deleted pour lister les backups
- Endpoint POST /api/backups/restore/{backup_id} pour restaurer
-
Commande de restauration manuelle
- Script PHP :
php scripts/restore_deleted.php --table=ope_pass --id=19500576
- Script PHP :
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_geopour l'environnement DEV - Base
rca_geopour l'environnement RECETTE - Base
adresses(déjà existante)
- Base
- 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_dashboardcassé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-tablesau mysqldump
- Solution : Ajout de
- Problème 1 : Vue
-
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_geocréée dans maria3 (vide) - ✅ Utilisateur
rca_geo_usercréé avec ALL PRIVILEGES - ✅ Vue problématique
v_stripe_amicale_dashboardsupprimée de la base source (rca-geo localhost)
Migration réalisée le 16/10/2025 :
- ✅ Dump de la base
geo_appdepuis 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/mysqlet/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-geopour l'API (exporté depuis dva-geo) - Container
maria4pour la base de données - Base :
pra_geoavec 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()etApiService::encryptSearchableData() - Tables concernées : entites (name, phone, email, iban, bic), users (name, email, phone), ope_pass (name, email, phone)
✅ 3. Mappings de champs
rowid→idactive→chk_activedate_creat→created_atdate_modif→updated_atlibelle→encrypted_name(entites)nom→encrypted_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 UPDATEpour 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) :
x_devises,x_entites_types,x_types_passages,x_types_reglements,x_users_roles,x_users_titresx_pays,x_regions,x_departements,x_villesentites(avec chiffrement)users(avec chiffrement et détection mobile)operationsope_sectorssectors_adressesope_usersope_users_sectorsope_pass(avec chiffrement, traitement par lots)ope_pass_histomedias
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 avecope_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->sectorMappingpour utilisation par les tables suivantes
✅ 2. Migration sectors_adresses (lignes 774-881)
- Utilise le mapping pour remplacer
fk_sectorancien 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_sectorancien 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_sectorancien 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 :
-
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
-
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 UPDATEne fonctionnait pas, permettant la création de lignes en double
Corrections appliquées :
-
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)surope_users - Ligne 430 : Ajout de
UNIQUE KEY idx_operation_user_sector (fk_operation, fk_user, fk_sector)surope_users_sectors
- Ligne 403 : Ajout de
-
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 -
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 dansrca_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 danspra_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-rca→whsec_avExshr0MeWTI7wXP8478XVUkrbYG8hs - PRODUCTION :
webhook-pra→whsec_gFnA6pR92RLdbAS2T6CSC18xsSdNBZHR
- RECETTE :
- ✅ 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 :
-
Variables d'environnement (
.envpar container)- Fichier
.env.dev,.env.rec,.env.prod - Chargement dynamique selon l'environnement
- Exclusion des
.envdu versionning Git
- Fichier
-
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)
-
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 dansope_pass - Contrainte FK
ope_pass_ibfk_3empêche la suppression (comportement attendu) - Génère des emails d'alerte "GEOSECTOR SECURITY" de type SQL_ERROR
Solution à implémenter (côté Flutter) :
-
Vérification avant suppression
- Endpoint suggéré :
GET /api/users/{id}/can-delete - Retourne : total passages, passages opérations actives, passages opérations inactives
- Endpoint suggéré :
-
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
-
Soft delete (recommandé)
- Ajouter
deleted_at TIMESTAMP NULLdans tableusers - Modifier
UserController::deleteUser()pour faire un soft delete - Les utilisateurs soft-deleted ne sont plus affichés mais conservés pour l'historique
- Ajouter
Côté API (préparation) :
- Ajouter endpoint
GET /api/users/{id}/can-deletepour vérification - Ajouter colonne
deleted_atdans tableusers - 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.createdpassage.updatedpassage.deletedsector.createdsector.updatedsector.deletedoperation.createdoperation.updatedoperation.status_changeduser.addeduser.removedsystem.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_subet 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.mdavec :- 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 passagesSectorController.php: Publication événements secteursOperationController.php: Publication événements opérations
Configuration modifiée :
AppConfig.php: Section 'mqtt'composer.json: Dépendancephp-mqtt/client
Nouveaux endpoints :
POST /api/mqtt/token: Génération token JWT pour Flutter
⚠️ Points d'attention
- Performance : MQTT est asynchrone, ne doit JAMAIS bloquer l'API
- Mode dégradé : L'API doit fonctionner même si MQTT est HS
- Sécurité : ACL strictes, authentification obligatoire
- Scalabilité : Prévoir un broker MQTT dédié si > 1000 clients
- Monitoring : Logs Mosquitto à surveiller (connexions, déconnexions, erreurs)
- Certificats SSL : Obligatoires en production (Let's Encrypt)
- 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
entitespossède déjà le champchk_accept_sms(boolean) - Actuellement, les reçus sont envoyés par email pour les passages
fk_type = 1ou5 - 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 templateincrementSmsCounter()- Incrémente le compteur de facturationdecrementCredits()- 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 envoisGET /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 = 1avec 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.mdavec :- 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 envoissms_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épendanceovh/ovh
⚠️ Points d'attention
- Validation numéro mobile : Uniquement 06/07 (10 chiffres)
- Longueur SMS : 160 caractères max (template à optimiser)
- Coût : ~0.035€/SMS en France
- Priorité : SMS si mobile valide, sinon Email en fallback
- Comptabilisation :
sms_sent_countpour facturation - Credentials OVH : À ne JAMAIS commiter, stockés en base chiffrés
- Template personnalisable : Dans
sms_config.sms_receipt_template
🔧 Configuration OVH requise
Étapes préalables :
- Créer application OVH : https://eu.api.ovh.com/createApp/
- Générer Consumer Key avec droits
/sms/* - Commander service SMS OVH
- 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 baseadresses) - Structure : Tables par département
bat01,bat02, etc. - Lien :
bat{dept}.cle_interop_adr→cp{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 :
- Grouper les adresses par département
- Pour chaque département, faire un JOIN entre
cp{dept}etbat{dept} - Enrichir les adresses avec les données bâtiment trouvées
- Définir
fk_habitat = 2(collectif) si bâtiment trouvé, sinonfk_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éthodecreate())
Étape 6 : Modifier SectorController::update()
Modifications dans la méthode update() (ligne 539-938) :
- Ligne 702-773 : Mise à jour de
sectors_adressesavec nouvelles colonnes - 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éthodesupdate()etupdatePassagesForSector())
É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 colonnesope_pass: Modification colonnefk_habitat(si nécessaire)
Services créés/modifiés :
- Nouveau :
BuildingService.php - Modifié :
AddressService.php(enrichissement)
Controllers modifiés :
SectorController.php: Méthodescreate(),update(),updatePassagesForSector()
Configuration modifiée :
AppConfig.php: Ajoutbuildings_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
-
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
- Optimiser les INSERT multiple dans
-
Cohérence des données : La base
batimentsdoit être à jour- Vérifier la présence des départements concernés
- Gérer le cas où la base batiments est inaccessible (fallback)
-
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
-
Synchronisation : Base
batimentsvsadresses- Le lien
cle_interop_adrdoit être valide - Gérer les adresses sans bâtiment correspondant
- Le lien
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\Securityont 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 :
- Ajouter le namespace au début du fichier
namespace App\Services;
- Ajouter les imports pour les classes globales
use PDO;
use Exception;
// etc.
-
Vérifier les dépendances : si le service utilise d'autres services, s'assurer que les imports sont corrects
-
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é :
- EmailTemplates.php (utilisé par ApiService, ReceiptService)
- ApiService.php (utilisé par beaucoup de contrôleurs)
- FileService.php (utilisé par ExportService, ReceiptService)
- ExportService.php (autonome)
- OperationDataService.php (autonome)
- BackupEncryptionService.php (autonome)
- EventLogService.php (autonome)
- MigrationService.php (scripts uniquement)
Fichiers à vérifier après migration :
Controllers utilisant ces services :
src/Controllers/*.php- vérifier tous les importsscripts/cron/*.php- vérifier les scripts cronscripts/php/*.php- vérifier les scripts de migration
Tests à effectuer :
-
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)
-
Sur RCA :
- Tests de non-régression complets
- Validation par un utilisateur test
-
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