- 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>
2702 lines
79 KiB
Markdown
2702 lines
79 KiB
Markdown
# 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 :**
|
||
|
||
```sql
|
||
-- 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)
|
||
|
||
- [x] **1.1** Vérifier l'état actuel de la base dans dva-geo
|
||
|
||
```bash
|
||
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;"
|
||
```
|
||
|
||
- [x] **1.2** Créer une sauvegarde complète de la base actuelle
|
||
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
- [x] **1.3** Vérifier l'intégrité de la sauvegarde
|
||
|
||
```bash
|
||
ls -lh /var/back/dva_geo_backup_20251007.sql
|
||
# Taille : 1.2GB (1217046899 bytes)
|
||
```
|
||
|
||
- [x] **1.4** Supprimer la vue problématique et refaire un dump propre
|
||
|
||
```bash
|
||
# 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
|
||
|
||
- [x] **2.1** Se connecter à maria3 et créer la base dva_geo
|
||
|
||
```bash
|
||
incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' -e "CREATE DATABASE IF NOT EXISTS dva_geo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||
```
|
||
|
||
- [x] **2.2** Créer l'utilisateur dédié dva_geo_user
|
||
|
||
```bash
|
||
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;"
|
||
```
|
||
|
||
- [x] **2.3** Vérifier les permissions
|
||
|
||
```bash
|
||
incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' -e "SHOW GRANTS FOR 'dva_geo_user'@'%';"
|
||
# Résultat : ALL PRIVILEGES sur dva_geo.*
|
||
```
|
||
|
||
- [x] **2.4** Tester la connexion avec le nouvel utilisateur depuis dva-geo
|
||
```bash
|
||
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
|
||
|
||
- [x] **3.1** Copier le dump depuis le host vers maria3
|
||
|
||
```bash
|
||
incus file push /var/back/dva_geo_backup_final_20251007.sql maria3/tmp/
|
||
# ✅ Fichier transféré
|
||
```
|
||
|
||
- [x] **3.2** Importer le dump dans maria3
|
||
|
||
```bash
|
||
incus exec maria3 -- mysql -u root -p'MyAlpLocal,90b' dva_geo < /tmp/dva_geo_backup_final_20251007.sql
|
||
# ✅ Import réussi
|
||
```
|
||
|
||
- [x] **3.3** Vérifier l'importation
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
- [x] **3.4** Comparer les comptages avec la base source
|
||
|
||
```bash
|
||
# 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
|
||
|
||
- [x] **4.1** Mettre à jour AppConfig.php dans dva-geo
|
||
|
||
```php
|
||
// 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',
|
||
],
|
||
```
|
||
|
||
- [x] **4.2** Déployer la nouvelle configuration
|
||
|
||
```bash
|
||
# Depuis /home/pierre/dev/geosector/api
|
||
./deploy-api.sh
|
||
# ✅ Déploiement réussi sur dva-geo
|
||
```
|
||
|
||
- [x] **4.3** Redémarrer PHP-FPM
|
||
```bash
|
||
incus exec dva-geo -- rc-service php-fpm83 restart
|
||
# ✅ PHP-FPM redémarré
|
||
```
|
||
|
||
##### Phase 5️⃣ : Tests de l'API
|
||
|
||
- [x] **5.1** Tester la connexion API à la base
|
||
|
||
```bash
|
||
curl -X GET https://dapp.geosector.fr/api/health
|
||
# ✅ API opérationnelle
|
||
```
|
||
|
||
- [x] **5.2** Tester l'authentification
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
- [x] **5.3** Tester la récupération de données
|
||
|
||
```bash
|
||
curl -X GET https://dapp.geosector.fr/api/users \
|
||
-H "Authorization: Bearer {session_id}"
|
||
# ✅ Données récupérées depuis maria3
|
||
```
|
||
|
||
- [x] **5.4** Tester l'application Flutter
|
||
|
||
```bash
|
||
# Test depuis l'application mobile Flutter
|
||
# ✅ Connexion, récupération données, création passages : OK
|
||
```
|
||
|
||
- [x] **5.5** Vérifier les logs
|
||
```bash
|
||
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
|
||
|
||
- [x] **6.1** Faire une dernière sauvegarde de sécurité
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
- [x] **6.2** Arrêter le serveur MariaDB local dans dva-geo
|
||
|
||
```bash
|
||
incus exec dva-geo -- rc-service mariadb stop
|
||
# ✅ MariaDB arrêté
|
||
```
|
||
|
||
- [x] **6.3** Désactiver le démarrage automatique de MariaDB
|
||
|
||
```bash
|
||
incus exec dva-geo -- rc-update del mariadb default
|
||
# ✅ MariaDB retiré du runlevel default
|
||
```
|
||
|
||
- [x] **6.4** Désinstaller MariaDB
|
||
|
||
```bash
|
||
incus exec dva-geo -- apk del mariadb mariadb-client mariadb-common
|
||
# ✅ MariaDB désinstallé du container
|
||
```
|
||
|
||
- [x] **6.5** Archiver les données MariaDB locales
|
||
|
||
```bash
|
||
incus exec dva-geo -- tar -czf /var/back/mysql_data_archive_$(date +%Y%m%d).tar.gz /var/lib/mysql
|
||
# ✅ Archive créée
|
||
```
|
||
|
||
- [x] **6.6** Supprimer les données MariaDB locales
|
||
```bash
|
||
incus exec dva-geo -- rm -rf /var/lib/mysql /run/mysqld
|
||
# ✅ Données supprimées, espace disque libéré
|
||
```
|
||
|
||
##### Phase 7️⃣ : Documentation
|
||
|
||
- [x] **7.1** Mettre à jour TODO-API.md avec toutes les commandes
|
||
|
||
- Toutes les phases documentées avec commandes réelles
|
||
- Statut mis à jour : ✅ TERMINÉ
|
||
|
||
- [x] **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
|
||
|
||
- [x] **0.1** Vérifier l'accès SSH au serveur IN4
|
||
|
||
```bash
|
||
ssh root@51.159.7.190
|
||
# ✅ Serveur accessible
|
||
```
|
||
|
||
- [x] **0.2** Vérifier Incus sur IN4
|
||
|
||
```bash
|
||
ssh root@51.159.7.190 "incus list"
|
||
# ✅ Incus opérationnel
|
||
```
|
||
|
||
- [x] **0.3** Vérifier l'espace disque disponible
|
||
|
||
```bash
|
||
ssh root@51.159.7.190 "df -h"
|
||
# ✅ Espace suffisant
|
||
```
|
||
|
||
- [x] **0.4** Préparer le répertoire de transfert
|
||
```bash
|
||
ssh root@51.159.7.190 "mkdir -p /var/back/imports"
|
||
# ✅ Répertoire créé
|
||
```
|
||
|
||
##### Phase 1️⃣ : Export du container dva-geo depuis IN3
|
||
|
||
- [x] **1.1** Export dva-geo réalisé
|
||
|
||
```bash
|
||
# ✅ Container dva-geo exporté depuis IN3
|
||
# Note: Export effectué, détails exacts non documentés
|
||
```
|
||
|
||
- [x] **1.2** Snapshot et archive créés
|
||
```bash
|
||
# ✅ Archive dva-geo créée et transférée vers IN4
|
||
```
|
||
|
||
##### Phase 2️⃣ : Transfert vers IN4
|
||
|
||
- [x] **2.1** Archive transférée vers IN4
|
||
```bash
|
||
# ✅ Archive transférée sur IN4
|
||
```
|
||
|
||
##### Phase 3️⃣ : Import et configuration de pra-geo sur IN4
|
||
|
||
- [x] **3.1** Container pra-geo importé et lancé
|
||
|
||
```bash
|
||
# ✅ Container pra-geo créé sur IN4
|
||
# IP: 13.23.33.22 (réseau Incus)
|
||
```
|
||
|
||
- [x] **3.2** Configuration réseau vérifiée
|
||
|
||
```bash
|
||
# ✅ Réseau configuré, ping et connectivité OK
|
||
```
|
||
|
||
- [x] **3.3** Container pra-geo opérationnel
|
||
|
||
```bash
|
||
# ✅ Container démarré et accessible
|
||
```
|
||
|
||
- [x] **3.4** Client MariaDB installé
|
||
```bash
|
||
# ✅ mariadb-client installé sur pra-geo
|
||
```
|
||
|
||
##### Phase 4️⃣ : Création du container maria4 sur IN4
|
||
|
||
- [x] **4.1** Container maria4 créé
|
||
|
||
```bash
|
||
# ✅ Container maria4 créé sur IN4
|
||
# IP: 13.23.33.4 (même IP que maria3 pour cohérence)
|
||
```
|
||
|
||
- [x] **4.2** MariaDB installé et initialisé
|
||
|
||
```bash
|
||
# ✅ MariaDB 11.4 installé et démarré
|
||
# ✅ Mot de passe root: MyAlpLocal,90b
|
||
```
|
||
|
||
- [x] **4.3** Connexions distantes autorisées
|
||
|
||
```bash
|
||
# ✅ bind-address = 0.0.0.0
|
||
# ✅ MariaDB redémarré
|
||
```
|
||
|
||
- [x] **4.4** Base pra_geo créée
|
||
|
||
```bash
|
||
# ✅ Base: pra_geo (utf8mb4_unicode_ci)
|
||
# ✅ User: pra_geo_user / d2jAAGGWi8fxFrWgXjOA
|
||
# ✅ ALL PRIVILEGES accordés
|
||
```
|
||
|
||
- [x] **4.5** Base adresses présente
|
||
|
||
```bash
|
||
# ✅ 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
|
||
```
|
||
|
||
- [x] **4.6** Firewall UFW configuré
|
||
|
||
```bash
|
||
# ✅ UFW: allow from 13.23.33.0/24 to any port 3306
|
||
# ✅ Connexions depuis pra-geo (13.23.33.22) opérationnelles
|
||
```
|
||
|
||
- [x] **4.7** Tests de connexion réussis
|
||
```bash
|
||
# ✅ 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)
|
||
|
||
- [x] **5.1** Dump dva_geo réalisé
|
||
|
||
```bash
|
||
# ✅ Dump de dva_geo (sans vue problématique v_stripe_amicale_dashboard)
|
||
# Source: dva_geo (maria3 sur IN3)
|
||
```
|
||
|
||
- [x] **5.2** Données importées dans pra_geo
|
||
|
||
```bash
|
||
# ✅ Import réussi dans maria4/pra_geo
|
||
```
|
||
|
||
- [x] **5.3** Vérification des données
|
||
```bash
|
||
# ✅ 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
|
||
|
||
- [x] **6.1** AppConfig.php modifié localement
|
||
|
||
```php
|
||
// 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
|
||
|
||
```bash
|
||
# ⏳ EN ATTENTE validation client
|
||
./deploy-api.sh pra
|
||
```
|
||
|
||
- [ ] **6.3** Redémarrer PHP-FPM
|
||
```bash
|
||
# ⏳ À 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
|
||
|
||
```bash
|
||
ssh root@51.159.7.190 "cat /etc/nginx/sites-available/app.geosector.fr"
|
||
```
|
||
|
||
- [ ] **7.2** Configurer le reverse proxy vers pra-geo
|
||
|
||
```nginx
|
||
# /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
|
||
|
||
```bash
|
||
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
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
curl -X GET https://app3.geosector.fr/api/health
|
||
# ✅ Doit retourner {"status":"ok"}
|
||
```
|
||
|
||
- [ ] **8.2** Tester l'authentification
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
# 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
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
ssh root@51.159.7.190 "crontab -l | { cat; echo '0 2 * * * /root/backup-maria4.sh'; } | crontab -"
|
||
```
|
||
|
||
- [ ] **9.3** Configurer les logs de rotation
|
||
|
||
```bash
|
||
# 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)
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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**
|
||
|
||
- `rowid` → `id`
|
||
- `active` → `chk_active`
|
||
- `date_creat` → `created_at`
|
||
- `date_modif` → `updated_at`
|
||
- `libelle` → `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 UPDATE` pour idempotence
|
||
- Logging détaillé avec progression
|
||
|
||
**Utilisation :**
|
||
|
||
```bash
|
||
# 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éé :**
|
||
|
||
```php
|
||
$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 :**
|
||
|
||
```bash
|
||
# 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`) :
|
||
|
||
```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** :
|
||
|
||
```sql
|
||
-- 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 :**
|
||
|
||
```bash
|
||
# 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-rca` → `whsec_avExshr0MeWTI7wXP8478XVUkrbYG8hs`
|
||
- PRODUCTION : `webhook-pra` → `whsec_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) :
|
||
|
||
```bash
|
||
# 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`) :
|
||
|
||
```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** :
|
||
|
||
```bash
|
||
# 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`) :
|
||
|
||
```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** :
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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 :
|
||
|
||
```php
|
||
// 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 :**
|
||
|
||
```php
|
||
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 :
|
||
|
||
```php
|
||
// 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 :
|
||
|
||
```php
|
||
// 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 :
|
||
|
||
```php
|
||
// 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é :**
|
||
|
||
```json
|
||
{
|
||
"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 :**
|
||
```json
|
||
{
|
||
"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 :**
|
||
```yaml
|
||
dependencies:
|
||
mqtt_client: ^10.0.0
|
||
```
|
||
|
||
**Exemple de souscription :**
|
||
```dart
|
||
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 :
|
||
|
||
```sql
|
||
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 :
|
||
|
||
```sql
|
||
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`** :
|
||
|
||
```sql
|
||
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 :
|
||
|
||
```php
|
||
// 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
|
||
|
||
```bash
|
||
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 :
|
||
|
||
```php
|
||
$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 :**
|
||
|
||
```sql
|
||
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_adr` → `cp{dept}.id`
|
||
- **Contenu** : Uniquement les immeubles avec métadonnées (nb_niveau, nb_log, residence, etc.)
|
||
|
||
**Structure de la table bat{dept} :**
|
||
|
||
```sql
|
||
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 :
|
||
|
||
```sql
|
||
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 :
|
||
|
||
```sql
|
||
-- 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 :
|
||
|
||
```php
|
||
// 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 :**
|
||
|
||
```php
|
||
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 :**
|
||
|
||
```php
|
||
/**
|
||
* 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) :**
|
||
|
||
```php
|
||
// 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()` :**
|
||
|
||
```php
|
||
// 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
|
||
```json
|
||
// composer.json - remplacer classmap par psr-4
|
||
"autoload": {
|
||
"psr-4": {
|
||
"App\\": "src/"
|
||
}
|
||
}
|
||
```
|
||
|
||
Puis exécuter sur DVA, RCA et PROD :
|
||
```bash
|
||
composer dump-autoload -o
|
||
```
|
||
|
||
#### Étape 2 : Migrer les 8 services restants
|
||
|
||
Pour chaque service :
|
||
|
||
1. **Ajouter le namespace** au début du fichier
|
||
```php
|
||
namespace App\Services;
|
||
```
|
||
|
||
2. **Ajouter les imports** pour les classes globales
|
||
```php
|
||
use PDO;
|
||
use Exception;
|
||
// etc.
|
||
```
|
||
|
||
3. **Vérifier les dépendances** : si le service utilise d'autres services, s'assurer que les imports sont corrects
|
||
|
||
4. **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
|