# Department Membership and Department Heads Report

**Date:** 2026-05-19  
**Scope:** Department membership pivot, scoped user management, department head assignment, invites, admin UI  
**Primary super admin:** said.abdulaziem@mountainview-eg.com (seeded via `SuperAdminSeeder`)

---

## 1. Current state found (audit)

| Area | Before | Issue |
|------|--------|-------|
| `department_user` pivot | `role`, `is_manager`, `visibility_settings` | Missing `is_department_head`, `is_primary`, `joined_at`; head implied by `is_manager` only |
| User admin APIs | CRUD list/update only | No dedicated membership or head endpoints |
| `UserAdminService` | Company-wide list for admins | No department-head scoping; dept sync via bulk `departments[]` without Spatie sync |
| `UserInviteService` | Email + optional dept | Dept heads could pass other departments; no head flag; company scope not enforced for dept heads |
| Policies | `UserPolicy` allowed view but not update for dept managers | No `viewUsers` / `manageHeads` on departments |
| Permissions | `department.users.manage` only | Missing view/invite/deactivate/roles/heads permissions |
| Frontend admin users | Toggle active only | No department assignment, filters, or head management |
| Frontend invites | Email only | No company/department/role/head controls |
| Role naming | `department_manager_{uuid}`, pivot `agent` | No separate Agent user type in product model, but legacy Spatie `agent_{uuid}` roles remain for permissions |

---

## 2. Data model changes

Migration `2026_05_19_120000_add_department_membership_columns_to_department_user.php`:

- Added `is_department_head` (backfilled from `is_manager`)
- Added `is_primary`
- Added `joined_at` (backfilled from `created_at`)
- Kept `is_manager` in sync via `DepartmentUser` model hooks for backward compatibility

Migration `2026_05_19_120001_add_is_department_head_to_user_invites.php`:

- Added `is_department_head` on invites (super-admin only)

Pivot semantics:

- Users can belong to multiple departments
- One primary department per user (`is_primary` cleared on others when set)
- Department head = `is_department_head` (+ synced `is_manager`, Spatie `department_manager_{deptId}`)
- Head is not a separate user type

---

## 3. Backend APIs added/updated

### User department assignment

| Method | Path |
|--------|------|
| GET | `/api/v1/users/{user}/departments` |
| POST | `/api/v1/users/{user}/departments` |
| PATCH | `/api/v1/users/{user}/departments/{department}` |
| DELETE | `/api/v1/users/{user}/departments/{department}` |

### Department users

| Method | Path |
|--------|------|
| GET | `/api/v1/departments/{department}/users` |
| POST | `/api/v1/departments/{department}/users` |
| DELETE | `/api/v1/departments/{department}/users/{user}` |

### Department heads (super admin / `department.heads.manage`)

| Method | Path |
|--------|------|
| GET | `/api/v1/departments/{department}/heads` |
| POST | `/api/v1/departments/{department}/heads` |
| DELETE | `/api/v1/departments/{department}/heads/{user}` |

### Other updates

- `UserController@index` — `department_id` filter
- `UserAdminService` — department-head scoped listing; soft-deactivate; role guardrails
- `UserInviteService` — dept-head forced department/company; head flag super-admin only; accept uses `DepartmentMembershipService`
- `AuthenticatedUserResource` — `is_super_admin`, department pivot fields on `/auth/me`

Core service: `DepartmentMembershipService` (assign, update, remove, primary uniqueness, Spatie role sync).

---

## 4. Policies and permissions

### New permissions (seeded via `Permission` enum + `RolePermissionSeeder`)

- `department.users.view`
- `department.heads.manage`
- `users.invite`
- `users.deactivate`
- `users.roles.manage`

### Policy behavior

| Actor | Scope |
|-------|-------|
| Super admin | Full access; only actor who can assign/remove department heads by default |
| Company admin | Company users/departments; can manage heads if granted `department.heads.manage` |
| Department head | Users in managed departments only; cannot assign heads or elevated roles |
| Normal user | No admin access |

`DepartmentPolicy`: `viewUsers`, `manageUsers`, `manageHeads`  
`UserPolicy`: department-scoped view/update/delete; `viewDepartments`, `manageDepartments`

Department manager role receives: view/manage users, invite, deactivate (not heads manage).

---

## 5. Frontend changes

- **`AdminUsersPage`** — company/department filters (super admin), locked department (dept head), user detail modal with memberships, primary/head/remove actions
- **`AdminInvitesPage`** — company/department/role selectors; locked department for dept head; department head checkbox for super admin
- **`AdminDepartmentsPage`** — users count, heads count, link to user management
- **`departmentMembershipService.ts`** — API client for all membership/head endpoints
- **Route permissions** — admin users/invites accessible to `department.users.manage`
- **Translations (EN/AR)** — User management, Department head, Department users; assignee terminology preserved from prior refactor

---

## 6. Tests added

### Backend (`tests/Feature/Identity/DepartmentMembershipTest.php`)

1. Super admin assigns user to any department  
2. Department head lists own department users  
3. Department head cannot list other department users  
4. Department head invite forces own department  
5. Department head cannot assign department head flag  
6. Super admin assigns department head  
7. Primary department uniqueness  
8. Removing membership  
9. Cross-company assignment denied  
10. Inactive users excluded from assignable-users picker  

### Frontend

- `AdminUsersPage.test.tsx` — super admin filters, locked dept head filter, membership modal, head action hidden  
- `AdminInvitesPage.test.tsx` — locked department for dept head invite form  
- `departmentMembershipService.test.ts` — assign mutation payload  

---

## 7. Commands executed

```bash
php artisan migrate --force
php artisan test                    # 122 passed
npm run type-check                  # passed
npm run lint                      # passed
npm run test -- --run             # 64 passed
npm run build                     # passed
```

---

## 8. Known gaps

- Admin users page does not yet expose full Spatie role picker (roles managed via existing update API; UI minimal)
- Department filter on users page is not deep-linked from departments table
- Legacy Spatie role names (`agent_{deptId}`, `department_manager_{deptId}`) retained for permission compatibility
- `DepartmentRole::AGENT` pivot value remains internally; UI shows “User” not “Agent”
- E2E coverage for full admin membership workflow not added in this pass

---

## 9. Department head scoping — correct?

**Yes, for implemented surfaces:**

- User list scoped to managed departments for department heads
- Department user/head APIs enforce `DepartmentPolicy` + `UserPolicy`
- Invites from department heads force company + department
- Department heads cannot set `is_department_head` on invites or membership updates
- Super admin retains global control including head assignment

---

## 10. All users treated as Users (not Agents)?

**Yes, at product level:**

- No separate Agent user type in admin UI or business rules
- Assignability uses `department.tickets.assignable` permission
- Admin copy uses “User” / “Assignee” (EN) and “مستخدم” / “المكلّف” (AR)
- Internal pivot/Spatie `agent_{uuid}` roles remain as permission carriers only

---

## Super admin seeder

`database/seeders/SuperAdminSeeder.php` ensures:

- Email: `said.abdulaziem@mountainview-eg.com`
- `is_super_admin = true`
- Spatie role: `super_admin`

Run after deploy: `php artisan db:seed --class=RolePermissionSeeder` (new permissions) and `SuperAdminSeeder` if needed.
