From fa6521f8f5964933c02354a2998ccc773baf0934 Mon Sep 17 00:00:00 2001 From: "fefe.clochette" Date: Sun, 21 Jun 2026 10:57:24 +0200 Subject: [PATCH] multi cat recap budget --- modules/budget/views/recap.php | 34 ++++++++++++++--- modules/budget/views/suivi.php | 68 +++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/modules/budget/views/recap.php b/modules/budget/views/recap.php index d9bd92a..a69b24d 100644 --- a/modules/budget/views/recap.php +++ b/modules/budget/views/recap.php @@ -154,6 +154,20 @@ foreach ($items as $item): } } + // 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))); @@ -265,11 +279,12 @@ foreach ($items as $item):
- - - + @@ -327,7 +342,11 @@ function editRecapItem(item) { document.getElementById("item_name").value = data.name; document.getElementById("item_keywords").value = data.mapping_keywords || ''; document.getElementById("item_amount").value = Math.abs(data.amount); - document.getElementById("item_category").value = data.category; + const selectedCats = data.category ? data.category.split(',') : []; + const selectEl = document.getElementById("item_category"); + Array.from(selectEl.options).forEach(opt => { + opt.selected = selectedCats.includes(opt.value); + }); document.getElementById("item_type").value = data.type; document.getElementById("item_is_estimate").value = data.is_estimate; @@ -341,6 +360,11 @@ document.getElementById('recapForm').addEventListener('submit', async function(e btn.disabled = true; btn.innerText = '⏳ ...'; const formData = new FormData(this); + + const cats = formData.getAll('category_multi[]'); + formData.delete('category_multi[]'); + formData.append('category', cats.join(',')); + formData.append('ajax', '1'); const url = this.getAttribute('action'); diff --git a/modules/budget/views/suivi.php b/modules/budget/views/suivi.php index 8b598f9..438695a 100644 --- a/modules/budget/views/suivi.php +++ b/modules/budget/views/suivi.php @@ -237,12 +237,15 @@ $fixedChargesList = []; $incomeList = []; $pending_charges = []; // ============================================================================ // MAPPING DYNAMIQUE DES BUDGETS PRÉVISIONNELS (NOUVELLE LOGIQUE) // ============================================================================ -$stmt = $pdo->query("SELECT id, name, amount, type, category, is_estimate, payment_day, mapping_keywords FROM pf_budget_items ORDER BY name ASC"); +// On exclut "SAVINGS" pour s'aligner à 100% avec le recap.php +$stmt = $pdo->query("SELECT id, name, amount, type, category, is_estimate, payment_day, mapping_keywords FROM pf_budget_items WHERE category != 'SAVINGS' ORDER BY name ASC"); +$estimatesList = []; // On prépare la liste pour retenir nos estimations multi-catégories + while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) { $absAmount = abs((float)$item['amount']); $amt = ($item['type'] === 'Annuel') ? $absAmount / 12 : $absAmount; $name = trim($item['name']); - $catCode = $item['category']; // Ex: FIXED, FMCG, INCOME ou income + $catCode = $item['category']; $isIncome = (strtoupper($catCode) === 'INCOME'); @@ -251,18 +254,16 @@ while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) { $total_income += $amt; $budget_income_prevu += $amt; - // Attribution au compte de revenu défini, sinon au premier disponible if (!empty($catCode) && isset($categoriesConfig[$catCode])) { $categoriesConfig[$catCode]['budget'] += $amt; } else { $incomeCatKey = array_key_first(array_filter($categoriesConfig, fn($c) => $c['db_type'] === 'Income')); if ($incomeCatKey) $categoriesConfig[$incomeCatKey]['budget'] += $amt; } - } else { $total_expenses_prevues += $amt; - // C'est une charge fixe (Mensuel + Is_Estimate = 0) + // 1. C'est une charge fixe (Non-variable) if ($item['type'] === 'Mensuel' && (int)$item['is_estimate'] === 0) { $fixedChargesList[] = ['id' => $item['id'], 'name' => $name, 'amount' => $absAmount]; @@ -279,13 +280,28 @@ while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) { } if (!$isPaid) { $reste_a_venir_calc += $absAmount; - $pending_charges[] = ['name' => $name, 'amount' => $absAmount]; + // Ajouté dans le tableau avec un tooltip vide + $pending_charges[] = ['name' => $name, 'amount' => $absAmount, 'tooltip' => 'Charge fixe en attente']; } } - // NOUVEAU : Attribution du budget prévisionnel (le Plafond) via la catégorie dynamique - if (!empty($catCode) && isset($categoriesConfig[$catCode])) { - $categoriesConfig[$catCode]['budget'] += $amt; + // 2. C'est une estimation (Variable) + if ((int)$item['is_estimate'] === 1) { + $estimatesList[] = [ + 'name' => $name, + 'amount' => $amt, + 'categories' => !empty($catCode) ? array_map('trim', explode(',', $catCode)) : [] + ]; + } + + // 3. Attribution du budget aux jauges visuelles de la page + // Pour ne pas tout casser, on donne tout le plafond visuel à la 1ère catégorie de la liste + if (!empty($catCode)) { + $catCodesArray = array_map('trim', explode(',', $catCode)); + $firstCat = $catCodesArray[0]; + if (isset($categoriesConfig[$firstCat])) { + $categoriesConfig[$firstCat]['budget'] += $amt; + } } } } @@ -331,16 +347,32 @@ foreach ($allExpenses as $exp) { } } -// RESTE A VENIR PAR CATEGORIE (Remplace les variables codées en dur) -foreach ($categoriesConfig as $code => $conf) { - if ($conf['db_type'] === 'Expense' && $conf['budget'] > 0) { - $spent = $totals[$code] ?? 0; - $rem = max(0, $conf['budget'] - $spent); - if ($rem > 0) { - $reste_a_venir_calc += $rem; - $pending_charges[] = ['name' => 'Reste ' . strip_tags($conf['label']), 'amount' => $rem]; +// CALCUL DES RESTES À VENIR BASÉ SUR LES ESTIMATIONS (MACRO) +foreach ($estimatesList as $est) { + $spentForEstimate = 0; + $detailsHover = []; + + // On additionne les dépenses de toutes les catégories liées à cette estimation + foreach ($est['categories'] as $cCode) { + if (!empty($cCode) && isset($totals[$cCode])) { + $spentForEstimate += $totals[$cCode]; + // On prépare le détail pour la petite bulle d'info (hover) + if ($totals[$cCode] > 0 && isset($categoriesConfig[$cCode])) { + $detailsHover[] = strip_tags($categoriesConfig[$cCode]['label']) . " : " . number_format($totals[$cCode], 0) . "€"; + } } } + + $rem = max(0, $est['amount'] - $spentForEstimate); + if ($rem > 0) { + $reste_a_venir_calc += $rem; + $tooltip = !empty($detailsHover) ? implode(' | ', $detailsHover) : 'Aucune dépense pour le moment'; + $pending_charges[] = [ + 'name' => 'Reste ' . $est['name'], + 'amount' => $rem, + 'tooltip' => $tooltip + ]; + } } // F. Calculs des KPIs finaux @@ -468,7 +500,7 @@ $monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
-
+
>