diff --git a/app/Http/Controllers/Api/Admin/HR/EmployeeController.php b/app/Http/Controllers/Api/Admin/HR/EmployeeController.php index 806e6c59..db9a35ed 100644 --- a/app/Http/Controllers/Api/Admin/HR/EmployeeController.php +++ b/app/Http/Controllers/Api/Admin/HR/EmployeeController.php @@ -40,6 +40,19 @@ public function index(Request $request): JsonResponse|Response ]); } + /** + * 기존 사용자 검색 (사원 미등록, 테넌트 소속) + */ + public function searchUsers(Request $request): JsonResponse + { + $users = $this->employeeService->searchTenantUsers($request->get('q', '')); + + return response()->json([ + 'success' => true, + 'data' => $users, + ]); + } + /** * 사원 통계 */ @@ -58,9 +71,10 @@ public function stats(): JsonResponse */ public function store(Request $request): JsonResponse { - $validated = $request->validate([ + $rules = [ + 'existing_user_id' => 'nullable|integer|exists:users,id', 'name' => 'required|string|max:50', - 'email' => 'nullable|email|max:100|unique:users,email', + 'email' => 'nullable|email|max:100', 'phone' => 'nullable|string|max:20', 'password' => 'nullable|string|min:6', 'department_id' => 'nullable|integer|exists:departments,id', @@ -75,7 +89,14 @@ public function store(Request $request): JsonResponse 'hire_date' => 'nullable|date', 'address' => 'nullable|string|max:200', 'emergency_contact' => 'nullable|string|max:100', - ]); + ]; + + // 신규 사용자일 때만 이메일 unique 검증 + if (! $request->filled('existing_user_id')) { + $rules['email'] = 'nullable|email|max:100|unique:users,email'; + } + + $validated = $request->validate($rules); try { $employee = $this->employeeService->createEmployee($validated); diff --git a/app/Services/HR/EmployeeService.php b/app/Services/HR/EmployeeService.php index 1eb2798a..011dd0dc 100644 --- a/app/Services/HR/EmployeeService.php +++ b/app/Services/HR/EmployeeService.php @@ -85,6 +85,39 @@ public function getStats(): array ]; } + /** + * 테넌트 소속이지만 사원 미등록인 사용자 검색 + */ + public function searchTenantUsers(string $query): array + { + $tenantId = session('selected_tenant_id'); + + $builder = User::query() + ->select('users.id', 'users.name', 'users.email', 'users.phone') + ->join('user_tenants as ut', function ($join) use ($tenantId) { + $join->on('users.id', '=', 'ut.user_id') + ->where('ut.tenant_id', $tenantId) + ->whereNull('ut.deleted_at'); + }) + ->leftJoin('tenant_user_profiles as tup', function ($join) use ($tenantId) { + $join->on('users.id', '=', 'tup.user_id') + ->where('tup.tenant_id', $tenantId); + }) + ->whereNull('tup.id') + ->whereNull('users.deleted_at'); + + if ($query !== '') { + $like = "%{$query}%"; + $builder->where(function ($q) use ($like) { + $q->where('users.name', 'like', $like) + ->orWhere('users.email', 'like', $like) + ->orWhere('users.phone', 'like', $like); + }); + } + + return $builder->orderBy('users.name')->limit(20)->get()->toArray(); + } + /** * 사원 등록 (User + TenantUserProfile 동시 생성) */ @@ -93,46 +126,65 @@ public function createEmployee(array $data): Employee $tenantId = session('selected_tenant_id'); return DB::transaction(function () use ($data, $tenantId) { - // user_id 생성: 이메일 있으면 @ 앞부분, 없으면 EMP_랜덤6자 - $userId = ! empty($data['email']) - ? Str::before($data['email'], '@') - : 'EMP_'.strtolower(Str::random(6)); + // 기존 사용자 선택 분기 + if (! empty($data['existing_user_id'])) { + $user = User::findOrFail($data['existing_user_id']); - // user_id 중복 방지 - while (User::where('user_id', $userId)->exists()) { - $userId = $userId.'_'.Str::random(3); - } + // 테넌트 소속 검증 + $isMember = $user->tenants() + ->wherePivot('tenant_id', $tenantId) + ->wherePivotNull('deleted_at') + ->exists(); - // 이메일: 미입력 시 임시 이메일 생성 (NOT NULL 제약) - $email = ! empty($data['email']) - ? $data['email'] - : $userId.'@placeholder.local'; + if (! $isMember) { + throw new \RuntimeException('해당 사용자는 현재 테넌트에 소속되어 있지 않습니다.'); + } - // email 중복 방지 - while (User::where('email', $email)->exists()) { - $email = $userId.'_'.Str::random(3).'@placeholder.local'; - } + // 이미 사원 등록 여부 확인 + $alreadyEmployee = Employee::where('tenant_id', $tenantId) + ->where('user_id', $user->id) + ->exists(); - // User 생성 - $user = User::create([ - 'user_id' => $userId, - 'name' => $data['name'], - 'email' => $email, - 'phone' => $data['phone'] ?? null, - 'password' => Hash::make($data['password'] ?? 'sam1234!'), - 'role' => 'ops', - 'is_active' => true, - 'must_change_password' => true, - 'created_by' => auth()->id(), - ]); + if ($alreadyEmployee) { + throw new \RuntimeException('이미 사원으로 등록된 사용자입니다.'); + } + } else { + // 신규 사용자 생성 + $loginId = ! empty($data['email']) + ? Str::before($data['email'], '@') + : 'EMP_'.strtolower(Str::random(6)); - // user_tenants pivot 연동 (멀티테넌트 필수) - if ($tenantId) { - $user->tenants()->attach($tenantId, [ + while (User::where('user_id', $loginId)->exists()) { + $loginId = $loginId.'_'.Str::random(3); + } + + $email = ! empty($data['email']) + ? $data['email'] + : $loginId.'@placeholder.local'; + + while (User::where('email', $email)->exists()) { + $email = $loginId.'_'.Str::random(3).'@placeholder.local'; + } + + $user = User::create([ + 'user_id' => $loginId, + 'name' => $data['name'], + 'email' => $email, + 'phone' => $data['phone'] ?? null, + 'password' => Hash::make($data['password'] ?? 'sam1234!'), + 'role' => 'ops', 'is_active' => true, - 'is_default' => true, - 'joined_at' => now(), + 'must_change_password' => true, + 'created_by' => auth()->id(), ]); + + if ($tenantId) { + $user->tenants()->attach($tenantId, [ + 'is_active' => true, + 'is_default' => true, + 'joined_at' => now(), + ]); + } } // json_extra 구성 diff --git a/resources/views/hr/employees/create.blade.php b/resources/views/hr/employees/create.blade.php index 30cb6b64..8d5cd55e 100644 --- a/resources/views/hr/employees/create.blade.php +++ b/resources/views/hr/employees/create.blade.php @@ -26,6 +26,74 @@ class="space-y-6">
+ {{-- 기존 직원 불러오기 --}} +
+

기존 직원 불러오기

+ + + + {{-- 선택된 사용자 표시 --}} + + + +

이미 시스템에 등록된 사용자를 사원으로 추가할 수 있습니다.

+
+ {{-- 기본 정보 --}}

기본 정보

@@ -191,6 +259,92 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition- @push('scripts')