Ga naar hoofdinhoud

Compensation cycles

Before this capability, a salary change was a bare hand-edit of Employee.grossMonthlySalary — no band, no proposal trail, no separation of duties, no effective-dating, no record of who proposed what against which scale. Compensation cycles add the recurring management ritual: a periodic review round where managers propose adjustments against a band, a second person approves them, and the approved figure lands on the employee's compensation on a chosen effective date.

This lives as a personnel-dossier detail surface (Medewerkers › Functie & comp), not a tenth top-level menu.

Salary bands

SalaryBand models the employer's internal min/reference/max structure for a role or grade — optionally linked into the maintained CAO corpus. Bands are authored reference data (not hand-typed per adjustment) and may not be created ad hoc from the UI.

External market-data benchmarking (positioning a band against industry P25/P50/P75 salary-survey percentiles) is explicitly out of scope — it needs a licensed external feed that does not exist in the fleet today.

Review cycles and proposals

A CompReviewCycle groups a review round (a period, a default effective date). It is a grouping container only — it does not carry an approval lifecycle itself; a cycle may close while adjustments inside it are still in any state.

Each proposed change is a CompAdjustment running a declarative lifecycle:

draft → proposed → approved → effective

(reject → draft)

Separation of duties: approve reuses the same NoSelfApprovalGuard used everywhere else in HRMQ — the approver may never be the proposer.

Effective-dating is enforced, not just labelled: effectuate (approved → effective) is gated by a read-only guard that denies the transition unless the adjustment's effectiveDate has actually arrived. The salary write itself happens in an imperative service, not the guard — the guard only decides when, never what.

What happens on effectuation

For a due, approved adjustment, the service validates proposedSalary is within the target band, writes it onto Employee.grossMonthlySalary — the field the payroll engine reads — and drives the effectuate transition. An out-of-band proposal is flagged by a machine-checkable rule (vacuous when no target band is set); an already-effective adjustment is a no-op on retry.

occ hrmq:comp:effectuate --cycle <cycleId>
occ hrmq:comp:effectuate --cycle <cycleId> --date 2026-07-01 --dry-run

The endpoint that backs the "Effectueren" detail-page action resolves the target adjustment through the caller's ambient RBAC before any write — an unauthorized or unknown adjustment id never reaches the salary write.

Non-goals

Market-data benchmarking (above), effective-dating an hourly wage onto an EmploymentContract, and bulk/matrix comp-planning UI are named fast-follows, not silently missing scope.