# Phase 9: Employee Self-Service and organization reports

## Access and identity

ESS reuses `/login`, `users`, existing sessions, organization memberships, role assignments and employee-account links. No separate authentication, new accounts or automatic role assignment is introduced.

Every ESS request requires an active user, organization, membership and linked **active** employee, and at least one registered employee own-data permission in that organization. Each page additionally checks its own permission. `EmployeeContext` always queries with the verified organization ID **and** authenticated user ID. Submitted own-data employee/organization selectors are rejected by report/attendance requests or ignored by fixed self-only profile/photo queries. No employee ID route permits browsing colleagues.

An active link without an employee permission does not grant access. A global role alone does not grant access. Deactivation, unlinking and role revocation apply on the next request. An employee with profiles in several organizations sees one verified profile at a time. The employee header lists only ESS-eligible organizations; switching verifies the target again and regenerates the session ID. A valid selected organization without ESS eligibility is denied rather than silently revealing a different profile.

The user menu links to authorized panels through `/panels/employee` and `/panels/organization`; explicit panel entry selects an eligible organization if needed. Central Admin remains separate and retains first priority at the login home dispatcher. Other users with ESS access land in ESS; organization-only users land on an authorized organization entry page. Being an employee does not grant administration permissions.

## Pages

| Page | Permission / behavior |
| --- | --- |
| `/employee/dashboard` | Active employee context; identity, department/designation and permission-aware attendance/schedule widgets |
| `/employee/profile` | `employee.profile.view_own`; own read-only profile, own phone, private photo, work information and login email |
| `/employee/attendance` | `employee.attendance.view_own`; date/month filters, summary and daily details |
| `/employee/shifts` | `employee.roster.view_own`; current/upcoming default assignments, timing, break, grace and effective dates |
| `/employee/roster` | `employee.roster.view_own`; weekly/monthly read-only schedule with previous/next/current shortcuts |
| `/employee/reports` | `employee.reports.view_own` and the relevant own attendance/schedule permission |
| `/employee/account` | Verified ESS identity; view account information and change own password |

HR-controlled profile fields are read-only. No broad self-edit permission was added. Full CNIC, personal address, private notes/documents, internal audit history and other users' credentials are excluded. A reporting manager's name may appear on the employee's own profile; it is not a link to that manager's private profile.

Password changes require current password, confirmation and the existing Laravel password policy. `User`'s hashed cast remains the hashing mechanism. `SessionRevoker` is shared with central user management; self changes revoke other database sessions, rotate the remember token, remove password-reset tokens and regenerate the current session. Audit stores only the security action, never credentials.

## Published roster policy

The existing resolver remains authoritative: roster override → effective default assignment → not scheduled. ESS calls it with `publishedOnly: true`, excluding unpublished rows before resolution. Published off days override defaults; overnight timing remains explicit. Administrators continue to see draft and published states through the normal resolver.

**Existing model limitation:** editing a published roster row makes that same row a draft; there is no separate retained last-published version. The safe policy is to hide the draft entirely and show the default assignment, or Not scheduled if no default exists, until HR republishes. Earlier published overrides cannot be reconstructed reliably. No history was rewritten or new schedule records created to work around this limitation. HR should republish changed plans promptly.

## Attendance definitions

`AttendanceTimeline` is a shared read-only projection for ESS, organization expected dates and reports. It does not save missing days, recalculate records or reopen periods.

| Label / count | Meaning |
| --- | --- |
| Processed days | Dates with a saved attendance record |
| Present | Saved `present` or `short` status; short is also shown as a flag |
| Absent (processed) | A saved `absent` record, never inferred from missing data |
| Missing punch | Saved `incomplete` record |
| Scheduled off | Resolved off day or saved off record |
| Not scheduled | No working plan, or saved unscheduled record |
| Unprocessed | No record and a scheduled shift has ended in organization local time |
| Attendance not recorded yet | No record while the shift is ongoing; dashboard uses this wording whenever today's record is absent |
| Upcoming | No record and the scheduled shift has not started |

Expected dates start at joining date. Overnight days belong to the shift start date and become Unprocessed only after the next-day shift end. Future dates are never inferred absences. Inactive employees retain saved attendance in reports, but no unsaved expected dates are inferred because the model has no effective-dated inactivity history.

Post-audit history protection: the joining-date cutoff applies only to unsaved expected dates. Saved attendance remains visible in timelines, reports and eligible ESS access even if legacy metadata places the joining date later. New joining-date edits cannot move past existing attendance; the edit is rejected with a clear message while the organization write lock is held. No saved or finalized record is rewritten to repair metadata.

Late, early departure, short day, half day and corrected are **independent flags**. A Present + Late day contributes once to Present and once to the Late flag count. Half day is an explicit reporting convention: complete endpoints, positive worked minutes, and work of at most 50% of the saved scheduled net minutes (planned duration less break). It does not rewrite Phase 8 status, establish payroll entitlement or implement overtime. Missing punches are not classified as half days. Counts of flags overlap and must not be added as mutually exclusive statuses.

Saved schedule snapshots continue to govern Phase 8 calculations. This phase reads their results; finalized records remain intact. ESS schedules hide drafts, while administrators use the full schedule. This can produce different expected-day projections where a draft override exists; it is a deliberate confidentiality boundary, not a change to saved attendance.

