auto check recap

This commit is contained in:
2026-02-16 09:20:18 +01:00
parent 92424bdb51
commit 4731efe615
+134 -92
View File
@@ -26,22 +26,20 @@ if (isset($_GET['del_cat'])) {
header("Location: ?tab=suivi"); exit; 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') { if (isset($_POST['action']) && $_POST['action'] === 'save_snapshot') {
$date = $_POST['snapshot_date']; $date = $_POST['snapshot_date'];
$amount = floatval($_POST['snapshot_amount']); $amount = floatval($_POST['snapshot_amount']);
$pdo->query("DELETE FROM pf_bank_snapshots");
// On vide la table pour que le nouveau remplace l'ancien
$pdo->query("DELETE FROM pf_bank_snapshots");
$pdo->prepare("INSERT INTO pf_bank_snapshots (snapshot_date, amount) VALUES (?, ?)")->execute([$date, $amount]); $pdo->prepare("INSERT INTO pf_bank_snapshots (snapshot_date, amount) VALUES (?, ?)")->execute([$date, $amount]);
header("Location: ?tab=suivi"); exit; 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') { if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
$count = 0; $count = 0;
// 1. Catégories temporaires
$tempCatMapping = []; $tempCatMapping = [];
if (!empty($_POST['new_temp_cats'])) { if (!empty($_POST['new_temp_cats'])) {
$stmtTemp = $pdo->prepare("INSERT INTO pf_monthly_categories (month_year, name, type, budget) VALUES (?, ?, ?, 0)"); $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)"); $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'])) { 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'])) { if (isset($line['import_check'])) {
$cat = $line['cat']; $cat = $line['cat'];
$is_credit = isset($line['is_credit']) ? (int)$line['is_credit'] : 0; $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;
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']); $finalAmount = $is_credit ? -abs($line['amount']) : abs($line['amount']);
try { 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]); $stmtRule->execute([$line['label'], $cat]);
$count++; $count++;
} catch (Exception $e) { continue; } } catch (Exception $e) { continue; }
@@ -80,18 +80,26 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
header("Location: ?tab=suivi&msg=imported_$count"); exit; 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') { if (isset($_POST['action']) && $_POST['action'] === 'add_expense') {
$cat = $_POST['category']; $cat = $_POST['category'];
$amount = floatval($_POST['amount']); $amount = floatval($_POST['amount']);
$date = $_POST['date']; $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) { if ($label && $amount > 0) {
$uniqueRef = "MANUAL_" . uniqid(); $uniqueRef = "MANUAL_" . uniqid();
$pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref) VALUES (?, ?, ?, ?, ?)") $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id) VALUES (?, ?, ?, ?, ?, ?)")
->execute([$date, $cat, $label, $amount, $uniqueRef]); ->execute([$date, $cat, $label, $amount, $uniqueRef, $budgetItemId]);
header("Location: ?tab=suivi"); exit; 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; $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; $reste_a_venir = 0;
$today_day = (int)date('j'); $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]; $snapshot = ['date' => date('Y-m-d'), 'amount' => 0];
$solde_theorique = 0; $solde_theorique = 0;
try { 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)) { if ($s = $snapStmt->fetch(PDO::FETCH_ASSOC)) {
$snapshot = ['date' => $s['snapshot_date'], 'amount' => (float)$s['amount']]; $snapshot = ['date' => $s['snapshot_date'], 'amount' => (float)$s['amount']];
} }
} catch (Exception $e) {} } catch (Exception $e) {}
$solde_theorique = $snapshot['amount']; $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'])) { if (!empty($snapshot['date'])) {
try { try {
$stmtCalc = $pdo->prepare("SELECT SUM(amount) as total_diff FROM pf_expenses WHERE date_exp > ?"); $stmtCalc = $pdo->prepare("SELECT SUM(amount) as total_diff FROM pf_expenses WHERE date_exp > ?");
$stmtCalc->execute([$snapshot['date']]); $stmtCalc->execute([$snapshot['date']]);
$resDiff = $stmtCalc->fetch(PDO::FETCH_ASSOC); $resDiff = $stmtCalc->fetch(PDO::FETCH_ASSOC);
if ($resDiff && $resDiff['total_diff'] !== null) { if ($resDiff && $resDiff['total_diff'] !== null) $solde_theorique -= (float)$resDiff['total_diff'];
// 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'];
}
} catch (Exception $e) {} } catch (Exception $e) {}
} }
// Lecture Budget - Ajout de 'is_checked' à la requête
// --- LECTURE BUDGET PREVISIONNEL --- $stmt = $pdo->query("SELECT id, name, amount, type, category, is_estimate, payment_day, is_checked FROM pf_budget_items ORDER BY name ASC");
$stmt = $pdo->query("SELECT name, amount, type, category, is_estimate, payment_day FROM pf_budget_items");
while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) { while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
$rawAmount = (float)$item['amount']; $rawAmount = (float)$item['amount'];
$amt = ($item['type'] === 'Annuel') ? $rawAmount / 12 : $rawAmount; $amt = ($item['type'] === 'Annuel') ? $rawAmount / 12 : $rawAmount;
$name = trim($item['name']); $name = trim($item['name']);
$pDay = (int)$item['payment_day']; $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') { if ($item['category'] === 'income') {
$total_income += $amt; $total_income += $amt;
} else { } else {
$total_expenses_prevues += $amt; $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 ($item['category'] === 'expense' && $item['type'] === 'Mensuel') {
if ($name === 'Estimacio escola' || $pDay > $today_day) { if ($isChecked === 0) {
$reste_a_venir += $rawAmount; $reste_a_venir += $rawAmount;
} }
} }
@@ -173,13 +183,7 @@ try {
$stmt = $pdo->prepare("SELECT * FROM pf_monthly_categories WHERE month_year = ?"); $stmt = $pdo->prepare("SELECT * FROM pf_monthly_categories WHERE month_year = ?");
$stmt->execute([$currentMonthKey]); $stmt->execute([$currentMonthKey]);
$tempCats = $stmt->fetchAll(PDO::FETCH_ASSOC); $tempCats = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($tempCats as $tc) { foreach($tempCats as $tc) if ($tc['type'] === 'debit') $total_temp_budget += $tc['budget'];
// 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'];
}
}
} catch (Exception $e) {} } catch (Exception $e) {}
$budget_autres = $total_income - ($total_expenses_prevues + $total_temp_budget); $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']], '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']], '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'=>[]], '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']; $tempColors = ['#ec4899', '#06b6d4', '#84cc16', '#d946ef', '#f97316'];
$colorIdx = 0; $colorIdx = 0;
foreach ($tempCats as $tc) { foreach ($tempCats as $tc) {
$catKey = 'TEMP_' . $tc['id']; $catKey = 'TEMP_' . $tc['id'];
$catType = isset($tc['type']) ? $tc['type'] : 'debit';
$categoriesConfig[$catKey] = [ $categoriesConfig[$catKey] = [
'type' => $catType, 'type' => $tc['type'], 'label' => $tc['name'], 'budget' => $tc['budget'], 'color' => $tempColors[$colorIdx++ % count($tempColors)], 'suggestions' => [], 'is_temp' => true, 'id' => $tc['id']
'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['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']]; $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) { if (isset($_FILES['csv_file']) && $_FILES['csv_file']['error'] == 0) {
$file = $_FILES['csv_file']['tmp_name']; $file = $_FILES['csv_file']['tmp_name'];
$handle = fopen($file, "r"); $handle = fopen($file, "r");
$rules = []; try { $rules = $pdo->query("SELECT keyword, category FROM pf_import_rules")->fetchAll(PDO::FETCH_KEY_PAIR); } catch(Exception $e){}
$rules = []; $existingRefs = []; try { $existingRefs = $pdo->query("SELECT import_ref FROM pf_expenses WHERE import_ref IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN); } catch(Exception $e){}
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)
fgetcsv($handle, 1000, ";", "\"", "\\"); fgetcsv($handle, 1000, ";", "\"", "\\");
while (($data = fgetcsv($handle, 1000, ";", "\"", "\\")) !== FALSE) { while (($data = fgetcsv($handle, 1000, ";", "\"", "\\")) !== FALSE) {
$rawDebit = $data[8] ?? ''; $rawDebit = $data[8] ?? ''; $rawCredit = $data[9] ?? '';
$rawCredit = $data[9] ?? '';
$amount = 0; $isCredit = 0; $amount = 0; $isCredit = 0;
if (!empty(trim($rawCredit))) { if (!empty(trim($rawCredit))) { $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawCredit))); $isCredit = 1; }
$amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawCredit))); elseif (!empty(trim($rawDebit))) { $amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawDebit))); }
$isCredit = 1; else continue;
} elseif (!empty(trim($rawDebit))) {
$amount = abs((float)str_replace(',', '.', str_replace(' ', '', $rawDebit)));
} else {
continue;
}
$dateParts = explode('/', $data[0]); $dateParts = explode('/', $data[0]);
$dateSql = (count($dateParts) == 3) ? $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0] : date('Y-m-d'); $dateSql = (count($dateParts) == 3) ? $dateParts[2].'-'.$dateParts[1].'-'.$dateParts[0] : date('Y-m-d');
$label = trim($data[1]) ?: trim($data[2]); $label = trim($data[1]) ?: trim($data[2]);
$refCSV = trim($data[3]); $refCSV = trim($data[3]);
$uniqueKey = !empty($refCSV) ? "REF_".$refCSV : "HASH_".md5($dateSql.$label.number_format($amount, 2).$isCredit); $uniqueKey = !empty($refCSV) ? "REF_".$refCSV : "HASH_".md5($dateSql.$label.number_format($amount, 2).$isCredit);
$isDuplicate = in_array($uniqueKey, $existingRefs); $isDuplicate = in_array($uniqueKey, $existingRefs);
$suggestedCat = ''; $suggestedCat = '';
foreach ($rules as $kw => $c) { if (stripos($label, $kw) !== false) { $suggestedCat = $c; break; } } 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]; $csvData[] = ['date'=>$dateSql, 'label'=>$label, 'amount'=>$amount, 'cat'=>$suggestedCat, 'ref'=>$uniqueKey, 'is_duplicate'=>$isDuplicate, 'is_credit'=>$isCredit];
} }
fclose($handle); fclose($handle);
@@ -280,12 +257,8 @@ foreach ($allExpenses as $exp) {
$cat = $exp['category']; $cat = $exp['category'];
if (!isset($totals[$cat])) $cat = 'Autres'; 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']);
if ($exp['amount'] < 0) { else $totals[$cat] += $exp['amount'];
$categoriesConfig[$cat]['budget'] += abs($exp['amount']);
} else {
$totals[$cat] += $exp['amount'];
}
$expensesByCategory[$cat][] = $exp; $expensesByCategory[$cat][] = $exp;
} }
@@ -293,7 +266,6 @@ foreach ($allExpenses as $exp) {
$globalSpent = array_sum($totals); $globalSpent = array_sum($totals);
$globalBudget = array_sum(array_column($categoriesConfig, 'budget')); $globalBudget = array_sum(array_column($categoriesConfig, 'budget'));
// --- FONCTION D'AFFICHAGE ---
function getDisplayLogic($spent, $bg, $type) { function getDisplayLogic($spent, $bg, $type) {
if ($type === 'credit') { if ($type === 'credit') {
$remaining = $bg - $spent; $remaining = $bg - $spent;
@@ -340,7 +312,6 @@ function getDisplayLogic($spent, $bg, $type) {
Solde théorique au <?= date('d/m') ?> : Solde théorique au <?= date('d/m') ?> :
<strong style="color:#3b82f6;"><?= number_format($solde_theorique, 2, ',', ' ') ?> €</strong> <strong style="color:#3b82f6;"><?= number_format($solde_theorique, 2, ',', ' ') ?> €</strong>
</div> </div>
</div> </div>
<div style="text-align:right;"> <div style="text-align:right;">
<strong style="font-size:1.4rem; color:#1e293b;"><?= number_format(ceil($globalSpent), 0, ',', ' ') ?> €</strong> <strong style="font-size:1.4rem; color:#1e293b;"><?= number_format(ceil($globalSpent), 0, ',', ' ') ?> €</strong>
@@ -442,12 +413,21 @@ function getDisplayLogic($spent, $bg, $type) {
<?= $isCrd ? '+' : '-' ?> <?= number_format($row['amount'],2) ?> € <?= $isCrd ? '+' : '-' ?> <?= number_format($row['amount'],2) ?> €
</td> </td>
<td> <td>
<select name="lines[<?= $idx ?>][cat]" class="pf-input line-select" onchange="checkValidation()" <?= $dis ?>> <div style="display:flex; gap:5px;">
<option value="">-- <?= $isCrd ? 'Ignorer (Crédit)' : 'À définir' ?> --</option> <select name="lines[<?= $idx ?>][cat]" class="pf-input line-select" onchange="handleLineCatChange(this)" <?= $dis ?> style="flex:1;">
<?php foreach ($categoriesConfig as $k => $c): ?> <option value="">-- <?= $isCrd ? 'Ignorer (Crédit)' : 'À définir' ?> --</option>
<option value="<?= $k ?>" <?= ($row['cat']===$k)?'selected':'' ?>><?= $c['label'] ?></option> <?php foreach ($categoriesConfig as $k => $c): ?>
<?php endforeach; ?> <option value="<?= $k ?>" <?= ($row['cat']===$k)?'selected':'' ?>><?= $c['label'] ?></option>
</select> <?php endforeach; ?>
</select>
<select name="lines[<?= $idx ?>][budget_item_id]" class="pf-input budget-item-select" style="display:none; flex:1; border-color:#ef4444; background:#fef2f2;">
<option value="">-- Quelle Charge ? --</option>
<?php foreach ($fixedChargesList as $fc): ?>
<option value="<?= $fc['id'] ?>"><?= htmlspecialchars($fc['name']) ?> (<?= number_format($fc['amount'],0) ?>€)</option>
<?php endforeach; ?>
</select>
</div>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@@ -493,7 +473,12 @@ function getDisplayLogic($spent, $bg, $type) {
<?php foreach ($expensesByCategory[$key] as $exp): ?> <?php foreach ($expensesByCategory[$key] as $exp): ?>
<tr style="border-bottom:1px solid #f8fafc;"> <tr style="border-bottom:1px solid #f8fafc;">
<td style="padding:10px 15px; color:#94a3b8;"><?= date('d/m', strtotime($exp['date_exp'])) ?></td> <td style="padding:10px 15px; color:#94a3b8;"><?= date('d/m', strtotime($exp['date_exp'])) ?></td>
<td style="padding:10px 5px; font-weight:500;"><?= htmlspecialchars($exp['label']) ?></td> <td style="padding:10px 5px; font-weight:500;">
<?= htmlspecialchars($exp['label']) ?>
<?php if(!empty($exp['budget_item_id'])): ?>
<span title="Lié à une charge fixe" style="font-size:0.7rem; cursor:help;">🔗</span>
<?php endif; ?>
</td>
<?php if($exp['amount'] < 0): ?> <?php if($exp['amount'] < 0): ?>
<td style="padding:10px 15px; text-align:right; font-weight:600; color:#10b981;">+<?= number_format(abs($exp['amount']), 2) ?></td> <td style="padding:10px 15px; text-align:right; font-weight:600; color:#10b981;">+<?= number_format(abs($exp['amount']), 2) ?></td>
<?php else: ?> <?php else: ?>
@@ -539,6 +524,16 @@ function getDisplayLogic($spent, $bg, $type) {
<option value="Carole">Carole</option> <option value="Carole">Carole</option>
</select> </select>
</div> </div>
<div class="form-group" id="blockInputFrais" style="margin-bottom:15px; display:none;">
<label class="pf-label" style="color:#ef4444;">Choisir la charge fixe</label>
<select name="budget_item_id" id="fraisSelect" class="pf-input" style="border-color:#ef4444; background:#fef2f2;">
<option value="">-- Sélectionner --</option>
<?php foreach ($fixedChargesList as $fc): ?>
<option value="<?= $fc['id'] ?>"><?= htmlspecialchars($fc['name']) ?> (<?= number_format($fc['amount'],2) ?>€)</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="margin-bottom:15px;"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Montant (€)</label> <label class="pf-label">Montant (€)</label>
@@ -617,12 +612,29 @@ function openAddModal(catKey, catLabel) {
const blockText = document.getElementById('blockInputText'); const blockText = document.getElementById('blockInputText');
const blockSelect = document.getElementById('blockInputSelect'); const blockSelect = document.getElementById('blockInputSelect');
const blockFrais = document.getElementById('blockInputFrais');
const inputLabel = document.getElementById('modalLabelInput'); const inputLabel = document.getElementById('modalLabelInput');
const selectFrais = document.getElementById('fraisSelect');
// Reset visibility
blockText.style.display = 'none';
blockSelect.style.display = 'none';
blockFrais.style.display = 'none';
inputLabel.required = false;
selectFrais.required = false;
if (catKey === 'School') { if (catKey === 'School') {
blockText.style.display = 'none'; blockSelect.style.display = 'block'; inputLabel.required = false; blockSelect.style.display = 'block';
} else { }
blockText.style.display = 'block'; blockSelect.style.display = 'none'; inputLabel.required = true; else if (catKey === 'Frais') {
blockText.style.display = 'block';
blockFrais.style.display = 'block';
selectFrais.required = true;
}
else {
blockText.style.display = 'block';
inputLabel.required = true;
const list = document.getElementById('modalSuggestions'); const list = document.getElementById('modalSuggestions');
list.innerHTML = ''; inputLabel.value = ''; list.innerHTML = ''; inputLabel.value = '';
if (suggestions[catKey]) suggestions[catKey].forEach(i => { const op = document.createElement('option'); op.value = i; list.appendChild(op); }); if (suggestions[catKey]) suggestions[catKey].forEach(i => { const op = document.createElement('option'); op.value = i; list.appendChild(op); });
@@ -648,15 +660,41 @@ function confirmNewCat() {
} }
} }
function handleLineCatChange(select) {
const row = select.closest('tr');
const secondarySelect = row.querySelector('.budget-item-select');
if (select.value === 'Frais') {
secondarySelect.style.display = 'block';
} else {
secondarySelect.style.display = 'none';
secondarySelect.value = '';
}
checkValidation();
}
function toggleAll(src) { document.querySelectorAll('.line-checkbox:not([disabled])').forEach(c => c.checked = src.checked); checkValidation(); } function toggleAll(src) { document.querySelectorAll('.line-checkbox:not([disabled])').forEach(c => c.checked = src.checked); checkValidation(); }
function checkValidation() { function checkValidation() {
const cbs = document.querySelectorAll('.line-checkbox:checked'); const cbs = document.querySelectorAll('.line-checkbox:checked');
let miss = 0; let miss = 0;
cbs.forEach(cb => { cbs.forEach(cb => {
const row = cb.closest('tr'); const row = cb.closest('tr');
const isCredit = row.querySelector('.is-credit-flag').value === '1'; const isCredit = row.querySelector('.is-credit-flag').value === '1';
if (row.querySelector('.line-select').value === "") { const mainCat = row.querySelector('.line-select').value;
if(!isCredit) miss++; const subCatSelect = row.querySelector('.budget-item-select');
let rowValid = true;
if (mainCat === "") {
if(!isCredit) rowValid = false;
}
else if (mainCat === 'Frais') {
if (subCatSelect.value === "") rowValid = false;
}
if (!rowValid) {
miss++;
row.style.background = isCredit ? '' : '#fff1f2'; row.style.background = isCredit ? '' : '#fff1f2';
} else { } else {
row.style.background = ''; row.style.background = '';
@@ -667,11 +705,15 @@ function checkValidation() {
const msg = document.getElementById('missingCount'); const msg = document.getElementById('missingCount');
if(miss>0) { if(miss>0) {
btn.disabled = true; btn.style.opacity=0.5; btn.style.cursor='not-allowed'; btn.disabled = true; btn.style.opacity=0.5; btn.style.cursor='not-allowed';
msg.style.display='inline'; msg.innerText=miss+' à définir (débits)'; msg.style.display='inline'; msg.innerText=miss+' à définir';
} else { } else {
btn.disabled = false; btn.style.opacity=1; btn.style.cursor='pointer'; btn.disabled = false; btn.style.opacity=1; btn.style.cursor='pointer';
msg.style.display='none'; msg.style.display='none';
} }
} }
if(document.getElementById('formMapping')) checkValidation();
if(document.getElementById('formMapping')) {
document.querySelectorAll('.line-select').forEach(s => handleLineCatChange(s));
checkValidation();
}
</script> </script>