# Phase 1 — Module 2: User Management

Builds on the Authentication module. Full CRUD for user accounts, plus the
administrative controls an ERP needs: activation, soft delete/restore,
profile photos, admin-initiated password resets, and online-status/last-login
visibility.

## What's included

| Layer | Files |
|---|---|
| Migrations | `roles`, `branches`, `departments` — minimal reference tables so Users can assign a role/branch/department. Full CRUD for these ships with their own modules (3 and 4); these tables just gain more columns via additive migrations at that point. |
| Seeders | `RoleSeeder` (Administrator), `BranchSeeder` (Head Office), `DepartmentSeeder` (Administration) — run before `DefaultAdminSeeder` so the seeded admin gets real role/branch/department ids. |
| Config | `app/Config/Uploads.php` — avatar size/mime limits, shared with the future File Manager module. |
| Models | `App\Models\{RoleModel,BranchModel,DepartmentModel}` (shared lookups), extended `Modules\Auth\Models\UserModel` with `scopeForList()`, `isUsernameOrEmailTaken()`, `restore()`, `setStatus()`. |
| Services | `Modules\Users\Services\UserService` — create/update, activate/deactivate (also revokes live sessions), soft delete/restore, avatar upload, admin password reset, online-status lookup. |
| Controllers | `Modules\Users\Controllers\UserController` — thin, delegates everything to `UserService`. |
| Views | `Modules/Users/Views/{index,create,edit,show}.php` |
| Routes | `Modules/Users/Config/Routes.php`, required from `app/Config/Routes.php` |

## Features mapped to the spec

- **User CRUD** — `index`/`createForm`+`store`/`editForm`+`update`
- **Profile Photo** — `show.php` upload form → `UserController::uploadAvatar()` → `UserService::uploadAvatar()` (mime/size validated against `Config\Uploads`)
- **User Profile** — `show.php`
- **Activate / Disable** — `UserController::activate()/deactivate()` (deactivating also revokes all live sessions immediately)
- **Soft Delete / Restore** — uses the `deleted_at` column already on `users` (from the Auth migration); trashed users are excluded from the default list and visible via the "Show deleted users" filter
- **User Search / Filters / Pagination** — `UserModel::scopeForList()` + CI4's built-in `paginate()`/pager, exposed via the filter bar on `index.php`
- **Assign Branch / Department / Role** — dropdowns on create/edit, backed by the new reference tables
- **Last Login** — already tracked by the Auth module (`users.last_login_at/ip`), surfaced here
- **Online Status** — `UserService::isOnline()`: true if the user has a non-revoked session (from Auth's `user_sessions` table) active in the last 5 minutes
- **Password Reset by Administrator** — `UserController::resetPassword()` generates a new temporary password, forces `must_change_password`, shown once via flash message (never logged, never emailed in plaintext by this module — wire that into the Email/Notifications module later if you want it delivered instead of hand-copied)

## Setup (adds to Module 1's steps)

```bash
php spark migrate       # picks up the 3 new reference-table migrations
php spark db:seed DatabaseSeeder   # now also seeds Role/Branch/Department
```

No other steps change from the Module 1 setup guide.

## Known placeholders

- Roles/Branches/Departments are read-only dropdowns here — no create/edit UI
  for them yet. That's Modules 3 and 4.
- No permission enforcement yet (`filter => 'auth'` only — any logged-in user
  can manage users). Swap for a permission-aware filter once the Roles &
  Permissions module ships.
- Avatar files are stored on local disk under `public/assets/uploads/avatars`
  — fine for a single app server; move to object storage if you scale
  horizontally.

## Testing checklist

1. `/users` loads, search box filters by name/username/email, status/role/branch/department filters work together.
2. "Add User" creates an account, shows a one-time temporary password, and the new user can log in and is forced to change their password.
3. Deactivating a user immediately kills their active session (log in as them first, deactivate from another session, confirm they're bounced to `/login`).
4. Delete → user disappears from the default list, appears when "Show deleted users" is checked, "Restore" brings it back.
5. Upload an avatar on `/users/{id}` — rejects files over 2MB or non-image types, accepts valid JPEG/PNG/WEBP.
6. "Reset Password" shows a new temporary password and forces a change on the user's next login.
