🚀 Début de la migration Multi-Tenant (Auto-Sync)";
// ---------------------------------------------------------
// 1. LECTURE ET PARSING DU FICHIER SCHEMA_FAMILY.SQL
// ---------------------------------------------------------
$schemaPath = __DIR__ . '/schema_family.sql'; // Modifie si rangé dans /docker/
if (!file_exists($schemaPath)) {
$schemaPath = __DIR__ . '/docker/schema_family.sql';
}
if (!file_exists($schemaPath)) {
die("❌ Impossible de trouver le fichier schema_family.sql");
}
$sqlContent = file_get_contents($schemaPath);
/**
* Analyse le code SQL pour en extraire la structure [Table => [Colonnes]]
*/
function parseExpectedSchema($sql) {
$schema = [];
// Récupère tout ce qui se trouve entre CREATE TABLE (...) ENGINE
preg_match_all('/CREATE TABLE (?:IF NOT EXISTS )?`?([a-zA-Z0-9_]+)`?\s*\((.*?)\)\s*ENGINE/si', $sql, $tableMatches, PREG_SET_ORDER);
foreach($tableMatches as $match) {
$tableName = $match[1];
$body = $match[2];
// Sépare les lignes par virgule, en ignorant les virgules entre parenthèses (ex: DECIMAL(10,2) ou ENUM('a','b'))
$lines = preg_split('/,(?![^\(]*\))/', $body);
$columns = [];
foreach($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
// On ignore la déclaration des clés, contraintes et index
if (preg_match('/^(PRIMARY KEY|UNIQUE KEY|FOREIGN KEY|KEY|INDEX|FULLTEXT KEY|CONSTRAINT)\b/i', $line)) {
continue;
}
// Extrait le nom de la colonne et sa définition SQL
if (preg_match('/^`?([a-zA-Z0-9_]+)`?\s+(.*)$/i', $line, $colMatch)) {
$colName = $colMatch[1];
$colDef = rtrim($colMatch[2], ',');
$columns[$colName] = $colDef;
}
}
$schema[$tableName] = $columns;
}
return $schema;
}
$expectedSchema = parseExpectedSchema($sqlContent);
echo "ℹ️ Modèle SQL chargé et analysé : " . count($expectedSchema) . " tables détectées.
";
try {
$stmt = $meta_pdo->query("SELECT db_name, name FROM families WHERE is_active = 1");
$families = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($families as $family) {
$dbName = $family['db_name'];
echo "🏡 Famille : {$family['name']} ($dbName)
";
try {
$pdo = new PDO(
"mysql:host=$db_host;dbname=$dbName;charset=utf8mb4",
$db_user, $db_pass,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
// ---------------------------------------------------------
// 2. EXÉCUTION DU SQL (CRÉATION TABLES MANQUANTES + DONNÉES PAR DÉFAUT)
// ---------------------------------------------------------
try {
$pdo->exec($sqlContent);
echo "✅ Structure de base validée (les tables manquantes ont été créées).
";
} catch (PDOException $e) {
echo "⚠️ Avertissement SQL brut : " . $e->getMessage() . "
";
}
// ---------------------------------------------------------
// 3. DIFFING (AJOUT DYNAMIQUE DES COLONNES MANQUANTES)
// ---------------------------------------------------------
$colsAdded = 0;
foreach ($expectedSchema as $table => $expectedCols) {
$stmtCol = $pdo->query("SHOW COLUMNS FROM `$table`");
$existingCols = $stmtCol->fetchAll(PDO::FETCH_COLUMN);
foreach ($expectedCols as $colName => $colDef) {
if (!in_array($colName, $existingCols)) {
$pdo->exec("ALTER TABLE `$table` ADD COLUMN `$colName` $colDef");
echo "➕ Nouvelle colonne ajoutée : $table.$colName
";
$colsAdded++;
}
}
}
if ($colsAdded === 0) {
echo "✅ Toutes les colonnes de toutes les tables sont déjà à jour.
";
}
// ---------------------------------------------------------
// 4. MIGRATION DES DONNÉES (TRANSFORMATIONS HISTORIQUES)
// ---------------------------------------------------------
echo "🔄 Exécution des transformations de données héritées...
";
// A. Changement de types spécifiques (Car le parseur n'ajoute que les colonnes manquantes)
$pdo->exec("ALTER TABLE pf_budget_items MODIFY category VARCHAR(100) DEFAULT NULL");
// B. Migration de l'historique calendrier (Enfants & Helpers)
$helperId = $pdo->query("SELECT id FROM pf_people WHERE role = 'helper' LIMIT 1")->fetchColumn();
if ($helperId) {
$pdo->exec("UPDATE pf_events SET event_type = 'HELPER_OFF', person_id = $helperId WHERE event_type = 'OFF_CAROLE'");
$pdo->exec("UPDATE pf_events SET event_type = 'HELPER_EXTRA', person_id = $helperId WHERE event_type = 'EXTRA_OFF_CAROLE'");
}
$kidId = $pdo->query("SELECT id FROM pf_people WHERE role IN ('child', 'enfant') ORDER BY id ASC LIMIT 1")->fetchColumn();
if ($kidId) {
$pdo->exec("UPDATE pf_events SET event_type = 'CHILD_SICK', person_id = $kidId WHERE event_type = 'PEP_SICK'");
}
// C. Mapping Intelligent du Budget (is_estimate)
$itemsMigres = 0;
$itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'FIXED' WHERE category = 'expense' AND is_estimate = 0");
$itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'FUEL' WHERE category = 'expense' AND is_estimate = 1 AND (mapping_keywords LIKE '%ESSENCE%' OR name LIKE '%gasolina%')");
$itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'SCHOOL' WHERE category = 'expense' AND is_estimate = 1 AND (mapping_keywords LIKE '%ESCOLA%' OR mapping_keywords LIKE '%PARASCOL%' OR name LIKE '%escola%')");
$itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'FMCG' WHERE category = 'expense' AND is_estimate = 1 AND (mapping_keywords LIKE '%FMCG%' OR name LIKE '%F&B%')");
$itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'AUTRES' WHERE category = 'expense'");
$itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'INCOME' WHERE category = 'income'");
// D. Nettoyage des virgules orphelines dans mapping_keywords
$budgetCodes = ['INCOME', 'FMCG', 'FUEL', 'SCHOOL', 'HEALTH', 'FIXED', 'SAVINGS', 'AUTRES'];
foreach ($budgetCodes as $code) {
$pdo->exec("UPDATE pf_budget_items SET mapping_keywords = REPLACE(mapping_keywords, '$code', '') WHERE mapping_keywords LIKE '%$code%'");
}
$pdo->exec("UPDATE pf_budget_items SET mapping_keywords = TRIM(BOTH ',' FROM REPLACE(REPLACE(mapping_keywords, ' ', ''), ',,', ','))");
// E. Migration de l'historique des dépenses et des règles d'import
$budgetMapping = [
'Income' => 'INCOME',
'FMCG' => 'FMCG',
'Essence' => 'FUEL',
'School' => 'SCHOOL',
'Frais' => 'FIXED',
'LivretA' => 'SAVINGS',
'Autres' => 'AUTRES'
];
$stmtExp = $pdo->prepare("UPDATE pf_expenses SET category = ? WHERE category = ?");
$stmtRules = $pdo->prepare("UPDATE pf_import_rules SET category = ? WHERE category = ?");
foreach ($budgetMapping as $old => $newCode) {
$stmtExp->execute([$newCode, $old]);
$stmtRules->execute([$newCode, $old]);
}
echo "✅ Transformations Data terminées ($itemsMigres prévisions remappées).
";
} catch (PDOException $e) {
echo "❌ Erreur sur la base $dbName : " . $e->getMessage() . "
";
}
} // FIN DE LA BOUCLE FOREACH FAMILLES
echo "🎉 Synchronisation et Migration terminées avec succès !
";
} catch (Exception $e) {
die("❌ Erreur fatale Meta DB : " . $e->getMessage());
}
?>