# Onboarding Critical Fixes Report

**Date:** 2026-05-20  
**Scope:** Critical blockers C-01 through C-04 from `docs/ONBOARDING_PRODUCTION_AUDIT.md`  
**Goal:** Unblock Department Head UAT without building new onboarding features or redesigning the system.

---

## 1. Critical Issues Fixed

| ID | Issue | Resolution |
|----|-------|------------|
| **C-01** | Department head portal wizard step returned **403** on `PATCH /departments/{id}` | Added dedicated `PATCH /api/v1/departments/{department}/portal-settings` with `DepartmentPolicy::updatePortalSettings()`. Department heads with `department.settings.manage` can update portal-safe fields on their own department only. |
| **C-02** | Notifications & SLA wizard step used **local-only toggles** | Replaced fake state with real notification preference mutations (`sla_warning` email, `ticket_assigned` in-app). Added explicit SLA CTA button to `/settings/sla` and link to notification preferences. |
| **C-03** | Wizard ✕ called `complete()` and marked onboarding finished | ✕ now **save and exit**: persists current step via `PATCH /onboarding/step` and minimizes wizard. `POST /onboarding/complete` only runs from final go-live actions. |
| **C-04** | Checklist `portal_configured` CTA sent dept heads to `/admin/departments` | Department head checklist now routes to `/dashboard?onboarding=portal` (wizard portal step). Company admin checklist unchanged (`/admin/departments`). |

---

## 2. Files Changed

### Backend
- `app/Modules/Identity/Policies/DepartmentPolicy.php` — `updatePortalSettings()`
- `app/Modules/Identity/Requests/UpdateDepartmentPortalSettingsRequest.php` — **new**
- `app/Modules/Identity/Services/DepartmentAdminService.php` — `updatePortalSettings()`
- `app/Modules/Identity/Controllers/DepartmentController.php` — `updatePortalSettings` action
- `app/Modules/Identity/Routes/api.php` — portal-settings route
- `app/Modules/Identity/Services/ChecklistProgressService.php` — dept head portal CTA route

### Frontend
- `resources/js/src/features/admin/services/departmentAdminService.ts` — `updateDepartmentPortalSettings()`
- `resources/js/src/shared/constants/apiEndpoints.ts` — `PORTAL_SETTINGS` endpoint
- `resources/js/src/features/onboarding/components/SetupWizard.tsx` — portal API, close vs complete, notifications step, query invalidation, checklist deep-link
- `resources/js/src/features/onboarding/components/OnboardingModal.tsx` — configurable close aria label
- `resources/js/src/locales/en/onboarding.json` — save/exit + notifications copy
- `resources/js/src/locales/ar/onboarding.json` — matching Arabic strings

### Tests
- `tests/Feature/Identity/DepartmentPortalSettingsApiTest.php` — **new** (5 tests)
- `tests/Feature/Identity/OnboardingApiTest.php` — close/complete/checklist CTA tests
- `resources/js/src/__tests__/unit/features/onboarding/SetupWizard.test.tsx` — **new** (3 tests)
- `resources/js/src/__tests__/e2e/onboarding-wizard-smoke.spec.ts` — **new** Playwright smoke
- `resources/js/src/__tests__/unit/features/AdminUsersPage.test.tsx` — removed unused `waitFor` import
- `tests/Feature/Identity/OtpAuthTest.php` — test isolation for rate limits (unrelated blocker)
- `tests/Feature/Identity/MagicLinkAuthTest.php` — test isolation for rate limits (unrelated blocker)

### Documentation
- `docs/ONBOARDING_PRODUCTION_AUDIT.md` — updated verdict
- `docs/ONBOARDING_CRITICAL_FIXES_REPORT.md` — this file

---

## 3. Backend Routes Added/Changed

| Method | Route | Purpose |
|--------|-------|---------|
| **PATCH** | `/api/v1/departments/{department}/portal-settings` | Portal-safe department settings update |

**Authorization matrix**

