# User Assignment Terminology Refactor

Date: 2026-05-19

## 1. Findings (pre-refactor audit)

The codebase incorrectly treated **“agents” as a separate user type** in several places:

| Area | Finding | Risk |
|------|---------|------|
| `DepartmentSupportController::agents()` | Listed users by hardcoded Spatie role `agent_{departmentId}` | Only legacy agent-role users appeared in assignee picker |
| `GET /departments/{id}/agents` | Endpoint name implied a user type | Misleading API + frontend coupling |
| `AgentUserResource` | Resource name implied agent account type | Terminology leak into API layer |
| `TicketPolicy::assign()` | Required department pivot role `manager` or `agent` | Permission alone was insufficient; blocked valid assigners |
| `TicketService::assignTicket()` | No assignee eligibility validation | Could assign inactive / wrong-dept / non-assignable users |
| Dashboard | `agentPerformance`, `agent_id`, audience `agent` | Product language implied agent accounts |
| Admin UI | `/admin/agent-performance`, “Agent performance” nav | Visible misleading terminology (EN/AR) |
| Frontend | `AgentUser`, `fetchDepartmentAgents`, `useDepartmentAgents` | Types/services reinforced wrong model |
| Translations | “Search agents…”, Arabic “الوكلاء” | UI implied special agent accounts |
| Seeder | Department Spatie role still named `agent_{id}` | Internal legacy name retained for RBAC compatibility |
| `DepartmentRole::AGENT` enum | Department **membership** label (pivot), not user type | Kept; distinct from “user type” — used for visibility defaults only |

**Not changed (intentionally):**

- HTTP `user_agent` fields (browser/device metadata)
- Spatie role name `agent_{departmentId}` — kept as legacy RBAC identifier; assignability now driven by `department.tickets.assignable` permission
- `DepartmentRole::AGENT` pivot value — department membership classification, not a separate account type

---

## 2. Backend files changed

| File | Change |
|------|--------|
| `app/Modules/Identity/Enums/Permission.php` | Added `DEPARTMENT_TICKETS_ASSIGNABLE` (`department.tickets.assignable`) |
| `app/Modules/Ticketing/Services/AssignableUserService.php` | **New** — query + eligibility checks |
| `app/Modules/Ticketing/Resources/AssignableUserResource.php` | **New** — replaces `AgentUserResource` |
| `app/Modules/Ticketing/Resources/AgentUserResource.php` | **Removed** |
| `app/Modules/Ticketing/Controllers/DepartmentSupportController.php` | `assignableUsers()` + deprecated `agents()` alias |
| `app/Modules/Ticketing/Routes/api.php` | Added `/assignable-users`; kept `/agents` |
| `app/Modules/Ticketing/Services/TicketService.php` | Validates assignee eligibility on create/assign |
| `app/Modules/Ticketing/Policies/TicketPolicy.php` | `assign()` uses permission + membership only (no hardcoded agent role) |
| `database/seeders/RolePermissionSeeder.php` | Grants `assignable` to manager + legacy agent roles |
| `app/Modules/Dashboard/*` | Renamed performance methods/keys; backward-compatible aliases |
| `tests/Feature/Ticketing/DepartmentSupportTest.php` | Updated + expanded |
| `tests/Feature/Ticketing/TicketAssignmentTest.php` | **New** assignment eligibility tests |
| `tests/Feature/Dashboard/DashboardPhase7Test.php` | Audience `assignee` |

---

## 3. Frontend files changed

| File | Change |
|------|--------|
| `resources/js/src/features/tickets/types/ticketForm.types.ts` | `AssignableUser` (+ deprecated `AgentUser` alias) |
| `resources/js/src/features/tickets/services/departmentSupportService.ts` | `fetchDepartmentAssignableUsers()` |
| `resources/js/src/features/tickets/hooks/useDepartmentAssignableUsers.ts` | **New** hook (replaces `useDepartmentAgents`) |
| `resources/js/src/features/tickets/components/AssigneePicker.tsx` | Assignable-user terminology |
| `resources/js/src/features/admin/pages/AdminAssigneePerformancePage.tsx` | **New** (replaces AdminAgentPerformancePage) |
| `resources/js/src/features/admin/services/adminService.ts` | `fetchAssigneePerformance()`, normalized response |
| `resources/js/src/shared/constants/apiEndpoints.ts` | New endpoints + deprecated aliases |
| `resources/js/src/shared/constants/routes.ts` | `/admin/assignee-performance` + redirect from old route |
| `resources/js/src/app/Router.tsx` | New page route + legacy redirect |
| `resources/js/src/shared/config/navConfig.ts` | Nav label key updated |
| `resources/js/src/shared/config/routePermissions.ts` | New route permission entry |
| `resources/js/src/shared/components/layout/Sidebar/Sidebar.tsx` | Icon map key |
| `resources/js/src/features/auth/constants/permissions.ts` | Added `DEPARTMENT_TICKETS_ASSIGNABLE` |
| Unit tests | Updated AssigneePicker, CreateTicketPage, TicketDetailPage, AdminAssigneePerformancePage |

