fix recap annuel
Deploy HouseHub / deploy (push) Successful in 1s

This commit is contained in:
2026-06-17 16:06:59 +02:00
parent f9aa368768
commit f36bfa828e
2 changed files with 51 additions and 52 deletions
+48 -39
View File
@@ -6,43 +6,54 @@ require_login();
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? ''; $action = $_POST['action'] ?? '';
// --- ACTION : SAUVEGARDER (AJOUT OU MODIF) --- // --- ACTION : SAUVEGARDER (AJOUT OU MODIF DU BUDGET) ---
if ($action === 'save') { if ($action === 'save') {
$id = $_POST['id'] ?? ''; try {
$name = $_POST['name']; // 1. Sécurisation des données entrantes
$category = $_POST['category']; $id = !empty($_POST['id']) ? (int)$_POST['id'] : null;
$type = $_POST['type']; $name = trim($_POST['name'] ?? '');
$payment_day = empty($_POST['payment_day']) ? null : (int)$_POST['payment_day']; $category = $_POST['category'] ?? '';
$is_estimate = $_POST['is_estimate']; $type = $_POST['type'] ?? 'Mensuel';
$reg_month = $_POST['reg_month']; $payment_day = !empty($_POST['payment_day']) ? (int)$_POST['payment_day'] : null;
$keywords = $_POST['mapping_keywords'] ?? ''; $is_estimate = isset($_POST['is_estimate']) ? (int)$_POST['is_estimate'] : 0;
$holiday_id = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null; $reg_month = !empty($_POST['reg_month']) ? trim($_POST['reg_month']) : null;
$keywords = trim($_POST['mapping_keywords'] ?? '');
$holiday_id = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null;
// ========================================== // 2. Gestion du montant (toujours stocker les dépenses en négatif)
// NOUVELLE NORME COMPTABLE : Dépense = Négatif $amount = abs((float)($_POST['amount'] ?? 0));
// ========================================== $amount = -$amount; // On force le négatif car le modal Recap ne gère que des charges
// On récupère le montant en valeur absolue (pour éviter les erreurs si l'utilisateur a tapé un '-')
$amount = abs((float)$_POST['amount']);
// Si c'est un frais, on l'enregistre en négatif // 3. Exécution de la requête
if ($category === 'expense') { if ($id) {
$amount = -$amount; // UPDATE
$stmt = $pdo->prepare("UPDATE pf_budget_items SET name=?, amount=?, category=?, type=?, payment_day=?, is_estimate=?, reg_month=?, mapping_keywords=?, holiday_id=? WHERE id=?");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $holiday_id, $id]);
} else {
// INSERT
$stmt = $pdo->prepare("INSERT INTO pf_budget_items (name, amount, category, type, payment_day, is_estimate, reg_month, mapping_keywords, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $holiday_id]);
}
// 4. Réponse appropriée (JSON si appel via JS fetch, Redirection sinon)
if (!empty($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['success' => true]);
exit;
} else {
header('Location: /budget.php?tab=recap');
exit;
}
} catch (PDOException $e) {
// En cas d'erreur BDD (ex: contrainte de clé étrangère, colonne manquante)
if (!empty($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Erreur BDD : ' . $e->getMessage()]);
exit;
}
die("Erreur fatale : " . $e->getMessage());
} }
// ==========================================
if ($id) {
// UPDATE
$stmt = $pdo->prepare("UPDATE pf_budget_items SET name=?, amount=?, category=?, type=?, payment_day=?, is_estimate=?, reg_month=?, mapping_keywords=?, holiday_id=? WHERE id=?");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $holiday_id, $id]);
} else {
// INSERT
$stmt = $pdo->prepare("INSERT INTO pf_budget_items (name, amount, category, type, payment_day, is_estimate, reg_month, mapping_keywords, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $holiday_id]);
}
// Redirection
header('Location: /budget.php?tab=recap');
exit;
} }
// --- ACTION : COCHER/DÉCOCHER RAPIDE (VIA JS FETCH) --- // --- ACTION : COCHER/DÉCOCHER RAPIDE (VIA JS FETCH) ---
@@ -59,7 +70,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
exit; exit;
} }
// --- ACTION : SUPPRIMER --- // --- ACTION : SUPPRIMER UNE LIGNE DU BUDGET ---
if ($action === 'delete') { if ($action === 'delete') {
$id = $_POST['id']; $id = $_POST['id'];
$pdo->prepare("DELETE FROM pf_budget_items WHERE id = ?")->execute([$id]); $pdo->prepare("DELETE FROM pf_budget_items WHERE id = ?")->execute([$id]);
@@ -84,7 +95,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// --- ACTION : SAUVEGARDER UNE DÉPENSE MANUELLE (FETCH) --- // --- ACTION : SAUVEGARDER UNE DÉPENSE MANUELLE (FETCH) ---
if ($action === 'save_expense_manual') { if ($action === 'save_expense_manual') {
header('Content-Type: application/json'); // On force le retour JSON pour notre script JS header('Content-Type: application/json');
try { try {
$id = !empty($_POST['expense_id']) ? (int)$_POST['expense_id'] : null; $id = !empty($_POST['expense_id']) ? (int)$_POST['expense_id'] : null;
@@ -92,15 +103,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$amount = floatval($_POST['amount'] ?? 0); $amount = floatval($_POST['amount'] ?? 0);
$date = $_POST['date'] ?? date('Y-m-d'); $date = $_POST['date'] ?? date('Y-m-d');
// 🛡️ SÉCURITÉ : Format attendu pour gestion_month (qui vient de input type="month") : YYYY-MM. // Format attendu : YYYY-MM
$gestionMonthRaw = $_POST['gestion_month'] ?? ''; $gestionMonthRaw = $_POST['gestion_month'] ?? '';
// Si la valeur est vide ou '0000-00-00' // Si la valeur est vide ou invalide
if (empty($gestionMonthRaw) || strpos($gestionMonthRaw, '0000') !== false) { if (empty($gestionMonthRaw) || strpos($gestionMonthRaw, '0000') !== false) {
// On force le mois de gestion au 1er jour de la date de la dépense
$gestionMonth = date('Y-m-01', strtotime($date)); $gestionMonth = date('Y-m-01', strtotime($date));
} else { } else {
// Si la valeur vient d'un input "month" (ex: "2026-04"), on rajoute "-01"
$gestionMonth = (strlen($gestionMonthRaw) === 7) ? $gestionMonthRaw . '-01' : $gestionMonthRaw; $gestionMonth = (strlen($gestionMonthRaw) === 7) ? $gestionMonthRaw . '-01' : $gestionMonthRaw;
} }
+3 -13
View File
@@ -102,9 +102,6 @@ $totalRevenus = 0;
<?php endforeach; ?> <?php endforeach; ?>
<?php <?php
// Initialisation du tableau de verrouillage des catégories avant la boucle
$usedCategories = [];
foreach ($items as $item): foreach ($items as $item):
// 1. Calculs de base // 1. Calculs de base
$targetAbs = abs((float)$item['amount']); $targetAbs = abs((float)$item['amount']);
@@ -115,19 +112,12 @@ foreach ($items as $item):
$realSum = 0; $realSum = 0;
$hasMatchingExpense = false; $hasMatchingExpense = false;
// A. Correspondance par ID direct (Priorité 1) // A. Correspondance par ID direct (Le lien dur)
if (isset($realTotalsById[$item['id']])) { if (isset($realTotalsById[$item['id']])) {
$realSum = (float)$realTotalsById[$item['id']]; $realSum = (float)$realTotalsById[$item['id']];
$hasMatchingExpense = true; $hasMatchingExpense = true;
} }
// B. Correspondance par Catégorie (Priorité 2 : avec verrouillage pour éviter le cumul) // B. Correspondance par mots-clés bancaires (Le lien intelligent pour les orphelines)
elseif (!empty($item['category']) && isset($realTotalsByCat[$item['category']]) && !isset($usedCategories[$item['category']])) {
$realSum = (float)$realTotalsByCat[$item['category']];
$hasMatchingExpense = true;
// On marque la catégorie comme "consommée" pour ne pas la recompter sur la ligne suivante
$usedCategories[$item['category']] = true;
}
// C. Correspondance par mots-clés bancaires (Priorité 3)
elseif (!empty($item['mapping_keywords'])) { elseif (!empty($item['mapping_keywords'])) {
$keywords = array_map('trim', explode(',', $item['mapping_keywords'])); $keywords = array_map('trim', explode(',', $item['mapping_keywords']));
foreach ($unlinkedExpenses as $uexp) { foreach ($unlinkedExpenses as $uexp) {
@@ -135,7 +125,7 @@ foreach ($items as $item):
if (!empty($kw) && stripos($uexp['label'], $kw) !== false) { if (!empty($kw) && stripos($uexp['label'], $kw) !== false) {
$realSum += (float)$uexp['amount']; $realSum += (float)$uexp['amount'];
$hasMatchingExpense = true; $hasMatchingExpense = true;
break; break; // On a trouvé un mot clé sur cette dépense, on passe à l'orpheline suivante
} }
} }
} }