From f50e0fee7ed91e31ad3638a4db951ab2701a9f55 Mon Sep 17 00:00:00 2001 From: Felix Schlusche Date: Wed, 4 Mar 2026 14:44:01 +0100 Subject: [PATCH] feat: enhance balance calculation to include total balance from all months and exclude sick days --- public/js/main.js | 74 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index af0269b..cd7a35c 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1279,8 +1279,8 @@ async function updateStatistics(entries) { // Calculate previous month balance const previousBalance = await calculatePreviousBalance(); - // Total balance = previous balance + current month balance - const totalBalance = previousBalance + balance; + // Total balance = all months from first entry up to today (independent of displayed month) + const totalBalance = await calculateTotalBalance(); // Count actual work entries (excluding vacation/flextime/sickday, only up to today) const workEntriesCount = entries.filter(e => { @@ -1513,10 +1513,11 @@ async function addBridgeDaysAsVacation(days) { /** * Calculate balance from all previous months (starting from first month with entries) + * up to (but not including) the given month. Defaults to the currently displayed month. */ -async function calculatePreviousBalance() { - const currentYear = displayYear; - const currentMonth = displayMonth; +async function calculatePreviousBalance(upToYear, upToMonth) { + const currentYear = (upToYear !== undefined) ? upToYear : displayYear; + const currentMonth = (upToMonth !== undefined) ? upToMonth : displayMonth; // Find the first month with any entries by checking all entries const allEntries = await fetchEntries(); @@ -1576,6 +1577,13 @@ async function calculatePreviousBalance() { .map(e => e.date) ); + // Count sick days to exclude from workdays + const sickDays = new Set( + entries + .filter(e => e.entryType === 'sickday') + .map(e => e.date) + ); + // Count flextime days (they are workdays with 0 hours worked) const flextimeDays = new Set( entries @@ -1599,8 +1607,8 @@ async function calculatePreviousBalance() { const dateISO = `${year}-${month}-${dayStr}`; if (!isWeekendOrHoliday(dateObj)) { - // Exclude vacation days from workdays count - if (!vacationDays.has(dateISO)) { + // Exclude vacation and sick days from workdays count + if (!vacationDays.has(dateISO) && !sickDays.has(dateISO)) { workdaysPassed++; } } else if (flextimeDays.has(dateISO)) { @@ -1629,6 +1637,58 @@ async function calculatePreviousBalance() { return totalBalance; } +/** + * Calculate the total balance from first entry up to and including the displayed month. + * For the current or future months the calculation is capped at today. + */ +async function calculateTotalBalance() { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // End of the displayed month + const displayedMonthEnd = new Date(displayYear, displayMonth + 1, 0); + displayedMonthEnd.setHours(0, 0, 0, 0); + + // Effective cut-off: end of displayed month, but never beyond today + const effectiveCutoff = displayedMonthEnd < today ? displayedMonthEnd : today; + const effectiveYear = effectiveCutoff.getFullYear(); + const effectiveMonth = effectiveCutoff.getMonth(); + + // All months before the effective month + const previousBalance = await calculatePreviousBalance(effectiveYear, effectiveMonth); + + // Add the effective month up to the effective cut-off date + const firstDay = `${effectiveYear}-${String(effectiveMonth + 1).padStart(2, '0')}-01`; + const lastDayNum = new Date(effectiveYear, effectiveMonth + 1, 0).getDate(); + const lastDayStr = `${effectiveYear}-${String(effectiveMonth + 1).padStart(2, '0')}-${String(lastDayNum).padStart(2, '0')}`; + const entries = await fetchEntries(firstDay, lastDayStr); + + const vacationDays = new Set(entries.filter(e => e.entryType === 'vacation').map(e => e.date)); + const sickDays = new Set(entries.filter(e => e.entryType === 'sickday').map(e => e.date)); + const flextimeDays = new Set(entries.filter(e => e.entryType === 'flextime').map(e => e.date)); + + let workdaysPassed = 0; + for (let day = 1; day <= lastDayNum; day++) { + const dateObj = new Date(effectiveYear, effectiveMonth, day); + dateObj.setHours(0, 0, 0, 0); + if (dateObj > effectiveCutoff) break; + const dateISO = `${effectiveYear}-${String(effectiveMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + if (!isWeekendOrHoliday(dateObj)) { + if (!vacationDays.has(dateISO) && !sickDays.has(dateISO)) workdaysPassed++; + } else if (flextimeDays.has(dateISO)) { + workdaysPassed++; + } + } + + const targetHours = workdaysPassed * 8; + const actualHours = entries.reduce((sum, entry) => sum + entry.netHours, 0); + const monthBalance = actualHours - targetHours; + + const total = previousBalance + monthBalance; + console.log(`calculateTotalBalance: displayed=${displayYear}-${displayMonth + 1}, cutoff=${effectiveYear}-${effectiveMonth + 1}, previousBalance=${previousBalance.toFixed(1)}h, effectiveMonth=${monthBalance.toFixed(1)}h, total=${total.toFixed(1)}h`); + return total; +} + /** * Open modal for adding/editing entry */