Skip to main content

Timesheets

Timesheet models a submitted timesheet as an OpenRegister object in the hrmq register, whose approval workflow is governed by a declarative x-openregister-lifecycle state machine — not bespoke PHP transition code.

The lifecycle

draft → submitted → approved

rejected
TransitionFrom → to
submitdraft or rejectedsubmitted
approvesubmittedapproved
rejectsubmittedrejected
reopenapproveddraft

A rejected timesheet can be corrected and resubmitted through the same submit transition. An invalid transition — for example attempting approve on a draft timesheet — is refused, and the state stays unchanged.

Every Timesheet carries at least employeeId, period, hours, status, submittedAt, approvedBy, approvedAt, and rejectionReason.

Separation of duties

An employee cannot approve or reject their own timesheet. The approve and reject transitions each declare requires: OCA\Hrmq\Lifecycle\NoSelfApprovalGuard — an OpenRegister lifecycle guard that denies the transition when the acting user equals the timesheet's employeeId. The guard fails closed: if the acting user or the claiming employee cannot be identified at all, the transition is denied rather than allowed.

A manager (or any other user) approving a different employee's timesheet is allowed normally, and the transition stamps approvedBy and approvedAt.

Pages

Timesheets is a plain index list. TimesheetApproval is the same schema pre-filtered to status == submitted — the pending-approval queue. TimesheetDetail drives the lifecycle actions. All three are declarative manifest pages rendered generically by @conduction/nextcloud-vue — configured only with { register: "hrmq", schema: "Timesheet", ... }, no bespoke Vue components.

Managers get a team-scoped view of the same queue — see Self-service for the Team-urengoedkeuring page.