Organization attendance now has **Review expected dates** at `/organization/attendance/coverage`, requiring attendance view permission without generic report access. Filters include date range, employee, department and classification. Reading the page never writes records. The original saved-record attendance screen and CSV endpoint remain available.

## Organization reports and permissions

`/organization/reports` provides four simple categories, showing only categories the user may view:

- Employees: all, active/inactive, department/branch/designation grouping, employment-type totals and reporting manager directory.
- Attendance: daily/monthly, summary, late, early, half-day flags, processed absentees, missing punches, unprocessed days and corrected records.
- Shifts: active definitions, employee assignments, department grouping, upcoming changes and overnight assignments.
- Rosters: weekly/monthly, employee, department and branch, with administrator publication indicators.

Every report requires `organization.reports.view` **plus** its module view permission: `organization.employees.view`, `organization.attendance.view`, `organization.shifts.view` or `organization.rosters.view`. Export also requires `organization.reports.export`; employee and attendance exports additionally require their existing module export permissions. Generic report access alone retrieves no module data.

Own reports require `employee.reports.view_own` plus `employee.attendance.view_own` or `employee.roster.view_own`. Download additionally requires `employee.reports.export_own`. Employee report endpoints always supply the server-resolved employee to the report service and never offer organization employee reports or shift-definition listings.

Employee filters cover employee/branch/department/designation, employment type/status and joining range. Attendance filters add date range/month and classification. Shift/roster reports reuse organization filters and the resolver. Invalid/foreign IDs are rejected using current-organization validation. A month takes priority over a date range. Explicit weekly/monthly selections default to the corresponding calendar period; upcoming shift reports default to the next 30 days.

## Downloads and dependencies

All applicable reports offer CSV, PDF and XLSX. Reports are fixed, curated projections; no arbitrary table/column/query builder exists. Dates are limited to **62 days inclusive**, tabular rows to **5,000**, and PDF to **500** rows. Exceeding a limit is refused with an explanation; results are never silently truncated. Narrow filters for larger organizations. HTML tables paginate 25 rows; ESS dates are a bounded responsive list.

CSV/XLSX cells use the existing formula-escaping helper. XLSX values are written as string cells, not formulas. PDF uses a fixed escaped Blade template with remote resources, PHP and JavaScript disabled. Downloads are private/no-store, have meaningful filenames, omit private HR fields and log only report title, format, period, row count and whether the report is self-only. Raw records or personal values are not copied into download audit metadata.

Approved by the user after a Composer dry run:

- `barryvdh/laravel-dompdf` **3.1.2**, using `dompdf/dompdf` **3.1.6**. [Official package source](https://github.com/barryvdh/laravel-dompdf) documents the Blade integration; its Composer requirements include Laravel 13 support.
- `openspout/openspout` **4.32.0**, for bounded XLSX writing. [Official source](https://github.com/openspout/openspout) provides the writer implementation. Composer resolved compatibility against the installed PHP 8.4 runtime.

Seven packages were added, with zero existing package updates/removals. `composer.lock` records exact versions. Composer audit reported no advisories or abandoned packages at verification time. No frontend framework or additional browser package was installed.

## Database, testing and review

Phase 9 adds **no migrations or tables**. All 18 prior migrations remain applied in `hrms_db`. The seven existing ESS/report permission definitions are marked implemented in configuration; permission sync reported zero added/updated database definitions and zero new system roles. No employee accounts, links, memberships or roles were automatically assigned.

Unit/Feature suites use in-memory SQLite. MySQL constraint/concurrency and new ESS/report isolation tests run only against `hrms_db_testing`, using the forward-only migration harness. See [PHASE_9_REPORT.md](PHASE_9_REPORT.md) for final results and inventory.

Manual browser review remains required:

1. Set up two organizations, active memberships, explicit employee role permissions and linked employee profiles through existing administrative screens; no demo data is seeded automatically.
2. Sign in and exercise every ESS link, own private photo, attendance details, current/upcoming shifts, weekly/monthly navigation and password settings.
3. Switch organizations and panels. Confirm only the selected own profile appears and that unlinked/inactive users and memberships are refused.
4. Enter late/short/incomplete attendance; compare overlapping flag counts with saved records. Check an overnight shift before/after completion, a future day, a published off day and an unprocessed completed shift.
5. Create a draft override and confirm it is hidden in ESS and own exports; publish it and confirm it appears. Check the fallback after editing a published row.
6. Exercise every organization report category, filters, empty state and Clear filters. Test a generic-report-only role, a module-view-only role and a user lacking export permission.
7. Download and open CSV/PDF/XLSX; compare date filters, row counts, non-ASCII names, formula escaping, and private-field exclusion. Confirm large PDF/range requests fail clearly.
8. At phone/tablet/desktop widths, check menus, Filters, responsive secondary details, roster list, dialogs and keyboard focus.
9. Change a password with an incorrect current value, weak/unconfirmed values and a valid value. Confirm other sessions are revoked and current login remains usable.

No browser automation or real hardware verification was performed. Physical biometric connectivity remains outside this phase. Leave, holidays, overtime, payroll, notifications, employment lifecycle, recruitment and appraisals remain excluded. Stop for review before deployment or another phase.
