Overtime & Hours
Hours worked, hours paid, and the overtime between them. The trap is assuming the math: what counts toward the threshold is tenant configuration, and paid hours are the only hours that cost money.
◆ The traps— configured thresholds, multiple positions, rounding, and the approval gap.
| Trap | What goes wrong |
|---|---|
| Overtime is configuration | Which hours count toward the threshold, worked hours, holiday hours in a configured order, some benefit hours, is tenant setup. A warehouse that re-derives OT as hours over 40 will disagree with payroll wherever the rules are richer. The rulebar's Never: extract the calculated result. |
| Multiple positions | For workers holding several positions, overtime attaches to the position worked when the threshold crossed. Cost by org must follow the position on the line, not the worker's primary assignment. |
| The approval gap | Time unapproved by the payroll deadline is not paid in that run; it lands later or not at all. The operational control is a pre-deadline unapproved-time report from the entries; the cost reports never see the gap, which is exactly why the control exists. |
| Rounding and partial hours | Entries round on configured rules before calculation. Reconcile reported-to-paid at the period grain, not the punch grain. |
| Comp time instead of pay | Overtime can accrue as compensatory time at premium rates instead of cash, with expiry and forced payout. Those hours cost money later; they belong to the liability report, not this one. |
◆ The owned model, and the SQL— paid hours and cost from the payroll fact, and the pre-deadline control from the entries.
Cost reads fct_payroll_result, where hours ride the earning lines their components classified. Overtime share by org:
-- paid hours and overtime share by org, May, earned-for view
SELECT o.level_2 AS organization,
SUM(f.hours) AS paid_hours,
SUM(CASE WHEN pc.is_overtime THEN f.hours ELSE 0 END) AS ot_hours,
ROUND(100.0 * SUM(CASE WHEN pc.is_overtime THEN f.hours ELSE 0 END)
/ SUM(f.hours), 1) AS ot_pct,
ROUND(SUM(CASE WHEN pc.is_overtime THEN f.amount ELSE 0 END), 0) AS ot_cost
FROM fct_payroll_result f
JOIN dim_pay_component pc ON f.pay_component_key = pc.pay_component_key
JOIN dim_org o ON f.org_key = o.org_key
WHERE f.earned_for_period_key = 202605
AND pc.class = 'Earning' AND pc.carries_hours
GROUP BY 1
ORDER BY 4 DESC
Sample output. The percentages divide exactly, and overtime cost per hour runs above the base rate as it should:
| organization | paid_hours | ot_hours | ot_pct | ot_cost |
|---|---|---|---|---|
| Field Sales West | 24,000 | 1,200 | 5.0 | 93,600 |
| Sales Ops | 7,500 | 150 | 2.0 | 10,800 |
The operational control is one query on the landed entries: unapproved hours by manager, run daily in the two days before the payroll deadline, so nothing misses the run. A persistent overtime cluster in one org, read next to the span report, is usually a staffing answer, not a payroll one. Sample values are illustrative, never client data.
- worked hours
- What was entered and calculated. Operational truth.
- paid hours
- Hours on committed payroll result lines. The only hours that cost money.
- overtime calculation
- Tenant-configured rules deciding which hours cross the threshold. Extracted, never re-derived.
- approval gap
- Time unapproved by the payroll deadline. Unpaid until approved; controlled before the deadline.
- rounding rules
- Configured entry rounding before calculation. Reconcile at the period grain.
- comp time
- Overtime banked as premium-rate time off instead of cash. A liability, with expiry.
- position on the line
- Where multi-position workers' overtime attaches. Cost follows it.
- pre-deadline control
- The daily unapproved-hours query before each payroll cutoff.