Self-service
Humaniq gives every logged-in employee an in-app self-service surface — never
a separate portal app — by scoping the same schemas and pages employees
already use with the OpenRegister renderer's @me filter token.
Mijn HR
Mijn HR is a routed group: clicking its title opens the personal
dashboard at /mijn, and the chevron still folds its children away. Every
Mijn surface lives under the /mijn/... prefix (the older
/mijn-hr/gebruikelijk-loon path redirects there, so existing bookmarks
keep working).
Its children are the @me-scoped index pages: uren
(TimeEntry — the booking surface, see
Hours & timesheets), urenstaten (Timesheet,
read-only), declaraties (Expenses), verlof (LeaveRequests), and
loonstroken (Payslips) — each pre-filtered to the current user's own
records.
Scoping works through a denormalized userId property on Timesheet,
TimeEntry, Expense, LeaveRequest, LeaveBalance and Payslip — a plain copy of
the linked Employee.nextcloudUserId, filtered with the renderer's @me
token. This is the one mechanism verified to work with today's renderer
and OpenRegister; it is not a live join. Employee.nextcloudUserId links
an employee record to its Nextcloud account and stays null for
portal-only external employees (that scoping remains UUID-claim based in
portaliq and is untouched here). For Timesheet and TimeEntry the
stamp is server-maintained: it is re-derived from employeeId on
every write, so it can never drift and employees never set it. For
Expense and LeaveRequest HR maintains it (their process redesigns
follow), Payslip's userId is set by payroll alongside employeeId
since employees never author their own payslips, and LeaveBalance's is
written by the leave-accrual job — the schema's only systematic writer —
on both the balances it creates and the ones it accrues onto each month,
so a balance from before the property existed picks the link up on the
next run without a migration. Records whose employee
has no linked account keep userId: null and never appear on a Mijn
page — fail-closed.
External (no-NC-account) self-service — for candidates or contractors without a Nextcloud login — stays with portaliq and is out of scope here.
The personal dashboard
/mijn is the employee's own landing surface — the app Dashboard is the
management steering view and carries no @me-scoped widgets. Six widgets,
every one of them filtered to the caller:
- Mijn uren deze maand — the sum of the caller's own booked hours since the first of the current month, opening their booking page.
- Mijn open declaraties — how many of their expense claims are with the
organisation awaiting a decision (
submitted); drafts are their own unfinished input and are not counted. - Te beoordelen urenstaten — the caller's own approval queue. It always
renders, so somebody who manages nobody sees a plain
0rather than a tile that silently disappears. - Mijn urenstaten — the most recent timesheets with their period, hours, entry count and status; a row opens the timesheet, which is where submission happens.
- Mijn verlofsaldo — this year's balance per leave type: entitled,
bovenwettelijk and used hours. It reads the denormalized
userIdabove, so a balance not yet linked to an account is left out rather than shown to the wrong person. - Mijn recente loonstroken — the last few payslips, read-only.
Where a widget shows nothing it is saying "nothing of yours here", never "nothing exists" — the filters fail closed by design.
Team-scoped approval for managers
Managers get their own approval queues, never a "Manager portaal" — Humaniq
adds Dashboard widgets and scoped pages inside the existing menus
instead. A denormalized managerUserId (the same pattern as userId)
on Timesheet, Expense, and LeaveRequest drives three team-scoped
pages. For Timesheet it is a server-maintained cache of the org
chain (OrgAssignment → OrgUnit.managerId → the manager's
nextcloudUserId), stamped on every write; for Expense and
LeaveRequest it remains HR/back-office maintained until their own
process redesigns:
Team-urengoedkeuring— Timesheet, filtered tomanagerUserId: @meTeam-declaratiegoedkeuring— Expense, same patternTeam-verlofgoedkeuring— LeaveRequest, same pattern
Each sits directly under its existing menu group, right after the global
approval entry — TimesheetsGroup, ExpensesGroup,
VerlofVerzuimGroup — composing the manager's @me filter with the
approval page's existing status: submitted filter. A manager's own
queue also surfaces on the personal dashboard above, as the
"Te beoordelen urenstaten" tile that opens Team-urengoedkeuring.
Kept honest by a consistency rule
Because managerUserId is a denormalized stamp rather than a live lookup,
a recommended-severity rule cross-checks it against the actual
organisation structure: nl-mss-manager-consistency resolves each
record's employee to their active OrgAssignment, that unit's manager,
and the manager's nextcloudUserId — and flags a stamp that doesn't match
what the org chart says. It never punishes missing org data: unresolvable
hops (no active assignment, an unmanaged unit, a manager without an NC
account) pass vacuously rather than flagging false violations.
occ humaniq:rules:audit
See The compliance rule engine for how the
audit and its coverage metric work, and Org chart
for the OrgUnit/OrgAssignment structure the consistency rule checks
against.