WorkestraDocs
PlatformTime Tracking

Approvals

Multi-level approval chains, period locking after final approval, notifications at every step. Single-level fallback for free workspaces.

Approvals

When a user submits their weekly timesheet, it walks an approval chain: a sequence of approvers that each have to approve before the next sees it. Free workspaces get a single level (workspace admins / managers approve everything). Time Pro unlocks routes per user / team / project / workspace, with stacked levels.

After the final level approves, all entries in the period are locked — no edits, no deletes — so finance teams can trust the numbers.

Approval queue with multi-level chain

Screenshot needed — /time/approvals showing a queued timesheet with the chain preview ('After you, this goes to: Sarah → Finance').

The approval queue

/time/approvals is the inbox-style page where approvers see what needs their attention. Only timesheets where the current user is the current-level approver show up.

For each timesheet:

  • Submitter name + period (Mon Apr 28 – Sun May 4)
  • Total hours · Billable hours
  • A chain preview: "After you, this goes to → next approver"
  • Inline buttons: Approve · Reject with reason · View details

Bulk-approve with comment is supported when multiple are selected.

Multi-level chains (Pro)

Configurable at /time/settings/approvers. Define routes by scope + level:

Scope typeMatches whenExample
userThe submitter is this specific user"All of Alice's timesheets go to Bob → Carol"
teamThe submitter belongs to this team"Engineering team → Dave (lead) → Erin (manager)"
projectThe timesheet contains entries on this project"Project Acme → Acme PM → Finance"
workspaceFallback for everything else"Anyone else → Workspace admin"

Routing picks the most-specific applicable scope first. Within a route, levels are sequential (1, 2, 3, …) — each must approve before the next sees it.

Fallback approvers

Each route step can have an optional fallback_user_id — used if the primary approver is OOO (per People module's time_off records) or has been deactivated. Falls back without manual intervention.

Editing routes

/time/settings/approvers shows every route grouped by scope. Edit inline; deletions cascade safely (active timesheets in flight finish on the chain that was active when they submitted).

Period locking (Pro)

When the final approval lands:

  • All time_entries in the period get locked_at = now().
  • RLS policy denies UPDATE / DELETE on locked entries.
  • The timesheet status flips from submitted to approved.

Workspace admins can unlock a period via /time/timesheet/[id] → … → Unlock for editing. The unlock action is audit-logged and invalidates downstream invoices that pulled from the period (a notice surfaces).

Free workspaces don't get period locking — the equivalent guardrail is the standard status = 'approved' check at write time.

Single-level approval (free)

When time_approval_routes has no rows, the system falls back to the single-level model:

  • Anyone with time:approve permission can approve any submitted timesheet.
  • That maps to workspace admins and the manager role by default.
  • No chain preview, no period locking, no fallback approver logic.

Most small teams (≤10 people) live happily on this for the lifetime of the workspace.

Submitter side — what users see

When a user submits at the end of the week:

  1. Their timesheet flips from draft to submitted.
  2. The current-level approver gets an in-app notification + (if configured) email / Slack / Teams notification — all routed through the central notificationService.
  3. The user can see "Awaiting approval from Sarah (Team Lead)" on /time/timesheet.
  4. As each level approves, the chain advances. The user gets a notification when their timesheet reaches a new level.
  5. Final approval → notification "Your timesheet was approved (37.5h)".

Rejecting

Approvers can reject with a reason. The submitter gets a notification with the reason; the timesheet flips back to draft so they can edit and re-submit. The chain restarts from level 1 on resubmission.

Bulk-reject is supported but each rejected timesheet gets the same reason — for nuanced feedback, reject one at a time.

Auto-submit (Pro)

Don't want users to have to remember to submit? Set time_tracking_settings.auto_submit_dow to a day-of-week (e.g. Friday). A daily cron at 22:00 workspace-local checks: for any user whose draft timesheet for the current week has total_hours > 0, auto-submit it.

Users can opt out per-user via /settings/profile.

Notifications

EventWho gets notifiedChannels
Timesheet submittedCurrent-level approverIn-app + email + Slack/Teams (per channel prefs)
Approved (intermediate level)SubmitterIn-app
Approved (final)SubmitterIn-app + email
RejectedSubmitterIn-app + email
Reached new levelSubmitter (informational)In-app
OOO fallback usedOriginal approver + fallback approverIn-app

All routing through @/lib/services/notification-service — never direct per-channel calls.


Next steps

  • Time Pro — multi-level + period locking are Pro features
  • Rates & Billing — approved entries are what feeds the invoice bridge
  • Settings — auto-submit day-of-week, runaway-timer threshold