'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', 'accountExpiry' => 'nullable|date|after:today', 'notes' => 'nullable|string|max:1000', ]; } 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.', 'accountExpiry.after' => 'Account expiry must be in the future.', 'notes.max' => 'Notes cannot exceed 1000 characters.', ]; public function mount() { $this->hire_date = now()->format('Y-m-d'); $this->branch_code = auth()->user()->branch_code ?? ''; // Auto-generate employee ID if needed $this->generateEmployeeId(); } 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 = Branch::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 generateEmployeeId() { if (empty($this->employee_id) && !empty($this->branch_code)) { $lastEmployee = User::where('branch_code', $this->branch_code) ->where('employee_id', 'like', $this->branch_code . '%') ->orderByDesc('employee_id') ->first(); if ($lastEmployee) { $number = (int) substr($lastEmployee->employee_id, strlen($this->branch_code)) + 1; } else { $number = 1; } $this->employee_id = $this->branch_code . str_pad($number, 4, '0', STR_PAD_LEFT); } } public function updatedBranchCode() { $this->generateEmployeeId(); } public function generatePassword() { $this->generatedPassword = Str::random(12); $this->password = $this->generatedPassword; $this->password_confirmation = $this->generatedPassword; $this->showPasswordGenerator = false; $this->temporaryPassword = true; $this->requirePasswordChange = true; } public function applyRolePreset($preset) { $this->selectedRoles = []; $this->selectedPermissions = []; switch ($preset) { case 'manager': $roles = Role::whereIn('name', ['manager', 'service_supervisor'])->get(); break; case 'technician': $roles = Role::whereIn('name', ['technician'])->get(); break; case 'receptionist': $roles = Role::whereIn('name', ['receptionist', 'customer_service'])->get(); break; case 'service_coordinator': $roles = Role::whereIn('name', ['service_coordinator'])->get(); break; default: $roles = collect(); } $this->selectedRoles = $roles->pluck('id')->toArray(); } public function nextStep() { $this->validateCurrentStep(); if ($this->currentStep < $this->totalSteps) { $this->currentStep++; } } public function previousStep() { if ($this->currentStep > 1) { $this->currentStep--; } } public function validateCurrentStep() { $rules = $this->rules(); switch ($this->currentStep) { case 1: // Basic Information $stepRules = [ 'name' => $rules['name'], 'email' => $rules['email'], 'employee_id' => $rules['employee_id'], 'branch_code' => $rules['branch_code'], ]; break; case 2: // Employment Details $stepRules = [ 'department' => $rules['department'], 'position' => $rules['position'], 'hire_date' => $rules['hire_date'], 'salary' => $rules['salary'], ]; break; case 3: // Security & Access $stepRules = [ 'password' => $rules['password'], 'selectedRoles' => $rules['selectedRoles'], 'selectedRoles.*' => $rules['selectedRoles.*'], ]; break; case 4: // Additional Information $stepRules = [ 'phone' => $rules['phone'], 'address' => $rules['address'], 'emergency_contact_name' => $rules['emergency_contact_name'], 'emergency_contact_phone' => $rules['emergency_contact_phone'], ]; break; default: $stepRules = []; } $this->validate($stepRules); } public function getPositionsForDepartment($department) { if (empty($department)) return []; // Get positions from existing users in the same department $existingPositions = User::select('position') ->where('department', $department) ->distinct() ->whereNotNull('position') ->where('position', '!=', '') ->orderBy('position') ->pluck('position') ->toArray(); // Common positions by department $commonPositions = [ 'Service' => ['Service Advisor', 'Service Manager', 'Service Coordinator', 'Service Writer'], 'Technical' => ['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'], ]; $predefinedPositions = $commonPositions[$department] ?? []; // Merge and deduplicate $allPositions = array_unique(array_merge($existingPositions, $predefinedPositions)); sort($allPositions); return $allPositions; } 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 updatedDepartment() { // Clear position when department changes $this->position = ''; } 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; } }