# CLAUDE.md

Guidance for Claude Code when working in this repository.

## Project

Multi-organization HRMS on Laravel 13, PHP 8.4 and MySQL 8.4, built with traditional MVC, Blade, Tailwind CSS and Alpine.js. See [README.md](README.md) for architecture, phases and commands.

- **Banned:** Filament, Livewire, React, Vue, Inertia and admin-panel generators. Don't add large frontend dependencies.
- **Delivery:** work proceeds in phases. Stop after each phase and wait for review. Don't commit or push unless asked.

## UI standard (mandatory)

Every screen follows **[docs/UI_DESIGN_GUIDELINES.md](docs/UI_DESIGN_GUIDELINES.md)**: simple, plain-language interfaces for non-technical office staff.

- **Tables:** 4–6 columns; secondary details on the details page.
- **Row actions:** one primary action (*View*/*Manage*) plus a **More** menu (`<x-row-actions>`). Show only permitted actions; no icon-only actions.
- **Filters:** search, one main filter, and a **Filters** panel (`<x-filter-bar>`), with **Clear filters**.
- **Components:** reuse the existing Blade components; never build a parallel component set.
- **Content:** no dead links, fake buttons, placeholder figures or invented statistics.
- **Layout:** responsive, with no page-level horizontal scroll on phones.
- **Wording:** plain user-facing labels; technical identifiers only in details sections. Internal names (columns, permissions, routes, classes) stay unchanged.

## Security and data rules

- **Organization scope:** organization-owned models use `BelongsToOrganization`; the organization comes only from the verified `CurrentOrganization`, never from input. Validate IDs with `existsInOrganization()`.
- **Authorization:** every action is authorized server-side with policies or gates. Menus are never the only protection.
- **Audit:** state changes are audited through `AuditLogService`.
- **Organization account creation:** Organization Users → Add user creates only NEW Standard User accounts in the verified current organization (`organization.users.create_accounts`, via `OrganizationMembershipService::createAccount`). Never attach, modify or reveal an existing account from the organization panel; existing accounts are enrolled through Central. Organization admins never deactivate or delete accounts.
- **Databases:**
  - Never run `migrate:fresh`, `migrate:reset`, `db:wipe` or destructive reseeding on `hrms_db`.
  - Touch only `hrms_db`, plus `hrms_db_testing` through the `tests/MySql` suite.
  - Preview migrations (`--pretend`) before applying them.
- **Secrets:** never print or expose database credentials from `.env`.
- **Dev servers:** don't interrupt a running `composer run dev`. Check ports 8000 and 5173 first.

## Commands

```bash
php artisan test            # Unit/Feature (SQLite in memory) + MySql suite (hrms_db_testing)
vendor/bin/pint --test
npm run build
php artisan hrms:sync-permissions   # after editing config/rbac.php
```

## Attendance and deduction policies (Phase 10)

- Read [docs/PHASE_10_REPORT.md](docs/PHASE_10_REPORT.md) before changing attendance classification or deductions.
- Precedence: employee exception → organization default → none (Phase 9 conventions). `PolicyResolver` is the only place this is decided.
- Saved records keep `attendance_policy_snapshot`/`deduction_policy_snapshot` (NULL = saved before Phase 10, evaluated exactly as Phase 9). Never re-snapshot on correction, recalculation or device updates; only `AttendanceRulesReconciler` (explicit, audited, open months, skips corrected records) changes saved snapshots.
- Classification is read-only (`AttendancePolicyEvaluator`); never write it into Phase 8 status. Never classify an unfinished shift as absent.
- Money rules stay separate: `DeductionCalculator` is pure (no persistence, no payroll). Keep one deduction per missed time, the daily-pay cap and mandatory review for missing punches. Do not claim legal compliance.
- Deduction details are visible only with `organization.attendance.deductions.manage`, never in ESS or general lists.
- `LeaveLookup` (bound to `ApprovedLeaveLookup` since Phase 11) is the leave hook: paid leave must never classify as absence.

## Leave management (Phase 11)

- Read [docs/PHASE_11_REPORT.md](docs/PHASE_11_REPORT.md) before changing leave or its attendance integration.
- Units are integer half days (1 day = 2) in every table and calculation; convert only at the edges (`LeaveUnits`). Never use floats.
- All leave writes go through `LeaveService`/`LeaveAllocationService`/`LeaveTypeService` (permission, organization lock, audit). `leave_days.active_marker` + its unique index must stay the source of truth for "one active leave per employee per day"; keep `status`/`active_marker` in sync and call `LeaveLookup::flush()` after any change.
- Only APPROVED leave affects attendance. Precedence: saved record → approved leave → schedule. Never delete or rewrite attendance because of leave; report conflicts. Refuse leave changes in finalized attendance months.
- ESS leave always uses `EmployeeContext`; never accept a submitted employee/organization ID. Nobody approves/rejects/reverses their own leave.
- No salary in Phase 11: `LeaveLedger` exposes units only. No carry-forward, holiday calendar or statutory entitlements.

## Employee compensation (Phase 12)

- Read [docs/PHASE_12_REPORT.md](docs/PHASE_12_REPORT.md) before changing salary setups, pay periods or their confidentiality.
- Money is integer minor units of an ISO currency (`rate_amount_minor`, `config/currencies.php`). Parse and format only through `App\Support\Money`; never use floats, and never add or convert amounts across currencies.
- Rate type (how earnings are measured) and payment frequency (when they are paid) are independent; the only refused combinations are those in `SalaryRateType::supports()` (weekly rate → weekly only; monthly salary → monthly only). Keep the form, service and MySQL CHECK in sync.
- All writes go through `CompensationService`/`PaySettingsService` (permission, organization row lock, audit). At most one active setup covers a date. Never overwrite past rates: a new salary is a "change from a date" (ends the current setup the day before). Setups are never deleted; a setup with `payroll_locked_at` never changes its financial terms.
- Pay periods come only from `PayCalendar` (local dates in the organization time zone; calendar months, never "30 days"; payment days 29–31 clamp to the month's last day). Weekly setups keep the `week_starts_on` they were saved with.
- Confidential: viewing needs `organization.compensation.view` (or `.manage`) plus employee access; only the protected Organization Admin role gets them automatically. Never show salary in ordinary lists, dashboards, exports, reports or ESS. Compensation audit events are `isConfidential()`: their values render only where `showConfidential` is passed after a compensation check.
- Phase 13 reads salary only through `CompensationLedger` (authorized, read-only). No payroll calculation, deductions, payslips or bank details in Phase 12.

## Draft payroll (Phase 13)

- Read [docs/PHASE_13_REPORT.md](docs/PHASE_13_REPORT.md) before changing payroll formulas, statuses or adjustments.
- `PayrollEngine` is the only place pay is calculated. It is read-only and reuses `AttendanceTimeline`, `AttendancePolicyEvaluator`, `DeductionCalculator` (on each record's own deduction snapshot), `CompensationLedger` and `PayCalendar`. Keep one basis per salary type: hourly pays worked minutes, daily pays day units, weekly/monthly prorate by scheduled working days of the whole calendar period (never ÷ 30). Never deduct missed time that is already unpaid; per day, reductions plus deductions never exceed what the day is worth or earned. Rules marked "needs HR review" are proposed, never applied automatically. Unresolved days are listed and need review: never silently paid or unpaid.
- Integer minor units only, rounded half up once per line (`PayrollEngine::roundDiv`); totals are sums of lines. Phase 10 fixed amounts are 2-decimal amounts in the default salary currency: convert, or refuse with a review note.
- All writes go through `PayrollService` (permission, organization lock, audit). One current calculation per employee and pay period (`current_marker` unique index); recalculation is explicit with a reason and keeps the old version as `replaced`. Calculated amounts and replaced versions are never edited (model guard). Manual adjustments belong to the employee and period, survive recalculation and never touch source data. Net is never negative (shortfall → review). No payment, approval, locking, payslip or bank workflow.
- Permissions `organization.payroll.view/.run/.adjust` (sensitive; Organization Admin only) plus employee and compensation viewing. Payroll audit events are confidential (`AuditEvent::isConfidential()`); never show payroll in ESS, general lists, reports or exports.

## Custom attendance rules (after Phase 13)

- Read [docs/CUSTOM_ATTENDANCE_RULES_REPORT.md](docs/CUSTOM_ATTENDANCE_RULES_REPORT.md) before changing occurrence rules or their payroll effect.
- Rules NEVER change attendance (records, status, classification, finalized months). `CustomRuleEvaluator` is read-only: it counts factual events from `AttendanceTimeline` and derives warnings, review items and day equivalents shown separately from actual attendance.
- Precedence: day-equivalent rules of one event run in priority order and consume each occurrence once; warning/review rules only observe; rules count events, never equivalents (no chaining). Late days saved under an attendance policy with its own repeated-lateness rule (and half days it created) are left to Phase 10. A day consumed by chargeable rules of two events → HR review. Day-equivalent rules for one event must share a counting period; missing punches can never be a day equivalent.
- Payroll (`PayrollEngine::customRules`): an equivalent is worth the trigger day's pay × units ÷ 2 LESS what the counted days already lost (each day credited once), capped by the remaining pay; proposed unless the rule allows automatic application. Monthly triggers are charged in the pay period containing the completing occurrence.
- Writes via `CustomRuleService` / `CustomRuleExceptionService` (permission, organization lock, audit). Changes touching finalized attendance months need an explicit acknowledgement; affected current draft payroll is flagged for review, never rewritten. Permissions `organization.attendance.custom_rules.view/.manage`.

## Employee portal and reports (Phase 9)

- Read [docs/PHASE_9.md](docs/PHASE_9.md) and its verification report before changing ESS/report behavior.
- ESS resolves an active employee using **both authenticated user ID and verified current organization ID** through `EmployeeContext`. Never use a submitted employee/organization ID to select self-service data. No automatic accounts or role grants.
- Employee rosters use the existing `ScheduleResolver` with `publishedOnly: true`. Draft overrides are excluded; default assignments remain visible. There is no retained previous published version after an edit; do not reveal drafts to compensate.
- `AttendanceTimeline` is read-only. Unprocessed is a completed scheduled day without a record, not absence. Saved records/finalized history stay unchanged. Late/early/half-day are independent reporting flags; see the documented half-day convention.
- Organization reports require report permission **and** the relevant module permission. Employee reports require explicit own report and own module permissions. Export requires export permission too; never expose arbitrary columns or build unrestricted report queries.
- Reports use fixed templates, at most 62 days/5,000 rows; PDFs at most 500 rows. Keep CSV/XLSX formula-safe and PDF remote/PHP/JavaScript execution disabled.
- Approved export dependencies: Laravel Dompdf 3.1.2 / Dompdf 3.1.6, OpenSpout 4.32.0 (see composer.lock). Do not add further export packages without review.
- Password changes reuse Laravel's password policy, User's hashed cast and `SessionRevoker`. Require the current password, revoke other sessions and reset tokens, and audit without passwords/tokens.
- Phase 9 adds no schema changes. Stop before deployment or another phase unless explicitly requested.
- Phase 12 must not expose compensation in ESS; employees never see or change their own salary.
