- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
358 lines
13 KiB
PHP
Executable File
358 lines
13 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Script de migration générique pour transférer des données entre les bases geosector et geosector_app
|
|
*
|
|
* Usage:
|
|
* php migrate.php - Exécute toutes les migrations dans l'ordre
|
|
* php migrate.php <table_name> - Exécute la migration pour une table spécifique
|
|
*
|
|
* Options:
|
|
* --truncate Vide la table cible avant la migration
|
|
* --create-table Crée la table cible si elle n'existe pas
|
|
* --help Affiche l'aide
|
|
*/
|
|
|
|
require_once dirname(__DIR__) . '/config.php';
|
|
|
|
// Définition des mappages de colonnes pour chaque table
|
|
$tableMappings = [
|
|
'x_devises' => [
|
|
'source_to_target' => [
|
|
'rowid' => 'id',
|
|
'active' => 'chk_active'
|
|
],
|
|
'primary_key' => 'rowid', // Clé primaire dans la table source
|
|
'target_primary_key' => 'id' // Clé primaire dans la table cible
|
|
],
|
|
'users' => [
|
|
'source_to_target' => [
|
|
'id' => 'id',
|
|
'user_pass_hash' => 'password_hash',
|
|
'encrypted_user_name' => 'encrypted_last_name',
|
|
'lang' => 'preferred_language',
|
|
'chk_active' => 'is_active'
|
|
],
|
|
'primary_key' => 'id',
|
|
'target_primary_key' => 'id',
|
|
'encrypted_fields' => [
|
|
'email' => ['field' => 'encrypted_email', 'searchable' => true],
|
|
'user_name' => ['field' => 'encrypted_last_name', 'searchable' => false],
|
|
'phone' => ['field' => 'encrypted_phone', 'searchable' => false],
|
|
'mobile' => ['field' => 'encrypted_mobile', 'searchable' => false]
|
|
]
|
|
],
|
|
// Ajoutez d'autres tables selon les besoins
|
|
];
|
|
|
|
// Fonction pour afficher l'aide
|
|
function showHelp() {
|
|
echo "Usage: php migrate.php <table_name> [--truncate] [--create-table]\n";
|
|
echo "\nOptions disponibles:\n";
|
|
echo " --truncate Vide la table cible avant la migration\n";
|
|
echo " --create-table Crée la table cible si elle n'existe pas\n";
|
|
echo " --help Affiche cette aide\n";
|
|
echo "\nTables disponibles:\n";
|
|
|
|
global $tableMappings;
|
|
foreach (array_keys($tableMappings) as $table) {
|
|
echo " - $table\n";
|
|
}
|
|
|
|
exit(0);
|
|
}
|
|
|
|
// Définition des scripts de migration disponibles dans l'ordre d'exécution
|
|
$migrationScripts = [
|
|
'x_devises' => __DIR__ . '/migrate_x_devises.php',
|
|
'x_entites_types' => __DIR__ . '/migrate_x_entites_types.php',
|
|
'x_types_passages' => __DIR__ . '/migrate_x_types_passages.php',
|
|
'x_types_reglements' => __DIR__ . '/migrate_x_types_reglements.php',
|
|
'x_users_roles' => __DIR__ . '/migrate_x_users_roles.php',
|
|
'x_pays' => __DIR__ . '/migrate_x_pays.php',
|
|
'x_regions' => __DIR__ . '/migrate_x_regions.php',
|
|
'x_departements' => __DIR__ . '/migrate_x_departements.php',
|
|
'x_villes' => __DIR__ . '/migrate_x_villes.php',
|
|
'entites' => __DIR__ . '/migrate_entites.php',
|
|
'users' => __DIR__ . '/migrate_users.php',
|
|
'operations' => __DIR__ . '/migrate_operations.php',
|
|
'ope_sectors' => __DIR__ . '/migrate_ope_sectors.php',
|
|
'sectors_adresses' => __DIR__ . '/migrate_sectors_adresses.php',
|
|
'ope_users' => __DIR__ . '/migrate_ope_users.php',
|
|
'ope_users_sectors' => __DIR__ . '/migrate_ope_users_sectors.php',
|
|
'ope_pass' => __DIR__ . '/migrate_ope_pass.php',
|
|
'ope_pass_histo' => __DIR__ . '/migrate_ope_pass_histo.php',
|
|
'medias' => __DIR__ . '/migrate_medias.php'
|
|
// Ajoutez d'autres scripts de migration au fur et à mesure
|
|
];
|
|
|
|
// Fonction pour exécuter un script de migration spécifique
|
|
function executeMigrationScript($scriptPath) {
|
|
echo "\nExécution du script: " . basename($scriptPath) . "\n";
|
|
echo "------------------------------------------------\n";
|
|
|
|
// Exécuter le script PHP
|
|
include $scriptPath;
|
|
|
|
echo "\n";
|
|
return true;
|
|
}
|
|
|
|
// Traitement des arguments de ligne de commande
|
|
$tableName = null;
|
|
$truncateTable = false;
|
|
$createTable = false;
|
|
$runAllScripts = true;
|
|
|
|
for ($i = 1; $i < $_SERVER['argc']; $i++) {
|
|
$arg = $_SERVER['argv'][$i];
|
|
|
|
if ($arg === '--help') {
|
|
showHelp();
|
|
} elseif ($arg === '--truncate') {
|
|
$truncateTable = true;
|
|
} elseif ($arg === '--create-table') {
|
|
$createTable = true;
|
|
} elseif (substr($arg, 0, 2) !== '--') {
|
|
$tableName = $arg;
|
|
$runAllScripts = false;
|
|
}
|
|
}
|
|
|
|
// Si aucune table n'est spécifiée, exécuter tous les scripts de migration dans l'ordre
|
|
if ($runAllScripts) {
|
|
echo "\nDémarrage de la migration complète de la base de données\n";
|
|
echo "=======================================================\n";
|
|
|
|
$startTime = microtime(true);
|
|
$successCount = 0;
|
|
$failCount = 0;
|
|
|
|
foreach ($migrationScripts as $table => $scriptPath) {
|
|
if (file_exists($scriptPath)) {
|
|
try {
|
|
executeMigrationScript($scriptPath);
|
|
$successCount++;
|
|
} catch (Exception $e) {
|
|
echo "\nErreur lors de la migration de la table $table: " . $e->getMessage() . "\n";
|
|
$failCount++;
|
|
}
|
|
} else {
|
|
echo "\nScript de migration introuvable pour la table $table: $scriptPath\n";
|
|
$failCount++;
|
|
}
|
|
}
|
|
|
|
$endTime = microtime(true);
|
|
$executionTime = round($endTime - $startTime, 2);
|
|
|
|
echo "\n=======================================================\n";
|
|
echo "Migration terminée en $executionTime secondes\n";
|
|
echo "Tables migrées avec succès: $successCount\n";
|
|
echo "Tables en échec: $failCount\n";
|
|
|
|
exit(0);
|
|
}
|
|
|
|
// Si une table spécifique est demandée, vérifier si un script dédié existe
|
|
if (isset($migrationScripts[$tableName])) {
|
|
$scriptPath = $migrationScripts[$tableName];
|
|
if (file_exists($scriptPath)) {
|
|
executeMigrationScript($scriptPath);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
// Sinon, utiliser la méthode générique de migration (code existant)
|
|
if (!isset($tableMappings[$tableName])) {
|
|
echo "Erreur: La table '$tableName' n'est pas configurée pour la migration.\n";
|
|
echo "Tables disponibles: " . implode(', ', array_keys($tableMappings)) . "\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Création du dossier de logs si nécessaire
|
|
if (!is_dir(dirname(__DIR__) . '/logs')) {
|
|
mkdir(dirname(__DIR__) . '/logs', 0755, true);
|
|
}
|
|
|
|
logOperation("Démarrage de la migration de la table $tableName");
|
|
|
|
try {
|
|
// Connexion aux bases de données
|
|
$sourceDb = getSourceConnection();
|
|
$targetDb = getTargetConnection();
|
|
|
|
// Obtenir le service API pour le chiffrement si nécessaire
|
|
$apiService = null;
|
|
if (isset($tableMappings[$tableName]['encrypted_fields'])) {
|
|
$container = $GLOBALS['container'];
|
|
$apiService = $container->get('ApiService');
|
|
}
|
|
|
|
// Récupération des données de la source
|
|
$stmt = $sourceDb->query("SELECT * FROM $tableName");
|
|
$records = $stmt->fetchAll();
|
|
|
|
logOperation("Nombre d'enregistrements à migrer: " . count($records));
|
|
|
|
// Vérifier si la table existe dans la cible
|
|
try {
|
|
$targetDb->query("SELECT 1 FROM $tableName LIMIT 1");
|
|
$tableExists = true;
|
|
} catch (PDOException $e) {
|
|
$tableExists = false;
|
|
}
|
|
|
|
// Créer la table si elle n'existe pas et si l'option est activée
|
|
if (!$tableExists && $createTable) {
|
|
logOperation("La table $tableName n'existe pas dans la base cible. Création de la table...");
|
|
|
|
// Récupérer la structure de la table source
|
|
$stmt = $sourceDb->query("SHOW CREATE TABLE $tableName");
|
|
$tableStructure = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (isset($tableStructure['Create Table'])) {
|
|
$createTableSql = $tableStructure['Create Table'];
|
|
|
|
// Adapter les noms de colonnes selon le mapping
|
|
$mapping = $tableMappings[$tableName]['source_to_target'];
|
|
foreach ($mapping as $sourceCol => $targetCol) {
|
|
$createTableSql = str_replace("`$sourceCol`", "`$targetCol`", $createTableSql);
|
|
}
|
|
|
|
// Remplacer le nom de la clé primaire si nécessaire
|
|
$sourcePk = $tableMappings[$tableName]['primary_key'];
|
|
$targetPk = $tableMappings[$tableName]['target_primary_key'];
|
|
if ($sourcePk !== $targetPk) {
|
|
$createTableSql = str_replace("PRIMARY KEY (`$sourcePk`)", "PRIMARY KEY (`$targetPk`)", $createTableSql);
|
|
}
|
|
|
|
// Créer la table
|
|
$targetDb->exec($createTableSql);
|
|
logOperation("Table $tableName créée avec succès");
|
|
} else {
|
|
throw new Exception("Impossible de récupérer la structure de la table source");
|
|
}
|
|
}
|
|
|
|
// Vider la table cible si l'option est activée
|
|
if ($truncateTable && $tableExists) {
|
|
logOperation("Vidage de la table cible $tableName...");
|
|
$targetDb->exec("TRUNCATE TABLE $tableName");
|
|
}
|
|
|
|
// Construire la requête d'insertion dynamiquement
|
|
$mapping = $tableMappings[$tableName]['source_to_target'];
|
|
|
|
// Récupérer les colonnes de la table cible
|
|
$stmt = $targetDb->query("DESCRIBE $tableName");
|
|
$targetColumns = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
// Construire les listes de colonnes pour l'insertion
|
|
$insertColumns = [];
|
|
$insertPlaceholders = [];
|
|
$updateClauses = [];
|
|
|
|
foreach ($targetColumns as $column) {
|
|
$insertColumns[] = "`$column`";
|
|
$insertPlaceholders[] = ":$column";
|
|
$updateClauses[] = "`$column` = VALUES(`$column`)";
|
|
}
|
|
|
|
$insertColumnsSql = implode(", ", $insertColumns);
|
|
$insertPlaceholdersSql = implode(", ", $insertPlaceholders);
|
|
$updateClausesSql = implode(", ", $updateClauses);
|
|
|
|
$insertQuery = "INSERT INTO $tableName ($insertColumnsSql) "
|
|
. "VALUES ($insertPlaceholdersSql) "
|
|
. "ON DUPLICATE KEY UPDATE $updateClausesSql";
|
|
|
|
$insertStmt = $targetDb->prepare($insertQuery);
|
|
|
|
// Compteurs
|
|
$successCount = 0;
|
|
$errorCount = 0;
|
|
|
|
// Traitement de chaque enregistrement
|
|
foreach ($records as $record) {
|
|
try {
|
|
$data = [];
|
|
|
|
// Mappage des colonnes selon la configuration
|
|
foreach ($targetColumns as $targetCol) {
|
|
// Trouver la colonne source correspondante
|
|
$sourceCol = array_search($targetCol, $mapping);
|
|
|
|
if ($sourceCol !== false) {
|
|
// La colonne existe dans le mapping
|
|
$data[$targetCol] = $record[$sourceCol];
|
|
} elseif (isset($record[$targetCol])) {
|
|
// La colonne a le même nom dans les deux tables
|
|
$data[$targetCol] = $record[$targetCol];
|
|
} else {
|
|
// Colonne non trouvée, utiliser NULL ou une valeur par défaut
|
|
$data[$targetCol] = null;
|
|
}
|
|
}
|
|
|
|
// Traitement des champs chiffrés si nécessaire
|
|
if (isset($tableMappings[$tableName]['encrypted_fields']) && $apiService) {
|
|
foreach ($tableMappings[$tableName]['encrypted_fields'] as $sourceField => $config) {
|
|
$targetField = $config['field'];
|
|
$isSearchable = $config['searchable'];
|
|
|
|
if (isset($record[$sourceField]) && !empty($record[$sourceField])) {
|
|
// Vérifier si le champ est déjà chiffré
|
|
if (isset($record["encrypted_$sourceField"])) {
|
|
$data[$targetField] = $record["encrypted_$sourceField"];
|
|
} else {
|
|
// Chiffrer la donnée selon qu'elle est recherchable ou non
|
|
if ($isSearchable) {
|
|
$data[$targetField] = $apiService->encryptSearchableData($record[$sourceField]);
|
|
} else {
|
|
$data[$targetField] = $apiService->encryptData($record[$sourceField]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gestion spécifique pour certaines tables
|
|
if ($tableName === 'users') {
|
|
// Conversion de chk_active (0,1,2) vers is_active (booléen)
|
|
if (isset($record['chk_active'])) {
|
|
$data['is_active'] = ($record['chk_active'] > 0) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
// Insertion dans la base cible
|
|
$insertStmt->execute($data);
|
|
$successCount++;
|
|
|
|
// Identifiant pour le log
|
|
$pkField = $tableMappings[$tableName]['primary_key'];
|
|
$recordId = $record[$pkField] ?? 'inconnu';
|
|
|
|
logOperation("Enregistrement ID $recordId migré avec succès", "INFO");
|
|
} catch (Exception $e) {
|
|
$errorCount++;
|
|
$pkField = $tableMappings[$tableName]['primary_key'];
|
|
$recordId = $record[$pkField] ?? 'inconnu';
|
|
|
|
logOperation("Erreur lors de la migration de l'enregistrement ID $recordId: " . $e->getMessage(), "ERROR");
|
|
}
|
|
}
|
|
|
|
logOperation("Migration terminée. Succès: $successCount, Erreurs: $errorCount");
|
|
|
|
// Fermer le tunnel SSH
|
|
closeSshTunnel();
|
|
|
|
} catch (Exception $e) {
|
|
logOperation("Erreur critique: " . $e->getMessage(), "ERROR");
|
|
|
|
// Fermer le tunnel SSH en cas d'erreur
|
|
closeSshTunnel();
|
|
|
|
exit(1);
|
|
}
|