depassement

This commit is contained in:
2026-02-16 09:51:53 +01:00
parent 0e6c3edbc3
commit c01d793a1e
+71 -28
View File
@@ -5,13 +5,14 @@
$stmt = $pdo->query("SELECT * FROM pf_budget_items ORDER BY category DESC, sort_order ASC, name ASC"); $stmt = $pdo->query("SELECT * FROM pf_budget_items ORDER BY category DESC, sort_order ASC, name ASC");
$items = $stmt->fetchAll(); $items = $stmt->fetchAll();
// 2. Récupération des Dépenses Réelles du mois (Pour le matching auto) // 2. Récupération des Dépenses Réelles du mois
$currentMonth = date('m'); $currentMonth = date('m');
$currentYear = date('Y'); $currentYear = date('Y');
$stmtExp = $pdo->prepare("SELECT label FROM pf_expenses WHERE MONTH(date_exp) = ? AND YEAR(date_exp) = ?");
// On récupère TOUTES les dépenses du mois pour faire le calcul en PHP
$stmtExp = $pdo->prepare("SELECT amount, label, budget_item_id FROM pf_expenses WHERE MONTH(date_exp) = ? AND YEAR(date_exp) = ?");
$stmtExp->execute([$currentMonth, $currentYear]); $stmtExp->execute([$currentMonth, $currentYear]);
// On stocke tous les libellés réels dans un tableau simple $allExpenses = $stmtExp->fetchAll(PDO::FETCH_ASSOC);
$realExpensesLabels = $stmtExp->fetchAll(PDO::FETCH_COLUMN);
$totalDepenses = 0; $totalDepenses = 0;
$totalRevenus = 0; $totalRevenus = 0;
@@ -28,42 +29,60 @@ $totalRevenus = 0;
<thead style="background:#f8fafc;"> <thead style="background:#f8fafc;">
<tr> <tr>
<th>Nom</th> <th>Nom</th>
<th>Montant</th> <th>Montant Prévu</th>
<th>Type</th> <th>Type</th>
<th>Jour</th> <th>Jour</th>
<th>État prélèvement</th> <th>État</th>
<th>Régularisation</th> <th>Régularisation</th>
<th style="text-align:right;">Actions</th> <th style="text-align:right;">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($items as $item): <?php foreach ($items as $item):
// --- CALCUL DES TOTAUX --- // --- 1. CALCUL DES TOTAUX PRÉVUS ---
$amountToAdd = ($item['type'] === 'Annuel') ? $item['amount'] / 12 : $item['amount']; $amountToAdd = ($item['type'] === 'Annuel') ? $item['amount'] / 12 : $item['amount'];
if ($item['category'] === 'expense') $totalDepenses += $amountToAdd; if ($item['category'] === 'expense') $totalDepenses += $amountToAdd;
else $totalRevenus += $amountToAdd; else $totalRevenus += $amountToAdd;
// --- LOGIQUE DE DETECTION AUTOMATIQUE --- // --- 2. CALCUL DU RÉEL (SOMME DES LIGNES ASSOCIÉES) ---
$isAutoChecked = false; $realSum = 0;
// Si des mots-clés sont définis pour cet item $hasMatchingExpense = false;
if (!empty($item['mapping_keywords'])) {
// On explose la chaine "Netflix, Spotify" en tableau foreach ($allExpenses as $exp) {
$match = false;
// A. Matching par ID (Prioritaire & Précis)
if (!empty($exp['budget_item_id']) && (int)$exp['budget_item_id'] === (int)$item['id']) {
$match = true;
}
// B. Matching par Mots-clés (Si pas d'ID sur la dépense)
elseif (empty($exp['budget_item_id']) && !empty($item['mapping_keywords'])) {
$keywords = array_map('trim', explode(',', $item['mapping_keywords'])); $keywords = array_map('trim', explode(',', $item['mapping_keywords']));
foreach ($keywords as $kw) { foreach ($keywords as $kw) {
if (empty($kw)) continue; if (!empty($kw) && stripos($exp['label'], $kw) !== false) {
// On cherche ce mot clé dans TOUTES les dépenses réelles du mois $match = true;
foreach ($realExpensesLabels as $realLabel) { break;
// stripos pour insensible à la casse
if (stripos($realLabel, $kw) !== false) {
$isAutoChecked = true;
break 2; // On sort des deux boucles si trouvé
}
} }
} }
} }
// L'état final est : Soit coché manuellement (BDD), soit détecté auto if ($match) {
$realSum += (float)$exp['amount']; // Additionne débits (+) et crédits (-)
$hasMatchingExpense = true;
}
}
// --- 3. LOGIQUE D'ÉTAT (AUTO-CHECK) ---
// On coche si la somme réelle atteint au moins 98% du montant prévu (tolérance petit écart)
// ou si c'est coché manuellement.
$isAutoChecked = false;
$gap = $realSum - $item['amount'];
// Seuil de tolérance (ex: 0.10€) pour considérer que c'est payé
if ($hasMatchingExpense && ($realSum >= ($item['amount'] - 0.10))) {
$isAutoChecked = true;
}
$isPaid = $item['is_checked'] || $isAutoChecked; $isPaid = $item['is_checked'] || $isAutoChecked;
// Styles // Styles
@@ -75,21 +94,40 @@ $totalRevenus = 0;
<strong><?= htmlspecialchars($item['name']) ?></strong> <strong><?= htmlspecialchars($item['name']) ?></strong>
<?= $item['is_estimate'] ? ' <small style="color:#64748b;">(Est.)</small>' : '' ?> <?= $item['is_estimate'] ? ' <small style="color:#64748b;">(Est.)</small>' : '' ?>
<?php if(!empty($item['mapping_keywords'])): ?> <?php if(!empty($item['mapping_keywords'])): ?>
<span title="Mapping auto activé: <?= htmlspecialchars($item['mapping_keywords']) ?>" style="font-size:0.7rem; cursor:help;">🔗</span> <span title="<?= htmlspecialchars($item['mapping_keywords']) ?>" style="font-size:0.7rem; cursor:help;">🔗</span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td class="cell-amount" style="font-weight:600; padding:15px; color:<?= $item['category']==='income'?'#10b981':'#1e293b' ?>;"> <td class="cell-amount" style="font-weight:600; padding:15px; color:<?= $item['category']==='income'?'#10b981':'#1e293b' ?>;">
<?= number_format($item['amount'], 2, ',', ' ') ?> € <?= number_format($item['amount'], 2, ',', ' ') ?> €
<?php if($item['type'] === 'Annuel'): ?>
<?php if ($hasMatchingExpense): ?>
<?php if ($gap > 0.05): ?>
<div style="font-size:0.75rem; color:#ef4444; font-weight:bold;">
Dépassement : +<?= number_format($gap, 2, ',', ' ') ?> €
</div>
<?php elseif ($gap < -0.05): ?>
<div style="font-size:0.75rem; color:#f59e0b; font-weight:normal;">
Reste : <?= number_format(abs($gap), 2, ',', ' ') ?> €
</div>
<?php else: ?>
<div style="font-size:0.75rem; color:#10b981; font-weight:normal;">
Montant exact ✓
</div>
<?php endif; ?>
<?php elseif ($item['type'] === 'Annuel'): ?>
<div style="font-size:0.75rem; color:#94a3b8; font-weight:normal;">Soit <?= number_format($amountToAdd, 2, ',', ' ') ?>/mois</div> <div style="font-size:0.75rem; color:#94a3b8; font-weight:normal;">Soit <?= number_format($amountToAdd, 2, ',', ' ') ?>/mois</div>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td style="padding:15px;"> <td style="padding:15px;">
<span class="badge-type <?= strtolower($item['type']) ?>" style="background:#e2e8f0; padding:4px 8px; border-radius:12px; font-size:0.8rem; font-weight:600; color:#475569;"> <span class="badge-type <?= strtolower($item['type']) ?>" style="background:#e2e8f0; padding:4px 8px; border-radius:12px; font-size:0.8rem; font-weight:600; color:#475569;">
<?= $item['type'] ?> <?= $item['type'] ?>
</span> </span>
</td> </td>
<td style="padding:15px; color:#64748b;"><?= $item['payment_day'] ? $item['payment_day'] : '-' ?></td> <td style="padding:15px; color:#64748b;"><?= $item['payment_day'] ? $item['payment_day'] : '-' ?></td>
<td style="padding:15px;"> <td style="padding:15px;">
<div style="display:flex; align-items:center; gap:8px;"> <div style="display:flex; align-items:center; gap:8px;">
<?php if ($isAutoChecked): ?> <?php if ($isAutoChecked): ?>
@@ -101,10 +139,17 @@ $totalRevenus = 0;
onclick="toggleItemCheck(<?= $item['id'] ?>, this.checked)" onclick="toggleItemCheck(<?= $item['id'] ?>, this.checked)"
title="Marquer comme payé" title="Marquer comme payé"
style="width:18px; height:18px; cursor:pointer;"> style="width:18px; height:18px; cursor:pointer;">
<?= $item['is_checked'] ? ' <span style="color:#10b981; font-weight:500; font-size:0.9rem;">Payé</span>' : ' <span style="color:#f59e0b; font-weight:500; font-size:0.9rem;">Attente</span>' ?> <?php if($item['is_checked']): ?>
<span style="color:#10b981; font-weight:500; font-size:0.9rem;">Payé</span>
<?php elseif($hasMatchingExpense): ?>
<span style="color:#f59e0b; font-weight:bold; font-size:0.8rem;">Partiel</span>
<?php else: ?>
<span style="color:#94a3b8; font-weight:500; font-size:0.9rem;">Attente</span>
<?php endif; ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
</td> </td>
<td style="padding:15px; color:#64748b; font-size:0.9rem;"> <td style="padding:15px; color:#64748b; font-size:0.9rem;">
<em><?= htmlspecialchars($item['reg_month'] ?: '-') ?></em> <em><?= htmlspecialchars($item['reg_month'] ?: '-') ?></em>
</td> </td>
@@ -138,7 +183,7 @@ $totalRevenus = 0;
</div> </div>
<div class="budget-note" style="margin-top:15px; font-size:0.85rem; color:#64748b;"> <div class="budget-note" style="margin-top:15px; font-size:0.85rem; color:#64748b;">
<p>* Les lignes marquées <strong>Auto ✅</strong> ont été détectées automatiquement dans les dépenses réelles du mois.</p> <p>* Les lignes <strong>Auto ✅</strong> sont validées quand la somme des transactions atteint le montant prévu.</p>
</div> </div>
</div> </div>
@@ -252,9 +297,7 @@ function editRecapItem(item) {
document.getElementById("recapModalTitle").innerText = "Modifier : " + data.name; document.getElementById("recapModalTitle").innerText = "Modifier : " + data.name;
document.getElementById("item_id").value = data.id; document.getElementById("item_id").value = data.id;
document.getElementById("item_name").value = data.name; document.getElementById("item_name").value = data.name;
// Remplissage du nouveau champ Keywords
document.getElementById("item_keywords").value = data.mapping_keywords || ''; document.getElementById("item_keywords").value = data.mapping_keywords || '';
document.getElementById("item_amount").value = data.amount; document.getElementById("item_amount").value = data.amount;
document.getElementById("item_category").value = data.category; document.getElementById("item_category").value = data.category;
document.getElementById("item_type").value = data.type; document.getElementById("item_type").value = data.type;