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

56 KiB
Raw Blame History

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 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 :

  • libelleencrypted_name
  • emailencrypted_email
  • tel1, tel2encrypted_phone, encrypted_mobile (avec détection 06/07)
  • ibanencrypted_iban
  • bicencrypted_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 :

  • libelleencrypted_name
  • usernameencrypted_user_name (chiffrement recherchable)
  • telephoneencrypted_phone
  • mobileencrypted_mobile
  • emailencrypted_email (chiffrement recherchable)

Mappings spécifiques :

  • userpswd ou userpassuser_pass_hash
  • prenomfirst_name
  • nom_tourneesect_name
  • alert_emailchk_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 :

  • libelleencrypted_name
  • emailencrypted_email (chiffrement recherchable)
  • phoneencrypted_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 :

  • recunom_recu
  • date_evepassed_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_rowidsupport_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 :

  • rowidid
  • activechk_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 :

  • rowidid
  • activechk_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 :

  • rowidid
  • activechk_active
  • Conservation de code, fk_continent, fk_devise, libelle

Statut : Fonctionnel


migrate_x_regions.php

Table : x_regions (Régions) Mappings :

  • rowidid
  • activechk_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 :

  • rowidid
  • activechk_active
  • libelleencrypted_name (chiffré)
  • tel1, tel2encrypted_phone, encrypted_mobile (logique de détection 06/07)
  • emailencrypted_email (chiffré et recherchable)
  • ibanencrypted_iban (chiffré)
  • bicencrypted_bic (chiffré)
  • cpcode_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 :

  • rowidid
  • activechk_active
  • libelleencrypted_name (chiffré)
  • prenomfirst_name
  • nom_tourneesect_name
  • usernameencrypted_user_name (chiffré et recherchable)
  • userpswd ou userpassuser_pass_hash
  • telephoneencrypted_phone (chiffré)
  • mobileencrypted_mobile (chiffré)
  • emailencrypted_email (chiffré et recherchable)
  • alert_emailchk_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) :

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 :

  • rowidid
  • activechk_active
  • date_creatcreated_at
  • date_modifupdated_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 sectorsfk_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 :

  • rowidid
  • activechk_active
  • date_creatcreated_at
  • date_modifupdated_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_evepassed_at 📅
  • libelleencrypted_name 🔒 (chiffré)
  • emailencrypted_email 🔒 (chiffré et recherchable)
  • phoneencrypted_phone 🔒 (chiffré)
  • recunom_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_usersupprimé (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_rowidsupport_id 📝
  • date_creatcreated_at
  • date_modifupdated_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é

// 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/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

// 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 :

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 :

  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 :

POST /api/migrations/entity/step
{
  "entity_id": 45,
  "step": "x_devises",
  "dry_run": true
}

Vérifications à effectuer :

  • Nombre d'enregistrements source == cible
  • Champs rowidid correctement mappés
  • Champs activechk_active correctement 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_entitesentites

Endpoint :

POST /api/migrations/entity/step
{
  "entity_id": 45,
  "step": "entites"
}

Mappings critiques à vérifier :

  • libelleencrypted_name (chiffrement AES-256)
  • emailencrypted_email (chiffrement recherchable)
  • tel1, tel2encrypted_phone, encrypted_mobile (détection 06/07)
  • ibanencrypted_iban
  • bicencrypted_bic
  • cpcode_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 par fk_entite = 45)

Endpoint :

POST /api/migrations/entity/step
{
  "entity_id": 45,
  "step": "users"
}

Mappings critiques :

  • libelleencrypted_name
  • usernameencrypted_user_name (chiffrement recherchable)
  • userpswd ou userpassuser_pass_hash
  • prenomfirst_name
  • nom_tourneesect_name
  • telephoneencrypted_phone
  • mobileencrypted_mobile
  • emailencrypted_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 par fk_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 :

  • rowidid
  • date_creatcreated_at
  • date_modifupdated_at
  • activechk_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_sectorsope_sectors

Endpoint :

POST /api/migrations/entity/step
{
  "entity_id": 45,
  "step": "ope_sectors"
}

Logique spécifique :

  • Fusion de sectors.rowidope_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 :

-- 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_id créé 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_users
  • ope_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 users si 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_evepassed_at
  • libelleencrypted_name (chiffrement)
  • emailencrypted_email (chiffrement recherchable + validation)
  • phoneencrypted_phone (chiffrement)
  • recunom_recu
  • fk_type : transformation 8→5, 9→6
  • fk_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_histo
  • medias

Endpoint :

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_rowidsupport_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.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

-- 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
  • 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

# 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 :

    // 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 :

    -- 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 :

# 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