Skip to content

Let HR record their own absence - #47

Merged
karlitschek merged 1 commit into
mainfrom
fix/noid/hr-can-record-own-absence
Aug 12, 2026
Merged

Let HR record their own absence#47
karlitschek merged 1 commit into
mainfrom
fix/noid/hr-can-record-own-absence

Conversation

@karlitschek

Copy link
Copy Markdown
Member

An HR member could not pick themselves in Record absence, so they could not record their own sick leave at all.

Not a permission problem

The server has always allowed this. RequestService::create() defaults employeeUid to the actor when none is given, so onBehalf is false for yourself — a supported path. More tellingly, both update() and cancel() carry explicit branches for "HR editing their own HR-recorded leave":

// "This is also the only edit path for HR-recorded leave (e.g. sick), even the HR
//  member's own record — the employee path below would reject it (§5.6)."

Those branches were unreachable for a record HR could never create in the first place.

The picker was the blocker

/api/employees/search wraps core's collaborator search, which deliberately removes the searching user from its own results:

// lib/private/Collaboration/Collaborators/UserPlugin.php:155
if (isset($users[$currentUser->getUID()])) {
    unset($users[$currentUser->getUID()]);
}

That rule is right for what it was built for — "who can I share with", and nobody shares with themselves. This picker asks a different question: "whose absence is this?", where you are a perfectly valid answer.

And there was no way around it. canSubmit() refuses until an employee is chosen:

if (this.hrMode && !this.selectedEmployee) {
    return false
}

while the only other route — New request — offers just requestableLeaveTypes (enabled && employeeRequestable), which excludes sick leave by design (§5.6). So for exactly the types Record-absence exists for, HR had no route at all.

The fix

The search now includes the signed-in user when they match what was typed, listed first — somebody typing their own name has matched it exactly and should not be pushed below looser matches.

Guests are still excluded, their own account included: a guest holds no entitlement and takes no leave whoever is doing the searching (§2.2).

This one result is deliberately not gated on the admin's user-enumeration settings. Those exist to stop people discovering colleagues they have no business seeing; returning you to yourself discloses nothing you do not already know. Every other result stays gated exactly as before.

I did not make the dialog default to you when nothing is typed — its main use is recording other people's sick leave, and a self-default there invites mis-filing.

Testing

171 tests, 471 assertions, 0 failures (5 new, in a new EmployeeControllerTest). Two of the five fail without the change. The other three pin the rules that must not regress: a non-matching self stays out, a guest self is never offered, and self is never listed twice should core ever stop filtering it.

No frontend change — the picker already renders whatever the endpoint returns — so there is no asset rebuild in this PR.

🤖 Generated with Claude Code

An HR member could not pick themselves in "Record absence", so they could not
record their own sick leave at all.

Not a permission problem — the server has always allowed it. create() defaults
employeeUid to the actor, onBehalf is false for yourself, and both update() and
cancel() carry explicit branches for "HR editing their own HR-recorded leave"
(§5.6), which were unreachable for a record HR could never create.

The picker was the blocker. It calls the app's employee search, which wraps core's
collaborator search — and that deliberately drops the searching user from its own
results, because it answers "who can I share with" and nobody shares with
themselves. This picker asks "whose absence is this?", where you are a valid
answer. The dialog then refuses to submit until an employee is chosen, and the
only other route, "New request", offers just the self-requestable types, which
sick leave deliberately is not. So there was no way through.

The search now adds the signed-in user when they match what was typed, listed
first because somebody typing their own name has matched it exactly. Guests are
still excluded, their own account included. This one result is deliberately not
gated on user-enumeration settings: those exist to stop people discovering
colleagues, and returning you to yourself discloses nothing you do not know.

Two of the five new tests fail without the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nextcloud-command nextcloud-command added the AI assisted This PR contains AI-assisted commits label Aug 12, 2026
@karlitschek
karlitschek merged commit 7472e94 into main Aug 12, 2026
30 of 34 checks passed
@karlitschek
karlitschek deleted the fix/noid/hr-can-record-own-absence branch August 12, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI assisted This PR contains AI-assisted commits

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants