Attachments, entitlement import, manager-absence escalation, integration tests - #54
Merged
Merged
Conversation
…tegration tests
Attachments (§3.8):
- Files on a leave request — the doctor's note above all. Metadata in
absence_attachments; bytes in the app's own appdata storage, keyed by row
id, never in anybody's Files. Visible to HR and the employee alone — the
manager reads the request, not the medical documents on it; confidential
requests (§5.7) hide attachments even from the employee. Upload for HR
always and for the employee on their own non-terminal request (including
HR-recorded sick leave). 10 MB / 10 files per request, sanitized names,
nosniff downloads. Adds and removals go to the always-on audit log, never
the manager-visible history timeline. GDPR purge removes rows and bytes.
Manager-absence-aware escalation (§5.4a):
- A request whose assigned manager is on approved leave today is created
ESCALATED immediately ("Manager is away — routed to HR"), and the hourly
job escalates pending requests early when the manager's absence outlasts
the request's own escalation deadline. Both ride the race-safe conditional
flip, so a concurrent decision is never clobbered. Deliberately not
delegated to the manager's replacement: deciding leave means reading
reasons and balances.
Entitlement import (§6.3):
- occ absence:import-entitlements <csv> [--year N] [--dry-run]: uid or
e-mail, comma or semicolon separated (German Excel), BOM tolerated,
all-or-nothing with per-line errors. Writes go through EntitlementService,
so the entitlement history records the import like any HR edit.
Integration tests:
- composer test:integration (picked up by the existing phpunit-mysql
workflow) exercising what mocked mappers cannot: the SQL aggregation,
markEscalated's flip-exactly-once semantics, the covering-absence query,
badge counts, and single/batch balance parity including the §5.3 netting
rule — plus a query-budget guard (tests/Integration/base-query-count.txt)
so the batch path's fixed query count cannot regress silently.
- tests/bootstrap.php honors NEXTCLOUD_ROOT and tolerates a packaged server,
so symlinked dev setups can run the suite too.
Docs updated throughout (README, SPECIFICATION §3.8/§5.4a/§6.3/§14,
handbooks); built js/ included per convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #53 — the next four items from the improvement list, developed and verified end-to-end on the devel instance.
📎 Attachments (doctor's notes) — §3.8
Files on a leave request. Metadata in
absence_attachments; bytes in the app's own appdata storage, never in anybody's Files — so the API is the only door. It opens for HR and the employee only (including on HR-recorded sick leave, exactly where a doctor's note belongs); the manager deliberately gets nothing, and confidential requests (§5.7) hide attachments even from the employee. 10 MB / 10 files per request, names sanitized, bytes stored by row id (no client input near a path),nosniffdownloads. Adds/removals hit the always-on audit log but never the manager-visible history timeline. GDPR purge removes rows and bytes. Sidebar UI with upload/download/remove.Verified live: employee uploads to own sick record ✓, HR downloads ✓, manager gets 403 and an empty list ✓.
⏰ Manager-absence-aware escalation — §5.4a
The app manages the decider's leave too, so it should act on it: a request whose manager is on approved leave today is escalated to HR at submission ("Manager is away — routed to HR"), and the hourly job escalates pending requests early when the manager's absence outlasts the request's own escalation deadline. Both paths use the race-safe conditional flip from #53, so a manager deciding at that second is never clobbered. Deliberately not delegated to the manager's replacement — deciding leave means reading reasons and balances.
📥 Entitlement import — §6.3
occ absence:import-entitlements balances.csv [--year N] [--dry-run]— the onboarding path from the spreadsheet. uid or e-mail, comma or semicolon separated (German Excel), BOM tolerated, and all-or-nothing: every broken line reported, nothing written. Writes go throughEntitlementService, so the entitlement history records the import like any HR edit.🧪 Integration tests on a real database
New
composer test:integration— the existingphpunit-mysqlworkflow picks it up automatically. Covers exactly what mocked mappers cannot: the balance SQL aggregation,markEscalated's flip-exactly-once semantics, the covering-absence query, badge counts, and single/batch balance parity including the netting rule — plus a query-budget guard (tests/Integration/base-query-count.txt) so the batch path's fixed query count can't regress one innocent lookup at a time.tests/bootstrap.phpnow honorsNEXTCLOUD_ROOTand tolerates packaged servers (symlinked dev setups can run the suite). Ran green against a real MySQL before pushing.Gates: 229 unit tests / 678 assertions (+14 new), 6 integration tests, psalm clean, php-cs clean, 43 frontend tests, eslint clean. Version bump intentionally left out.
🤖 Generated with Claude Code