- 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>
56 KiB
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) :
# 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 :
# 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) :
# 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 :
# 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)
# 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é)
# 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
# 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)
# 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"
# 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"
# 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 SSHcloseSshTunnel(): Ferme le tunnel SSHgetSourceConnection(): Retourne PDO vers base sourcegetTargetConnection(): Retourne PDO vers base ciblelogOperation(): Journalise les opérations danslogs/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
AppConfigsingleton - 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 :
- Tables de référence
x_*(devises, types, pays, régions, départements, villes) entites(entities)users(utilisateurs)operations(opérations)ope_sectors(secteurs d'opération)sectors_adresses(adresses de secteurs)ope_users(associations utilisateurs-opérations)ope_users_sectors(associations utilisateurs-secteurs)ope_pass(passages)ope_pass_histo(historique des passages)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 conversationchat_messages: Messageschat_participants: Participants aux conversationschat_read_receipts: Accusés de lecture
Intégration Stripe (7 tables)
stripe_accounts: Comptes Stripe connectésstripe_terminal_readers: Lecteurs de cartes (Tap to Pay)stripe_android_certified_devices: Devices Android certifiésstripe_payment_history: Historique des paiementsstripe_refunds: Remboursementsstripe_webhooks: Événements webhook Stripe
Sécurité et monitoring (4 tables)
sec_alerts: Alertes de sécuritésec_blocked_ips: IPs bloquéessec_failed_login_attempts: Tentatives de connexion échouéessec_performance_metrics: Métriques de performance
Autres nouvelles tables
user_devices: Informations des devices mobilesx_departements_contours: Contours géographiques des départementsx_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 avecfk_departement)
⚠️ x_departements
Ajouts dans cible :
dept_limitrophes(varchar 100) : Départements limitrophescontour(geometry) : Contour géographique
✅ entites (source: users_entites)
Champs chiffrés :
libelle→encrypted_nameemail→encrypted_emailtel1,tel2→encrypted_phone,encrypted_mobile(avec détection 06/07)iban→encrypted_ibanbic→encrypted_bic
Nouveaux champs :
chk_stripe(tinyint) : Intégration Stripe activéeencrypted_stripe_id(varchar 255) : ID Stripe chiffréchk_username_manuel(tinyint) : Gestion usernames manuelle/autochk_user_delete_pass(tinyint) : Autorisation suppression passageschk_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_nameusername→encrypted_user_name(chiffrement recherchable)telephone→encrypted_phonemobile→encrypted_mobileemail→encrypted_email(chiffrement recherchable)
Mappings spécifiques :
userpswdouuserpass→user_pass_hashprenom→first_namenom_tournee→sect_namealert_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_finchangent dedateàNOT NULL DEFAULT '0000-00-00'
✅ ope_sectors
Nouveaux champs :
fk_old_sector(int unsigned) : Référence à l'anciensectors.rowidpour le mapping
✅ ope_users
Nouveaux champs dans cible :
fk_role(int unsigned) : Rôle de l'utilisateur dans l'opérationfirst_name(varchar 45) : Prénomencrypted_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_nameemail→encrypted_email(chiffrement recherchable)phone→encrypted_phone
Nouveaux champs dans cible :
residence(varchar 75) : Nom de la résidencedate_recu(timestamp) : Date de réceptiondate_creat_recu(timestamp) : Date de création du reçudate_sent_recu(timestamp) : Date d'envoi du reçustripe_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_recudate_eve→passed_atfk_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édiafk_operation(int unsigned) : Opération liéefile_type,file_category,file_size,mime_type: Métadonnées fichieroriginal_name,file_path: Informations fichieroriginal_width,original_height,processed_width,processed_height: Dimensions imagesis_processed: Statut traitement image
Champs supprimés :
dir0,dir1,dir2: Ancienne structure de dossierstype_fichier,position,hauteur,largeur,niveaugris: Anciens champs métier
✅ sectors_adresses
Nouveaux champs dans cible :
id(auto-increment) : Clé primaire ajoutéeosm_id(int) : ID OpenStreetMaposm_name(varchar 50) : Nom OSMosm_date_creat(timestamp) : Date de création OSMcreated_at,updated_at: Timestamps standards
Mappings :
fk_sector: ancien ID → nouvel ID via mappingope_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 dansope_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 enope_sectors)users_lastpos: Dernière position utilisateurs (archivé ?)x_civilites: Civilités (remplacé parx_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 cascadeON DELETE CASCADEouON 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 messagev_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→idactive→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→idactive→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→idactive→chk_active- Conservation de
code,fk_continent,fk_devise,libelle
Statut : ✅ Fonctionnel
migrate_x_regions.php
Table : x_regions (Régions)
Mappings :
rowid→idactive→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→idactive→chk_activelibelle→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()etApiService::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→idactive→chk_activelibelle→encrypted_name(chiffré)prenom→first_namenom_tournee→sect_nameusername→encrypted_user_name(chiffré et recherchable)userpswdouuserpass→user_pass_hashtelephone→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=1sifk_role=0 - Gestion titre : Force
fk_titre=1si différent de 1 ou 2
⚠️ PROBLÈME CRITIQUE (lignes 227-239) :
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→idactive→chk_activedate_creat→created_atdate_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
--truncatepour 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
sectorsetope_users_sectors rowiddesectors→fk_old_sector- Génère un nouvel
idauto-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 = 1sur 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 = 0osm_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 → iddansope_sectors - Ignore les adresses dont le secteur n'a pas été migré
Statut : ✅ Fonctionnel
migrate_ope_users.php
Table : ope_users
Mappings :
rowid→idactive→chk_activedate_creat→created_atdate_modif→updated_at
Particularités :
- Vérifie que
fk_operationetfk_userexistent 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 :
- Opération migrée
- Utilisateur migré
- Secteur existe dans mapping ope_sectors
- Génère la clé de recherche :
fk_operation . '_' . fk_old_sector - Compteur
skippedpour 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_recufk_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)
- Configure
- 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_CHECKSpendant 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_passexiste dansope_passcible - 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_atdate_modif→updated_at
Particularités :
- Vérifie que
fk_user_createtfk_user_modifexistent - 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é
// Logique actuelle (INCORRECTE)
if ($exists) {
$insertStmt->execute($userData); // Update OK
$successCount++;
} else {
$errorCount++; // ❌ Devrait faire INSERT
}
Solution :
// 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/phpdotenvou parser manuel - Ajouter
scripts/.envau.gitignore
ℹ️ MINEURS (Améliorations)
5. Incohérence logging
Impact : Difficulté debugging
- Certains scripts :
logOperation() - D'autres :
echodirect
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
--transactionalpour 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
- ✨ Idempotence : Utilisation systématique de
ON DUPLICATE KEY UPDATE - 🔒 Sécurité données : Chiffrement des données sensibles
- 🎯 Filtrage intelligent : Ne migre que les données liées (pas d'orphelins)
- 📊 Optimisation gros volumes : Migration par lots (
ope_pass) - 🔗 Gestion dépendances : Ordre d'exécution respecté
- 🧹 Nettoyage mémoire : Garbage collection explicite
- 📝 Logging : Historique des migrations dans
logs/ - 🚀 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
// 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 limitrophescontour(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érationfirst_name(varchar 45) : Prénomencrypted_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érationsfile_type,file_category,file_size,mime_type: Métadonnéesoriginal_name,file_path: Informations fichieroriginal_width,original_height,processed_width,processed_height: Dimensions imagesis_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_entiteetfk_operationdepuissupportetsupport_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 Stripechk_username_manuel: Gestion usernameschk_user_delete_pass: Autorisation suppressionchk_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 :
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
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
{
"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 :
# 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 :
x_devisesx_entites_typesx_types_passagesx_types_reglementsx_users_rolesx_paysx_regionsx_departementsx_villes
Endpoint de test :
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "x_devises",
"dry_run": true
}
Vérifications à effectuer :
- Nombre d'enregistrements source == cible
- Champs
rowid→idcorrectement mappés - Champs
active→chk_activecorrectement mappés - Aucune erreur de contrainte FK
Requêtes SQL de vérification :
-- 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 :
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_ibanbic→encrypted_biccp→code_postal
Tests fonctionnels :
# 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 :
-- 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 parfk_entite = 45)
Endpoint :
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "users"
}
Mappings critiques :
libelle→encrypted_nameusername→encrypted_user_name(chiffrement recherchable)userpswdouuserpass→user_pass_hashprenom→first_namenom_tournee→sect_nametelephone→encrypted_phonemobile→encrypted_mobileemail→encrypted_email
Tests fonctionnels :
# 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 :
-- 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 parfk_entite = 45)
Endpoint :
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "operations",
"options": {
"limit": 0 // 0 = toutes les opérations (pas de limite à 3)
}
}
Mappings :
rowid→iddate_creat→created_atdate_modif→updated_atactive→chk_active
Tests fonctionnels :
# 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 :
-- 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 :
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_idpour les étapes suivantes
Vérifications 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 :
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "sectors_adresses"
}
Mappings :
- Utilisation du mapping
fk_old_sector → new_idcréé en Phase 5 - Ajout de colonnes OSM avec valeurs par défaut
Vérifications 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_usersope_users_sectors
Endpoint :
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
userssi disponibles
Vérifications 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 :
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_atlibelle→encrypted_name(chiffrement)email→encrypted_email(chiffrement recherchable + validation)phone→encrypted_phone(chiffrement)recu→nom_recufk_type: transformation 8→5, 9→6fk_sector: ancien ID → nouvel ID via mapping Phase 5
Tests fonctionnels :
# 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 :
-- 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_histomedias
Endpoint :
POST /api/migrations/entity/step
{
"entity_id": 45,
"step": "ope_pass_histo"
}
Mappings :
ope_pass_histo: Suppression du champfk_user(n'existe plus)medias: Mappingsupport_rowid→support_id
Vérifications 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
# 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
# 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
# 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.entitesavec 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_debetdate_finsont 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_typesont corrects (vérifier transformations 8→5, 9→6)
✅ Intégrité référentielle
- Aucune contrainte FK violée
- Tous les
fk_userexistent dansusers - Tous les
fk_operationexistent dansoperations - Tous les
fk_sectorexistent dansope_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
-- 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
-- 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
- Fichier :
-
Configurer la connexion à la base source
- Fichier :
src/Config/AppConfig.php - Action : Ajouter les paramètres de connexion à
geosectorvia tunnel SSH
- Fichier :
⚠️ Priorité HAUTE (Cette semaine)
-
Externaliser configuration sensible
- Créer
scripts/.env.example - Parser
.envdansconfig.php - Ajouter
scripts/.envau.gitignore - Mettre à jour documentation
- Créer
-
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)
- Fichier :
-
Tests de chiffrement optionnels
- Fichier :
scripts/php/migrate_users.php - Créer flag
--test-encryption - Désactiver par défaut
- Fichier :
📊 Priorité MOYENNE (Ce mois-ci)
-
Uniformiser logging
- Remplacer tous les
echoparlogOperation() - Ajouter niveaux : DEBUG, INFO, WARNING, ERROR
- Format uniforme
- Remplacer tous les
-
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
- Créer
-
Remplacer dates
0000-00-00parNULL- Fichiers concernés :
migrate_operations.php,sectors_adresses.php - Remplacer par
NULLpour compatibilité MariaDB strict mode
- Fichiers concernés :
-
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
--transactionaldansmigrate.php - Englober toutes migrations dans 1 transaction
- Warning si échec = rollback complet
- Flag
-
Barre de progression
- Utiliser library CLI (ex:
symfony/console) - Afficher progression en temps réel
- ETA par table
- Utiliser library CLI (ex:
-
Mode dry-run
- Flag
--dry-runpour simuler sans écrire - Afficher ce qui serait fait
- Utile pour tests
- Flag
-
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
# 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 UPDATEdans le script PHP ne fonctionnait pas car aucune contrainte UNIQUE n'existait
Solution appliquée :
-
Mise à jour de
geo_app_structure.sql(structure de référence) :- Ajout de
UNIQUE KEY idx_operation_user (fk_operation, fk_user)surope_users(ligne 403) - Ajout de
UNIQUE KEY idx_operation_user_sector (fk_operation, fk_user, fk_sector)surope_users_sectors(ligne 430)
- Ajout de
-
Correction du code PHP dans
migrate_from_backup.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 -
Application sur les bases de données :
-- 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_usersetope_users_sectors - ✅ Le
ON DUPLICATE KEY UPDATEfonctionne 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 :
# 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 :
# 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