Skip to content

Attachments, entitlement import, manager-absence escalation, integration tests - #54

Merged
karlitschek merged 1 commit into
mainfrom
feat/attachments-import-escalation-integration
Aug 18, 2026
Merged

Attachments, entitlement import, manager-absence escalation, integration tests#54
karlitschek merged 1 commit into
mainfrom
feat/attachments-import-escalation-integration

Conversation

@karlitschek

Copy link
Copy Markdown
Member

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), nosniff downloads. 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 through EntitlementService, so the entitlement history records the import like any HR edit.

🧪 Integration tests on a real database

New composer test:integration — the existing phpunit-mysql workflow 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.php now honors NEXTCLOUD_ROOT and 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

…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>
@nextcloud-command nextcloud-command added the AI assisted This PR contains AI-assisted commits label Aug 18, 2026
@karlitschek
karlitschek merged commit 020cdc2 into main Aug 18, 2026
24 of 30 checks passed
@karlitschek
karlitschek deleted the feat/attachments-import-escalation-integration branch August 18, 2026 19:34
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