diff --git a/modules/budget/views/suivi.php b/modules/budget/views/suivi.php index fd36c7c..4fb4f4d 100644 --- a/modules/budget/views/suivi.php +++ b/modules/budget/views/suivi.php @@ -26,22 +26,20 @@ if (isset($_GET['del_cat'])) { header("Location: ?tab=suivi"); exit; } -// C. SAUVEGARDE SNAPSHOT BANCAIRE (MODIFIÉ : Remplacement de l'ancien) +// C. SAUVEGARDE SNAPSHOT BANCAIRE if (isset($_POST['action']) && $_POST['action'] === 'save_snapshot') { $date = $_POST['snapshot_date']; $amount = floatval($_POST['snapshot_amount']); - - // On vide la table pour que le nouveau remplace l'ancien - $pdo->query("DELETE FROM pf_bank_snapshots"); - + $pdo->query("DELETE FROM pf_bank_snapshots"); $pdo->prepare("INSERT INTO pf_bank_snapshots (snapshot_date, amount) VALUES (?, ?)")->execute([$date, $amount]); header("Location: ?tab=suivi"); exit; } -// D. SAUVEGARDE IMPORT CSV +// D. SAUVEGARDE IMPORT CSV (AVEC BUDGET_ITEM_ID) if (isset($_POST['action']) && $_POST['action'] === 'save_import') { $count = 0; + // 1. Catégories temporaires $tempCatMapping = []; if (!empty($_POST['new_temp_cats'])) { $stmtTemp = $pdo->prepare("INSERT INTO pf_monthly_categories (month_year, name, type, budget) VALUES (?, ?, ?, 0)"); @@ -51,7 +49,8 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') { } } - $stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref) VALUES (?, ?, ?, ?, ?)"); + // 2. Insertion avec budget_item_id + $stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id) VALUES (?, ?, ?, ?, ?, ?)"); $stmtRule = $pdo->prepare("INSERT INTO pf_import_rules (keyword, category) VALUES (?, ?) ON DUPLICATE KEY UPDATE category = VALUES(category)"); if (isset($_POST['lines']) && is_array($_POST['lines'])) { @@ -59,6 +58,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') { if (isset($line['import_check'])) { $cat = $line['cat']; $is_credit = isset($line['is_credit']) ? (int)$line['is_credit'] : 0; + $budgetItemId = !empty($line['budget_item_id']) ? (int)$line['budget_item_id'] : null; if ($is_credit && empty($cat)) continue; if (!$is_credit && empty($cat)) continue; @@ -70,7 +70,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') { $finalAmount = $is_credit ? -abs($line['amount']) : abs($line['amount']); try { - $stmtExp->execute([$line['date'], $cat, $line['label'], $finalAmount, $line['ref']]); + $stmtExp->execute([$line['date'], $cat, $line['label'], $finalAmount, $line['ref'], $budgetItemId]); $stmtRule->execute([$line['label'], $cat]); $count++; } catch (Exception $e) { continue; } @@ -80,18 +80,26 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') { header("Location: ?tab=suivi&msg=imported_$count"); exit; } -// E. AJOUT DÉPENSE MANUELLE +// E. AJOUT DÉPENSE MANUELLE (AVEC BUDGET_ITEM_ID) if (isset($_POST['action']) && $_POST['action'] === 'add_expense') { $cat = $_POST['category']; $amount = floatval($_POST['amount']); $date = $_POST['date']; - $label = ($cat === 'School' && !empty($_POST['label_select'])) ? trim($_POST['label_select']) : trim($_POST['label']); + $label = trim($_POST['label']); + $budgetItemId = null; + + if ($cat === 'School' && !empty($_POST['label_select'])) { + $label = trim($_POST['label_select']); + } + elseif ($cat === 'Frais' && !empty($_POST['budget_item_id'])) { + $budgetItemId = (int)$_POST['budget_item_id']; + } if ($label && $amount > 0) { $uniqueRef = "MANUAL_" . uniqid(); - $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref) VALUES (?, ?, ?, ?, ?)") - ->execute([$date, $cat, $label, $amount, $uniqueRef]); + $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id) VALUES (?, ?, ?, ?, ?, ?)") + ->execute([$date, $cat, $label, $amount, $uniqueRef, $budgetItemId]); header("Location: ?tab=suivi"); exit; } } @@ -103,7 +111,7 @@ if (isset($_GET['delete_expense'])) { } // ============================================================================ -// 2. CALCUL DES BUDGETS & SNAPSHOT +// 2. CALCUL DES BUDGETS & CHARGES FIXES // ============================================================================ $budget_fmcg = 0; $budget_school = 0; $budget_essence = 0; $budget_frais = 0; @@ -111,49 +119,51 @@ $total_income = 0; $total_expenses_prevues = 0; $reste_a_venir = 0; $today_day = (int)date('j'); -// --- SNAPSHOT & SOLDE THÉORIQUE --- +// Liste des charges pour le selecteur +$fixedChargesList = []; + +// Snapshot $snapshot = ['date' => date('Y-m-d'), 'amount' => 0]; $solde_theorique = 0; - try { - $snapStmt = $pdo->query("SELECT * FROM pf_bank_snapshots ORDER BY snapshot_date DESC, id DESC LIMIT 1"); + $snapStmt = $pdo->query("SELECT * FROM pf_bank_snapshots ORDER BY id DESC LIMIT 1"); if ($s = $snapStmt->fetch(PDO::FETCH_ASSOC)) { $snapshot = ['date' => $s['snapshot_date'], 'amount' => (float)$s['amount']]; } } catch (Exception $e) {} - $solde_theorique = $snapshot['amount']; - -// Calcul du solde théorique : On soustrait les opérations saisies APRÈS la date du snapshot if (!empty($snapshot['date'])) { try { $stmtCalc = $pdo->prepare("SELECT SUM(amount) as total_diff FROM pf_expenses WHERE date_exp > ?"); $stmtCalc->execute([$snapshot['date']]); $resDiff = $stmtCalc->fetch(PDO::FETCH_ASSOC); - if ($resDiff && $resDiff['total_diff'] !== null) { - // amount est positif pour les débits, négatif pour les crédits - // On soustrait donc le total diff. (Ex: 3500 - 50 = 3450) - $solde_theorique -= (float)$resDiff['total_diff']; - } + if ($resDiff && $resDiff['total_diff'] !== null) $solde_theorique -= (float)$resDiff['total_diff']; } catch (Exception $e) {} } - -// --- LECTURE BUDGET PREVISIONNEL --- -$stmt = $pdo->query("SELECT name, amount, type, category, is_estimate, payment_day FROM pf_budget_items"); +// Lecture Budget - Ajout de 'is_checked' à la requête +$stmt = $pdo->query("SELECT id, name, amount, type, category, is_estimate, payment_day, is_checked FROM pf_budget_items ORDER BY name ASC"); while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) { $rawAmount = (float)$item['amount']; $amt = ($item['type'] === 'Annuel') ? $rawAmount / 12 : $rawAmount; $name = trim($item['name']); $pDay = (int)$item['payment_day']; + $isChecked = (int)$item['is_checked']; + // Remplissage liste Charges Fixes + if ($item['category'] === 'expense' && $item['type'] === 'Mensuel' && (int)$item['is_estimate'] === 0) { + $fixedChargesList[] = $item; + } + if ($item['category'] === 'income') { $total_income += $amt; } else { $total_expenses_prevues += $amt; + // --- NOUVEAU CALCUL RESTE A VENIR --- + // On additionne si c'est une dépense mensuelle NON cochée if ($item['category'] === 'expense' && $item['type'] === 'Mensuel') { - if ($name === 'Estimacio escola' || $pDay > $today_day) { + if ($isChecked === 0) { $reste_a_venir += $rawAmount; } } @@ -173,13 +183,7 @@ try { $stmt = $pdo->prepare("SELECT * FROM pf_monthly_categories WHERE month_year = ?"); $stmt->execute([$currentMonthKey]); $tempCats = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach($tempCats as $tc) { - // On n'ajoute au "budget prévisionnel" que si c'est un débit. - // Si c'est un crédit (réserve), l'argent est censé déjà être ou arriver sur le compte - if ($tc['type'] === 'debit') { - $total_temp_budget += $tc['budget']; - } - } + foreach($tempCats as $tc) if ($tc['type'] === 'debit') $total_temp_budget += $tc['budget']; } catch (Exception $e) {} $budget_autres = $total_income - ($total_expenses_prevues + $total_temp_budget); @@ -193,27 +197,17 @@ $categoriesConfig = [ 'FMCG' => ['type'=>'debit', 'label'=>'Courses (FMCG)', 'budget'=>$budget_fmcg, 'color'=>'#3b82f6', 'suggestions'=>['Action', 'Carrefour', 'Lidl']], 'Essence' => ['type'=>'debit', 'label'=>'Essence', 'budget'=>$budget_essence, 'color'=>'#f59e0b', 'suggestions'=>['Audi', 'Polo']], 'School' => ['type'=>'debit', 'label'=>'École / Garde', 'budget'=>$budget_school, 'color'=>'#10b981', 'suggestions'=>[]], - 'Frais' => ['type'=>'debit', 'label'=>'Charges Fixes', 'budget'=>$budget_frais, 'color'=>'#ef4444', 'suggestions'=>['Netflix', 'Assurance', 'Prêt']], + 'Frais' => ['type'=>'debit', 'label'=>'Charges Fixes', 'budget'=>$budget_frais, 'color'=>'#ef4444', 'suggestions'=>[]], // Suggestions vides car gérées par ID ]; $tempColors = ['#ec4899', '#06b6d4', '#84cc16', '#d946ef', '#f97316']; $colorIdx = 0; - foreach ($tempCats as $tc) { $catKey = 'TEMP_' . $tc['id']; - $catType = isset($tc['type']) ? $tc['type'] : 'debit'; $categoriesConfig[$catKey] = [ - 'type' => $catType, - 'label' => $tc['name'], - 'budget' => $tc['budget'], - 'color' => $tempColors[$colorIdx % count($tempColors)], - 'suggestions' => [], - 'is_temp' => true, - 'id' => $tc['id'] + 'type' => $tc['type'], 'label' => $tc['name'], 'budget' => $tc['budget'], 'color' => $tempColors[$colorIdx++ % count($tempColors)], 'suggestions' => [], 'is_temp' => true, 'id' => $tc['id'] ]; - $colorIdx++; } - $categoriesConfig['Autres'] = ['type'=>'debit', 'label'=>'Autres / Imprévus', 'budget'=>$budget_autres, 'color'=>'#64748b', 'suggestions'=>['Restaurant', 'Cadeau']]; $categoriesConfig['LivretA'] = ['type'=>'debit', 'label'=>'Epargne', 'budget'=>0, 'color'=>'#8b5cf6', 'suggestions'=>['Virement']]; @@ -226,41 +220,24 @@ $showPreview = false; if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] == 0) { $file = $_FILES['csv_file']['tmp_name']; $handle = fopen($file, "r"); - - $rules = []; - try { $rules = $pdo->query("SELECT keyword, category FROM pf_import_rules")->fetchAll(PDO::FETCH_KEY_PAIR); } catch(Exception $e){} - - $existingRefs = []; - try { $existingRefs = $pdo->query("SELECT import_ref FROM pf_expenses WHERE import_ref IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN); } catch(Exception $e){} - - // CORRECTION ICI : Ajout des paramètres explicitement : fgetcsv(stream, length, separator, enclosure, escape) + $rules = []; try { $rules = $pdo->query("SELECT keyword, category FROM pf_import_rules")->fetchAll(PDO::FETCH_KEY_PAIR); } catch(Exception $e){} + $existingRefs = []; try { $existingRefs = $pdo->query("SELECT import_ref FROM pf_expenses WHERE import_ref IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN); } catch(Exception $e){} fgetcsv($handle, 1000, ";", "\"", "\\"); - while (($data = fgetcsv($handle, 1000, ";", "\"", "\\")) !== FALSE) { - $rawDebit = $data[8] ?? ''; - $rawCredit = $data[9] ?? ''; - + $rawDebit = $data[8] ?? ''; $rawCredit = $data[9] ?? ''; $amount = 0; $isCredit = 0; - if (!empty(trim($rawCredit))) { - $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawCredit))); - $isCredit = 1; - } elseif (!empty(trim($rawDebit))) { - $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawDebit))); - } else { - continue; - } + if (!empty(trim($rawCredit))) { $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawCredit))); $isCredit = 1; } + elseif (!empty(trim($rawDebit))) { $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawDebit))); } + else continue; $dateParts = explode('/', $data[0]); $dateSql = (count($dateParts) == 3) ? $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0] : date('Y-m-d'); $label = trim($data[1]) ?: trim($data[2]); $refCSV = trim($data[3]); - $uniqueKey = !empty($refCSV) ? "REF_".$refCSV : "HASH_".md5($dateSql.$label.number_format($amount, 2).$isCredit); $isDuplicate = in_array($uniqueKey, $existingRefs); - $suggestedCat = ''; foreach ($rules as $kw => $c) { if (stripos($label, $kw) !== false) { $suggestedCat = $c; break; } } - $csvData[] = ['date'=>$dateSql, 'label'=>$label, 'amount'=>$amount, 'cat'=>$suggestedCat, 'ref'=>$uniqueKey, 'is_duplicate'=>$isDuplicate, 'is_credit'=>$isCredit]; } fclose($handle); @@ -280,12 +257,8 @@ foreach ($allExpenses as $exp) { $cat = $exp['category']; if (!isset($totals[$cat])) $cat = 'Autres'; - // Si la dépense est un crédit (montant < 0 en bdd), on augmente l'enveloppe - if ($exp['amount'] < 0) { - $categoriesConfig[$cat]['budget'] += abs($exp['amount']); - } else { - $totals[$cat] += $exp['amount']; - } + if ($exp['amount'] < 0) $categoriesConfig[$cat]['budget'] += abs($exp['amount']); + else $totals[$cat] += $exp['amount']; $expensesByCategory[$cat][] = $exp; } @@ -293,7 +266,6 @@ foreach ($allExpenses as $exp) { $globalSpent = array_sum($totals); $globalBudget = array_sum(array_column($categoriesConfig, 'budget')); -// --- FONCTION D'AFFICHAGE --- function getDisplayLogic($spent, $bg, $type) { if ($type === 'credit') { $remaining = $bg - $spent; @@ -340,7 +312,6 @@ function getDisplayLogic($spent, $bg, $type) { Solde théorique au = date('d/m') ?> : = number_format($solde_theorique, 2, ',', ' ') ?> € -