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

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

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

1926 lines
56 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Documentation des Scripts de Migration GeoSector
## 📋 Vue d'ensemble
Ce dossier contient l'ensemble des scripts PHP permettant de migrer les données de l'ancienne base **geosector** (MySQL distante) vers la nouvelle base **pra_geo** (MariaDB sur maria4/IN4).
### Statistiques
- **Nombre total de scripts** : 21 fichiers
- **Base source** : MySQL geosector (sauvegardée depuis PM7)
- **Base cible** : MariaDB 11.4 pra_geo (maria4 sur IN4)
- **Méthode** : Restauration backup PM7 → Migration sur maria4
- **Ordre d'exécution** : Défini dans `migrate.php`
---
## 🚀 Guide Rapide de Migration depuis Backup PM7
### Architecture de migration
```
PM7 (11.1.2.17)
└─ Backup nocturne chiffré : geosector_YYYYMMDD.sql.tar.gz.enc
Déchiffrement avec decpm7.sh
Transfert SCP vers IN4:/var/back/
Push vers maria4:/var/back/
Décompression et import dans maria4
Base geosector_YYYYMMDD créée dans maria4
Migration vers pra_geo (même serveur maria4)
```
### Étape 1⃣ : Récupération et déchiffrement du backup depuis PM7
**Sur le serveur PM7 (11.1.2.17)** :
```bash
# Se connecter à PM7
ssh root@11.1.2.17
# Aller dans le dossier des backups
cd /var/back/
# Lister les backups disponibles
ls -lh geosector_*.sql.tar.gz.enc | tail -5
# Identifier le dernier backup (exemple : geosector_20251007.sql.tar.gz.enc)
BACKUP_DATE=$(date +%Y%m%d)
BACKUP_FILE="geosector_${BACKUP_DATE}.sql.tar.gz.enc"
# Déchiffrer le backup avec le script decpm7.sh
./decpm7.sh ${BACKUP_FILE}
# Résultat : fichier geosector_20251007.sql.tar.gz
```
### Étape 2⃣ : Transfert du backup vers IN4
**Depuis PM7 ou depuis votre poste local** :
```bash
# Définir les variables
BACKUP_DATE=$(date +%Y%m%d)
BACKUP_FILE="geosector_${BACKUP_DATE}.sql.tar.gz"
# Transférer depuis PM7 vers IN4
scp root@11.1.2.17:/var/back/${BACKUP_FILE} root@51.159.7.190:/var/back/
# Vérifier la présence du fichier sur IN4
ssh root@51.159.7.190 "ls -lh /var/back/${BACKUP_FILE}"
```
### Étape 3⃣ : Push du backup vers le container maria4
**Sur le serveur IN4 (51.159.7.190)** :
```bash
# Se connecter à IN4
ssh root@51.159.7.190
# Définir la variable
BACKUP_DATE=$(date +%Y%m%d)
BACKUP_FILE="geosector_${BACKUP_DATE}.sql.tar.gz"
# Pousser le fichier vers le container maria4
incus file push /var/back/${BACKUP_FILE} maria4/var/back/
# Vérifier le fichier dans le container
incus exec maria4 -- ls -lh /var/back/${BACKUP_FILE}
```
### Étape 4⃣ : Décompression et import dans maria4
**Dans le container maria4** :
```bash
# Se connecter au container maria4
incus exec maria4 bash
# Définir les variables
BACKUP_DATE=$(date +%Y%m%d)
BACKUP_FILE="geosector_${BACKUP_DATE}.sql.tar.gz"
SQL_FILE="geosector_${BACKUP_DATE}.sql"
# Décompresser l'archive
cd /var/back
tar -xzf ${BACKUP_FILE}
# Vérifier la présence du fichier SQL
ls -lh ${SQL_FILE}
# Importer le SQL dans MariaDB
# Note : Le fichier SQL contient déjà CREATE DATABASE et USE geosector_YYYYMMDD
mariadb -u root -p'MyAlpLocal,90b' < ${SQL_FILE}
# Vérifier la création de la base
mariadb -u root -p'MyAlpLocal,90b' -e "SHOW DATABASES LIKE 'geosector_%';"
# Vérifier le nombre de tables
mariadb -u root -p'MyAlpLocal,90b' geosector_${BACKUP_DATE} -e "SHOW TABLES;"
# Vérifier quelques comptages
mariadb -u root -p'MyAlpLocal,90b' geosector_${BACKUP_DATE} -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 users_entites;"
# Sortir du container
exit
```
### Étape 5⃣ : Migration des données vers pra_geo
**Important** : Le script PHP s'exécute sur le container **pra-geo** (pas maria4), car :
- ✅ PHP 8.3 est disponible sur pra-geo
- ✅ L'API GeoSector avec `ApiService::encryptData()` est présente
- ✅ pra-geo se connecte à maria4 via l'IP 13.23.33.4
**Option A : Migration globale (toutes les amicales)**
```bash
# Sur IN4, exécuter le script depuis le container pra-geo
incus exec pra-geo bash
# Aller dans le dossier de l'API
cd /var/www/geosector/api
# Lister les bases disponibles dans maria4 pour vérifier
# (optionnel - pour voir les backups restaurés)
echo "SHOW DATABASES LIKE 'geosector_%';" | \
mysql -h 13.23.33.4 -u root -p'MyAlpLocal,90b'
# Lancer la migration complète
php scripts/php/migrate_from_backup.php \
--source-db=geosector_20251007 \
--target-db=pra_geo \
--mode=global \
--log=/var/www/geosector/api/logs/migration_global_20251007.log
# Suivre la progression dans les logs
tail -f /var/www/geosector/api/logs/migration_global_20251007.log
```
**Option B : Migration par amicale (recommandé)**
```bash
# Sur IN4, dans le container pra-geo
incus exec pra-geo bash
# Aller dans le dossier de l'API
cd /var/www/geosector/api
# Lister les amicales disponibles dans la base source
mysql -h 13.23.33.4 -u root -p'MyAlpLocal,90b' geosector_20251007 -e "
SELECT rowid, libelle, cp, ville, active
FROM users_entites
WHERE active = 1
ORDER BY rowid;"
# Migrer une amicale spécifique (exemple : ID 45)
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_20251007.log
# Suivre la progression dans un autre terminal
incus exec pra-geo -- tail -f /var/www/geosector/api/logs/migration_entity_45_20251007.log
```
### Étape 6⃣ : Vérification de la migration
```bash
# Depuis pra-geo ou directement sur IN4, vérifier les données migrées
# Comparer les comptages source vs cible pour l'entité 45
mysql -h 13.23.33.4 -u root -p'MyAlpLocal,90b' << 'EOF'
SELECT
'Users' as table_name,
(SELECT COUNT(*) FROM geosector_20251007.users WHERE fk_entite = 45) as source,
(SELECT COUNT(*) FROM pra_geo.users WHERE fk_entite = 45) as cible
UNION ALL SELECT
'Operations',
(SELECT COUNT(*) FROM geosector_20251007.operations WHERE fk_entite = 45),
(SELECT COUNT(*) FROM pra_geo.operations WHERE fk_entite = 45)
UNION ALL SELECT
'Passages',
(SELECT COUNT(*) FROM geosector_20251007.ope_pass
WHERE fk_operation IN (SELECT rowid FROM geosector_20251007.operations WHERE fk_entite = 45)),
(SELECT COUNT(*) FROM pra_geo.ope_pass
WHERE fk_operation IN (SELECT id FROM pra_geo.operations WHERE fk_entite = 45));
EOF
# Vérifier l'intégrité des montants
mariadb -u root -p'MyAlpLocal,90b' pra_geo << 'EOF'
SELECT p.fk_type, COUNT(*) as nb_passages, SUM(p.montant) as total_montant
FROM ope_pass p
JOIN operations o ON p.fk_operation = o.id
WHERE o.fk_entite = 45
GROUP BY p.fk_type;
EOF
```
### Étape 7⃣ : Nettoyage (optionnel)
```bash
# Supprimer la base temporaire geosector_YYYYMMDD après migration réussie
mariadb -u root -p'MyAlpLocal,90b' -e "DROP DATABASE IF EXISTS geosector_20251007;"
# Supprimer les fichiers de backup
rm -f /var/back/geosector_20251007.sql.tar.gz
rm -f /var/back/geosector_20251007.sql
```
---
## 📝 Notes importantes
### Sécurité et mot de passe decpm7.sh
Le script `decpm7.sh` sur PM7 nécessite un mot de passe de déchiffrement. Assurez-vous de :
- Connaître le mot de passe avant de lancer le déchiffrement
- Ne jamais commiter ce mot de passe dans Git
- Le stocker de manière sécurisée (gestionnaire de mots de passe)
### Durée estimée de la migration
| Taille de l'amicale | Temps estimé |
|---------------------|--------------|
| Petite (< 5000 passages) | 2-5 minutes |
| Moyenne (5000-20000 passages) | 5-15 minutes |
| Grande (> 20000 passages) | 15-60 minutes |
### Espace disque requis
- Backup chiffré : ~500 MB
- Backup déchiffré : ~1.2 GB
- Import dans MariaDB : ~2 GB
- Total recommandé : **5 GB d'espace libre**
### Gestion des erreurs courantes
**Erreur : "Disk full"**
```bash
# Vérifier l'espace disque
df -h /var/back
# Nettoyer les anciens backups
find /var/back -name "geosector_*.sql" -mtime +7 -delete
```
**Erreur : "Table already exists"**
```bash
# Si migration échouée à moitié, supprimer les données partielles
mariadb -u root -p'MyAlpLocal,90b' pra_geo -e "
DELETE FROM ope_pass WHERE fk_operation IN (SELECT id FROM operations WHERE fk_entite = 45);
DELETE FROM operations WHERE fk_entite = 45;
DELETE FROM users WHERE fk_entite = 45;
DELETE FROM entites WHERE id = 45;"
```
---
## 🏗️ Architecture
### Fichiers de configuration
#### `config.php`
**Rôle** : Configuration principale des connexions bases de données et tunnel SSH
**Fonctionnalités** :
- Constantes de connexion SSH (host, port, clé privée)
- Constantes de connexion base source (via tunnel SSH sur port 13306)
- Constantes de connexion base cible (localhost:3306)
- Fonctions utilitaires :
- `createSshTunnel()` : Établit le tunnel SSH
- `closeSshTunnel()` : Ferme le tunnel SSH
- `getSourceConnection()` : Retourne PDO vers base source
- `getTargetConnection()` : Retourne PDO vers base cible
- `logOperation()` : Journalise les opérations dans `logs/migration_YYYY-MM-DD.log`
**⚠️ Problèmes identifiés** :
- Mots de passe et clés SSH en dur (lignes 8-26)
- Pas de gestion de fichier `.env`
- Credentials visibles dans le code source
#### `MigrationConfig.php`
**Rôle** : Configuration simplifiée pour le chiffrement
**Fonctionnalités** :
- Classe `AppConfig` singleton
- Fournit la clé de chiffrement pour les données sensibles
- Pas de dépendance aux en-têtes HTTP (adapté pour CLI)
#### `migrate.php`
**Rôle** : Script orchestrateur principal
**Fonctionnalités** :
- Exécute toutes les migrations dans l'ordre
- Gestion des arguments CLI (`--truncate`, `--create-table`, `--help`)
- Peut exécuter une migration spécifique : `php migrate.php users`
- Peut exécuter toutes les migrations : `php migrate.php`
- Affiche des statistiques de migration (temps, succès, erreurs)
**Ordre d'exécution** :
1. Tables de référence `x_*` (devises, types, pays, régions, départements, villes)
2. `entites` (entities)
3. `users` (utilisateurs)
4. `operations` (opérations)
5. `ope_sectors` (secteurs d'opération)
6. `sectors_adresses` (adresses de secteurs)
7. `ope_users` (associations utilisateurs-opérations)
8. `ope_users_sectors` (associations utilisateurs-secteurs)
9. `ope_pass` (passages)
10. `ope_pass_histo` (historique des passages)
11. `medias` (fichiers médias)
---
## 🔄 Différences Structurelles entre les Bases
### Vue d'ensemble
**Base source (geosector)** - MariaDB 10.11.9 :
- Serveur version : `10.11.9-MariaDB-deb12`
- Clés primaires : `rowid` (int(11))
- Statut : `active` (tinyint(1))
- Dates : `date_creat`, `date_modif` (datetime)
- **Données en clair** (pas de chiffrement)
- **Pas de contraintes FK définies**
**Base cible (geo_app)** - MariaDB 11.4.5 :
- Serveur version : `11.4.5-MariaDB`
- Clés primaires : `id` (int(10) unsigned)
- Statut : `chk_active` (tinyint(1) unsigned)
- Dates : `created_at`, `updated_at` (timestamp avec ON UPDATE CURRENT_TIMESTAMP)
- **Données chiffrées** : `encrypted_name`, `encrypted_email`, `encrypted_phone`, `encrypted_user_name`, etc.
- **Contraintes FK définies** avec ON DELETE/ON UPDATE CASCADE
### Mappings globaux systématiques
```
rowid → id
active → chk_active
date_creat → created_at
date_modif → updated_at
date_eve → passed_at (ope_pass uniquement)
support_rowid → support_id (medias uniquement)
```
### Tables absentes de la source (nouvelles fonctionnalités)
Ces tables n'existent que dans `geo_app` et **ne sont pas migrées** :
#### Système de chat (4 tables)
- `chat_rooms` : Salles de conversation
- `chat_messages` : Messages
- `chat_participants` : Participants aux conversations
- `chat_read_receipts` : Accusés de lecture
#### Intégration Stripe (7 tables)
- `stripe_accounts` : Comptes Stripe connectés
- `stripe_terminal_readers` : Lecteurs de cartes (Tap to Pay)
- `stripe_android_certified_devices` : Devices Android certifiés
- `stripe_payment_history` : Historique des paiements
- `stripe_refunds` : Remboursements
- `stripe_webhooks` : Événements webhook Stripe
#### Sécurité et monitoring (4 tables)
- `sec_alerts` : Alertes de sécurité
- `sec_blocked_ips` : IPs bloquées
- `sec_failed_login_attempts` : Tentatives de connexion échouées
- `sec_performance_metrics` : Métriques de performance
#### Autres nouvelles tables
- `user_devices` : Informations des devices mobiles
- `x_departements_contours` : Contours géographiques des départements
- `x_users_titres` : Titres des utilisateurs (Mme, M., etc.)
### Tables renommées
| Source | Cible | Notes |
|--------|-------|-------|
| `users_entites` | `entites` | Migration via `migrate_entites.php` |
| `sectors` | `ope_sectors` | Intégrée dans les opérations |
### Différences par table migrée
#### ⚠️ `x_villes`
**Modifications structurelles** :
- Source : `cp` → Cible : `code_postal`
- Source : `departement` (varchar 65) → **Supprimé** (redondant avec `fk_departement`)
#### ⚠️ `x_departements`
**Ajouts dans cible** :
- `dept_limitrophes` (varchar 100) : Départements limitrophes
- `contour` (geometry) : Contour géographique
#### ✅ `entites` (source: `users_entites`)
**Champs chiffrés** :
- `libelle``encrypted_name`
- `email``encrypted_email`
- `tel1`, `tel2``encrypted_phone`, `encrypted_mobile` (avec détection 06/07)
- `iban``encrypted_iban`
- `bic``encrypted_bic`
**Nouveaux champs** :
- `chk_stripe` (tinyint) : Intégration Stripe activée
- `encrypted_stripe_id` (varchar 255) : ID Stripe chiffré
- `chk_username_manuel` (tinyint) : Gestion usernames manuelle/auto
- `chk_user_delete_pass` (tinyint) : Autorisation suppression passages
- `chk_lot_actif` (tinyint) : Lots actifs
**Champs supprimés** :
- Tous les champs métier spécifiques (`appname`, `http_host`, `tva_intra`, `rcs`, `siret`, `ape`, `couleur`, `prefecture`, `gerant_*`, `banque_*`, `genbase`, `groupebase`, `userbase`, `passbase`, `demo`, `lib_*`, `icon_*`, `btn_width`, `nbmembres`, `nbconnex`)
#### ✅ `users`
**Champs chiffrés** :
- `libelle``encrypted_name`
- `username``encrypted_user_name` (chiffrement recherchable)
- `telephone``encrypted_phone`
- `mobile``encrypted_mobile`
- `email``encrypted_email` (chiffrement recherchable)
**Mappings spécifiques** :
- `userpswd` ou `userpass``user_pass_hash`
- `prenom``first_name`
- `nom_tournee``sect_name`
- `alert_email``chk_alert_email`
**Champs supprimés** :
- Nombreux champs métier : `num_adherent`, `libelle_naissance`, `josh`, `email_secondaire`, `infos`, `ltt`, `lng`, `sector`, `dept_naissance`, `commune_naissance`, `anciennete`, `fk_categorie`, `fk_sous_categorie`, `adresse_*`, `cp`, `ville`, `matricule`, `fk_grade`, `chk_adherent_*`, `chk_archive`, `chk_double_affectation`
#### ✅ `operations`
**Modifications** :
- `chk_api_adresse` : **Supprimé** dans cible
- Dates : `date_deb`, `date_fin` changent de `date` à `NOT NULL DEFAULT '0000-00-00'`
#### ✅ `ope_sectors`
**Nouveaux champs** :
- `fk_old_sector` (int unsigned) : Référence à l'ancien `sectors.rowid` pour le mapping
#### ✅ `ope_users`
**Nouveaux champs dans cible** :
- `fk_role` (int unsigned) : Rôle de l'utilisateur dans l'opération
- `first_name` (varchar 45) : Prénom
- `encrypted_name` (varchar 255) : Nom chiffré
- `sect_name` (varchar 60) : Nom de tournée
**Impact** : Le script `migrate_ope_users.php` doit remplir ces champs ou les laisser vides
#### ✅ `ope_pass`
**Champs chiffrés** :
- `libelle``encrypted_name`
- `email``encrypted_email` (chiffrement recherchable)
- `phone``encrypted_phone`
**Nouveaux champs dans cible** :
- `residence` (varchar 75) : Nom de la résidence
- `date_recu` (timestamp) : Date de réception
- `date_creat_recu` (timestamp) : Date de création du reçu
- `date_sent_recu` (timestamp) : Date d'envoi du reçu
- `stripe_payment_id` (varchar 50) : ID du PaymentIntent Stripe
**Champs supprimés** :
- `lieudit` (varchar 75)
- `chk_habitat_vide` (tinyint)
- `lot_nb_passages` (int)
**Mappings spécifiques** :
- `recu``nom_recu`
- `date_eve``passed_at`
- `fk_type` : transformation 8→5, 9→6
#### ✅ `ope_pass_histo`
**Champ supprimé** :
- `fk_user` : **N'existe plus** dans la nouvelle structure
#### ✅ `medias`
**Mapping spécifique** :
- `support_rowid``support_id`
**Nouveaux champs dans cible** :
- `fk_entite` (int unsigned) : Propriétaire du média
- `fk_operation` (int unsigned) : Opération liée
- `file_type`, `file_category`, `file_size`, `mime_type` : Métadonnées fichier
- `original_name`, `file_path` : Informations fichier
- `original_width`, `original_height`, `processed_width`, `processed_height` : Dimensions images
- `is_processed` : Statut traitement image
**Champs supprimés** :
- `dir0`, `dir1`, `dir2` : Ancienne structure de dossiers
- `type_fichier`, `position`, `hauteur`, `largeur`, `niveaugris` : Anciens champs métier
#### ✅ `sectors_adresses`
**Nouveaux champs dans cible** :
- `id` (auto-increment) : Clé primaire ajoutée
- `osm_id` (int) : ID OpenStreetMap
- `osm_name` (varchar 50) : Nom OSM
- `osm_date_creat` (timestamp) : Date de création OSM
- `created_at`, `updated_at` : Timestamps standards
**Mappings** :
- `fk_sector` : ancien ID → nouvel ID via mapping `ope_sectors`
### Tables non migrées de la source
Ces tables existent dans `geosector` mais **ne sont pas migrées** vers `geo_app` :
- `articles`, `articles_pages` : Système d'articles (obsolète ?)
- `blog_articles`, `blog_pages` : Système de blog (obsolète ?)
- `email_counter`, `email_queue` : File d'attente emails (recréée dans cible)
- `ope_pass_recus` : Table séparée des reçus (intégrée dans `ope_pass`)
- `ope_users_suivis` : Suivi GPS des utilisateurs (archivé ?)
- `operations_docs`, `operations_eve_docs` : Documents opérations (archivé ?)
- `params` : Paramètres globaux (reconfiguré ?)
- `sectors`, `sectors_streets` : Tables sectors (transformée en `ope_sectors`)
- `users_lastpos` : Dernière position utilisateurs (archivé ?)
- `x_civilites` : Civilités (remplacé par `x_users_titres`)
- `x_users_categories`, `x_users_sous_categories`, `x_users_grades` : Catégories utilisateurs (supprimé)
- `y_conf`, `y_menus`, `y_modules`, `y_modules_rules`, `y_pages` : Configuration interface (obsolète)
- `z_logs`, `z_sessions`, `z_stats` : Logs et sessions (recréés dans cible)
### Contraintes de clés étrangères
**Source** : Aucune contrainte FK définie explicitement
**Cible** : Toutes les FK définies avec :
- `ON UPDATE CASCADE` : Mise à jour en cascade
- `ON DELETE CASCADE` ou `ON DELETE SET NULL` : Suppression gérée
**Impact migration** : Les scripts doivent respecter l'ordre des dépendances pour éviter les erreurs d'intégrité référentielle.
### Vues dans geo_app
- `chat_rooms_with_last_message` : Vue des salles de chat avec dernier message
- `v_stripe_entite_stats` : Statistiques Stripe par entité
- `v_stripe_payment_stats` : Statistiques paiements Stripe
**Note** : Les vues ne contiennent pas de données à migrer.
---
## 📊 Analyse Script par Script
### 1. Tables de référence `x_*`
#### `migrate_x_devises.php`
**Table** : `x_devises` (Devises)
**Mappings** :
- `rowid``id`
- `active``chk_active`
**Particularités** :
- Crée automatiquement la table si elle n'existe pas
- Utilise `logOperation()` pour le logging
- Pattern : `ON DUPLICATE KEY UPDATE`
**Statut** : ✅ Fonctionnel
---
#### `migrate_x_entites_types.php`
**Table** : `x_entites_types` (Types d'entités)
**Mappings** :
- `rowid``id`
- `active``chk_active`
**Particularités** :
- Logging simple via `echo`
- Pattern : `ON DUPLICATE KEY UPDATE`
**Statut** : ✅ Fonctionnel
---
#### `migrate_x_types_passages.php`, `migrate_x_types_reglements.php`, `migrate_x_users_roles.php`
**Tables** : Types de passages, types de règlements, rôles utilisateurs
**Pattern** : Identique aux autres tables `x_*`
**Statut** : ✅ Fonctionnel (non lu mais présumé identique)
---
#### `migrate_x_pays.php`
**Table** : `x_pays` (Pays)
**Mappings** :
- `rowid``id`
- `active``chk_active`
- Conservation de `code`, `fk_continent`, `fk_devise`, `libelle`
**Statut** : ✅ Fonctionnel
---
#### `migrate_x_regions.php`
**Table** : `x_regions` (Régions)
**Mappings** :
- `rowid``id`
- `active``chk_active`
- Conservation de tous les champs métier (`fk_pays`, `libelle`, `libelle_long`, `table_osm`, `departements`)
**Statut** : ✅ Fonctionnel
---
#### `migrate_x_departements.php`, `migrate_x_villes.php`
**Tables** : Départements, Villes
**Pattern** : Identique aux autres tables `x_*`
**Statut** : ✅ Fonctionnel (non lu mais présumé identique)
---
### 2. Tables métier principales
#### `migrate_entites.php`
**Table source** : `users_entites`
**Table cible** : `entites`
**Mappings** :
- `rowid``id`
- `active``chk_active`
- `libelle``encrypted_name` (chiffré)
- `tel1`, `tel2``encrypted_phone`, `encrypted_mobile` (logique de détection 06/07)
- `email``encrypted_email` (chiffré et recherchable)
- `iban``encrypted_iban` (chiffré)
- `bic``encrypted_bic` (chiffré)
- `cp``code_postal`
**Particularités** :
- **Chiffrement** : Utilise `ApiService::encryptData()` et `ApiService::encryptSearchableData()`
- **Logique téléphones** : Détecte mobiles (06/07) vs fixes
- **Valeur par défaut** : `chk_demo = 0` (forcé)
**Statut** : ✅ Fonctionnel
---
#### `migrate_users.php`
**Table** : `users`
**Mappings** :
- `rowid``id`
- `active``chk_active`
- `libelle``encrypted_name` (chiffré)
- `prenom``first_name`
- `nom_tournee``sect_name`
- `username``encrypted_user_name` (chiffré et recherchable)
- `userpswd` ou `userpass``user_pass_hash`
- `telephone``encrypted_phone` (chiffré)
- `mobile``encrypted_mobile` (chiffré)
- `email``encrypted_email` (chiffré et recherchable)
- `alert_email``chk_alert_email`
**Particularités** :
- **Tests de chiffrement** : Pour les 100 premiers utilisateurs (lignes 130-158)
- Chiffre et déchiffre email et username
- Affiche les valeurs pour vérification
- **⚠️ Impact performance** : Ralentit la migration
- **Gestion rôle** : Force `fk_role=1` si `fk_role=0`
- **Gestion titre** : Force `fk_titre=1` si différent de 1 ou 2
**⚠️ PROBLÈME CRITIQUE** (lignes 227-239) :
```php
if ($exists) {
$insertStmt->execute($userData); // OK : update
$successCount++;
} else {
$errorCount++; // ❌ ERREUR : devrait insérer, pas compter comme erreur
}
```
**Conséquence** : Les nouveaux utilisateurs ne sont jamais insérés, comptés comme erreurs
**Statut** : 🔴 Bug critique - logique d'insertion inversée
---
#### `migrate_operations.php`
**Table** : `operations`
**Mappings** :
- `rowid``id`
- `active``chk_active`
- `date_creat``created_at`
- `date_modif``updated_at`
**Particularités** :
- **Limitation arbitraire** : Ne migre que les **3 dernières opérations par entité** (lignes 54-70)
- Vérifie que les entités référencées ont été migrées
- Filtre basé sur `fk_entite IN (IDs des entités migrées)`
- Option `--truncate` pour vider la table avant migration
**⚠️ Problème** :
- Limite de 3 opérations non documentée et non paramétrable
- Pourrait perdre des données historiques importantes
**Statut** : ⚠️ Limitation fonctionnelle - à valider métier
---
#### `migrate_ope_sectors.php`
**Table** : `ope_sectors`
**Mappings** :
- Fusionne données de `sectors` et `ope_users_sectors`
- `rowid` de `sectors``fk_old_sector`
- Génère un nouvel `id` auto-incrémenté
- Conservation de `libelle`, `sector` (géométrie), `color`
**Particularités** :
- Crée une correspondance `fk_operation + fk_old_sector → id`
- Utile pour les migrations suivantes (ope_users_sectors, ope_pass)
- Ne migre que les secteurs liés aux opérations migrées
- Filtre `active = 1` sur source
**Statut** : ✅ Fonctionnel
---
#### `migrate_sectors_adresses.php`
**Table** : `sectors_adresses`
**Mappings** :
- `fk_sector` (ancien ID) → `fk_sector` (nouvel ID via mapping)
- Ajout de colonnes OSM avec valeurs par défaut :
- `osm_id = 0`
- `osm_name = ''`
- `osm_date_creat = '0000-00-00 00:00:00'`
**Particularités** :
- Utilise le mapping créé par `migrate_ope_sectors.php`
- Recherche `fk_old_sector → id` dans `ope_sectors`
- Ignore les adresses dont le secteur n'a pas été migré
**Statut** : ✅ Fonctionnel
---
#### `migrate_ope_users.php`
**Table** : `ope_users`
**Mappings** :
- `rowid``id`
- `active``chk_active`
- `date_creat``created_at`
- `date_modif``updated_at`
**Particularités** :
- Vérifie que `fk_operation` et `fk_user` existent dans les tables cibles
- Double filtrage par IDs migrés
- Ne migre que les associations valides
**Statut** : ✅ Fonctionnel
---
#### `migrate_ope_users_sectors.php`
**Table** : `ope_users_sectors`
**Mappings** :
- `fk_sector` (ancien ID) → `fk_sector` (nouvel ID via mapping ope_sectors)
- Pas de `rowid` → utilise clé composite `(fk_operation, fk_user, fk_sector)`
**Particularités** :
- Triple vérification :
1. Opération migrée
2. Utilisateur migré
3. Secteur existe dans mapping ope_sectors
- Génère la clé de recherche : `fk_operation . '_' . fk_old_sector`
- Compteur `skipped` pour associations ignorées
**Statut** : ✅ Fonctionnel
---
#### `migrate_ope_pass.php` ⭐ Script le plus complexe
**Table** : `ope_pass` (Passages)
**Mappings** :
- `date_eve``passed_at` 📅
- `libelle``encrypted_name` 🔒 (chiffré)
- `email``encrypted_email` 🔒 (chiffré et recherchable)
- `phone``encrypted_phone` 🔒 (chiffré)
- `recu``nom_recu`
- `fk_sector` (ancien ID) → `fk_sector` (nouvel ID via mapping)
- `fk_type` : transformation 8→5, 9→6
**Particularités** :
- **Gestion avancée des timeouts** (lignes 46-80) :
- Configure `PDO::ATTR_TIMEOUT = 600` (10 minutes)
- Configure variables MariaDB 10.11 :
- `wait_timeout = 3600` (1h)
- `net_read_timeout = 3600` (1h)
- `net_write_timeout = 3600` (1h)
- `innodb_lock_wait_timeout = 3600` (1h)
- **Suppression des contraintes FK** avant migration (lignes 98-120)
- **Suppression par lots** : Delete par lots de 100 000 (lignes 136-160)
- **Migration par lots** : 5 000 passages par lot (lignes 238-543)
- **Transactions par lot** : `BEGIN TRANSACTION` → traitement → `COMMIT`
- **Garbage collector** : Appel explicite `gc_collect_cycles()` pour libérer mémoire
- **Validation email** : `filter_var($email, FILTER_VALIDATE_EMAIL)` avant chiffrement
- **Gestion type_reglement** : Force à 4 si différent de 1, 2 ou 3
- **Recréation des FK** après migration (lignes 547-575)
**⚠️ Points d'attention** :
- Tue le processus SSH sur port 13306 au démarrage (ligne 23)
- Désactive `FOREIGN_KEY_CHECKS` pendant suppression
- Gestion silencieuse des passages dont secteur/utilisateur non migré
**Statut** : ✅ Fonctionnel - Optimisé pour gros volumes
---
#### `migrate_ope_pass_histo.php`
**Table** : `ope_pass_histo`
**Mappings** :
- `rowid` (non conservé, auto-increment)
- `date_histo` (conversion datetime)
- `fk_user`**supprimé** (n'existe plus dans nouvelle structure)
**Particularités** :
- Ne migre que si `fk_pass` existe dans `ope_pass` cible
- Suppression complète avant migration (`DELETE FROM ope_pass_histo`)
- Mode "silencieux" : affiche uniquement les erreurs
**Statut** : ✅ Fonctionnel
---
#### `migrate_medias.php`
**Table** : `medias`
**Mappings** :
- `support_rowid``support_id` 📝
- `date_creat``created_at`
- `date_modif``updated_at`
**Particularités** :
- Vérifie que `fk_user_creat` et `fk_user_modif` existent
- Force à `0` (système) si utilisateur non migré
- Suppression complète avant migration
- Pas de chiffrement (fichiers référencés, pas de données sensibles)
**Statut** : ✅ Fonctionnel
---
## 🔍 Problèmes Identifiés par Priorité
### 🔴 CRITIQUES (Bloquants)
#### 1. **Bug logique `migrate_users.php` (lignes 227-239)**
**Impact** : Aucun nouvel utilisateur ne peut être inséré
```php
// Logique actuelle (INCORRECTE)
if ($exists) {
$insertStmt->execute($userData); // Update OK
$successCount++;
} else {
$errorCount++; // ❌ Devrait faire INSERT
}
```
**Solution** :
```php
// Logique corrigée
$insertStmt->execute($userData); // ON DUPLICATE KEY fait le travail
$successCount++;
```
---
### ⚠️ MAJEURS (Fonctionnels)
#### 2. **Limitation arbitraire opérations `migrate_operations.php` (lignes 54-70)**
**Impact** : Perte potentielle de données historiques
- Ne migre que 3 dernières opérations par entité
- Pas paramétrable
- Pas documenté dans le code
**Solution proposée** :
- Ajouter paramètre `--limit-operations=N` (défaut : 0 = toutes)
- Documenter dans `--help`
---
#### 3. **Tests de chiffrement dans production `migrate_users.php` (lignes 130-158)**
**Impact** : Ralentissement x2 de la migration users
- Teste chiffrement/déchiffrement pour 100 premiers users
- S'exécute à chaque migration
**Solution proposée** :
- Créer flag `--test-encryption`
- Désactiver par défaut
---
#### 4. **Configuration sensible en dur `config.php`**
**Impact** : Sécurité compromise
- Mots de passe en clair
- Clé SSH en dur
- Visible dans Git
**Solution proposée** :
- Créer `scripts/.env.example`
- Utiliser `vlucas/phpdotenv` ou parser manuel
- Ajouter `scripts/.env` au `.gitignore`
---
### MINEURS (Améliorations)
#### 5. **Incohérence logging**
**Impact** : Difficulté debugging
- Certains scripts : `logOperation()`
- D'autres : `echo` direct
**Solution** : Uniformiser avec `logOperation()` partout
---
#### 6. **Pas de transaction globale**
**Impact** : État incohérent si échec en milieu de migration
- Chaque table = migration indépendante
- Si échec table N, tables 1..N-1 déjà modifiées
**Solution proposée** :
- Option `--transactional` pour tout englobé dans 1 transaction
- Par défaut : comportement actuel (plus sûr)
---
#### 7. **Gestion FK manuelle dans `migrate_ope_pass.php`**
**Impact** : Complexité maintenance
- Désactivation/réactivation manuelle
- Suppression/recréation manuelle
**Solution** :
- Vérifier si vraiment nécessaire
- Documenter pourquoi (gros volumes)
---
#### 8. **Dates avec valeurs `0000-00-00`**
**Impact** : Warnings MariaDB en mode strict
- Plusieurs scripts utilisent `'0000-00-00'` ou `'0000-00-00 00:00:00'`
- MariaDB 10.11 en mode strict refuse ces valeurs
**Solution** : Remplacer par `NULL`
---
#### 9. **Pas de rapport détaillé post-migration**
**Impact** : Difficile de valider la migration
- Pas de récapitulatif des données migrées
- Pas de comparaison source vs cible
**Solution proposée** :
- Créer script `scripts/php/verify_migration.php`
- Compare counts par table
- Liste les incohérences
---
## ✅ Points Positifs
1.**Idempotence** : Utilisation systématique de `ON DUPLICATE KEY UPDATE`
2. 🔒 **Sécurité données** : Chiffrement des données sensibles
3. 🎯 **Filtrage intelligent** : Ne migre que les données liées (pas d'orphelins)
4. 📊 **Optimisation gros volumes** : Migration par lots (`ope_pass`)
5. 🔗 **Gestion dépendances** : Ordre d'exécution respecté
6. 🧹 **Nettoyage mémoire** : Garbage collection explicite
7. 📝 **Logging** : Historique des migrations dans `logs/`
8. 🚀 **Tunnel SSH automatique** : Connexion transparente
---
## ⚙️ Incohérences Détectées (Scripts vs Structures Réelles)
Suite à l'analyse comparative des scripts de migration avec les structures SQL réelles, voici les incohérences identifiées :
### 🔴 CRITIQUES
#### 1. `migrate_users.php` - Logique d'insertion inversée
**Lignes** : 227-239
**Problème** : Les nouveaux utilisateurs ne sont jamais insérés
```php
// Code actuel (INCORRECT)
if ($exists) {
$insertStmt->execute($userData); // Update OK
$successCount++;
} else {
$errorCount++; // ❌ Devrait faire INSERT, pas compter comme erreur
}
```
**Impact** : **BLOQUANT** - Impossible d'ajouter de nouveaux utilisateurs
**Solution** : Utiliser directement `ON DUPLICATE KEY UPDATE` sans vérification préalable
---
### ⚠️ MAJEURS
#### 2. `migrate_x_departements.php` - Nouveaux champs non remplis
**Champs manquants** :
- `dept_limitrophes` (varchar 100) : Départements limitrophes
- `contour` (geometry) : Contour géographique
**Impact** : Ces champs resteront `NULL` après migration
**Solution** : Acceptable si ces données seront ajoutées ultérieurement
---
#### 3. `migrate_ope_users.php` - Nouveaux champs non remplis
**Champs manquants** :
- `fk_role` (int) : Rôle de l'utilisateur dans l'opération
- `first_name` (varchar 45) : Prénom
- `encrypted_name` (varchar 255) : Nom chiffré
- `sect_name` (varchar 60) : Nom de tournée
**Impact** : Fonctionnalités limitées - ces informations manqueront dans les opérations
**Solution** : Enrichir le script pour copier ces données depuis `users` si disponibles
---
#### 4. `migrate_medias.php` - Métadonnées fichiers manquantes
**Champs manquants** :
- `fk_entite`, `fk_operation` : Liens vers entités/opérations
- `file_type`, `file_category`, `file_size`, `mime_type` : Métadonnées
- `original_name`, `file_path` : Informations fichier
- `original_width`, `original_height`, `processed_width`, `processed_height` : Dimensions images
- `is_processed` : Statut traitement
**Impact** : Fonctionnalités de gestion de médias limitées
**Solution** :
- Analyser les fichiers existants pour extraire les métadonnées
- Déduire `fk_entite` et `fk_operation` depuis `support` et `support_id`
---
### MINEURS (Informations)
#### 5. `migrate_x_villes.php` - Champ `departement` ignoré
**Champ supprimé** : `departement` (varchar 65)
**Raison** : Redondant avec `fk_departement`
**Impact** : ✅ Correct - pas de perte de données
---
#### 6. `migrate_ope_pass.php` - Nouveaux champs Stripe
**Champs non remplis** :
- `residence` (varchar 75)
- `date_recu`, `date_creat_recu`, `date_sent_recu` (timestamps)
- `stripe_payment_id` (varchar 50)
**Impact** : Attendu - ces fonctionnalités sont nouvelles
**Solution** : ✅ Aucune action requise - champs remplis lors de l'utilisation future
---
#### 7. `migrate_entites.php` - Nouveaux champs Stripe/Config
**Champs non remplis** :
- `chk_stripe`, `encrypted_stripe_id` : Intégration Stripe
- `chk_username_manuel` : Gestion usernames
- `chk_user_delete_pass` : Autorisation suppression
- `chk_lot_actif` : Gestion lots
**Impact** : Attendu - nouvelles fonctionnalités
**Solution** : ✅ Valeurs par défaut appropriées définies dans la structure
---
### 📊 Résumé des vérifications
| Script | Statut | Problèmes critiques | Problèmes majeurs | Avertissements |
|--------|--------|---------------------|-------------------|----------------|
| `migrate_users.php` | 🔴 | 1 | 0 | 0 |
| `migrate_x_departements.php` | ⚠️ | 0 | 1 | 0 |
| `migrate_ope_users.php` | ⚠️ | 0 | 1 | 0 |
| `migrate_medias.php` | ⚠️ | 0 | 1 | 0 |
| `migrate_x_villes.php` | ✅ | 0 | 0 | 1 |
| `migrate_ope_pass.php` | ✅ | 0 | 0 | 1 |
| `migrate_entites.php` | ✅ | 0 | 0 | 1 |
| Autres scripts `x_*` | ✅ | 0 | 0 | 0 |
### 🛠️ Outil de vérification
Un script de vérification automatique a été créé : **`verify_migration_structure.php`**
**Usage** :
```bash
php scripts/php/verify_migration_structure.php
```
**Fonctionnalités** :
- Compare les colonnes source vs cible pour chaque table
- Identifie les colonnes non mappées
- Liste les nouvelles colonnes qui seront NULL
- Affichage coloré avec compteurs d'erreurs/avertissements
---
---
## 🚀 Migration via Endpoint API (Approche Recommandée)
### Vue d'ensemble
Au lieu d'exécuter les scripts PHP en ligne de commande, nous recommandons d'utiliser un **endpoint API REST** qui permet de migrer **UNE entité (amicale) à la fois** de manière contrôlée et testable.
### Avantages de cette approche
**Migration progressive** : Une entité à la fois, avec validation entre chaque étape
**Interface utilisateur** : Suivi visuel de la progression depuis Flutter/Web
**Tests granulaires** : Vérification table par table avant de continuer
**Rollback possible** : Annulation par entité en cas de problème
**Logs détaillés** : Traçabilité complète dans l'API
**Sécurité renforcée** : Authentification et autorisation via l'API
**Moins risqué** : Pas de migration globale "big bang"
### Architecture de l'endpoint
#### Endpoint principal
```http
POST /api/migrations/entity
Authorization: Bearer {session_id}
Content-Type: application/json
{
"entity_id": 45, // ID de l'entité dans l'ancienne base
"steps": ["users", "operations", "ope_pass"], // Étapes à exécuter (optionnel)
"dry_run": false, // Mode simulation (optionnel)
"truncate": false // Vider les tables cible avant migration (optionnel)
}
```
#### Réponse
```json
{
"status": "success",
"entity_id": 45,
"entity_name": "Amicale de Grenoble",
"migration_id": "mig_abc123",
"steps_completed": [
{
"step": "x_devises",
"status": "success",
"records_migrated": 1,
"duration_ms": 45
},
{
"step": "users",
"status": "success",
"records_migrated": 38,
"duration_ms": 234
}
],
"total_duration_ms": 1234,
"summary": {
"total_records": 156,
"total_errors": 0,
"total_warnings": 2
}
}
```
### Plan de migration étape par étape
#### Phase 0⃣ : Préparation
**Actions à effectuer :**
- [ ] Créer le controller `MigrationController.php`
- [ ] Créer le service `MigrationService.php`
- [ ] Ajouter les routes dans `index.php`
- [ ] Configurer la connexion à la base source dans `AppConfig.php`
- [ ] Tester la connexion aux deux bases
**Tests de préparation :**
```bash
# Tester la connexion aux bases
GET /api/migrations/test-connections
# Lister les entités disponibles à migrer
GET /api/migrations/entities/available
# Récupérer les détails d'une entité source
GET /api/migrations/entities/{source_id}
```
---
#### Phase 1⃣ : Tables de référence `x_*`
**Ordre d'exécution :**
1. `x_devises`
2. `x_entites_types`
3. `x_types_passages`
4. `x_types_reglements`
5. `x_users_roles`
6. `x_pays`
7. `x_regions`
8. `x_departements`
9. `x_villes`
**Endpoint de test :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "x_devises",
"dry_run": true
}
```
**Vérifications à effectuer :**
- [ ] Nombre d'enregistrements source == cible
- [ ] Champs `rowid``id` correctement mappés
- [ ] Champs `active``chk_active` correctement mappés
- [ ] Aucune erreur de contrainte FK
**Requêtes SQL de vérification :**
```sql
-- Comparer les counts
SELECT 'source' as db, COUNT(*) as count FROM geosector.x_devises
UNION ALL
SELECT 'cible' as db, COUNT(*) as count FROM geo_app.x_devises;
-- Vérifier les IDs manquants
SELECT s.rowid
FROM geosector.x_devises s
LEFT JOIN geo_app.x_devises t ON s.rowid = t.id
WHERE t.id IS NULL;
```
---
#### Phase 2⃣ : Entité (Amicale)
**Tables concernées :**
- `users_entites``entites`
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "entites"
}
```
**Mappings critiques à vérifier :**
- [ ] `libelle``encrypted_name` (chiffrement AES-256)
- [ ] `email``encrypted_email` (chiffrement recherchable)
- [ ] `tel1`, `tel2``encrypted_phone`, `encrypted_mobile` (détection 06/07)
- [ ] `iban``encrypted_iban`
- [ ] `bic``encrypted_bic`
- [ ] `cp``code_postal`
**Tests fonctionnels :**
```http
# Récupérer l'entité migrée
GET /api/entites/45
# Vérifier le déchiffrement
# Le nom doit être lisible dans la réponse
```
**Vérifications SQL :**
```sql
-- Vérifier la présence de l'entité
SELECT id, encrypted_name, encrypted_email
FROM geo_app.entites
WHERE id = 45;
-- Les champs chiffrés doivent contenir des données base64
-- encrypted_name devrait ressembler à : "eyJpdiI6Ij..."
```
---
#### Phase 3⃣ : Utilisateurs de l'entité
**Tables concernées :**
- `users` (filtrés par `fk_entite = 45`)
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "users"
}
```
**Mappings critiques :**
- [ ] `libelle``encrypted_name`
- [ ] `username``encrypted_user_name` (chiffrement recherchable)
- [ ] `userpswd` ou `userpass``user_pass_hash`
- [ ] `prenom``first_name`
- [ ] `nom_tournee``sect_name`
- [ ] `telephone``encrypted_phone`
- [ ] `mobile``encrypted_mobile`
- [ ] `email``encrypted_email`
**Tests fonctionnels :**
```http
# Lister les utilisateurs de l'entité
GET /api/users?fk_entite=45
# Tester un login avec un utilisateur migré
POST /api/login
{
"username": "j.dupont",
"password": "MotDePasseOriginal123"
}
```
**Vérifications SQL :**
```sql
-- Comparer les counts
SELECT COUNT(*) FROM geosector.users WHERE fk_entite = 45;
SELECT COUNT(*) FROM geo_app.users WHERE fk_entite = 45;
-- Vérifier les hash de mots de passe (doivent être identiques)
SELECT u1.rowid, u1.userpswd, u2.user_pass_hash
FROM geosector.users u1
JOIN geo_app.users u2 ON u1.rowid = u2.id
WHERE u1.fk_entite = 45
LIMIT 5;
```
**⚠️ Point critique :**
- Les mots de passe doivent être migrés **tels quels** (hash déjà fait)
- Ne PAS re-hasher les mots de passe
- Vérifier que le login fonctionne avec les anciens identifiants
---
#### Phase 4⃣ : Opérations de l'entité
**Tables concernées :**
- `operations` (filtrées par `fk_entite = 45`)
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "operations",
"options": {
"limit": 0 // 0 = toutes les opérations (pas de limite à 3)
}
}
```
**Mappings :**
- [ ] `rowid``id`
- [ ] `date_creat``created_at`
- [ ] `date_modif``updated_at`
- [ ] `active``chk_active`
**Tests fonctionnels :**
```http
# Lister les opérations de l'entité
GET /api/operations?fk_entite=45
# Récupérer une opération spécifique
GET /api/operations/{operation_id}
```
**Vérifications SQL :**
```sql
-- Comparer les counts
SELECT COUNT(*) FROM geosector.operations WHERE fk_entite = 45;
SELECT COUNT(*) FROM geo_app.operations WHERE fk_entite = 45;
-- Vérifier les 3 dernières opérations
SELECT id, libelle, date_deb, date_fin
FROM geo_app.operations
WHERE fk_entite = 45
ORDER BY id DESC
LIMIT 3;
```
---
#### Phase 5⃣ : Secteurs des opérations
**Tables concernées :**
- `sectors` + `ope_users_sectors``ope_sectors`
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "ope_sectors"
}
```
**Logique spécifique :**
- Fusion de `sectors.rowid``ope_sectors.fk_old_sector`
- Génération de nouveaux `ope_sectors.id` (auto-increment)
- Création d'un mapping `old_id → new_id` pour les étapes suivantes
**Vérifications SQL :**
```sql
-- Vérifier le mapping des secteurs
SELECT os.id, os.fk_old_sector, os.libelle, os.fk_operation
FROM geo_app.ope_sectors os
JOIN geo_app.operations o ON os.fk_operation = o.id
WHERE o.fk_entite = 45
ORDER BY os.fk_operation, os.id;
```
---
#### Phase 6⃣ : Adresses des secteurs
**Tables concernées :**
- `sectors_adresses`
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "sectors_adresses"
}
```
**Mappings :**
- [ ] Utilisation du mapping `fk_old_sector → new_id` créé en Phase 5
- [ ] Ajout de colonnes OSM avec valeurs par défaut
**Vérifications SQL :**
```sql
-- Comparer les counts
SELECT COUNT(*) FROM geosector.sectors_adresses sa
JOIN geosector.sectors s ON sa.fk_sector = s.rowid
JOIN geosector.ope_users_sectors ous ON ous.fk_sector = s.rowid
JOIN geosector.operations o ON ous.fk_operation = o.rowid
WHERE o.fk_entite = 45;
-- Vérifier dans la cible
SELECT COUNT(*) FROM geo_app.sectors_adresses sa
JOIN geo_app.ope_sectors os ON sa.fk_sector = os.id
JOIN geo_app.operations o ON os.fk_operation = o.id
WHERE o.fk_entite = 45;
```
---
#### Phase 7⃣ : Associations opérations-utilisateurs
**Tables concernées :**
- `ope_users`
- `ope_users_sectors`
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "ope_users"
}
```
**⚠️ Point d'attention :**
- Nouveaux champs dans cible : `fk_role`, `first_name`, `encrypted_name`, `sect_name`
- **Solution recommandée** : Enrichir avec les données depuis `users` si disponibles
**Vérifications SQL :**
```sql
-- Vérifier les associations
SELECT ou.id, ou.fk_operation, ou.fk_user, u.encrypted_user_name
FROM geo_app.ope_users ou
JOIN geo_app.users u ON ou.fk_user = u.id
JOIN geo_app.operations o ON ou.fk_operation = o.id
WHERE o.fk_entite = 45
LIMIT 10;
```
---
#### Phase 8⃣ : Passages (Données critiques)
**Tables concernées :**
- `ope_pass`
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "ope_pass",
"options": {
"batch_size": 5000 // Migration par lots de 5000
}
}
```
**Mappings critiques :**
- [ ] `date_eve``passed_at`
- [ ] `libelle``encrypted_name` (chiffrement)
- [ ] `email``encrypted_email` (chiffrement recherchable + validation)
- [ ] `phone``encrypted_phone` (chiffrement)
- [ ] `recu``nom_recu`
- [ ] `fk_type` : transformation 8→5, 9→6
- [ ] `fk_sector` : ancien ID → nouvel ID via mapping Phase 5
**Tests fonctionnels :**
```http
# Compter les passages de l'entité
GET /api/passages/count?entity_id=45
# Récupérer les derniers passages
GET /api/passages?entity_id=45&limit=10&order=desc
```
**Vérifications SQL :**
```sql
-- Comparer les counts par opération
SELECT o.id, o.libelle, COUNT(p.id) as nb_passages
FROM geo_app.operations o
LEFT JOIN geo_app.ope_pass p ON p.fk_operation = o.id
WHERE o.fk_entite = 45
GROUP BY o.id, o.libelle
ORDER BY o.id;
-- Vérifier les montants totaux
SELECT SUM(montant) FROM geosector.ope_pass
WHERE fk_operation IN (SELECT rowid FROM geosector.operations WHERE fk_entite = 45);
SELECT SUM(montant) FROM geo_app.ope_pass
WHERE fk_operation IN (SELECT id FROM geo_app.operations WHERE fk_entite = 45);
```
**⚠️ TRÈS IMPORTANT :**
- Cette phase peut prendre du temps (nombreux passages)
- Utiliser la migration par lots (5000 par 5000)
- Afficher une progression dans l'interface
- Vérifier que les emails sont valides avant chiffrement
---
#### Phase 9⃣ : Historique et médias
**Tables concernées :**
- `ope_pass_histo`
- `medias`
**Endpoint :**
```http
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "ope_pass_histo"
}
```
**Mappings :**
- [ ] `ope_pass_histo` : Suppression du champ `fk_user` (n'existe plus)
- [ ] `medias` : Mapping `support_rowid``support_id`
**Vérifications SQL :**
```sql
-- Historique des passages
SELECT COUNT(*) FROM geo_app.ope_pass_histo h
JOIN geo_app.ope_pass p ON h.fk_pass = p.id
JOIN geo_app.operations o ON p.fk_operation = o.id
WHERE o.fk_entite = 45;
-- Médias de l'entité
SELECT COUNT(*) FROM geo_app.medias
WHERE support = 'entite' AND support_id = 45;
```
---
### Endpoints de gestion et monitoring
#### Endpoints de vérification
```http
# Statut de la migration d'une entité
GET /api/migrations/entity/{entity_id}/status
# Logs de migration
GET /api/migrations/entity/{entity_id}/logs
# Rapport de migration
GET /api/migrations/entity/{entity_id}/report
```
#### Endpoints de rollback
```http
# Annuler la migration d'une entité
DELETE /api/migrations/entity/{entity_id}
# Supprimer uniquement une étape
DELETE /api/migrations/entity/{entity_id}/step/{step_name}
```
#### Endpoints de comparaison
```http
# Comparer les données source vs cible
GET /api/migrations/entity/{entity_id}/compare
# Vérifier l'intégrité des données
GET /api/migrations/entity/{entity_id}/verify
```
---
### Checklist finale de validation
#### ✅ Données de base
- [ ] L'entité existe dans `geo_app.entites` avec toutes les données chiffrées
- [ ] Le déchiffrement fonctionne (appel `GET /api/entites/{id}`)
- [ ] Le logo de l'entité est présent (si applicable)
#### ✅ Utilisateurs
- [ ] Tous les utilisateurs de l'entité sont présents
- [ ] Le login fonctionne avec les anciens identifiants
- [ ] Les données chiffrées sont déchiffrables
- [ ] Les rôles sont corrects (`fk_role`)
#### ✅ Opérations
- [ ] Toutes les opérations sont migrées (pas de limite à 3)
- [ ] Les dates `date_deb` et `date_fin` sont correctes
- [ ] Les secteurs associés sont présents
#### ✅ Passages
- [ ] Le nombre total de passages correspond
- [ ] Les montants totaux correspondent
- [ ] Les emails chiffrés sont valides et déchiffrables
- [ ] Les `fk_type` sont corrects (vérifier transformations 8→5, 9→6)
#### ✅ Intégrité référentielle
- [ ] Aucune contrainte FK violée
- [ ] Tous les `fk_user` existent dans `users`
- [ ] Tous les `fk_operation` existent dans `operations`
- [ ] Tous les `fk_sector` existent dans `ope_sectors`
#### ✅ Tests fonctionnels
- [ ] Login avec un utilisateur de l'entité
- [ ] Affichage des opérations dans l'interface
- [ ] Affichage des passages dans une opération
- [ ] Création d'un nouveau passage (test post-migration)
- [ ] Génération d'un reçu fiscal (si applicable)
---
### Scripts SQL utiles
#### Comparer les totaux globaux
```sql
-- Script à exécuter après migration complète
SELECT
'Entité' as type,
(SELECT COUNT(*) FROM geosector.users_entites WHERE rowid = 45) as source,
(SELECT COUNT(*) FROM geo_app.entites WHERE id = 45) as cible
UNION ALL
SELECT
'Users',
(SELECT COUNT(*) FROM geosector.users WHERE fk_entite = 45),
(SELECT COUNT(*) FROM geo_app.users WHERE fk_entite = 45)
UNION ALL
SELECT
'Operations',
(SELECT COUNT(*) FROM geosector.operations WHERE fk_entite = 45),
(SELECT COUNT(*) FROM geo_app.operations WHERE fk_entite = 45)
UNION ALL
SELECT
'Passages',
(SELECT COUNT(*) FROM geosector.ope_pass WHERE fk_operation IN
(SELECT rowid FROM geosector.operations WHERE fk_entite = 45)),
(SELECT COUNT(*) FROM geo_app.ope_pass WHERE fk_operation IN
(SELECT id FROM geo_app.operations WHERE fk_entite = 45));
```
#### Vérifier l'intégrité des montants
```sql
-- Totaux des passages par type
SELECT p.fk_type, COUNT(*) as nb, SUM(p.montant) as total
FROM geo_app.ope_pass p
JOIN geo_app.operations o ON p.fk_operation = o.id
WHERE o.fk_entite = 45
GROUP BY p.fk_type;
```
---
## 📋 TODO List
### 🔴 Priorité CRITIQUE (À faire IMMÉDIATEMENT)
- [ ] **Créer l'endpoint API de migration**
- Fichier : `src/Controllers/MigrationController.php`
- Service : `src/Services/MigrationService.php`
- Routes : Ajouter dans `index.php`
- Action : Implémenter la logique de migration par étape
- [ ] **Configurer la connexion à la base source**
- Fichier : `src/Config/AppConfig.php`
- Action : Ajouter les paramètres de connexion à `geosector` via tunnel SSH
### ⚠️ Priorité HAUTE (Cette semaine)
- [ ] **Externaliser configuration sensible**
- Créer `scripts/.env.example`
- Parser `.env` dans `config.php`
- Ajouter `scripts/.env` au `.gitignore`
- Mettre à jour documentation
- [ ] **Paramétrer limite opérations**
- Fichier : `scripts/php/migrate_operations.php`
- Ajouter argument CLI `--limit-operations=N`
- Documenter dans `--help`
- Valeur par défaut : 0 (toutes)
- [ ] **Tests de chiffrement optionnels**
- Fichier : `scripts/php/migrate_users.php`
- Créer flag `--test-encryption`
- Désactiver par défaut
### 📊 Priorité MOYENNE (Ce mois-ci)
- [ ] **Uniformiser logging**
- Remplacer tous les `echo` par `logOperation()`
- Ajouter niveaux : DEBUG, INFO, WARNING, ERROR
- Format uniforme
- [ ] **Script de vérification post-migration**
- Créer `scripts/php/verify_migration.php`
- Comparer counts source vs cible
- Vérifier intégrité référentielle
- Générer rapport HTML
- [ ] **Remplacer dates `0000-00-00` par `NULL`**
- Fichiers concernés : `migrate_operations.php`, `sectors_adresses.php`
- Remplacer par `NULL` pour compatibilité MariaDB strict mode
- [ ] **Documentation utilisateur**
- Guide pas-à-pas migration complète
- Prérequis (tunnel SSH, accès bases)
- Procédure de rollback
- FAQ troubleshooting
### 💡 Priorité BASSE (Nice to have)
- [ ] **Option transaction globale**
- Flag `--transactional` dans `migrate.php`
- Englober toutes migrations dans 1 transaction
- Warning si échec = rollback complet
- [ ] **Barre de progression**
- Utiliser library CLI (ex: `symfony/console`)
- Afficher progression en temps réel
- ETA par table
- [ ] **Mode dry-run**
- Flag `--dry-run` pour simuler sans écrire
- Afficher ce qui serait fait
- Utile pour tests
- [ ] **Export/Import rapide**
- Alternative : dump SQL + sed pour mapping IDs
- Plus rapide pour gros volumes
- Comparer perf vs PHP
- [ ] **Tests automatisés**
- PHPUnit tests pour chaque script
- Mock des connexions DB
- CI/CD avec GitHub Actions
---
## 📚 Références
### Fichiers clés
- **Schéma DB** : `docs/geo_app.sql`
- **Configuration** : `scripts/config.php`
- **Orchestrateur** : `scripts/php/migrate.php`
- **Logs** : `scripts/logs/migration_YYYY-MM-DD.log`
### Ordre d'exécution (défini dans `migrate.php:64-85`)
```
x_devises → x_entites_types → x_types_passages → x_types_reglements →
x_users_roles → x_pays → x_regions → x_departements → x_villes →
entites → users → operations → ope_sectors → sectors_adresses →
ope_users → ope_users_sectors → ope_pass → ope_pass_histo → medias
```
### Commandes utiles
```bash
# Migration complète
php scripts/php/migrate.php
# Migration d'une table spécifique
php scripts/php/migrate.php users
# Migration avec truncate
php scripts/php/migrate.php users --truncate
# Afficher l'aide
php scripts/php/migrate.php --help
# Consulter les logs
tail -f scripts/logs/migration_$(date +%Y-%m-%d).log
```
---
## 🔧 Corrections Critiques Appliquées (10/10/2025)
### Correction #15 : Ajout de contraintes UNIQUE pour éviter les doublons
**Problème identifié** : Les tables `ope_users` et `ope_users_sectors` n'avaient PAS de contrainte UNIQUE sur leurs combinaisons de FK, permettant des doublons massifs lors de la migration.
**Diagnostic** :
- Table `ope_users` : Possibilité de centaines de doublons pour la même paire (fk_operation, fk_user)
- Table `ope_users_sectors` : Risque de doublons sur (fk_operation, fk_user, fk_sector)
- Le `ON DUPLICATE KEY UPDATE` dans le script PHP ne fonctionnait pas car aucune contrainte UNIQUE n'existait
**Solution appliquée** :
1. **Mise à jour de `geo_app_structure.sql`** (structure de référence) :
- Ajout de `UNIQUE KEY idx_operation_user (fk_operation, fk_user)` sur `ope_users` (ligne 403)
- Ajout de `UNIQUE KEY idx_operation_user_sector (fk_operation, fk_user, fk_sector)` sur `ope_users_sectors` (ligne 430)
2. **Correction du code PHP** dans `migrate_from_backup.php` :
```php
// AVANT (INCORRECT - créait des doublons)
$sql = "SELECT ou.rowid, ou.fk_operation, ou.fk_user, ...
INSERT INTO ope_users (id, fk_operation, fk_user, ...) VALUES (:id, ...
// APRÈS (CORRECT - évite les doublons)
$sql = "SELECT DISTINCT ou.fk_operation, ou.fk_user, ... // Pas de rowid
INSERT INTO ope_users (fk_operation, fk_user, ...) VALUES (... // Pas d'id, auto-increment
```
3. **Application sur les bases de données** :
```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 que les contraintes sont bien créées
SHOW INDEX FROM ope_users WHERE Key_name = 'idx_operation_user';
SHOW INDEX FROM ope_users_sectors WHERE Key_name = 'idx_operation_user_sector';
```
**Impact** :
- ✅ Empêche définitivement les doublons dans `ope_users` et `ope_users_sectors`
- ✅ Le `ON DUPLICATE KEY UPDATE` fonctionne désormais correctement
- ✅ Migration idempotente (peut être relancée sans créer de duplicatas)
**Fichiers modifiés** :
- `scripts/php/geo_app_structure.sql` - Structure de référence mise à jour (lignes 403, 430)
- `scripts/php/migrate_from_backup.php` - Code corrigé avec SELECT DISTINCT (lignes 1125-1191)
**À appliquer sur les environnements** :
```bash
# DEV (dva_geo sur maria3/IN3)
mysql -h 13.23.33.4 -u dva_geo_user -p'CBq9tKHj6PGPZuTmAHV7' dva_geo
# Puis exécuter les ALTER TABLE ci-dessus
# REC (rca_geo sur maria3/IN3)
mysql -h 13.23.33.4 -u rca_geo_user -p'UPf3C0cQ805LypyM71iW' rca_geo
# Puis exécuter les ALTER TABLE ci-dessus
# PROD (pra_geo sur maria4/IN4)
mysql -h 13.23.33.4 -u pra_geo_user -p'd2jAAGGWi8fxFrWgXjOA' pra_geo
# Puis exécuter les ALTER TABLE ci-dessus
```
**Pour re-migrer 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
```
---
**Dernière mise à jour** : 2025-10-10
**Auteur de l'analyse** : Claude Code
**Version** : 1.1