diff --git a/modules/budget/includes/api/manage-item.php b/modules/budget/includes/api/manage-item.php
index 507a650..8ed7aac 100644
--- a/modules/budget/includes/api/manage-item.php
+++ b/modules/budget/includes/api/manage-item.php
@@ -114,13 +114,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$label = trim($_POST['label'] ?? '');
+ $rawItemId = $_POST['budget_item_id'] ?? '';
$budgetItemId = null;
+ $salaryId = null;
$holidayId = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null;
if ($cat === 'School' && !empty($_POST['label_select'])) {
$label = trim($_POST['label_select']);
- } elseif (($cat === 'Frais' || $cat === 'Income') && !empty($_POST['budget_item_id'])) {
- $budgetItemId = (int)$_POST['budget_item_id'];
+ } elseif (!empty($rawItemId)) {
+ // 🔥 On intercepte si c'est un Salaire (SAL_x) ou une Charge Fixe classique (x)
+ if (strpos($rawItemId, 'SAL_') === 0) {
+ $salaryId = (int)str_replace('SAL_', '', $rawItemId);
+ } else {
+ $budgetItemId = (int)$rawItemId;
+ }
}
// Vérification de sécurité
@@ -131,16 +138,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$is_credit = isset($_POST['is_credit']) ? (int)$_POST['is_credit'] : 0;
$finalAmount = $is_credit ? abs($amount) : -abs($amount);
-
+
if ($id) {
- // UPDATE
- $pdo->prepare("UPDATE pf_expenses SET date_exp=?, gestion_month=?, category=?, label=?, amount=?, budget_item_id=?, holiday_id=? WHERE id=?")
- ->execute([$date, $gestionMonth, $cat, $label, $finalAmount, $budgetItemId, $holidayId, $id]);
+ // UPDATE (Ajout de salary_id)
+ $pdo->prepare("UPDATE pf_expenses SET date_exp=?, gestion_month=?, category=?, label=?, amount=?, budget_item_id=?, holiday_id=?, salary_id=? WHERE id=?")
+ ->execute([$date, $gestionMonth, $cat, $label, $finalAmount, $budgetItemId, $holidayId, $salaryId, $id]);
} else {
- // INSERT
+ // INSERT (Ajout de salary_id)
$uniqueRef = "MANUAL_" . uniqid();
- $pdo->prepare("INSERT INTO pf_expenses (date_exp, gestion_month, category, label, amount, import_ref, budget_item_id, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
- ->execute([$date, $gestionMonth, $cat, $label, $finalAmount, $uniqueRef, $budgetItemId, $holidayId]);
+ $pdo->prepare("INSERT INTO pf_expenses (date_exp, gestion_month, category, label, amount, import_ref, budget_item_id, holiday_id, salary_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
+ ->execute([$date, $gestionMonth, $cat, $label, $finalAmount, $uniqueRef, $budgetItemId, $holidayId, $salaryId]);
}
echo json_encode(['success' => true]);
diff --git a/modules/budget/views/recap.php b/modules/budget/views/recap.php
index a9fdb82..d9bd92a 100644
--- a/modules/budget/views/recap.php
+++ b/modules/budget/views/recap.php
@@ -7,7 +7,7 @@ $dbCategories = $stmtCats->fetchAll(PDO::FETCH_ASSOC);
// 2. Récupération des Salaires/Mensualités configurés (Revenus automatiques)
$currentYear = isset($_GET['y']) ? (int)$_GET['y'] : date('Y');
-$stmtSalaries = $pdo->prepare("SELECT person, mensualite FROM pf_salary_config WHERE year = ?");
+$stmtSalaries = $pdo->prepare("SELECT id, person, mensualite FROM pf_salary_config WHERE year = ?");
$stmtSalaries->execute([$currentYear]);
$salaries = $stmtSalaries->fetchAll(PDO::FETCH_ASSOC);
@@ -44,6 +44,10 @@ foreach ($allExpenses as $row) {
}
}
+$stmtSalariesReal = $pdo->prepare("SELECT salary_id, SUM(amount) as total_salary FROM pf_expenses WHERE gestion_month = ? AND salary_id IS NOT NULL GROUP BY salary_id");
+$stmtSalariesReal->execute([$viewMonthDate]);
+$realTotalsBySalary = $stmtSalariesReal->fetchAll(PDO::FETCH_KEY_PAIR);
+
// Récupération des détails pour le mapping par mots-clés (si besoin de précision)
$stmtLabels = $pdo->prepare("SELECT label, amount, category FROM pf_expenses WHERE gestion_month = ? AND budget_item_id IS NULL");
$stmtLabels->execute([$viewMonthDate]);
@@ -77,23 +81,42 @@ $totalRevenus = 0;
= ($mensualite - 0.10));
?>
|
Apport = htmlspecialchars($salary['person']) ?>
- ⚙️
+ ⚙️
|
+ = number_format($mensualite, 2, ',', ' ') ?> €
+
+ 0): ?>
+ 0.05): ?>
+ Bonus : += number_format($gap, 2, ',', ' ') ?> €
+
+ Manque : = number_format(abs($gap), 2, ',', ' ') ?> €
+
+ Atteint ✓
+
+
|
- Fixe (Auto)
+ Revenu Mensuel
|
- |
-
- Auto
-
+
+ ✓ Reçu
+ 0): ?>
+ ⏳ Partiel
+
+ ○ En attente
+
|
Via Paramètres
diff --git a/modules/budget/views/suivi.php b/modules/budget/views/suivi.php
index c86d05d..8b598f9 100644
--- a/modules/budget/views/suivi.php
+++ b/modules/budget/views/suivi.php
@@ -59,7 +59,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'reopen_month') {
if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
$count = 0;
- $stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, gestion_month, category, label, amount, import_ref, budget_item_id, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
+ $stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, gestion_month, category, label, amount, import_ref, budget_item_id, holiday_id, salary_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmtRule = $pdo->prepare("INSERT INTO pf_import_rules (keyword, category, budget_item_id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE category = VALUES(category), budget_item_id = VALUES(budget_item_id)");
if (isset($_POST['lines']) && is_array($_POST['lines'])) {
@@ -67,7 +67,19 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
if (isset($line['import_check'])) {
$cat = $line['cat'];
$is_credit = isset($line['is_credit']) ? (int)$line['is_credit'] : 0;
- $budgetItemId = !empty($line['budget_item_id']) ? (int)$line['budget_item_id'] : null;
+
+ // 🔥 NOUVEAU TRAITEMENT DU BUDGET_ITEM_ID
+ $rawItemId = $line['budget_item_id'] ?? '';
+ $budgetItemId = null;
+ $salaryId = null;
+ if (!empty($rawItemId)) {
+ if (strpos($rawItemId, 'SAL_') === 0) {
+ $salaryId = (int)str_replace('SAL_', '', $rawItemId);
+ } else {
+ $budgetItemId = (int)$rawItemId;
+ }
+ }
+
$holidayId = !empty($line['holiday_id']) ? (int)$line['holiday_id'] : null;
$gestionMonthLine = !empty($line['gestion_month']) ? $line['gestion_month'] . '-01' : $viewMonthDate;
@@ -78,7 +90,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
$dateToSave = $line['date'];
try {
- $stmtExp->execute([$dateToSave, $gestionMonthLine, $cat, $line['label'], $finalAmount, $line['ref'], $budgetItemId, $holidayId]);
+ $stmtExp->execute([$dateToSave, $gestionMonthLine, $cat, $line['label'], $finalAmount, $line['ref'], $budgetItemId, $holidayId, $salaryId]);
$stmtRule->execute([$line['label'], $cat, $budgetItemId]);
$count++;
} catch (Exception $e) { continue; }
@@ -232,7 +244,7 @@ while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
$name = trim($item['name']);
$catCode = $item['category']; // Ex: FIXED, FMCG, INCOME ou income
- $isIncome = (strtoupper($catCode) === 'INCOME' || (float)$item['amount'] > 0);
+ $isIncome = (strtoupper($catCode) === 'INCOME');
if ($isIncome) {
$incomeList[] = ['id' => $item['id'], 'name' => $name, 'amount' => $absAmount];
@@ -278,6 +290,15 @@ while ($item = $stmt->fetch(PDO::FETCH_ASSOC)) {
}
}
+$stmtSalaries = $pdo->query("SELECT id, person, salary FROM pf_salary_config WHERE year = " . $viewY);
+while ($sal = $stmtSalaries->fetch(PDO::FETCH_ASSOC)) {
+ $incomeList[] = [
+ 'id' => 'SAL_' . $sal['id'],
+ 'name' => 'Salaire ' . $sal['person'],
+ 'amount' => (float)$sal['salary']
+ ];
+}
+
// L'enveloppe "Autres" prend tout le reste du budget non alloué
$categoriesConfig['AUTRES']['budget'] = max(0, $total_income - $total_expenses_prevues);
@@ -621,12 +642,13 @@ $monthName = $monthNames[(int)$viewM] . ' ' . $viewY;
-
-
+
@@ -792,8 +814,8 @@ function closeSuiviModal(id) { document.getElementById(id).classList.remove('ope
// Gestion dynamique du formulaire modal
function handleModalCatChange(select) {
- const catKey = select.value;
- const conf = catConfigs[catKey] || {db_type: 'Expense', suggestions: []};
+ const catKey = select.value.toUpperCase(); // Sécurité sur la casse
+ const conf = catConfigs[select.value] || {db_type: 'Expense', suggestions: []};
document.getElementById('blockInputText').style.display = 'block';
document.getElementById('blockInputFrais').style.display = 'none';
@@ -806,13 +828,17 @@ function handleModalCatChange(select) {
document.getElementById('incomeSelect').disabled = true;
if (conf.db_type === 'Income') {
- document.getElementById('blockInputIncome').style.display = 'block';
- document.getElementById('incomeSelect').disabled = false;
- document.getElementById('incomeSelect').required = true;
- } else {
- // Optionnel : lier l'Expense à une charge fixe
+ const incSel = document.getElementById('incomeSelect');
+ // 👁️ Ne s'affiche que s'il y a des revenus prévus à lier, sinon reste invisible
+ if (incSel.options.length > 1) {
+ document.getElementById('blockInputIncome').style.display = 'block';
+ incSel.disabled = false;
+ }
+ }
+ else if (catKey === 'FIXED') { // 🔒 Strictement pour les Charges Fixes
document.getElementById('blockInputFrais').style.display = 'block';
document.getElementById('fraisSelect').disabled = false;
+ document.getElementById('fraisSelect').required = true; // 🔥 Devient OBLIGATOIRE
}
const list = document.getElementById('modalSuggestions'); list.innerHTML = '';
@@ -857,6 +883,7 @@ function handleLineCatChange(select, isInit = false) {
const row = select.closest('tr');
const fSel = row.querySelector('.select-frais');
const iSel = row.querySelector('.select-income');
+ const catKey = select.value.toUpperCase();
const conf = catConfigs[select.value] || null;
fSel.style.display = 'none'; iSel.style.display = 'none';
@@ -864,8 +891,12 @@ function handleLineCatChange(select, isInit = false) {
fSel.disabled = true; iSel.disabled = true;
if (conf && conf.db_type === 'Income') {
- iSel.style.display = 'block'; iSel.disabled = false;
- } else if (conf) {
+ // 👁️ Ne s'affiche que s'il y a des revenus à lier
+ if (iSel.options.length > 1) {
+ iSel.style.display = 'block'; iSel.disabled = false;
+ }
+ }
+ else if (catKey === 'FIXED') { // 🔒 Strictement pour les Charges Fixes
fSel.style.display = 'block'; fSel.disabled = false;
}
checkValidation();
@@ -879,18 +910,23 @@ function checkValidation() {
const row = cb.closest('tr');
const isCrd = row.querySelector('.is-credit-flag').value === '1';
const cat = row.querySelector('.line-select').value;
- const conf = catConfigs[cat] || null;
+ const catKey = cat.toUpperCase();
+ const fSel = row.querySelector('.select-frais');
let v = true;
if (cat === "") {
+ // La catégorie est obligatoire pour les dépenses (les revenus sont acceptés sans cat si souhaité)
if (!isCrd) v = false;
- } else if (conf && conf.db_type === 'Income' && row.querySelector('.select-income').value === "") {
- v = false;
+ }
+ else if (catKey === 'FIXED' && fSel.value === "") {
+ v = false; // 🔥 Bloque l'import si une Charge Fixe n'est pas liée à une ligne du budget !
}
+
if (!v) { miss++; row.style.background = '#fff1f2'; } else row.style.background = '';
});
+
const btn = document.getElementById('btnImport'); const msg = document.getElementById('missingCount');
- if(miss > 0) { btn.disabled = true; btn.style.opacity = 0.5; msg.style.display = 'inline'; msg.innerText = miss + ' ' + (window.I18N['bud_to_define_js'] || ''); }
+ if(miss > 0) { btn.disabled = true; btn.style.opacity = 0.5; msg.style.display = 'inline'; msg.innerText = miss + ' ' + (window.I18N['bud_to_define_js'] || 'à définir'); }
else { btn.disabled = false; btn.style.opacity = 1; msg.style.display = 'none'; }
}
@@ -901,7 +937,8 @@ if (document.getElementById('formMapping')) {
window.addEventListener('click', (e) => {
if (e.target.classList.contains('pf-modal')) {
- e.target.style.display = 'none';
+ e.target.classList.remove('open', 'is-active');
+ e.target.style.display = '';
document.body.classList.remove('no-scroll');
}
});
|