All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- CSRF protection on all state-changing endpoints: Removed
#[NoCSRFRequired]from POST/PUT/DELETE methods inAbsenceController,TimeEntryController,TimeTrackingController,ComplianceController,SubstituteController,SettingsController,MonthClosureController,GdprController, andAdminController. The frontend already submitsrequesttokenconsistently viaArbeitszeitCheckUtils.ajax, so all mutating routes now reject cross-site requests by default. GET-only endpoints intentionally remain#[NoCSRFRequired](CSRF is irrelevant for read-only GETs in Nextcloud's framework). - No raw exception leakage in JSON responses: Hardened
AbsenceController::getSafeErrorMessageso that exception messages are only forwarded when they are explicit business-rule\Exceptioninstances; messages containing technical fingerprints (SQL fragments, file paths, stack traces, oversized payloads) are replaced with a generic localized error. Applied the hardened helper toAbsenceController::store/update. Replaced directgetMessage()leakage inAdminController::getTeams,SettingsController::index_api, andPageControllerpage-render error paths with sanitized localized messages. - Correct HTTP status for authentication errors:
SettingsController::updatenow returnsHTTP 401 Unauthorized(wasHTTP 400 Bad Request) when the request is unauthenticated, matching what API clients and load balancers expect.
- Organization-scope monthly report downloads:
reports.jsnow forwards user IDs resolved during preview to thereport.teamendpoint and falls back to a clear "no organization members had time entries in the selected period" message instead of the misleading "preview first" hint when an organization-wide preview yields zero results. - Sanitized dashboard load errors:
dashboard.js/dashboard.cssnow surface a localized "Some dashboard data could not be loaded." live-region message instead of raw widget exceptions. - Resume-after-break clarity: Clock-in copy and l10n unified around "Resume after break" instead of the legacy
clock_in_resumeplaceholder.
- Main landmark on every page: 17 page templates now expose a single, properly labelled
<main id="app-content" role="main" aria-label="...">landmark for assistive technologies (dashboard,index,timeline,calendar,settings,personal-settings,reports,compliance-dashboard,compliance-reports,compliance-violations,working-time-models,admin-dashboard,admin-teams,admin-users,admin-holidays,manager-dashboard,manager-time-entries,manager-absences,manager-month-closures). - Skip link /
<main>consistency:time-entries,absences,admin-settings,admin-notifications,substitution-requests, andaudit-logpreviously hadid="app-content"on a plain<div>whilerole="main"lived on a child wrapper, so the "Skip to main content" target landed on a non-landmark. All six now use<main id="app-content" role="main">directly. Removed redundantrole="banner"from the<header>insideaudit-log's main region. - Accessible names on all data tables: Added
aria-label/aria-labelledbyand screen-reader captions to the holiday list table and to the two notification-matrix tables that previously had no accessible name. - Live error announcement on dashboard: Dashboard error section now lives inside an
aria-liveregion so partial widget failures are announced without disrupting focus. - Manager dashboard team metrics now announced: Stat numbers (Team Members / Active Today / Hours Today / Pending Absences) had
aria-hidden="true"on the value spans, which silenced every metric for screen reader users. Each card now exposes a single, fully readable accessible name (e.g. "5 team members active today") via arole="group"wrapper while keeping the visual layout intact. - Alert vs live-region conflicts resolved: Removed conflicting
aria-live="polite"fromrole="alert"containers inabsences.php(form error),admin-settings.php(global error banner), and three time-entry inline form errors.role="alert"already implies assertive announcements, so the previouspoliteoverride could delay critical validation feedback for assistive technology. - Page heading hierarchy normalized: Every primary page template now exposes exactly one
<h1>(dashboard, time-entries, absences, calendar, timeline, reports, settings, personal-settings, compliance-dashboard, compliance-reports, compliance-violations, working-time-models, admin-dashboard, admin-users, admin-holidays, admin-settings, admin-notifications, manager-dashboard). Previously most pages started at<h2>. Subordinate section headings in time-entries and absences were promoted toh2/h3so the ladder no longer skips levels. CSS rules for.section-header h1were added to inherit the existingh2styling. - Manager dashboard breadcrumb: Added the standard "Dashboard › Manager Dashboard" breadcrumb to align with all other primary pages and improve orientation for keyboard and screen reader users.
- Calendar loading state announced: The "Loading calendar…" placeholder now uses
role="status"witharia-live="polite"so the loading and ready transitions are announced. The decorative spinner isaria-hidden. - Focus indicators restored:
outline: nonewas used on the timeline filter checkboxes and the admin user-picker items, breaking keyboard focus visibility. Added:focus-visibleoutlines using the primary color for both, preserving hover styling. - Mobile touch targets:
.btn--smwas 36 × 36 px on mobile, below WCAG 2.5.5's 44 × 44 advisory. The mobile media query now enforces 44 × 44 px on small buttons; desktop sizing is unchanged. - Empty-state row for legacy
index.phptime-entries view: Restored the missing empty-state row when no entries exist (parity with the other table views in the same template). - Reports access fallback navigation: Replaced an inline
onclickredirect inreports.php's no-access empty state with a real<a>anchor so the dashboard fallback works without JavaScript and inherits standard link semantics.
- Stale Nextcloud personal-settings panel placeholder: The old
personal-settings.phppanel rendered inside Nextcloud's user-settings shell with hardcoded vacation-days / working-hours fields and reminder checkboxes that were never wired to any backend. Replaced it with a clean, accurate panel pointing the user to the in-app personal settings page (where these preferences are actually persisted viaSettingsController::update) plus a short GDPR data-rights note. Kept the legacyindex.php"settings" branch (dead code, but still in the file) but pulled the previously hardcoded1.0.1version string fromIAppManager::getAppVersion('arbeitszeitcheck').
- AccessibilityTest hardened: Replaced the "must contain
<button>" assertion (which permitted pages without keyboard-reachable controls and false-flagged link-only panels) with two stricter checks: (1) explicitly forbid the<div onclick=…>anti-pattern and (2) require at least one<button>or<a href>per audited template. Total suite: 455 tests, 1 652 assertions, all green.
- Critical workflow audit checklist: Added
tests/WORKFLOW_AUDIT_CHECKLIST.mdas a concise release checklist for time tracking, manual entry corrections, absences/approvals, month closure, reporting/compliance/export behavior, and public error-surface expectations.
- Time tracking mutation safety: Clock/break mutations now use user-scoped locks and transactions; status polling remains read-only while automatic break fallback and daily maximum enforcement run through explicit mutation paths/background jobs.
- API input and error hardening: Report, export, compliance, manager, and time tracking endpoints now use stricter date/time parsing, safer validation responses, and generic public error messages for unexpected failures.
- Month-closure enforcement: Absence update/delete/cancel/shorten/approval/substitute flows now re-check month mutability before applying workflow mutations.
- Health endpoint fingerprinting: The public health response no longer exposes app or Nextcloud version fields.
- Absence approval forensics: Added
approved_by_user_idpersistence on absence records (approve/reject/auto-approve), with schema migration and API summary output.
- Vacation entitlement snapshot integrity: Added deterministic key-based upsert on
(user_id, period_key, as_of_date)and migration-backed unique index enforcement. - Concurrency control in critical workflows: Absence create/update/approve/reject/substitute flows now use user-scoped mutation locks plus transactional rechecks/row locks to prevent race-based overlap and over-approval inconsistencies.
- Release safety: Workflow/unit/integration tests were updated and executed against the hardened mutation paths.
- Legacy snapshot repair path: Upsert now handles historical malformed rows and concurrent unique-key conflicts safely by retrying as deterministic update.
- Vacation balance write races:
VacationYearBalanceMapper::upsertnow resolves concurrent unique-key collisions via re-read/update fallback.
- Release packaging refresh: Bumped app metadata to
1.2.5and regenerated the signed release artifact set for App Store and GitHub publication.
- Publishable release refresh: Bumped app metadata to
1.2.4and generated a new signed release artifact set (archive, checksums, and App Store signature) for App Store/GitHub publication.
- Release packaging refresh: Prepared a new signed App Store/GitHub release archive for the current code line using the Docker-based signing workflow.
- Localized decimal inputs in admin settings: Daily working-hour inputs now reliably accept comma-decimals like
7,74and preserve two-decimal precision. - Legacy hours API payload parsing: Time-entry endpoints now parse optional decimal hour fields consistently for both comma and dot separators, preventing silent truncation in backward-compatible request formats.
- Input precision hints: Updated settings input steps/help text to align with two-decimal hour values used in 38.7-hour week scenarios.
- Paused-entry recovery and lifecycle: Paused entries can now be accessed again in edit/delete workflows and are consistently finalized as
completedwhen edited with an end time. - Resume behavior for same-day paused sessions: Clock-in now resumes a same-day paused entry instead of creating duplicate automatic entries, while preserving the pause gap as break history.
- Historical paused leftovers: Added migration
Version1020Date20260421000000to repair all remaining orphanedpausedrows (including cases not covered by the earlier one-time migration).
- Vacation entitlement policy engine: New policy-driven calculation flow with support for
manual_fixed,model_based_simple,tariff_rule_based, andmanual_exception, plus admin simulation endpoint. - Tariff rule data model and APIs: Added versioned tariff rule sets/modules and admin endpoints to create, update, activate, retire, and assign policies to users.
- Entitlement computation snapshots: Added persistent entitlement snapshots (
at_entitlement_snapshots) with calculation trace/policy fingerprint for auditability and diagnostics. - Admin notifications page: New dedicated admin UI (
/admin/notifications) with HR recipient + event matrix management and a dedicated notifications settings API.
- Vacation allocation integration: Year allocation now resolves entitlement via
VacationEntitlementEngineand returns entitlement source/rule-set/trace metadata in allocation payloads. - Policy migration compatibility: Existing user model vacation values are backfilled into policy assignments during migration (
Version1018Date20260420123000) to keep legacy installs consistent. - Admin settings flow: Absence notification-related controls (carryover expiry/cap, rollover switches, substitute-required types, iCal and substitution-mail toggles) are centralized on admin notifications APIs/UI.
- Working time model schema: Added
work_days_per_weektoat_models(Version1019Date20260420150000) to support entitlement formulas.
- User deletion cleanup: Deleting a user now also removes vacation policy assignments and entitlement snapshots, preventing orphaned policy/computation data.
- Approver deadlock (app teams): Absence and time-entry correction workflows no longer treat “has colleagues” as “has a manager”. Auto-approval when no assignable approver exists now follows
TeamResolverService::hasAssignableManagerForEmployee()(explicit team managers in app-teams mode; legacy group mode still uses colleagues as a proxy). Prevents requests stuck in “awaiting manager approval” when nobody can approve. - Time entry corrections: Same assignability rule as absences (previously used colleague IDs only).
- Admin users API requests on
/index.phpinstances: Refresh/edit/history/update actions now reliably resolve app URLs and no longer produce invalid requests likesearch=[object PointerEvent]. - Admin teams and settings API reliability on rewrite-less setups: Central URL resolution now includes a robust
/index.phpfallback whenOC.generateUrl()is unavailable/incomplete in page context.
- Repair step
ReleaseStuckPendingAbsences: post-migration repair auto-approves legacypendingabsences that still match the “no assignable approver” condition (idempotent). - Frontend URL security guardrails: Shared AJAX layer now blocks external cross-origin calls by default (explicit
allowExternal: truerequired), with unit tests covering URL normalization and external URL handling. - Lint guardrails: ESLint rules now prevent introducing raw
fetch('/apps/arbeitszeitcheck/...')and implicit externalfetch(...)patterns outside approved abstractions.
- UX: Absences UI shows an informational callout when app teams are enabled and no approver is assigned; detail view shows a defensive warning if an old
pendingrow is still stuck (until repair/admin fixes team setup). - Frontend architecture:
ArbeitszeitCheckUtilsnow provides centralizedgetRequestToken(),resolveUrl(), andisExternalUrl()primitives used by page scripts (admin-users,reports,settings,validation). - Mobile UX consistency (WCAG 2.1 AA focused): iPhone-safe-area-aware spacing, improved touch targets, clearer section rhythm, and better visual hierarchy for normal user pages (
dashboard,time-entries,absences) and manager pages (manager-dashboard,manager-time-entries, employee absences view).
- User manuals (EN/DE),
tests/WORKFLOW_ROLE_MATRIX.md, and developer documentation updated for assignable-manager semantics and repair step. - README and developer documentation updated with centralized frontend URL policy, strict external-call behavior, and mobile/iOS layout guidance.
- Month closure grace period and auto-finalization: Admin setting
month_closure_grace_days_after_eom(0–90, default 0). After end-of-month, employees have that many calendar days to finalize manually; if the month is still open afterward, a daily background job finalizes it automatically (same snapshot as manual finalize). Pending time entry approvals and open absence workflow states block auto-finalization. Reopening remains admin-only. - App-admin allowlist: New admin setting
app_admin_user_idsto restrict ArbeitszeitCheck administration to a selected subset of Nextcloud admins. Empty selection keeps backward-compatible behavior (all Nextcloud admins can administer the app). - Security role-gating Docker test target: Added
scripts/test-security-role-gating-docker.shwiring viamake test-security-role-gating-dockerandcomposer test:security-role-gating:dockerfor fast authorization regression checks in containerized setups.
- Month closure UX and API: Employee UI uses a clearer card layout, visible feedback for success/errors (WCAG-friendly), server-driven
canFinalizewith localized block reasons (feature off, future month, pending approvals). Manual finalize rejects future calendar months. Absence workflow (pending,substitute_pending,substitute_declined) is enforced alongside pending time entry corrections. Unauthorized API access returns 401 where appropriate. Admin settings: dedicated “Month closure” section; grace-days field stays editable with copy explaining it is saved even when closure is off; reopen uses searchable employee picker and clearer administrator vs. employee wording. Form validation error callouts use higher-contrast text and tinted surfaces across themes. Auto-finalize job logs per-user failures for operations. - Release/signing workflow hardened for integrity checks:
make release-signednow signs the extracted release archive payload (not the local development checkout), validates forbidden development paths are excluded, and repacks the signed archive for deployment/App Store upload. - Admin authorization enforcement: Access to
AdminControllerroutes now uses middleware-level app-admin checks with a dedicated exception and a consistent 403 response page for authenticated users without app-admin rights.
- Deployment guidance: Release docs now explicitly require production deployment from the signed tarball only and document the common integrity-failure pattern (
.git/*/node_modules/*lists) caused by signing a dev tree. - Deployment helper script: Added
release/deploy-from-release.shto deploy from signed release archives with safety checks (forbidden path scan, requiredsignature.json, optional app disable/enable andocc integrity:check-app). - Admin operations: User/developer docs now describe how to configure app-admin allowlisting, what the default fallback is, and how to verify authorization gating in Docker-based test runs.
- Revision-safe month finalization (optional): Admin toggle
month_closure_enabled(default off). Employees can finalize a full calendar month; the app stores a canonical JSON snapshot, SHA-256 hash chain, append-only revision rows, audit events, and a minimal PDF download. Finalized months are read-only through normal app APIs; administrators may reopen a month with a mandatory reason (audit). Monthly reports for a finalized month use the stored snapshot. Database:at_month_closure,at_month_closure_revision(migrationVersion1014Date20260409120000).
- User manuals (EN/DE), developer documentation, and compliance notes updated for month closure, retention context, and limits (in-app tamper evidence, not QES).
- Manager employee absences view: New in-app page and API for managers/admins to review employee absences with secure scope filtering, pagination, and localized status labels.
- Working time model copy flow: Added copy action with modal UX, unique default naming, and safeguards against duplicate submits.
- Manager navigation structure: Sidebar regrouped into clearer manager/admin submenus; reports moved under manager context; compliance link placement adjusted for reduced top-level clutter.
- Manager employee time entries UX: Date defaults and formatting/translation handling improved for clearer filtering behavior.
- Calendar behavior (rollback cleanup): Removed in-progress direct calendar-write functionality and related admin controls/status/test endpoints. The supported behavior remains unchanged: no Nextcloud Calendar app sync; optional
.icsattachments are sent by email for configured absence workflows.
- Working time model modals: Corrected copy modal interaction flow, source-model presentation, and delete-confirmation localization/rendering issues.
- Absence iCal hardening: Added stricter status/date guards, recipient deduplication, and privacy-safe event descriptions for substitute/manager recipients.
- User manuals and changelogs updated to reflect the final calendar model (email
.icsoptional, no direct Nextcloud Calendar app sync) and current manager/admin UX structure.
- Vacation rollover:
VacationRolloverService, background job,occ arbeitszeitcheck:vacation-rollover, migrationVersion1013Date20260407120000withat_vacation_rollover_log; unit tests.
- Frontend l10n: Shared
templates/common/main-ui-l10n.phpandteams-l10n.phpso translated strings are available early across pages; related template and JS updates.
- Manager dashboard — pending absences: API includes
summary.typeLabel(server-localized absence type); UI prefers it so cards show translated labels (e.g. German Urlaub) instead of raw codes likevacation.
docs/Developer-Documentation.en.md: pending-approvals API note fortypeLabel; user manuals (EN/DE): manager pending approvals show localized absence types.
- Nextcloud Calendar app (CalDAV): Absence sync into the Calendar app is removed; migration
Version1012Date20260406120000drops theat_absence_calendartable. Calendars previously created in the Calendar app are not deleted automatically.
- Holiday service: Public holiday calendar logic consolidated in
HolidayService.
- AdminController: Duplicate
usestatement forHolidayServicecaused a PHP fatal error (e.g. when PHPUnit loaded the class).
- User manuals (EN/DE) in
docs/, README and developer documentation updated; helper scriptdocker/run-app-phpunit.shfor containerized PHPUnit.
- Vacation carryover (Resturlaub): Per user and calendar year, opening balance
carryover_daysinat_vacation_year_balance; global admin setting for carryover expiry (month/day, default 31 March).VacationAllocationServiceapplies FIFO consumption of approved vacation (bystart_date, thenid) and splits working days before/after expiry so carryover is used first where still valid. - Validation & approvals: Vacation requests are re-validated when a manager approves (and on auto-approve) so concurrent pending requests cannot overdraw balances after approval.
- API & UI:
AbsenceController::statsexposes entitlement, carryover, totals, expiry-related fields; dashboard and absences pages show a clear vacation summary; admin settings include expiry fields. - GDPR:
UserDeletedListenerremoves vacation year balance rows when a user account is deleted. - Migration / bulk setup:
occ arbeitszeitcheck:import-vacation-balanceimports CSVuser_id,year,carryover_dayswith--dry-run.
- Unit tests for
VacationAllocationService; extendedAbsenceServiceand related controller tests.
- Development tooling:
occ arbeitszeitcheck:generate-test-dataCLI for deterministic demo data (time entries, absences, optional violations, demo app team) to exercise UI, reports, and workflows locally. - Exports:
TimeEntryExportTransformercentralizes field mapping and CSV shaping for time-entry exports;ExportControllerdelegates to it for a single, testable pipeline.
- Reports UI: Report type cards are no longer incorrectly disabled when a team-related scope is selected (team scopes still use the team report API where applicable).
- Reports (tests): Team report CSV download test now reads download bodies via
DataDownloadResponse::render()(Nextcloud API). - Team reports: Deduplicate user IDs before permission checks and aggregation to avoid double-counting when users appear in multiple teams.
- Absence type badges: Stronger, theme-safe contrast for vacation / sick / home office / other badges (readable on pale Nextcloud palettes).
- Compatibility (dev): Local development stacks aligned with Nextcloud 33.x (example: official
nextcloudDocker image). - Reports layout: Reverted an overly aggressive “full width” parameter form rule that could interfere with scrolling/layout on the reports page.
- Reports UI: Templates, JavaScript, and styling updates for the reports page; admin settings hook for related options.
- Reporting:
ReportControllerandReportingServiceadjustments aligned with the export refactor.
- Unit tests for
TimeEntryExportTransformer; expandedReportControllertests;ExportControllertests updated for the new wiring.
- Admin settings API URL handling: Prevented duplicate
index.php/index.phppath generation when a route URL is already pre-generated by Nextcloud. - Frontend error handling: Avoided unhandled Promise rejections in callback-based
Utils.ajax()consumers after expected API failures.
- Routing/compatibility: Added
indexApi()compatibility aliases for legacy endpoints to prevent 500 errors in the Nextcloud log. - PHP fatal errors: Fixed constructor signature issues in
AbsenceServiceandComplianceServicethat could crash the app when loading services or saving settings. - Reports security hardening: Hardened report preview endpoints with
start <= endvalidation and a maximum date-range limit to reduce DoS risk from untrusted parameters. - Admin “whole organization” scope: Correctly handle admin organization scope (
userId=""= all enabled users) and enforce access checks so preview/download data stays consistent. - Reports rendering: Improved Preview rendering for absence and compliance reports to match the actual report data structure.
- Reports UI semantics: Team scope is limited to the team overview/export semantics that the backend actually returns (prevents misleading previews/downloads).
- Organization download guidance: Added explicit UI messaging for organization scope download limitations until organization-wide export endpoints are implemented.
- ArbZG compliance: Corrected break check logic (9h/45min branch now reachable; check ≥9h before ≥6h)
- Manager logic:
employeeHasManager()now usesgetManagerIdsForEmployee()instead ofgetColleagueIds() - Reporting:
getTeamHoursSummary()respects period parameter (week/month) - Admin users:
hasTimeEntriesTodayis now per-user, not system-wide - UserSettingsMapper: Fixed falsy zero/empty-string handling in getIntegerSetting, getFloatSetting, getStringSetting
- Routing: Moved exportUsers route above getUser to fix route shadowing
- Version1009 migration: Replaced MySQL backtick SQL with portable QueryBuilder; use OCP\DB\Types
- Duplicate notifier: Removed double registration from Application.php boot()
- API security: Generic error messages instead of raw exception output (SubstituteController, GdprController)
- PDF export: Returns HTTP 422 with clear message instead of silent CSV fallback
- LIKE injection: WorkingTimeModelMapper::searchByName() uses escapeLikeParameter()
- XSS: Modal titles escaped in components.js; compliance-violations.js innerHTML escaped
- Admin-settings form: Added CSRF requesttoken
- AbsenceService DI: Fixed constructor argument order (IDBConnection)
- Admin holidays and settings: English source strings for l10n keys
- UserDeletedListener: inject TeamMemberMapper and TeamManagerMapper
- XSS: sanitise team names in admin-teams.js
- CSS: Shadow-light variable, scoped resets, dark-mode color-mix fixes, semantic color variables, navigation height/z-index
- Clock buttons: Double-submit guard (disabled during API calls)
- initTimeline(): Max retry count (20) to prevent infinite loop
- Accessibility: aria-label on header buttons, label for admin user search, aria-modal on welcome dialog, English l10n keys in navigation
- Docs: Removed internal docs; added docs/README; corrected repo URLs
- Manager dashboard: Injected l10n from PHP so JS translations work
- Constants.php for magic numbers; user-facing error messages
- Version1010 migration: Compound indices on at_entries, at_violations, at_holidays, at_absences
- Long-term refactor: Replaced all
\OC::$serverusage with proper OCP APIs and constructor injection - CSPService: Injected ContentSecurityPolicyNonceManager via constructor
- Controllers: Removed manual cspNonce (configureCSP handles it); injected IURLGenerator, IConfig where needed
- PageController: Injected IURLGenerator, IConfig; passes urlGenerator to templates
- HealthController: Injected IDBConnection for database check
- ProjectCheckIntegrationService: Injected LoggerInterface instead of OC::$server->getLogger()
- Templates: Replaced
\OC::$serverwith\OCP\Server::get()(OCP public API) - Added GitHub Actions release workflow (
.github/workflows/release.yml) - Updated PageControllerTest with full constructor mocks
- Resolved duplicate route names in absence API (absence#store, absence#show, absence#update, absence#delete)
- Corrected settings class names in info.xml to use full OCA namespace
- Added declare(strict_types=1) to routes.php
- Removed non-existent screenshot references from info.xml until real screenshots are captured
- ProjectCheck integration for project time tracking
- Additional migrations for schema updates
- Further database schema refinements
- Working time models
- User working time model assignments
- Absence management
- Audit logging
- User settings
- Compliance violation tracking
- Initial release
- German labor law (ArbZG) compliant time tracking
- Clock in/out and break tracking
- Time entry management (create, edit, delete, manual entries)
- Basic compliance checks (max 8h/day, break requirements)
- GDPR-compliant data processing
- English and German translations
- WCAG 2.1 AAA accessibility compliance