= 0) { $stmt = $pdo->prepare("INSERT INTO pf_monthly_categories (month_year, name, budget) VALUES (?, ?, ?)"); $stmt->execute([$currentMonthKey, $name, $budget]); header("Location: ?tab=suivi"); exit; } } // B. SUPPRESSION CATÉGORIE TEMPORAIRE if (isset($_GET['del_cat'])) { $id = (int)$_GET['del_cat']; $pdo->prepare("DELETE FROM pf_monthly_categories WHERE id = ?")->execute([$id]); header("Location: ?tab=suivi"); exit; } // C. SAUVEGARDE IMPORT CSV if (isset($_POST['action']) && $_POST['action'] === 'save_import') { $count = 0; $stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref) 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'])) { foreach ($_POST['lines'] as $line) { if (!empty($line['cat']) && isset($line['import_check'])) { try { $stmtExp->execute([$line['date'], $line['cat'], $line['label'], $line['amount'], $line['ref']]); $stmtRule->execute([$line['label'], $line['cat']]); $count++; } catch (Exception $e) { continue; } } } } header("Location: ?tab=suivi&msg=imported_$count"); exit; } // D. AJOUT DÉPENSE MANUELLE if (isset($_POST['action']) && $_POST['action'] === 'add_expense') { $cat = $_POST['category']; $amount = floatval($_POST['amount']); $date = $_POST['date']; // Logique pour le label : Soit liste fermée (School), soit texte libre if ($cat === 'School' && !empty($_POST['label_select'])) { $label = trim($_POST['label_select']); } else { $label = trim($_POST['label']); } 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]); header("Location: ?tab=suivi"); exit; } } // E. SUPPRESSION DÉPENSE if (isset($_GET['delete_expense'])) { $pdo->prepare("DELETE FROM pf_expenses WHERE id = ?")->execute([(int)$_GET['delete_expense']]); header("Location: ?tab=suivi"); exit; } // ============================================================================ // 2. CALCUL DES BUDGETS & INDICATEURS // ============================================================================ $budget_fmcg = 0; $budget_school = 0; $budget_essence = 0; $budget_frais = 0; $total_income = 0; $total_expenses_prevues = 0; $reste_a_venir = 0; // Somme des frais futurs $today_day = (int)date('j'); // Jour du mois (1 à 31) // 2.1 Récupération Budget Fixe $stmt = $pdo->query("SELECT name, amount, type, category, is_estimate, payment_day FROM pf_budget_items"); 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']; if ($item['category'] === 'income') { $total_income += $amt; } else { $total_expenses_prevues += $amt; // --- CALCUL DU "RESTE À VENIR" --- // Conditions : Expense + Mensuel + (Jour > Aujourd'hui OU C'est l'école) if ($item['category'] === 'expense' && $item['type'] === 'Mensuel') { // Si c'est l'école, on l'ajoute toujours (selon ta demande) if ($name === 'Estimacio escola') { $reste_a_venir += $rawAmount; } // Sinon, si c'est une autre dépense avec une date future elseif ($pDay > $today_day) { $reste_a_venir += $rawAmount; } } // --- MAPPING CATÉGORIES --- if ($name === 'Estimacio F&B & beauty') $budget_fmcg = $amt; elseif ($name === 'Estimacio escola') $budget_school = $amt; elseif ($name === 'Estimation gasolina') $budget_essence = $amt; elseif ((int)$item['is_estimate'] === 0 && $item['type'] === 'Mensuel' && $item['category'] === 'expense') { $budget_frais += $rawAmount; } } } // 2.2 Récupération Catégories Temporaires $tempCats = []; $total_temp_budget = 0; 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) $total_temp_budget += $tc['budget']; } catch (Exception $e) {} // 2.3 Calcul Reste à vivre $budget_autres = $total_income - ($total_expenses_prevues + $total_temp_budget); if ($budget_autres < 0) $budget_autres = 0; // ============================================================================ // 3. CONFIGURATION CATÉGORIES // ============================================================================ $categoriesConfig = [ 'FMCG' => ['label' => 'Courses (FMCG)', 'budget' => $budget_fmcg, 'color' => '#3b82f6', 'suggestions' => ['Action', 'Carrefour', 'Lidl']], 'Essence' => ['label' => 'Essence', 'budget' => $budget_essence, 'color' => '#f59e0b', 'suggestions' => ['Audi', 'Polo']], 'School' => ['label' => 'École / Garde', 'budget' => $budget_school, 'color' => '#10b981', 'suggestions' => []], // Liste fermée gérée en JS 'Frais' => ['label' => 'Charges Fixes', 'budget' => $budget_frais, 'color' => '#ef4444', 'suggestions' => ['Netflix', 'Assurance', 'Prêt']], ]; // Couleurs temporaires $tempColors = ['#ec4899', '#06b6d4', '#84cc16', '#d946ef', '#f97316']; $colorIdx = 0; foreach ($tempCats as $tc) { $catKey = 'TEMP_' . $tc['id']; $categoriesConfig[$catKey] = [ 'label' => $tc['name'], 'budget' => $tc['budget'], 'color' => $tempColors[$colorIdx % count($tempColors)], 'suggestions' => [], 'is_temp' => true, 'id' => $tc['id'] ]; $colorIdx++; } $categoriesConfig['Autres'] = ['label' => 'Autres / Imprévus', 'budget' => $budget_autres, 'color' => '#64748b', 'suggestions' => ['Restaurant', 'Cadeau']]; $categoriesConfig['LivretA'] = ['label' => 'Epargne', 'budget' => 0, 'color' => '#8b5cf6', 'suggestions' => ['Virement']]; // ============================================================================ // 4. DONNÉES & IMPORT // ============================================================================ // CSV PREVIEW $csvData = []; $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){} fgetcsv($handle, 1000, ";"); while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) { $rawDebit = $data[8] ?? ''; if (!empty($rawDebit)) { $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawDebit))); $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)); $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]; } } fclose($handle); $showPreview = true; } // DÉPENSES RÉELLES $currentMonth = date('m'); $currentYear = date('Y'); $stmt = $pdo->prepare("SELECT * FROM pf_expenses WHERE MONTH(date_exp) = ? AND YEAR(date_exp) = ? ORDER BY date_exp DESC"); $stmt->execute([$currentMonth, $currentYear]); $allExpenses = $stmt->fetchAll(PDO::FETCH_ASSOC); $totals = array_fill_keys(array_keys($categoriesConfig), 0); $expensesByCategory = array_fill_keys(array_keys($categoriesConfig), []); foreach ($allExpenses as $exp) { $cat = $exp['category']; if (!isset($totals[$cat])) $cat = 'Autres'; $totals[$cat] += $exp['amount']; $expensesByCategory[$cat][] = $exp; } $globalSpent = array_sum($totals); $globalBudget = array_sum(array_column($categoriesConfig, 'budget')); ?>
| = date('d/m', strtotime($exp['date_exp'])) ?> | = htmlspecialchars($exp['label']) ?> | -= number_format($exp['amount'], 2) ?> | × |