feat: enhance balance calculation to include total balance from all months and exclude sick days
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m57s

This commit is contained in:
Felix Schlusche
2026-03-04 14:44:01 +01:00
parent a837a8af59
commit f50e0fee7e

View File

@@ -1279,8 +1279,8 @@ async function updateStatistics(entries) {
// Calculate previous month balance // Calculate previous month balance
const previousBalance = await calculatePreviousBalance(); const previousBalance = await calculatePreviousBalance();
// Total balance = previous balance + current month balance // Total balance = all months from first entry up to today (independent of displayed month)
const totalBalance = previousBalance + balance; const totalBalance = await calculateTotalBalance();
// Count actual work entries (excluding vacation/flextime/sickday, only up to today) // Count actual work entries (excluding vacation/flextime/sickday, only up to today)
const workEntriesCount = entries.filter(e => { 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) * 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() { async function calculatePreviousBalance(upToYear, upToMonth) {
const currentYear = displayYear; const currentYear = (upToYear !== undefined) ? upToYear : displayYear;
const currentMonth = displayMonth; const currentMonth = (upToMonth !== undefined) ? upToMonth : displayMonth;
// Find the first month with any entries by checking all entries // Find the first month with any entries by checking all entries
const allEntries = await fetchEntries(); const allEntries = await fetchEntries();
@@ -1576,6 +1577,13 @@ async function calculatePreviousBalance() {
.map(e => e.date) .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) // Count flextime days (they are workdays with 0 hours worked)
const flextimeDays = new Set( const flextimeDays = new Set(
entries entries
@@ -1599,8 +1607,8 @@ async function calculatePreviousBalance() {
const dateISO = `${year}-${month}-${dayStr}`; const dateISO = `${year}-${month}-${dayStr}`;
if (!isWeekendOrHoliday(dateObj)) { if (!isWeekendOrHoliday(dateObj)) {
// Exclude vacation days from workdays count // Exclude vacation and sick days from workdays count
if (!vacationDays.has(dateISO)) { if (!vacationDays.has(dateISO) && !sickDays.has(dateISO)) {
workdaysPassed++; workdaysPassed++;
} }
} else if (flextimeDays.has(dateISO)) { } else if (flextimeDays.has(dateISO)) {
@@ -1629,6 +1637,58 @@ async function calculatePreviousBalance() {
return totalBalance; 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 * Open modal for adding/editing entry
*/ */