---

## 4. Routes changed / added / deprecated

### API (v1)

| Route | Status |
|-------|--------|
| `GET /departments/{id}/assignable-users` | **Primary** |
| `GET /departments/{id}/agents` | **Deprecated alias** → same handler |
| `GET /dashboard/assignee-performance` | **Primary** |
| `GET /dashboard/agent-performance` | **Deprecated alias** |

### Frontend

| Route | Status |
|-------|--------|
| `/admin/assignee-performance` | **Primary** |
| `/admin/agent-performance` | **Redirect** → assignee performance |

---

## 5. Permission model used

**Primary permission:** `department.tickets.assignable` (`Permission::DEPARTMENT_TICKETS_ASSIGNABLE`)

A user is assignable when:

1. `is_active = true`
2. Same `company_id` as the ticket’s department
3. Member of the ticket’s department (`department_user`)
4. Has `department.tickets.assignable` via direct permission or department-scoped role
5. **Legacy fallback:** still has Spatie role `agent_{departmentId}` (backward compatibility)

**Who can assign tickets:** unchanged — `department.tickets.assign` permission + department membership (managers); super admin bypass.

Seeded roles receiving `department.tickets.assignable`:

- `department_manager_{departmentId}`
- `agent_{departmentId}` (legacy role name, now meaning “assignable support user”)

---

## 6. Tests added / updated

### Added

- `tests/Feature/Ticketing/TicketAssignmentTest.php`
  - Assignable user can be assigned
  - User without assignable permission cannot
  - Inactive user cannot
  - Cross-department user cannot

### Updated

- `tests/Feature/Ticketing/DepartmentSupportTest.php`
  - `/assignable-users` endpoint
  - Deprecated `/agents` alias
  - Permission-based listing (without legacy agent role)
  - Inactive / cross-company exclusions
- `tests/Feature/Dashboard/DashboardPhase7Test.php` — audience `assignee`
- Frontend unit tests for assignee picker, admin performance page

**Results:**

- `php artisan test` — 109 passed
- `npm run type-check` — passed
- `npm run lint` — passed
- `npm run build` — passed
- `npm run test -- --run` — 58 passed
- `npm run test:e2e` — passed (no failures reported)

---

## 7. Translation changes

### English

| Before | After |
|--------|-------|
| Agent performance | Assignee performance |
| Search agents by name or email | Search assignable users by name or email |
| Agent Performance (dashboard widget) | Assignee Performance |

### Arabic

| Before | After |
|--------|-------|
| أداء الوكلاء | أداء المكلّفين |
| ابحث عن الوكلاء… | ابحث عن المستخدمين القابلون للتعيين… |
| الوكلاء (empty/error states) | المستخدمون القابلون للتعيين |

Assignee field labels already used “Assigned To” / “المعيّن له” — unchanged.

---

## 8. Remaining risks

1. **Legacy Spatie role name `agent_{departmentId}`** still exists in DB seeds and invites — rename to something neutral (e.g. `support_user_{id}`) in a follow-up migration if desired.
2. **`DepartmentRole::AGENT` pivot value** remains for visibility defaults; not exposed as “user type” in UI but still in schema/docs.
3. **Dashboard API** returns both `assignees` and deprecated `agents` keys — clients should migrate to `assignees`.
4. **Performance metrics** still include deprecated `agent_id` fields in JSON for compatibility — remove in a future API version.
5. **E2E fixtures** still export `agentUser` variable name internally — cosmetic only, not shown in UI.
6. **Existing tenants** need permission seed/migration to add `department.tickets.assignable` to existing roles (seeder handles fresh installs; production may need one-time sync).

---

## 9. Correctness conclusion

**Yes — the app now treats everyone as Users, not Agents.**

- Ticket assignment eligibility is **permission- and scope-based**, not “agent user type” based.
- UI, translations, admin nav, and primary API routes use **assignee / assignable user** language.
- Legacy `/agents` and `/agent-performance` endpoints remain as **deprecated aliases** only.
- Agent performance metrics are preserved as **Assignee Performance** with the same underlying ticket data.

---

## Business rule reference

> There are NO separate “agents” as a user type. Everyone is a normal User. The difference is only roles and permissions. Any user may be assignable to a ticket if they have the required permission / role / department membership.

This refactor aligns product terminology, API surface, assignment validation, and tests with that rule.
