Skip to content

Request an integration

Tell us which system you want to connect. We review every request weekly and publish a step-by-step guide or build the connection.

Hour types and rule packs

How approved minutes are split into payroll hour types (normal, overtime, ORT bands, holidays) by a JSON rule pack, which compliance warnings it raises, and the full list of hour-type codes.

Last updated: · Markdown version

At export time, TimeTracking turns approved days into pay lines. A pay line is person × date × hour type × minutes (and an amount for allowances). The org's rule pack decides the hour types. The export mapping then turns pay lines into the payroll file.

The rule engine is deterministic: the output depends only on the input days and the rule pack version.

Hour-type codes

Code Produced by Notes
normal every pack Minutes not taken by a band, holiday or overtime rule
overtime_125, overtime_150, … dailyOvertime / weeklyOvertime rules Name is free ([a-z0-9_]{1,40})
ort_evening, ort_night, ort_saturday, ort_sunday NL basic pack bands ORT = irregular hours
night, sunday DE basic pack bands —
holiday holidayHourType Minutes on public holidays (NL, DE, BE calendars)
allowance_<code> placement components with unit per_day e.g. allowance_travel; minutes 0, amountCents set
expense_km, expense_travel_time, expense_public_transport, expense_parking, expense_meal, expense_other approved expenses travel_time carries minutes; the others carry amountCents
leave_vacation, leave_sick, leave_special, leave_unpaid, leave_<code> approved leave minutes_per_day on each working day (Mon–Fri, excluding public holidays)

Built-in packs

id Name Behaviour
plain Geen toeslagen (alles normaal) Everything is normal. The default.
nl-basic NL basis (voorbeeld — afstemmen op CAO) NL holidays → holiday; Sun → ort_sunday; Sat → ort_saturday; Mon–Fri 00:00–06:00 → ort_night; Mon–Fri 20:00–24:00 → ort_evening; weekly overtime after 40 h → overtime_125; ATW breaks (>5.5 h → 30 min, >10 h → 45 min); 11 h rest; max 12 h shift
de-basic DE Basis (Beispiel — an Tarifvertrag anpassen) DE holidays; Sun → sunday; nights (Mon–Sat 00:00–06:00 and 23:00–24:00) → night; daily overtime after 8 h → overtime_125; ArbZG breaks (>6 h → 30 min, >9 h → 45 min); 11 h rest; max 10 h

The NL and DE packs are examples. Align thresholds and bands with your CAO or Tarifvertrag. An admin picks a pack, or pastes a custom JSON pack, in "Regels & koppelingen" → "Looncomponenten en toeslagen". Custom packs are validated before saving. There is no API for rule packs.

Rule pack JSON schema

{
  "id": "acme-cao",
  "name": "ACME CAO 2026",
  "version": 1,
  "holidays": "NL",
  "includeOptionalHolidays": false,
  "holidayHourType": "holiday",
  "bands": [
    { "hourType": "ort_sunday", "days": [7], "from": 0, "to": 1440 },
    { "hourType": "ort_night", "days": [1, 2, 3, 4, 5], "from": 0, "to": 360 }
  ],
  "dailyOvertime": [{ "afterMin": 540, "hourType": "overtime_125" }],
  "weeklyOvertime": [{ "afterMin": 2400, "hourType": "overtime_125" }, { "afterMin": 2880, "hourType": "overtime_150" }],
  "breakRules": [{ "workedOverMin": 330, "minBreakMin": 30 }, { "workedOverMin": 600, "minBreakMin": 45 }],
  "minDailyRestMin": 660,
  "maxShiftMin": 720
}
Field Type Rules
id, name string required
version integer required
holidays NL | DE | BE | none required
includeOptionalHolidays boolean optional
holidayHourType [a-z0-9_]{1,40} optional. Without it, holidays are not special
bands[] {hourType, days: 1..7 (ISO weekday), from, to} minutes [from, to), to ≤ 1440, to > from. The first matching band wins
dailyOvertime[], weeklyOvertime[] {afterMin, hourType} afterMin ≤ 10080
breakRules[] {workedOverMin, minBreakMin} warnings only
minDailyRestMin, maxShiftMin integer minutes warnings only

How the split works

  1. Classify each day minute by minute. Holiday first, then the first matching band, else normal. Minutes after midnight use the next day's weekday and holiday status. When only a total is known (no start/end), the whole day gets the holiday type, a full-day band (from: 0, to: 1440), or normal.
  2. Deduct breaks. Breaks are unpaid. Without exact break times, they come out of normal minutes first, then out of the largest other type. Surcharge minutes are therefore never inflated.
  3. Daily overtime moves normal minutes above the threshold. The highest threshold applies first.
  4. Weekly overtime works per placement and ISO week, chronologically, on the remaining normal minutes. The engine loads the whole week even when the export range starts mid-week.
  5. Warnings (pay is never changed): break_too_short, rest_too_short, shift_too_long.

Warnings appear in the export preview and are stored on the export run. Captured example:

{ "key": "00000000-0000-4000-8000-0000000000e4", "date": "2026-12-02", "code": "break_too_short", "message": "Break 0 min, at least 30 min required", "person": "Sanne Janssen" }

Warnings never block a commit. Validation issues from the export mapping do.

Mapping hour types to payroll codes

Payroll systems use their own wage codes (looncomponenten / Lohnarten). Translate hour types with a map transform and a code table in the export mapping:

{ "header": "Looncomponent", "source": "hourType", "transform": { "kind": "map", "table": "wageCodes", "fallback": "" } }
"tables": { "wageCodes": { "normal": "1000", "overtime_125": "1125", "ort_night": "2100", "expense_km": "8010", "leave_vacation": "3000" } }

Unmapped hour types become the fallback (or empty). Add a required validation rule on that column so a missing code blocks the commit. See export mappings.