From 54c8503ed1589976f42e2299317b3ad3e7369fbc Mon Sep 17 00:00:00 2001 From: "fefe.clochette" Date: Mon, 9 Feb 2026 17:46:10 +0100 Subject: [PATCH] mensuel --- modules/budget/budget.css | 34 +++ modules/budget/views/suivi.php | 421 +++++++++++++++++++++++++++++++++ 2 files changed, 455 insertions(+) diff --git a/modules/budget/budget.css b/modules/budget/budget.css index f9ba07d..6429c61 100644 --- a/modules/budget/budget.css +++ b/modules/budget/budget.css @@ -661,3 +661,37 @@ th:hover .month-actions { .nens-table.theme-pep { border-top: 2px solid #b9e3b9 !important; /* Bordure verte */ } + +/* Styles spécifiques Suivi Mensuel */ + +/* Animation d'apparition */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-5px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Scrollbar fine pour les listes */ +.cat-card div::-webkit-scrollbar { + width: 6px; +} +.cat-card div::-webkit-scrollbar-track { + background: #f1f5f9; +} +.cat-card div::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 3px; +} +.cat-card div::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* Input datalist chrome fix */ +input::-webkit-calendar-picker-indicator { + opacity: 100; +} diff --git a/modules/budget/views/suivi.php b/modules/budget/views/suivi.php index e69de29..cb7efae 100644 --- a/modules/budget/views/suivi.php +++ b/modules/budget/views/suivi.php @@ -0,0 +1,421 @@ +query("SELECT name, amount, type, category, is_estimate FROM pf_budget_items"); +$budgetItems = $stmt->fetchAll(PDO::FETCH_ASSOC); + +foreach ($budgetItems as $item) { + $rawAmount = (float)$item['amount']; + $amt = $rawAmount; // Montant lissé pour les totaux globaux + + $name = trim($item['name']); + $type = $item['type']; // 'Mensuel' ou 'Annuel' + $cat = $item['category']; // 'expense' ou 'income' + $is_est = (int)$item['is_estimate']; // 0 ou 1 + + // Gestion du lissage pour le calcul des totaux globaux (Reste à vivre) + if ($type === 'Annuel') { + $amt = $rawAmount / 12; + } + + // 1. Calcul des totaux globaux (Entrées / Sorties) + if ($cat === 'income') { + $total_income += $amt; + } else { + $total_expenses += $amt; + } + + // 2. Mappage des ESTIMATIONS spécifiques (FMCG, School, Essence) + // Ces lignes ont généralement is_estimate = 1 dans ta base + if ($name === 'Estimacio F&B & beauty') { + $budget_fmcg = $amt; + } + elseif ($name === 'Estimacio escola') { + $budget_school = $amt; + } + elseif ($name === 'Estimation gasolina') { + $budget_essence = $amt; + } + + // 3. CALCUL DU BUDGET CHARGES FIXES (Selon tes critères stricts) + // - Pas une estimation + // - Type Mensuel + // - Catégorie Dépense + if ($is_est === 0 && $type === 'Mensuel' && $cat === 'expense') { + $budget_frais += $rawAmount; + } +} + +// Calcul du "Reste à vivre" pour la catégorie Autres +// Formule : Revenus Totaux - Toutes les dépenses lissées +$budget_autres = $total_income - $total_expenses; +if ($budget_autres < 0) $budget_autres = 0; + + +// ============================================================================ +// 2. CONFIGURATION DES CATÉGORIES +// ============================================================================ + +$categoriesConfig = [ + 'FMCG' => [ + 'label' => 'Courses (FMCG)', + 'budget' => $budget_fmcg, + 'color' => '#3b82f6', // Bleu + 'suggestions' => ['Action', 'Boucher', 'Boulangerie', 'Carrefour', 'Grand Frais', 'Lidl', 'Zooplus', 'Pharmacie', 'Decathlon'] + ], + 'Essence' => [ + 'label' => 'Essence', + 'budget' => $budget_essence, + 'color' => '#f59e0b', // Orange + 'suggestions' => ['Audi', 'Polo'] + ], + 'School' => [ + 'label' => 'École / Garde', + 'budget' => $budget_school, + 'color' => '#10b981', // Vert + 'suggestions' => ['Pep', 'Pol'] + ], + 'Frais' => [ + 'label' => 'Charges / Frais Fixes', + 'budget' => $budget_frais, // Calculé ci-dessus (Somme des fixes mensuels) + 'color' => '#ef4444', // Rouge + 'suggestions' => ['Prêt', 'Assurance', 'Banque', 'Netflix', 'Spotify', 'Verisure', 'Internet', 'Mobile', 'Eau', 'Elec', 'Cantine'] + ], + 'Autres' => [ + 'label' => 'Autres / Imprévus', + 'budget' => $budget_autres, // Reste à vivre + 'color' => '#64748b', // Gris + 'suggestions' => ['Restaurant', 'Cadeau', 'Maison', 'Sortie', 'Santé non remboursée'] + ], + 'LivretA' => [ + 'label' => 'Epargne / Livret A', + 'budget' => 0, + 'color' => '#8b5cf6', // Violet + 'suggestions' => ['Virement mensuel'] + ] +]; + + +// ============================================================================ +// 3. LOGIQUE D'IMPORTATION CSV (Avec Mémoire) +// ============================================================================ + +$csvData = []; +$showPreview = false; + +if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] == 0) { + $file = $_FILES['csv_file']['tmp_name']; + $handle = fopen($file, "r"); + + // Chargement de la mémoire + $rules = []; + try { + $rulesStmt = $pdo->query("SELECT keyword, category FROM pf_import_rules"); + $rules = $rulesStmt->fetchAll(PDO::FETCH_KEY_PAIR); + } catch (Exception $e) { /* Ignore */ } + + // Sauter l'en-tête + fgetcsv($handle, 1000, ";"); + + while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) { + // Mapping (Date;Libelle;...;Debit) + $rawDebit = $data[8] ?? ''; + + if (!empty($rawDebit)) { + $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawDebit))); + + $dateParts = explode('/', $data[0]); + if (count($dateParts) == 3) { + $dateSql = $dateParts[2] . '-' . $dateParts[1] . '-' . $dateParts[0]; + } else { + $dateSql = date('Y-m-d'); + } + + $label = trim($data[1]); + if(empty($label)) $label = trim($data[2]); + + // Intelligence + $suggestedCat = ''; + foreach ($rules as $keyword => $cat) { + if (stripos($label, $keyword) !== false) { + $suggestedCat = $cat; + break; + } + } + + $csvData[] = [ + 'date' => $dateSql, + 'label' => $label, + 'amount' => $amount, + 'cat' => $suggestedCat + ]; + } + } + fclose($handle); + $showPreview = true; +} + + +// ============================================================================ +// 4. TRAITEMENT DES ACTIONS (Sauvegardes) +// ============================================================================ + +// A. 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) 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'])) { + $stmtExp->execute([$line['date'], $line['cat'], $line['label'], $line['amount']]); + $stmtRule->execute([$line['label'], $line['cat']]); + $count++; + } + } + } + header("Location: ?tab=suivi&msg=imported_$count"); + exit; +} + +// B. AJOUT MANUEL +if (isset($_POST['action']) && $_POST['action'] === 'add_expense') { + $cat = $_POST['category']; + $label = trim($_POST['label']); + $amount = floatval($_POST['amount']); + $date = $_POST['date']; + + if ($label && $amount > 0 && isset($categoriesConfig[$cat])) { + $stmt = $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount) VALUES (?, ?, ?, ?)"); + $stmt->execute([$date, $cat, $label, $amount]); + header("Location: ?tab=suivi"); + exit; + } +} + +// C. SUPPRESSION +if (isset($_GET['delete_expense'])) { + $id = (int)$_GET['delete_expense']; + $pdo->prepare("DELETE FROM pf_expenses WHERE id = ?")->execute([$id]); + header("Location: ?tab=suivi"); + exit; +} + + +// ============================================================================ +// 5. RÉCUPÉRATION DES DONNÉES D'AFFICHAGE DU MOIS +// ============================================================================ + +$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, id DESC +"); +$stmt->execute([$currentMonth, $currentYear]); +$allExpenses = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Calcul des totaux par catégorie +$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])) { + $totals[$cat] += $exp['amount']; + $expensesByCategory[$cat][] = $exp; + } +} + +$globalSpent = array_sum($totals); +$globalBudget = array_sum(array_column($categoriesConfig, 'budget')); +?> + +
+ +
+
+

Suivi Mensuel :

+
+ Total Dépensé + + / € Prévu +
+
+ +
+ $conf): + $spent = $totals[$key]; + $budget = $conf['budget']; + + if ($budget > 0) { + $percent = ($spent / $budget) * 100; + } else { + $percent = ($spent > 0) ? 100 : 0; + } + + $barColor = ($spent > $budget && $budget > 0) ? '#ef4444' : $conf['color']; + $cssPercent = min(100, $percent); + ?> +
+
+ + / +
+
+
+
+ $budget && $budget > 0): ?> +
+ + € +
+ +
+ +
+
+ +
+ + +
+ + + +
+ +
+

Importer un relevé bancaire (CSV)

+

Le fichier doit utiliser des points-virgules (;) comme séparateur.

+
+ + +
+
+ +
+ +

Valider l'importation

+

Vérifiez les catégories. L'application apprendra de vos choix.

+
+ + + + + + + + + + + + $row): ?> + + + + + + + + + +
DateLibelléMontantCatégorie
+ + + + + - + +
+
+
+ Annuler + +
+
+ +
+ +
+ $conf): ?> +
+
+

+ +
+
+ +

Aucune dépense.

+ + + + + + + + + + +
- ×
+ +
+
+ +
+
+ + \ No newline at end of file