Concepts
Overtime calculation

How the overtime balance is calculated

The dashboard's overtime number for any staff member, year, and month follows a single recipe — applied month by month from the staff member's first logged WFM entry through today (or through disabled_at, if overtime tracking was turned off).

Per-month diff

For each calendar month M:

working_days       = Mon–Fri days in M
public_holiday_days = Zurich public holidays falling on Mon–Fri in M
daily_hours        = weekly_hours ÷ 5   (weekly_hours defaults to 40 → 8 h)

target_hours = working_days × daily_hours × FTE%  − public_holiday_days × daily_hours × FTE%
                ↑                                    ↑
                full month, FTE-weighted            PH days subtracted

actual_hours = sum of all WFM timesheet entries in M
                (worked time + paid absences: vacation, sick leave, etc.)

diff = actual_hours − target_hours

daily_hours is the full-time hours-per-working-day baseline. Most staff are on the standard 40 h week (8 h/day). Someone contracted for 38 h/week at 100 % FTE uses weekly_hours = 38 → 7.6 h/day. Set this per person in Staff Admin → Overtime tracking. It composes with FTE %: a 38-hour, 80 %-FTE employee = 7.6 × 0.8 per working day.

  • diff > 0 → overtime that month
  • diff < 0 → undertime that month

Running balance

The dashboard shows running_balance per row — the cumulative sum of diff from the first logged month onwards.

For the personal page, "Previous years" is the running balance at Dec 31 of (selected year − 1). The Total at the bottom is Previous years + sum of this year's diffs.

Three overrides change this baseline

All three live in Staff Admin and only affect the overtime calc (every other metrics page is unaffected).

FTE history

If a staff member's FTE % changed over time, an FTE history entry per change makes the target_hours for each historical month use the right percentage instead of today's.

Resolution rule: for any month M, FTE % is the entry with the greatest effective_from ≤ first day of M. Months before the earliest entry use the earliest entry's FTE. Staff with no entries fall back to today's WFM FTE — no behavioural change.

Base

A closing balance at the end of a chosen month. Months ≤ that month are discarded from the overtime calc (the underlying WFM data is untouched and still feeds every other report).

The running balance at the end of the base's month is the entered value; subsequent months accumulate on top. The latest base whose date is on or before today wins.

Correction

A signed-hours add/subtract on a specific date, applied at the end of its effective month after that month's diff.

Common uses:

  • Overtime payout — enter the paid-out hours as a negative value.
  • Contract change — a one-off positive or negative bump.
  • Reconciliation — manual adjustment for missed entries or deviations from prior tools.

Multiple corrections in the same month sum. Corrections in months a Base already covers are ignored.

Worked example: Dario, 2026

Suppose Dario's setup is:

  • Eligibility: enabled since Jan 2024.
  • FTE history: none (he's been 100 % the whole time).
  • Base: Dec 2025, 56.10 h (closing balance carried over from the manual xlsx).

Year 2026:

MonthTargetActualDiffRunning
Previous years56.10 ← from base
January160.00181.25+21.2577.35
February160.00106.00−54.0023.35
March176.00189.00+13.0036.35
April160.00160.50+0.5036.85

"Previous years" = 56.10 because the Base discards earlier months for overtime reporting and seeds the running balance at the Base's closing value.

If a manager then adds a Correction of −20 h on 2026-03-10 (overtime payout), the March row becomes:

| March | 176.00 | 189.00 | +13.00 (−20 adj) | 16.35 |

and April's running balance drops by 20 accordingly.

All three overrides are pure — no precomputed value is stored. Each page load walks the full WFM timesheet history, applies the overrides on top, and renders. Editing an override and pressing Load again reflects the new numbers immediately.