From 0ee6b9f77a3d2b2fb11804b428c7478463a1bef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 28 Feb 2026 08:07:21 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[users]=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=EC=97=90=20=EC=A7=81=EA=B8=89/=EC=A7=81?= =?UTF-8?q?=EC=B1=85=20=EC=9E=85=EB=A0=A5=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자 수정/생성 화면에 직급(position_key), 직책(job_title_key) 선택 필드 추가 - HR 사원관리의 position-add-modal 재사용 ([+] 버튼으로 새 직급/직책 추가) - UserService에서 tenant_user_profiles 테이블에 저장 (updateOrInsert) - UpdateUserRequest, StoreUserRequest에 validation 규칙 추가 --- app/Http/Controllers/UserController.php | 23 +++++++++++-- app/Http/Requests/StoreUserRequest.php | 2 ++ app/Http/Requests/UpdateUserRequest.php | 2 ++ app/Services/UserService.php | 43 +++++++++++++++++++++++-- resources/views/users/create.blade.php | 39 ++++++++++++++++++++++ resources/views/users/edit.blade.php | 43 +++++++++++++++++++++++++ 6 files changed, 147 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0ccd3095..4ab6c05d 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -3,11 +3,13 @@ namespace App\Http\Controllers; use App\Models\Department; +use App\Models\HR\Position; use App\Models\Role; use App\Models\Tenants\Tenant; use App\Services\UserService; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\DB; use Illuminate\View\View; class UserController extends Controller @@ -40,6 +42,10 @@ public function create(): View $roles = $tenantId ? Role::where('tenant_id', $tenantId)->orderBy('name')->get() : collect(); $departments = $tenantId ? Department::where('tenant_id', $tenantId)->where('is_active', true)->orderBy('name')->get() : collect(); + // 직급/직책 목록 + $ranks = $tenantId ? Position::forTenant($tenantId)->ranks()->where('is_active', true)->ordered()->get() : collect(); + $titles = $tenantId ? Position::forTenant($tenantId)->titles()->where('is_active', true)->ordered()->get() : collect(); + // 본사 테넌트 여부 확인 (본사: 이메일 인증, 그 외: 비밀번호 직접 입력) $isHQ = false; if ($tenantId) { @@ -47,7 +53,7 @@ public function create(): View $isHQ = $tenant?->tenant_type === 'HQ'; } - return view('users.create', compact('roles', 'departments', 'isHQ')); + return view('users.create', compact('roles', 'departments', 'isHQ', 'ranks', 'titles')); } /** @@ -76,6 +82,19 @@ public function edit(int $id): View $userRoleIds = $tenantId ? $user->userRoles()->where('tenant_id', $tenantId)->pluck('role_id')->toArray() : []; $userDepartmentIds = $tenantId ? $user->departmentUsers()->where('tenant_id', $tenantId)->pluck('department_id')->toArray() : []; - return view('users.edit', compact('user', 'roles', 'departments', 'userRoleIds', 'userDepartmentIds')); + // 직급/직책 목록 + $ranks = $tenantId ? Position::forTenant($tenantId)->ranks()->where('is_active', true)->ordered()->get() : collect(); + $titles = $tenantId ? Position::forTenant($tenantId)->titles()->where('is_active', true)->ordered()->get() : collect(); + + // tenant_user_profiles에서 현재 position_key, job_title_key 조회 + $profile = $tenantId + ? DB::table('tenant_user_profiles') + ->where('tenant_id', $tenantId) + ->where('user_id', $user->id) + ->first(['position_key', 'job_title_key']) + : null; + + return view('users.edit', compact('user', 'roles', 'departments', 'userRoleIds', 'userDepartmentIds', + 'ranks', 'titles', 'profile')); } } diff --git a/app/Http/Requests/StoreUserRequest.php b/app/Http/Requests/StoreUserRequest.php index ec4273f3..8f70a26e 100644 --- a/app/Http/Requests/StoreUserRequest.php +++ b/app/Http/Requests/StoreUserRequest.php @@ -63,6 +63,8 @@ public function rules(): array 'role_ids.*' => 'integer|exists:roles,id', 'department_ids' => 'nullable|array', 'department_ids.*' => 'integer|exists:departments,id', + 'position_key' => 'nullable|string|max:64', + 'job_title_key' => 'nullable|string|max:64', ]; // 비본사 테넌트: 비밀번호 직접 입력 필수 diff --git a/app/Http/Requests/UpdateUserRequest.php b/app/Http/Requests/UpdateUserRequest.php index 584a39b4..e8842948 100644 --- a/app/Http/Requests/UpdateUserRequest.php +++ b/app/Http/Requests/UpdateUserRequest.php @@ -68,6 +68,8 @@ public function rules(): array 'role_ids.*' => 'integer|exists:roles,id', 'department_ids' => 'nullable|array', 'department_ids.*' => 'integer|exists:departments,id', + 'position_key' => 'nullable|string|max:64', + 'job_title_key' => 'nullable|string|max:64', ]; } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index dda17f51..f4df8431 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -105,8 +105,11 @@ public function createUser(array $data): User $data['password'] = Hash::make($plainPassword); } - // password_confirmation은 User 모델의 fillable이 아니므로 제거 + // User 모델의 fillable이 아닌 필드 분리 unset($data['password_confirmation']); + $positionKey = $data['position_key'] ?? null; + $jobTitleKey = $data['job_title_key'] ?? null; + unset($data['position_key'], $data['job_title_key']); // is_active 처리 $data['is_active'] = isset($data['is_active']) && $data['is_active'] == '1'; @@ -136,6 +139,23 @@ public function createUser(array $data): User $this->syncDepartments($user, $tenantId, $departmentIds); } + // position_key, job_title_key → tenant_user_profiles 저장 + if ($tenantId) { + $profileFields = []; + if (! empty($positionKey)) { + $profileFields['position_key'] = $positionKey; + } + if (! empty($jobTitleKey)) { + $profileFields['job_title_key'] = $jobTitleKey; + } + if (! empty($profileFields)) { + DB::table('tenant_user_profiles')->updateOrInsert( + ['tenant_id' => $tenantId, 'user_id' => $user->id], + $profileFields + ); + } + } + // 본사만 비밀번호 안내 메일 발송 (비본사는 관리자가 직접 알려줌) if ($plainPassword !== null) { $this->sendPasswordMail($user, $plainPassword, true); @@ -228,8 +248,25 @@ public function updateUser(int $id, array $data): bool $this->syncDepartments($user, $tenantId, $departmentIds); } - // role_ids, department_ids는 User 모델의 fillable이 아니므로 제거 - unset($data['role_ids'], $data['department_ids']); + // position_key, job_title_key → tenant_user_profiles 저장 + if ($tenantId) { + $profileFields = []; + if (array_key_exists('position_key', $data)) { + $profileFields['position_key'] = $data['position_key'] ?: null; + } + if (array_key_exists('job_title_key', $data)) { + $profileFields['job_title_key'] = $data['job_title_key'] ?: null; + } + if (! empty($profileFields)) { + DB::table('tenant_user_profiles')->updateOrInsert( + ['tenant_id' => $tenantId, 'user_id' => $id], + $profileFields + ); + } + } + + // role_ids, department_ids, position/job_title은 User 모델의 fillable이 아니므로 제거 + unset($data['role_ids'], $data['department_ids'], $data['position_key'], $data['job_title_key']); return $user->update($data); } diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php index fa2849dd..5cde5724 100644 --- a/resources/views/users/create.blade.php +++ b/resources/views/users/create.blade.php @@ -66,6 +66,45 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc + +
+

직급/직책

+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + @include('hr.employees.partials.position-add-modal') +

비밀번호

diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 92f62f3d..10010100 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -70,6 +70,49 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
+ +
+

직급/직책

+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + @include('hr.employees.partials.position-add-modal') +

비밀번호