| Actor | Allowed fields |
|-------|----------------|
| Super admin | All portal fields + slug |
| Company admin (`company.departments.manage`) | All portal fields + slug |
| Department head (`department.settings.manage`, own dept) | `portal_enabled`, descriptions, `portal_requires_membership`, `portal_theme` |
| Department head | **Rejected:** `slug`, `name`, `company_id`, etc. (422) |

**Unchanged:** `PATCH /api/v1/departments/{id}` remains company-admin scoped for full department updates.

---

## 4. Frontend Behavior Changed

### Portal step (department head)
- Name and slug are **read-only** in the wizard (auto-generated / admin-managed).
- Save uses `updateDepartmentPortalSettings()` instead of `updateDepartment()`.
- Invalidates onboarding status and department queries after save.

### Notifications step
- SLA warning email toggle persists via notification preferences API.
- Ticket assigned in-app toggle persists via notification preferences API.
- **Configure SLA policies** button navigates to `/settings/sla` (minimizes wizard).
- **Open notification preferences** button navigates to `/notifications/preferences`.

### Wizard close (✕)
- Label/aria: **Save and exit setup** (`onboarding:wizard.saveAndExit`).
- Saves current step only; does **not** set `onboarding_completed_at`.
- Shows resume bar; wizard reopens on reload while onboarding incomplete.

### Checklist CTA
- Department head `portal_configured` → `/dashboard?onboarding=portal`
- SetupWizard reads `?onboarding=` query param and opens at the matching step.

---

## 5. Tests Added/Updated

| Suite | Count | Coverage |
|-------|-------|----------|
| `DepartmentPortalSettingsApiTest` | 5 | Dept head own dept, forbidden other dept, restricted slug, super admin, company admin |
| `OnboardingApiTest` | +4 | Close without complete, complete sets timestamp, dept head portal CTA, company admin CTA |
| `SetupWizard.test.tsx` | 3 | Close without complete, portal-settings API, SLA CTA |
| `onboarding-wizard-smoke.spec.ts` | 1 | Close + reload resumes wizard |

---

## 6. Commands Executed

```bash
php artisan test                                    # 166/166 passed
npm run type-check                                  # passed
npm run lint                                        # passed
npm run build                                       # passed
npm run test -- --run                               # 89/89 passed
php artisan test --filter='DepartmentPortalSettingsApiTest|OnboardingApiTest'
npm run test -- --run resources/js/src/__tests__/unit/features/onboarding/SetupWizard.test.tsx
```

Playwright smoke (`onboarding-wizard-smoke.spec.ts`) was added; run with the project's existing Playwright config when executing E2E locally.

---

## 7. Remaining Onboarding Issues (Non-Critical)

These were **not** in scope for this fix pass:

- Dual-role users (company admin + dept head) still resolve to company admin persona only
- `GET /onboarding/checklist?department_id=X` lacks authorization on arbitrary department IDs
- No server-side onboarding step enum validation
- Dead code: `WelcomeBanner`, `useChecklistProgress`
- Guided tour RTL positioning gaps
- No explicit "Skip onboarding" audit trail (product decision)
- Company admin wizard review step still navigation-only (acceptable for this pass)

---

## 8. Updated Onboarding Readiness Score

| Metric | Before | After |
|--------|--------|-------|
| **Overall onboarding readiness** | **42 / 100** | **74 / 100** |
| Functional correctness (dept head) | 30 | 78 |
| Tests | 45 | 68 |
| UX / product | 38 | 65 |
| Security (portal path) | 50 | 62 |

---

## 9. Department Head UAT Safety

**Verdict: Safe for Department Head UAT** for the scoped onboarding flows:

- Portal setup saves successfully for department heads
- Notifications step actions persist or navigate to real admin pages
- Closing the wizard no longer falsely completes onboarding
- Checklist portal link no longer routes to forbidden admin pages

**Recommended UAT checklist**

1. Log in as department head → wizard appears on portal step
2. Save portal descriptions → no 403; checklist updates after refresh
3. Notifications step → toggle SLA email; confirm preference saved on `/notifications/preferences`
4. Click ✕ → wizard closes; reload → wizard resumes same step; onboarding not marked complete
5. Complete go-live step → wizard does not reappear
6. Checklist "Configure portal" → opens wizard portal step (not `/admin/departments`)
