ajust calcul estim
Deploy HouseHub / deploy (push) Successful in 3s

This commit is contained in:
FeFeClochette committed 2026-09-15 21:05:16 +02:00
1 parent a69a8950b3
commit 693b277002
8 files changed
+190 -21

No files matched your search

+33
View File
@@ -2701,3 +2701,36 @@ body.sum-mode-active .sum-target {
text-align: right;
white-space: nowrap;
}
/* --- 16. AJUSTEMENTS CESU (JSON OVERRIDES) --- */
.bs-cesu-wrapper {
margin-top: 20px;
padding: 16px;
background: var(--bg-page);
border-radius: 8px;
border: 1px solid var(--border-light);
}
.bs-cesu-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.bs-cesu-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.bs-cesu-row {
display: flex;
gap: 10px;
align-items: center;
}
.bs-cesu-row input[type="month"] {
width: 140px;
flex-shrink: 0;
}
.bs-cesu-row input[type="number"] {
width: 100px;
flex-shrink: 0;
}
@@ -32,6 +32,10 @@ class DynamicBudgetCalculator {
return isset($this->settings[$key]) ? (float)$this->settings[$key] : (float)$default;
}
private function getSettingString(string $key, string $default = ''): string {
return isset($this->settings[$key]) ? (string)$this->settings[$key] : $default;
}
private function loadPeople(): void {
// 1. Chargement des enfants sans doublons
$stmt = $this->pdo->query("SELECT id, name, care_modes FROM pf_people WHERE role IN ('enfant', 'child') AND is_active = 1");
@@ -85,10 +89,11 @@ class DynamicBudgetCalculator {
$zone = $this->foyerSettings['zone_scolaire'] ?? 'C';
try {
// 🔥 FIX : On récupère les DEUX années scolaires qui chevauchent l'année calendaire (ex: 2025-2026 ET 2026-2027)
// 🔥 FIX STATE OF THE ART : On cible uniquement la population "Élèves"
$prevYear = $this->year - 1;
$nextYear = $this->year + 1;
$where = "(annee_scolaire='{$prevYear}-{$this->year}' OR annee_scolaire='{$this->year}-{$nextYear}') AND zones LIKE '%Zone {$zone}%'";
$where = "(annee_scolaire='{$prevYear}-{$this->year}' OR annee_scolaire='{$this->year}-{$nextYear}') AND zones LIKE '%Zone {$zone}%' AND population = 'Élèves'";
$url = "https://data.education.gouv.fr/api/explore/v2.1/catalog/datasets/fr-en-calendrier-scolaire/records?where=" . urlencode($where) . "&limit=100";
$jsonVacances = @file_get_contents($url);
@@ -127,7 +132,16 @@ class DynamicBudgetCalculator {
$nannyFixed = $this->getSetting('budget_nanny_fixed', 700);
$nannyDaily = $this->getSetting('budget_nanny_daily', 4);
$nannyAid = $this->getSetting('budget_nanny_aid', 120);
$cesuAvg = $this->getSetting('budget_nanny_cesu_avg', 170);
$cesuAvg = $this->getSetting('budget_nanny_cesu_avg', 170);
$overridesJson = $this->getSettingString('budget_cesu_overrides', '{}');
$cesuOverrides = json_decode($overridesJson, true) ?: [];
$monthKey = sprintf("%04d-%02d", $this->year, $this->month);
$isCesuOverride = isset($cesuOverrides[$monthKey]);
$cesuApplied = $isCesuOverride ? (float)$cesuOverrides[$monthKey] : $cesuAvg;
$cesuLabelText = $isCesuOverride ? tr('bud_audit_cesu_adj') : tr('bud_audit_cesu_avg');
// ------------------------------------
$schoolMeal = $this->getSetting('budget_school_meal', 5.62);
$schoolAftercare = $this->getSetting('budget_school_aftercare', 1.97);
@@ -168,6 +182,8 @@ class DynamicBudgetCalculator {
if (in_array($dateStr, $offDays['feries'])) continue;
$isVacances = in_array($dateStr, $offDays['vacances']);
$events = $this->calendarEvents[$dateStr] ?? [];
$nannyOffToday = false;
@@ -216,13 +232,15 @@ class DynamicBudgetCalculator {
}
$totalNannyFees = $totalNannyDays * $nannyDaily;
$nannyGlobalCost = $nannyFixed + $totalNannyFees - $nannyAid - $cesuAvg;
// On soustrait $cesuApplied au lieu de $cesuAvg
$nannyGlobalCost = $nannyFixed + $totalNannyFees - $nannyAid - $cesuApplied;
// 3. Rédaction du Bilan
if ($hasNanny) {
$totalCost += max(0, $nannyGlobalCost);
// On passe $cesuLabelText au sprintf
$details .= sprintf(tr('bud_audit_nanny_full') . "\n",
$this->helperName, $nannyFixed, $totalNannyDays, $nannyDaily, $totalNannyFees, $nannyAid, $cesuAvg, max(0, $nannyGlobalCost));
$this->helperName, $nannyFixed, $totalNannyDays, $nannyDaily, $totalNannyFees, $nannyAid, $cesuLabelText, $cesuApplied, max(0, $nannyGlobalCost));
if ($nannyOffDays > 0) {
$details .= sprintf(tr('bud_audit_nanny_abs') . "\n", $this->helperName, $nannyOffDays);
+2 -1
View File
@@ -205,7 +205,8 @@ try {
if ($action === 'save_dynamic_estimates') {
$keys = [
'budget_nanny_fixed', 'budget_nanny_daily', 'budget_nanny_aid', 'budget_nanny_cesu_avg',
'budget_school_meal', 'budget_school_aftercare', 'budget_school_fullday'
'budget_school_meal', 'budget_school_aftercare', 'budget_school_fullday',
'budget_cesu_overrides'
];
$pdo->beginTransaction();
+95 -5
View File
@@ -224,10 +224,11 @@ if (!defined('CURRENCY')) {
<!-- NOUVEAU PANE ENFANTS -->
<div id="pane-children" class="bs-pane">
<h4 class="bs-section-title">👶 <?= tr('bud_settings_dyn_title') ?></h4>
<p class="pf-muted-tiny bs-desc">Configurez ici les tarifs pour le calcul dynamique des frais de garde et de cantine.</p>
<p class="pf-muted-tiny bs-desc"><?= tr('bud_settings_dyn_desc') ?></p>
<form id="form-dynamic-estimates" onsubmit="saveDynamicEstimates(event)">
<h5 class="bs-section-subtitle bordered">Assistante Maternelle</h5>
<h5 class="bs-section-subtitle bordered"><?= tr('bud_dyn_sub_nanny') ?></h5>
<div class="bs-grid-2">
<div>
<label class="pf-label"><?= tr('bud_dyn_nanny_fixed') ?></label>
@@ -247,7 +248,17 @@ if (!defined('CURRENCY')) {
</div>
</div>
<h5 class="bs-section-subtitle bordered" style="margin-top: 20px;">École & Centre de loisirs</h5>
<!-- Ajustements ponctuels du CESU -->
<div style="margin-top: 20px;">
<div class="bs-cesu-header" style="display:flex; justify-content:space-between; margin-bottom:12px;">
<label class="pf-label pf-m-0"><?= tr('bud_dyn_cesu_overrides') ?></label>
<button type="button" class="pf-btn btn-secondary pf-btn-sm" onclick="addCesuOverride()">+ <?= tr('bud_dyn_cesu_add') ?></button>
</div>
<div id="cesu-overrides-list" class="bs-cesu-list"></div>
<input type="hidden" name="budget_cesu_overrides" id="budget_cesu_overrides_input" value="{}">
</div>
<h5 class="bs-section-subtitle bordered pf-mt-md"><?= tr('bud_dyn_sub_school') ?></h5>
<div class="bs-grid-2">
<div>
<label class="pf-label"><?= tr('bud_dyn_school_meal') ?></label>
@@ -263,8 +274,8 @@ if (!defined('CURRENCY')) {
</div>
</div>
<div class="bs-text-right" style="margin-top: 20px;">
<button type="submit" class="btn btn-secondary">💾 <?= tr('btn_save') ?></button>
<div class="bs-text-right pf-mt-md">
<button type="submit" class="pf-btn btn-secondary">💾 <?= tr('btn_save') ?></button>
</div>
</form>
</div>
@@ -341,6 +352,10 @@ async function loadBudgetSettingsData() {
if (document.getElementById('budget_school_aftercare')) document.getElementById('budget_school_aftercare').value = bs.budget_school_aftercare || 1.97;
if (document.getElementById('budget_school_fullday')) document.getElementById('budget_school_fullday').value = bs.budget_school_fullday || 21.89;
try { cesuOverrides = JSON.parse(bs.budget_cesu_overrides || '{}'); }
catch (e) { cesuOverrides = {}; }
renderCesuOverrides();
} catch (err) {
console.error("Erreur chargement paramètres :", err);
const errorMsg = `<div class="pf-alert pf-alert--error bs-input-mb">❌ ${tr('error_loading_settings')} : ${err.message}</div>`;
@@ -657,4 +672,79 @@ async function saveDynamicEstimates(e) {
btn.disabled = false;
}
}
// --- LOGIQUE DES AJUSTEMENTS CESU ---
let cesuOverrides = {};
function renderCesuOverrides() {
const list = document.getElementById('cesu-overrides-list');
const input = document.getElementById('budget_cesu_overrides_input');
if (!list || !input) return;
list.innerHTML = '';
// Tri décroissant pour afficher le mois le plus lointain en haut
const keys = Object.keys(cesuOverrides).sort().reverse();
if (keys.length === 0) {
list.innerHTML = `<div class="pf-empty-dashed" style="margin:0; padding:12px;"><em class="pf-muted-tiny">${window.I18N['bud_dyn_cesu_empty'] || 'Aucun ajustement enregistré.'}</em></div>`;
} else {
keys.forEach(k => {
const btnDelText = window.I18N['btn_delete'] || 'Supprimer';
// UI : Design aéré type "Carte HouseHub" avec des inputs sans bordures agressives
list.innerHTML += `
<div style="display: flex; gap: 10px; align-items: center; background: var(--bg-panel); padding: 8px 12px; border-radius: 8px; border: 1px solid var(--border-light); box-shadow: var(--shadow-sm);">
<input type="month" class="pf-input" style="width: 150px; margin: 0; padding: 6px 10px;" value="${k}" onchange="updateCesuOverrideKey('${k}', this.value)">
<input type="number" step="0.01" class="pf-input" style="flex: 1; margin: 0; padding: 6px 10px; text-align: right;" value="${cesuOverrides[k]}" onchange="updateCesuOverrideValue('${k}', this.value)">
<span style="color: var(--text-muted); font-weight: 600; margin-right: 5px;">€</span>
<button type="button" class="btn-icon-action delete" style="margin: 0; background: var(--bg-danger-soft); width: 32px; height: 32px;" onclick="removeCesuOverride('${k}')" title="${btnDelText}">🗑️</button>
</div>
`;
});
}
input.value = JSON.stringify(cesuOverrides);
}
function addCesuOverride() {
const keys = Object.keys(cesuOverrides).sort();
let nextDate = new Date();
nextDate.setMonth(nextDate.getMonth() + 1); // Base : le mois prochain
// S'il y a déjà des mois dans la liste, on prend le plus lointain et on ajoute 1 mois
if (keys.length > 0) {
const lastKey = keys[keys.length - 1]; // ex: "2026-10"
const parts = lastKey.split('-');
// L'astuce : Le mois en JS commence à 0. Donc en lui passant "10", on pointe déjà sur le mois d'après (Novembre) !
nextDate = new Date(parts[0], parseInt(parts[1], 10), 1);
}
// Sécurité anti-boucle infinie
let m = nextDate.getFullYear() + '-' + String(nextDate.getMonth() + 1).padStart(2, '0');
let safety = 0;
while (cesuOverrides[m] !== undefined && safety < 24) {
nextDate.setMonth(nextDate.getMonth() + 1);
m = nextDate.getFullYear() + '-' + String(nextDate.getMonth() + 1).padStart(2, '0');
safety++;
}
cesuOverrides[m] = 0;
renderCesuOverrides();
}
function updateCesuOverrideKey(oldK, newK) {
if (oldK !== newK && newK) {
cesuOverrides[newK] = cesuOverrides[oldK];
delete cesuOverrides[oldK];
renderCesuOverrides();
}
}
function updateCesuOverrideValue(k, val) {
cesuOverrides[k] = parseFloat(val) || 0;
renderCesuOverrides();
}
function removeCesuOverride(k) {
delete cesuOverrides[k];
renderCesuOverrides();
}
</script>