Version: 1.0
Last updated: December 10, 2025
User Management is the core administrative module for managing employee and system user accounts in the DTR System. Administrators can:
Key files involved:
resources/views/users/admin/user/ (index, create, edit, show)UserController.php, ProfileController.php, SettingController.phpApp\Models\User, App\Models\Profile, App\Models\Role, App\Models\Permissionresources/views/user/ (profile, settings)Purpose:
Scope:
Out of scope:
Required permissions:
view_users — view user list and detailscreate_users — create new user accountsedit_users — edit user informationdelete_users — delete user recordsmanage_roles — assign/modify rolesmanage_permissions — assign/modify permissionsreset_password — reset user passwordview_audit_logs — view user audit trailRecommended roles:
Implementation note:
UserController via middleware or gate checksauthorize() in controller methods or Gate policiesAdmin URLs:
/admin/user/admin/user/create/admin/user/{id}/edit/admin/user/{id}/admin/user/{id}/rolesEmployee URLs:
/user/profile/user/settings/user/settings/passwordBrowser requirements: Modern Chromium-based browsers, recent Safari/Firefox. Mobile viewing supported; recommend desktop for full management.
Purpose: Display all users with filtering, search and bulk actions.
UI elements:
Key features:
Purpose: Register a new user account (employee, contractor, system account).
Form fields:
Basic info:
Employment info:
Credentials:
Settings:
On submit (store method):
Controller method overview:
public function store(Request $request)
{
$validated = $request->validate([
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed|password_rules',
'department_id' => 'required|exists:departments,id',
'position_id' => 'required|exists:positions,id',
'employment_type' => 'required|in:full-time,part-time,contract,temporary',
'start_date' => 'required|date|before_or_equal:today',
'roles' => 'nullable|array|exists:roles,id',
'avatar' => 'nullable|image|mimes:png,jpg,jpeg|max:2048',
]);
$user = new User([
'first_name' => $validated['first_name'],
'last_name' => $validated['last_name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$user->save();
$user->profile()->create([
'department_id' => $validated['department_id'],
'position_id' => $validated['position_id'],
'employment_type' => $validated['employment_type'],
'start_date' => $validated['start_date'],
'manager_id' => $validated['manager_id'] ?? null,
]);
if ($request->has('roles')) {
$user->assignRole($validated['roles']);
}
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $path]);
}
return redirect()->route('admin.user.edit', $user->id)
->with('message', 'User created successfully');
}
Purpose: Modify existing user account and profile information.
Editable fields:
Non-editable fields:
UI behaviors:
Controller method overview:
public function edit(string $id)
{
$user = User::with(['profile', 'roles'])->findOrFail($id);
return view('users.admin.user.edit', compact('user'));
}
public function update(Request $request, string $id)
{
$user = User::findOrFail($id);
$validated = $request->validate([
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => "required|email|unique:users,email,{$id}",
'phone_number' => 'nullable|phone:AUTO',
'status' => 'required|in:active,inactive,suspended,terminated',
'roles' => 'nullable|array|exists:roles,id',
'avatar' => 'nullable|image|mimes:png,jpg,jpeg|max:2048',
]);
$user->update([
'first_name' => $validated['first_name'],
'last_name' => $validated['last_name'],
'email' => $validated['email'],
'phone_number' => $validated['phone_number'],
'status' => $validated['status'],
]);
if ($request->has('roles')) {
$user->syncRoles($validated['roles']);
}
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $path]);
}
return redirect()->back()->with('message', 'User updated successfully');
}
Purpose: View complete user profile and activity history.
Sections:
Controller method:
public function show(string $id)
{
$user = User::with([
'profile.department',
'profile.position',
'roles.permissions',
'loginHistory' => fn($q) => $q->orderBy('created_at', 'desc')->limit(10),
'auditLog' => fn($q) => $q->orderBy('created_at', 'desc')->limit(10),
])->findOrFail($id);
return view('users.admin.user.show', compact('user'));
}
Purpose: Allow users to view and update their own profile (employee-facing).
Employee Profile View (/user/profile):
Employee Settings (/user/settings):
Controller method:
public function showProfile()
{
return view('user.profile', ['user' => auth()->user()->load('profile')]);
}
public function updateProfile(Request $request)
{
$validated = $request->validate([
'phone_number' => 'nullable|phone:AUTO',
'timezone' => 'required|timezone',
'language' => 'required|in:en,es,fr,tl',
'avatar' => 'nullable|image|mimes:png,jpg,jpeg|max:2048',
]);
$user = auth()->user();
$user->update($validated);
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $path]);
}
return back()->with('message', 'Profile updated successfully');
}
User model fields:
id — Primary key
first_name — String (max 255)
last_name — String (max 255)
email — String (unique, max 255)
phone_number — String (nullable)
date_of_birth — Date (nullable)
password — String (hashed)
avatar — String/path (nullable, image URL)
status — Enum: active, inactive, suspended, terminated
timezone — String (default 'UTC')
language — String (default 'en')
last_login_at — Timestamp (nullable)
email_verified_at — Timestamp (nullable)
two_factor_enabled — Boolean (default false)
created_at — Timestamp
updated_at — Timestamp
deleted_at — Timestamp (soft delete)
Profile model fields:
id — Primary key
user_id — Foreign key to users
employee_id — String (unique, nullable)
department_id — Foreign key to departments
position_id — Foreign key to positions
employment_type — Enum: full-time, part-time, contract, temporary
start_date — Date
end_date — Date (nullable, for terminations)
manager_id — Foreign key to users (nullable, self-referencing)
bio — Text (nullable)
emergency_contact — String (nullable)
emergency_phone — String (nullable)
address — Text (nullable)
city — String (nullable)
country — String (nullable)
created_at — Timestamp
updated_at — Timestamp
Role model (Spatie Laravel-permission):
id — Primary key
name — String (unique, e.g., 'admin', 'hr-manager')
guard_name — String (default 'web')
created_at — Timestamp
Permission model:
id — Primary key
name — String (unique, e.g., 'view_users', 'edit_users')
guard_name — String (default 'web')
created_at — Timestamp
Sample records:
User:
id: 1
first_name: "Jane"
last_name: "Doe"
email: "jane.doe@company.com"
phone_number: "+1-555-0100"
date_of_birth: "1990-05-15"
password: "y$..." (bcrypt hash)
status: "active"
avatar: "storage/avatars/jane_doe.jpg"
last_login_at: "2025-12-10 09:45:00"
Profile:
id: 1
user_id: 1
employee_id: "EMP-00001"
department_id: 5 (HR)
position_id: 12 (HR Manager)
employment_type: "full-time"
start_date: "2020-03-01"
manager_id: null (no manager, reports to director)
Role:
id: 1
name: "hr-manager"
Permission:
id: 1
name: "view_users"
Predefined roles (suggested):
Super Admin
Admin
HR Manager
IT Admin
Department Lead
Employee
Permission matrix example:
| Permission | Super Admin | Admin | HR Manager | IT Admin | Dept Lead | Employee |
|---|---|---|---|---|---|---|
| view_users | ✓ | ✓ | ✓ | — | ✓ (own dept) | ✓ (own) |
| create_users | ✓ | ✓ | ✓ | — | — | — |
| edit_users | ✓ | ✓ | ✓ | — | — | ✓ (own) |
| delete_users | ✓ | ✓ | — | — | — | — |
| reset_password | ✓ | ✓ | — | ✓ | — | ✓ (own) |
| manage_roles | ✓ | ✓ | — | — | — | — |
| manage_permissions | ✓ | ✓ | — | — | — | — |
| view_audit_logs | ✓ | ✓ | ✓ | ✓ | — | ✓ (own) |
GET /api/admin/users
page, search, department_id, status, role{
"data": [
{
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone_number": "+1-555-0100",
"status": "active",
"department": "HR",
"position": "HR Manager",
"roles": ["hr-manager"],
"avatar_url": "https://cdn.../avatar.jpg",
"last_login_at": "2025-12-10T09:45:00Z"
}
],
"pagination": { "total": 150, "per_page": 25, "current_page": 1 }
}GET /api/admin/users/{id}
{
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone_number": "+1-555-0100",
"date_of_birth": "1990-05-15",
"status": "active",
"timezone": "Asia/Manila",
"language": "en",
"last_login_at": "2025-12-10T09:45:00Z",
"two_factor_enabled": true,
"profile": {
"employee_id": "EMP-00001",
"department": "HR",
"position": "HR Manager",
"employment_type": "full-time",
"start_date": "2020-03-01",
"manager": "John Smith"
},
"roles": [
{
"id": 3,
"name": "hr-manager",
"permissions": ["view_users", "create_users", "edit_users", ...]
}
]
}POST /api/admin/users
{
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"phone_number": "+1-555-0200",
"password": "SecurePass123!",
"department_id": 5,
"position_id": 12,
"employment_type": "full-time",
"start_date": "2025-01-01",
"roles": [3],
"send_welcome_email": true
}PUT /api/admin/users/{id}
DELETE /api/admin/users/{id}
POST /api/admin/users/{id}/reset-password
temporary_password: boolean{
"message": "Password reset email sent",
"temporary_password": "TempPass123!" (if requested)
}POST /api/admin/users/{id}/suspend
reason: "string"POST /api/admin/users/{id}/reactivate
POST /api/admin/users/bulk-import
file (CSV upload) or users (JSON array){
"message": "Import successful",
"created": 45,
"failed": 2,
"errors": [...]
}POST /api/admin/users/{id}/assign-roles
{
"roles": [3, 5, 7]
}GET /api/profile
PUT /api/profile
{
"phone_number": "+1-555-0100",
"timezone": "Asia/Manila",
"language": "en",
"avatar": "<file upload>"
}POST /api/profile/avatar
avatar (file)POST /api/login
{
"email": "jane@example.com",
"password": "SecurePass123!"
}{
"token": "eyJhbGc...",
"user": { ... }
}POST /api/logout
Authorization: Bearer {token}POST /api/password/reset-request
email: "jane@example.com"POST /api/password/reset
{
"token": "reset-token-123",
"password": "NewPass456!",
"password_confirmation": "NewPass456!"
}Location: app/Http/Controllers/Web/Admin/UserController.php or app/Http/Controllers/API/UserController.php
Key methods:
index() — List all users
public function index(Request $request)
{
$users = User::with('profile.department', 'roles')
->when($request->search, fn($q) =>
$q->where('first_name', 'like', "%{$request->search}%")
->orWhere('last_name', 'like', "%{$request->search}%")
->orWhere('email', 'like', "%{$request->search}%")
)
->when($request->status, fn($q) => $q->where('status', $request->status))
->when($request->department_id, fn($q) =>
$q->whereHas('profile', fn($q) => $q->where('department_id', $request->department_id))
)
->paginate(25);
return view('users.admin.user.index', compact('users'));
}
store() — Create user
public function store(Request $request)
{
$validated = $request->validate([
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'phone_number' => 'nullable|phone:AUTO',
'password' => 'required|min:8|confirmed|password_rules',
'department_id' => 'required|exists:departments,id',
'position_id' => 'required|exists:positions,id',
'employment_type' => 'required|in:full-time,part-time,contract,temporary',
'start_date' => 'required|date',
'roles' => 'nullable|array|exists:roles,id',
'avatar' => 'nullable|image|mimes:png,jpg,jpeg|max:2048',
]);
$user = new User([
'first_name' => $validated['first_name'],
'last_name' => $validated['last_name'],
'email' => $validated['email'],
'phone_number' => $validated['phone_number'] ?? null,
'password' => Hash::make($validated['password']),
]);
$user->save();
$user->profile()->create([
'department_id' => $validated['department_id'],
'position_id' => $validated['position_id'],
'employment_type' => $validated['employment_type'],
'start_date' => $validated['start_date'],
]);
if ($request->has('roles')) {
$user->assignRole($validated['roles']);
}
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $path]);
}
if ($request->boolean('send_welcome_email', true)) {
Mail::queue(new WelcomeUser($user));
}
return redirect()->route('admin.user.edit', $user->id)
->with('message', 'User created successfully');
}
update() — Modify user
public function update(Request $request, string $id)
{
$user = User::findOrFail($id);
$validated = $request->validate([
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'email' => "required|email|unique:users,email,{$id}",
'phone_number' => 'nullable|phone:AUTO',
'status' => 'required|in:active,inactive,suspended,terminated',
'roles' => 'nullable|array|exists:roles,id',
'avatar' => 'nullable|image|mimes:png,jpg,jpeg|max:2048',
]);
$user->update([
'first_name' => $validated['first_name'],
'last_name' => $validated['last_name'],
'email' => $validated['email'],
'phone_number' => $validated['phone_number'],
'status' => $validated['status'],
]);
if ($request->has('roles')) {
$user->syncRoles($validated['roles']);
}
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $path]);
}
return redirect()->back()->with('message', 'User updated successfully');
}
destroy() — Delete user
public function destroy(string $id)
{
$user = User::findOrFail($id);
// Soft delete
$user->delete();
// Audit log
activity()
->causedBy(auth()->user())
->performedOn($user)
->log('deleted');
return redirect()->route('admin.user.index')
->with('message', 'User deleted successfully');
}
resetPassword() — Reset user password
public function resetPassword(Request $request, string $id)
{
$user = User::findOrFail($id);
// Send password reset email
Password::sendResetLink([
'email' => $user->email
]);
return back()->with('message', 'Password reset email sent');
}
suspendAccount() — Suspend user
public function suspendAccount(Request $request, string $id)
{
$request->validate(['reason' => 'required|string']);
$user = User::findOrFail($id);
$user->update(['status' => 'suspended']);
activity()
->causedBy(auth()->user())
->performedOn($user)
->withProperties(['reason' => $request->reason])
->log('suspended');
Mail::queue(new AccountSuspended($user, $request->reason));
return back()->with('message', 'User account suspended');
}
Location: app/Http/Controllers/Web/ProfileController.php or app/Http/Controllers/API/ProfileController.php
showProfile() — Display current user's profile
public function showProfile()
{
$user = auth()->user()->load('profile.department', 'profile.position', 'profile.manager');
return view('user.profile', compact('user'));
}
updateProfile() — Update profile info
public function updateProfile(Request $request)
{
$validated = $request->validate([
'phone_number' => 'nullable|phone:AUTO',
'timezone' => 'required|timezone',
'language' => 'required|in:en,es,fr,tl',
'avatar' => 'nullable|image|mimes:png,jpg,jpeg|max:2048',
]);
$user = auth()->user();
$user->update($validated);
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $path]);
}
return back()->with('message', 'Profile updated successfully');
}
Location: app/Http/Controllers/Web/SettingController.php or app/Http/Controllers/API/SettingController.php
showSettings() — Display account settings
public function showSettings()
{
$user = auth()->user();
$settings = $user->settings;
$loginHistory = $user->loginHistory()->latest()->limit(10)->get();
return view('user.settings', compact('user', 'settings', 'loginHistory'));
}
changePassword() — Update password
public function changePassword(Request $request)
{
$validated = $request->validate([
'current_password' => ['required', 'current_password'],
'password' => ['required', 'min:8', 'confirmed', 'password_rules'],
]);
auth()->user()->update([
'password' => Hash::make($validated['password']),
]);
return back()->with('message', 'Password changed successfully');
}
enableTwoFactor() — Enable 2FA
public function enableTwoFactor(Request $request)
{
$user = auth()->user();
// Generate TOTP secret
$secret = (new Google2FA())->generateSecretKey();
$user->update(['two_factor_secret' => encrypt($secret)]);
$qrCode = (new Google2FA())->getQRCodeInline(
config('app.name'),
$user->email,
$secret
);
return view('user.enable-2fa', compact('qrCode', 'secret'));
}
Scenario: New employee joins company.
Steps:
HR creates user record in Admin → Users → Create
System actions:
Employee receives email:
Employee completes setup:
Manager confirmation:
Scenario: Employee changes department or updates personal info.
By employee (self-service):
By HR/Admin:
Effective date for changes:
Scenario: Promote employee to team lead (add role).
Steps:
assignRole())Role hierarchy (if using packages like larastan/role-permission):
Scenario A: Resignation/Termination
HR sets status to "Terminated"
System actions:
Offboarding tasks:
Scenario B: Temporary Inactivity
HR sets status to "Inactive" (e.g., leave of absence)
System:
On reactivation:
Scenario C: Account Suspension
IT or Security suspends account
System:
Unsuspension:
Admin-initiated password reset:
/password/reset?token=abc123User-initiated password reset:
Password policies (configurable):
Two-Factor Authentication:
DTR Integration:
Payroll Integration:
Scheduling Integration:
Audit/Logging Integration:
activity_log tableIndex filters:
Reports:
Export formats: CSV, Excel, PDF (roster only)
Audit logging:
created_at, updated_at on User modelcreated — new userupdated — field changes (name, email, status, roles)deleted — user deleted (soft or hard)password_reset — password changed2fa_enabled / 2fa_disabledlogin_successful / login_failedaccount_suspended / account_reactivatedNotifications:
Compliance:
Password security:
Account security:
Data security:
Access control:
Audit & logging:
Scenario: HR hires new employee "Alice Johnson" for Sales department, starting Jan 1, 2025.
Steps:
Navigate: Admin → Users → Create
Fill form:
Click "Create"
System:
Subject: Welcome to DTR System!
Body: Click link to set your password: /password/reset?token=...Alice receives email:
Result: Alice can now clock in/out, view shifts, request leaves ✓
Scenario: Company acquired subsidiary; need to import 200 new employees.
Steps:
Prepare CSV file:
first_name,last_name,email,phone,employee_id,department,position,employment_type,start_date,manager
Robert,Brown,robert.brown@subsidiary.com,+1-555-0400,SUB-001,Engineering,Software Engineer,full-time,2024-12-15,Michael Davis
Sarah,White,sarah.white@subsidiary.com,+1-555-0500,SUB-002,Engineering,DevOps Engineer,full-time,2024-12-15,Michael Davis
...
Navigate: Admin → Users → Bulk Import
Upload CSV file
System:
Import completes:
Employees receive:
Scenario: Promote Alice Johnson from "employee" to "team-lead" (Sales team).
Steps:
Navigate: Admin → Users → Alice Johnson
Open "Roles" tab
Current roles: employee
Click "Add Role" or checkbox "team-lead"
Click "Save"
System:
model_has_roles table: assigns role "team-lead"view_team_membersapprove_leavesassign_shiftsview_payroll_summaryResult: Alice can now approve leaves, assign shifts for her team ✓
Scenario: Alice forgot her password and cannot login.
Steps (via Admin):
Navigate: Admin → Users → Alice Johnson
Click "Reset Password" button
System:
Alice receives email:
/password/reset?token=abc123Alice clicks link:
System:
Alice logs in: NewPass789! ✓
Steps (via self-service):
Scenario: Alice resigns; HR needs to terminate her account.
Steps:
Navigate: Admin → Users → Alice Johnson
Click "Edit"
Set Status: Terminated (or Inactive)
Fill Termination Details:
Click "Save"
System:
Offboarding tasks (manual checklist):
Result: Alice's account inactive; data preserved in DB for compliance ✓
Unit tests:
Integration tests:
E2E tests (manual):
Security tests:
Performance tests:
Q: "User created but welcome email not received"
Q: "Terminated user can still login"
if($user->status !== 'active') abort(401)Q: "User assigned role but permissions not taking effect"
php artisan cache:clearroles tablerole_has_permissions tableQ: "Password reset link expired"
Q: "Bulk import fails on row 50 of 200"
Q: "How to export all users for payroll?"
GET /api/admin/users?export=1SQL: Get active users in HR department
SELECT u.* FROM users u
JOIN profiles p ON u.id = p.user_id
JOIN departments d ON p.department_id = d.id
WHERE u.status = 'active' AND d.name = 'HR'
ORDER BY u.last_name;
SQL: Get users with specific role
SELECT u.* FROM users u
JOIN model_has_roles mhr ON u.id = mhr.model_id
JOIN roles r ON mhr.role_id = r.id
WHERE r.name = 'team-lead' AND mhr.model_type = 'App\Models\User';
SQL: Find login attempts (failed) in last 7 days
SELECT * FROM login_attempts
WHERE status = 'failed' AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY created_at DESC
LIMIT 100;
SQL: Audit log — user changes
SELECT * FROM activity_log
WHERE model_type = 'App\Models\User' AND model_id = {user_id}
ORDER BY created_at DESC
LIMIT 50;
Laravel Model Hook: Auto-create profile on user creation
// In User model
protected static function booted()
{
static::created(function ($user) {
$user->profile()->create();
});
}
Laravel Blade: Display user avatar
@if($user->avatar)
<img src="{{ asset('storage/' . $user->avatar) }}" alt="{{ $user->name }}" class="avatar">
@else
<img src="/images/default-avatar.png" alt="Default" class="avatar">
@endif
Laravel API Resource: User response
namespace App\Http\Resources;
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'full_name' => $this->first_name . ' ' . $this->last_name,
'status' => $this->status,
'avatar_url' => $this->avatar ? asset('storage/' . $this->avatar) : null,
'profile' => ProfileResource::make($this->profile),
'roles' => $this->roles->pluck('name'),
];
}
}
Document version: 1.0
Maintainers: HR / Product / Engineering
Last updated: December 10, 2025