'required|string|max:255|min:2', 'email' => 'required|email|unique:users,email|max:255', 'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()], 'employee_id' => 'nullable|string|max:50|unique:users,employee_id|regex:/^[A-Z0-9-]+$/', 'phone' => 'nullable|string|max:20|regex:/^[\+]?[0-9\s\-\(\)]+$/', 'department' => 'nullable|string|max:100', 'position' => 'nullable|string|max:100', 'branch_code' => 'required|string|max:10|exists:branches,code', 'hire_date' => 'nullable|date|before_or_equal:today', 'salary' => 'nullable|numeric|min:0|max:999999.99', 'status' => 'required|in:active,inactive,suspended', 'emergency_contact_name' => 'nullable|string|max:255', 'emergency_contact_phone' => 'nullable|string|max:20|regex:/^[\+]?[0-9\s\-\(\)]+$/', 'address' => 'nullable|string|max:500', 'date_of_birth' => 'nullable|date|before:-18 years', 'national_id' => 'nullable|string|max:50|unique:users,national_id', 'selectedRoles' => 'array|min:1', 'selectedRoles.*' => 'exists:roles,id', 'selectedPermissions' => 'array', 'selectedPermissions.*' => 'exists:permissions,id', ]; } protected $messages = [ 'name.min' => 'Name must be at least 2 characters long.', 'branch_code.required' => 'Branch code is required.', 'branch_code.exists' => 'Selected branch code does not exist.', 'email.unique' => 'This email address is already registered.', 'employee_id.unique' => 'This employee ID is already in use.', 'employee_id.regex' => 'Employee ID can only contain letters, numbers, and hyphens.', 'phone.regex' => 'Please enter a valid phone number.', 'emergency_contact_phone.regex' => 'Please enter a valid emergency contact phone number.', 'date_of_birth.before' => 'Employee must be at least 18 years old.', 'hire_date.before_or_equal' => 'Hire date cannot be in the future.', 'national_id.unique' => 'This national ID is already registered.', 'selectedRoles.min' => 'Please assign at least one role to the user.', 'salary.max' => 'Salary cannot exceed 999,999.99.', ]; public function mount() { $this->hire_date = now()->format('Y-m-d'); $this->branch_code = auth()->user()->branch_code ?? ''; } public function render() { $roles = Role::where('is_active', true) ->orderBy('display_name') ->get(); $permissions = Permission::where('is_active', true) ->orderBy('module') ->orderBy('name') ->get() ->groupBy('module'); $departments = User::select('department') ->distinct() ->whereNotNull('department') ->where('department', '!=', '') ->orderBy('department') ->pluck('department'); $branches = \DB::table('branches') ->where('is_active', true) ->orderBy('name') ->get(['code', 'name']); $positions = $this->getPositionsForDepartment($this->department); return view('livewire.users.create', [ 'roles' => $roles, 'permissions' => $permissions, 'departments' => $departments, 'branches' => $branches, 'positions' => $positions, ]); } public function save() { $this->saving = true; $this->validate(); DB::beginTransaction(); try { // Create the user $user = User::create([ 'name' => trim($this->name), 'email' => strtolower(trim($this->email)), 'password' => Hash::make($this->password), 'employee_id' => $this->employee_id ? strtoupper(trim($this->employee_id)) : null, 'phone' => $this->phone ? preg_replace('/[^0-9+\-\s\(\)]/', '', $this->phone) : null, 'department' => $this->department ?: null, 'position' => $this->position ?: null, 'branch_code' => $this->branch_code, 'hire_date' => $this->hire_date ?: null, 'salary' => $this->salary ?: null, 'status' => $this->status, 'emergency_contact_name' => trim($this->emergency_contact_name) ?: null, 'emergency_contact_phone' => $this->emergency_contact_phone ? preg_replace('/[^0-9+\-\s\(\)]/', '', $this->emergency_contact_phone) : null, 'address' => trim($this->address) ?: null, 'date_of_birth' => $this->date_of_birth ?: null, 'national_id' => $this->national_id ? trim($this->national_id) : null, 'email_verified_at' => now(), 'created_by' => auth()->id(), ]); // Assign roles if (!empty($this->selectedRoles)) { foreach ($this->selectedRoles as $roleId) { $role = Role::find($roleId); if ($role) { $user->assignRole($role, $this->branch_code); } } } // Assign direct permissions if (!empty($this->selectedPermissions)) { foreach ($this->selectedPermissions as $permissionId) { $permission = Permission::find($permissionId); if ($permission) { $user->givePermission($permission, $this->branch_code); } } } // Log the creation activity() ->performedOn($user) ->causedBy(auth()->user()) ->withProperties([ 'user_data' => [ 'name' => $user->name, 'email' => $user->email, 'employee_id' => $user->employee_id, 'department' => $user->department, 'branch_code' => $user->branch_code, 'status' => $user->status, ], 'roles_assigned' => $this->selectedRoles, 'permissions_assigned' => $this->selectedPermissions, ]) ->log('User created'); // Send welcome email if requested if ($this->sendWelcomeEmail) { try { // TODO: Implement welcome email notification // $user->notify(new WelcomeNotification($this->password)); } catch (\Exception $e) { // Log email failure but don't fail the user creation \Log::warning('Failed to send welcome email to user: ' . $user->email, ['error' => $e->getMessage()]); } } DB::commit(); session()->flash('success', "User '{$user->name}' created successfully!"); $this->saving = false; return redirect()->route('users.show', $user); } catch (\Exception $e) { DB::rollBack(); $this->saving = false; \Log::error('Failed to create user', [ 'error' => $e->getMessage(), 'user_data' => [ 'name' => $this->name, 'email' => $this->email, 'employee_id' => $this->employee_id, ] ]); session()->flash('error', 'Failed to create user: ' . $e->getMessage()); } } public function cancel() { return redirect()->route('users.index'); } public function generatePassword() { $this->generatedPassword = $this->generateSecurePassword(); $this->password = $this->generatedPassword; $this->password_confirmation = $this->generatedPassword; $this->showPasswordGenerator = true; } public function generateSecurePassword($length = 12) { // Generate a secure password with mixed case, numbers, and symbols $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $lowercase = 'abcdefghijklmnopqrstuvwxyz'; $numbers = '0123456789'; $symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'; $password = ''; $password .= $uppercase[random_int(0, strlen($uppercase) - 1)]; $password .= $lowercase[random_int(0, strlen($lowercase) - 1)]; $password .= $numbers[random_int(0, strlen($numbers) - 1)]; $password .= $symbols[random_int(0, strlen($symbols) - 1)]; $allChars = $uppercase . $lowercase . $numbers . $symbols; for ($i = 4; $i < $length; $i++) { $password .= $allChars[random_int(0, strlen($allChars) - 1)]; } return str_shuffle($password); } public function nextStep() { if ($this->currentStep < $this->totalSteps) { $this->currentStep++; } } public function previousStep() { if ($this->currentStep > 1) { $this->currentStep--; } } public function updatedDepartment() { // Clear position when department changes $this->position = ''; } public function getPositionsForDepartment($department) { $positions = [ 'Service' => ['Service Advisor', 'Service Manager', 'Service Coordinator', 'Service Writer'], 'Technician' => ['Lead Technician', 'Senior Technician', 'Junior Technician', 'Apprentice Technician'], 'Parts' => ['Parts Manager', 'Parts Associate', 'Inventory Specialist', 'Parts Counter Person'], 'Administration' => ['Administrator', 'Office Manager', 'Receptionist', 'Data Entry Clerk'], 'Management' => ['General Manager', 'Assistant Manager', 'Supervisor', 'Team Lead'], 'Sales' => ['Sales Manager', 'Sales Associate', 'Sales Coordinator'], 'Finance' => ['Finance Manager', 'Accountant', 'Cashier', 'Billing Specialist'], ]; return $positions[$department] ?? []; } public function validateStep1() { $this->validateOnly([ 'name', 'email', 'password', 'password_confirmation', 'employee_id', 'phone', ]); } public function validateStep2() { $this->validateOnly([ 'department', 'position', 'branch_code', 'hire_date', 'salary', 'status', ]); } public function copyPasswordToClipboard() { // This will be handled by Alpine.js on the frontend $this->dispatch('password-copied'); } public function getRolePermissionCount($roleId) { $role = Role::find($roleId); return $role ? $role->permissions()->count() : 0; } public function getSelectedRolesPermissions() { if (empty($this->selectedRoles)) { return collect(); } return Permission::whereHas('roles', function($query) { $query->whereIn('roles.id', $this->selectedRoles); })->get(); } public function hasValidationErrors() { return $this->getErrorBag()->isNotEmpty(); } public function getProgressPercentage() { return ($this->currentStep / $this->totalSteps) * 100; } }