Pay Periods
Group approved timesheet entries into named date ranges, lock them for payroll, export a provider-ready CSV.
Pay Periods
A pay period is a named date range — a week, a fortnight, a month — that acts as a container for approved time entries. Locking a period freezes every approved entry inside it so the payroll export is reproducible and the entries can't be retroactively edited.
The page lives at Time & Payroll → Timesheets → Pay Periods. Admins and fleet managers only.
Lifecycle
Every period moves through three states:
| Status | What it means | Who sets it |
|---|---|---|
| Open | Just created. No entries frozen yet. Editable by any admin. | Default on creation |
| Locked | Every approved entry in the date range has been stamped with this period's id and flagged locked. Entries in the range can't be edited or deleted. | Admin clicks Lock. Stamps locked_at / locked_by_id. |
| Paid | Final state. Payroll was run. Can't be reopened. | Admin clicks Mark paid from a locked period. Stamps paid_at / paid_by_id. |
Creating a period
Click New pay period in the header. The dialog asks for:
- Name — any label, e.g.
2026-04 Bi-weekly 1. Shown in the list and in the payroll CSV. - Starts on / Ends on — inclusive date range. End-of-day on the
ends_ondate is used when sweeping entries in. - Notes — free text, never shown to employees.
The period starts in the Open state.
What locking does
When you click Lock:
- Every entry in the tenant where
clock_in_atfalls within[starts_on 00:00, ends_on 23:59]andapproval_status = approvedgets updated in a single transaction:pay_period_id→ this period's idlocked→true
- The period itself moves to Locked,
locked_at = now(),locked_by_id = you.
Pending and Rejected entries in the range are not swept in. Approve them first if they belong in this run. You can reopen, then re-approve the stragglers, then lock again — see below.
Entries not approved by the time you lock are permanently excluded from that pay period's CSV unless you reopen. There's no grace flow.
Reopening
Reopen is available from a locked period (but not a paid one). Clicking it:
- Unsets
pay_period_idand flipslocked = falseon every entry previously swept in. - Flips the period back to Open, clears
locked_at/locked_by_id.
Those entries are once again editable and can be re-approved or re-rejected. When you're satisfied, lock again.
Paid periods (status = paid) refuse to reopen. If you genuinely need to amend a paid run, contact support — it's intentionally a one-way door.
Summary preview
Click the period's name (or the Summary button) to open an inline dialog showing per-employee aggregates for this period:
| Column | Meaning |
|---|---|
| Employee | Display name + employee code from pay profile |
| Regular | Hours up to the weekly overtime threshold |
| Overtime | Hours between the OT and double-time thresholds, paid at OT multiplier |
| Double | Hours beyond the double-time threshold (blank column when DT isn't configured) |
| Total hrs | Regular + Overtime + Double |
| Gross | Amount paid, in the employee's pay-profile currency |
| Entries | Count of approved entries included |
The summary is computed live against the locked entries, so the preview and the exported CSV always match.
Payroll CSV
The CSV button on a locked or paid period downloads a file named payroll-YYYY-MM-DD-YYYY-MM-DD.csv. It streams through the server; no intermediate file is written.
Column spec
| CSV column | Source |
|---|---|
Employee | users.name of the entry owner |
Employee Code | user_pay_profiles.employee_code (optional) |
Currency | user_pay_profiles.currency, default EUR |
Regular Hours | Splitting by week, hours up to overtime_threshold_weekly |
Overtime Hours | Hours between OT and DT thresholds |
Double-time Hours | Hours past double_time_threshold_weekly (0.00 if DT not configured) |
Break Hours | Sum of break_minutes / 60 across entries |
Total Hours | Regular + Overtime + Double |
Regular Pay | Regular Hours × hourly_rate |
Overtime Pay | Overtime Hours × hourly_rate × overtime_multiplier |
Double-time Pay | Double-time Hours × hourly_rate × double_time_multiplier |
Gross Pay | Regular + Overtime + Double pay |
Entries | Count of approved entries aggregated |
Period | The period's name |
Period Starts / Period Ends | ISO dates |
Monetary columns are always plain decimals (800.00, not € 800.00) so they import cleanly into spreadsheets and payroll tools.
Worked example
Setup
- Pay profile: hourly rate €15.00, OT threshold 40h/wk, OT multiplier 1.5, DT not configured.
- Week 1 of a two-week period: the employee works 48h (across several approved entries).
- Week 2: 36h.
What ends up in the CSV row
| Column | Value | How it was computed |
|---|---|---|
Regular Hours | 76.00 | Week 1 capped at 40 + Week 2's 36 |
Overtime Hours | 8.00 | Week 1 above 40 |
Double-time Hours | 0.00 | DT threshold not set |
Total Hours | 84.00 | 76 + 8 |
Regular Pay | 1140.00 | 76 × 15 |
Overtime Pay | 180.00 | 8 × 15 × 1.5 |
Gross Pay | 1320.00 | 1140 + 180 |
Overtime resets weekly. The service splits entries per ISO week (Monday – Sunday) before applying thresholds, so a period that straddles a week boundary is handled correctly.
Importing into a payroll tool
The column names map directly onto common payroll providers:
- Gusto — use Regular Hours and Overtime Hours columns on their bulk import template.
- ADP RUN — map Regular Hours →
REG, Overtime Hours →OT, Double-time Hours →OT2. - QuickBooks Payroll — import with column mapping; QB handles the arithmetic from hours, so you can ignore the
*_Paycolumns if you'd rather they re-derive.
For systems that want employee IDs, set Employee Code in each user's pay profile — it lands in the CSV as-is.
Permissions
- Admin — every action (create, lock, reopen, mark paid, export).
- Fleet manager — every action above.
- Everyone else — no access.
Related
- Review Timesheets — approve entries before locking a period.
- Pay Profiles — rates, overtime rules, employee codes.
- Time Tracking (employee) — clock-in/out flow.