query("SELECT code, label, icon, type FROM pf_budget_categories ORDER BY type DESC, label ASC"); $dbCategories = $stmtCats->fetchAll(PDO::FETCH_ASSOC); // 2. Récupération des Salaires/Mensualités configurés (Revenus automatiques) $currentYear = isset($_GET['y']) ? (int)$_GET['y'] : date('Y'); $stmtSalaries = $pdo->prepare("SELECT id, person, mensualite FROM pf_salary_config WHERE year = ?"); $stmtSalaries->execute([$currentYear]); $salaries = $stmtSalaries->fetchAll(PDO::FETCH_ASSOC); // 3. Récupération des Items du Budget (Charges Fixes et Estimations) $stmt = $pdo->query("SELECT * FROM pf_budget_items WHERE category != 'SAVINGS' ORDER BY is_estimate ASC, sort_order ASC, name ASC"); $items = $stmt->fetchAll(PDO::FETCH_ASSOC); // 4. Gestion du mois actif $stmtActive = $pdo->query("SELECT content FROM pf_notes WHERE note_type = 'active_gestion_month' LIMIT 1"); $defaultActiveMonth = $stmtActive->fetchColumn() ?: date('Y-m-01'); $currentMonth = isset($_GET['m']) ? str_pad((int)$_GET['m'], 2, '0', STR_PAD_LEFT) : date('m', strtotime($defaultActiveMonth)); $viewMonthDate = "$currentYear-$currentMonth-01"; // 5. Récupération optimisée et unifiée du Réel // On récupère tout en une seule requête pour éviter les décalages entre ID et Catégorie $stmtReal = $pdo->prepare(" SELECT budget_item_id, category, SUM(amount) as total_real FROM pf_expenses WHERE gestion_month = ? GROUP BY budget_item_id, category "); $stmtReal->execute([$viewMonthDate]); $allExpenses = $stmtReal->fetchAll(PDO::FETCH_ASSOC); // On initialise les tableaux de correspondance $realTotalsById = []; // Pour les lignes liées par ID $realTotalsByCat = []; // Pour les lignes orphelines (NULL) liées par Catégorie foreach ($allExpenses as $row) { if (!empty($row['budget_item_id'])) { $realTotalsById[$row['budget_item_id']] = (float)$row['total_real']; } else { $realTotalsByCat[$row['category']] = (float)$row['total_real']; } } $stmtSalariesReal = $pdo->prepare("SELECT salary_id, SUM(amount) as total_salary FROM pf_expenses WHERE gestion_month = ? AND salary_id IS NOT NULL GROUP BY salary_id"); $stmtSalariesReal->execute([$viewMonthDate]); $realTotalsBySalary = $stmtSalariesReal->fetchAll(PDO::FETCH_KEY_PAIR); // Récupération des détails pour le mapping par mots-clés (si besoin de précision) $stmtLabels = $pdo->prepare("SELECT label, amount, category FROM pf_expenses WHERE gestion_month = ? AND budget_item_id IS NULL"); $stmtLabels->execute([$viewMonthDate]); $unlinkedExpenses = $stmtLabels->fetchAll(PDO::FETCH_ASSOC); // Gestion des libellés et totaux $currentMonthName = tr('month_' . str_pad((int)$currentMonth, 2, '0', STR_PAD_LEFT)) . ' ' . $currentYear; $totalDepenses = 0; $totalRevenus = 0; ?>

= ($mensualite - 0.10)); ?> getEstimate($item['dynamic_code']); $targetAbs = $estimateData['amount']; $calcDetails = $estimateData['details']; } // -------------------------------- $amountToAdd = ($item['type'] === 'Annuel') ? $targetAbs / 12 : $targetAbs; $totalDepenses += $amountToAdd; // 2. RÉINITIALISATION STRICTE $realSum = 0; $hasMatchingExpense = false; // A. Correspondance par ID direct (Le lien dur) if (isset($realTotalsById[$item['id']])) { $realSum = (float)$realTotalsById[$item['id']]; $hasMatchingExpense = true; } // B. Correspondance par mots-clés bancaires (Le lien intelligent pour les orphelines) elseif (!empty($item['mapping_keywords'])) { $keywords = array_map('trim', explode(',', $item['mapping_keywords'])); foreach ($unlinkedExpenses as $uexp) { foreach ($keywords as $kw) { if (!empty($kw) && stripos($uexp['label'], $kw) !== false) { $realSum += (float)$uexp['amount']; $hasMatchingExpense = true; break; // On a trouvé un mot clé sur cette dépense, on passe à l'orpheline suivante } } } } // C. Absorption des orphelines par catégories (Le lien Macro/Micro) if (!empty($item['category'])) { $catCodes = array_map('trim', explode(',', $item['category'])); foreach ($catCodes as $cCode) { // Si la catégorie existe dans les orphelines et n'a pas encore été consommée if (!empty($cCode) && isset($realTotalsByCat[$cCode]) && $realTotalsByCat[$cCode] != 0) { $realSum += $realTotalsByCat[$cCode]; $hasMatchingExpense = true; // On la vide pour ne pas la compter 2 fois si une autre ligne utilise la même catégorie $realTotalsByCat[$cCode] = 0; } } } // 3. Finalisation des variables d'affichage $realAbs = abs($realSum); $isAutoChecked = ($hasMatchingExpense && ($realAbs >= ($targetAbs - 0.10))); $rowClass = 'row-expense' . ($item['is_estimate'] ? ' row-estimate' : ''); ?>
()
Apport ⚙️ + € 0): ?> 0.05): ?>
Bonus : + €
Manque : €
Atteint ✓
Revenu Mensuel -
✓ Reçu
0): ?>
⏳ Partiel
○ En attente
Via Paramètres
(Variable)' : '' ?> 🔗 - € ℹ️ 0.05): ?>
Dépassé : + €
Reste : €
Atteint ✓
/mois
✓ Validé
⏳ Partiel
○ En attente
Total Revenus Lissés + €
Total Dépenses & Estimations - €
Reste à Vivre (Équilibre) € / mois