tenantId(); $query = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->with(['user', 'department', 'manager']); // 검색 (이름, 이메일, 사원코드) if (! empty($params['q'])) { $search = $params['q']; $query->where(function ($q) use ($search) { $q->whereHas('user', function ($uq) use ($search) { $uq->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%"); }) // json_extra에서 employee_code 검색 ->orWhereJsonContains('json_extra->employee_code', $search); }); } // 고용 상태 필터 (employee_status) if (! empty($params['status'])) { $query->where('employee_status', $params['status']); } // 부서 필터 if (! empty($params['department_id'])) { $query->where('department_id', $params['department_id']); } // 시스템 계정 보유 여부 필터 if (isset($params['has_account'])) { $hasAccount = filter_var($params['has_account'], FILTER_VALIDATE_BOOLEAN); $query->whereHas('user', function ($q) use ($hasAccount) { if ($hasAccount) { $q->whereNotNull('password'); } else { $q->whereNull('password'); } }); } // 정렬 $sortBy = $params['sort_by'] ?? 'created_at'; $sortDir = $params['sort_dir'] ?? 'desc'; $query->orderBy($sortBy, $sortDir); $perPage = $params['per_page'] ?? 20; return $query->paginate($perPage); } /** * 사원 상세 조회 */ public function show(int $id): TenantUserProfile { $tenantId = $this->tenantId(); $profile = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->with(['user', 'department', 'manager']) ->find($id); if (! $profile) { throw new NotFoundHttpException(__('error.not_found')); } return $profile; } /** * 사원 등록 (users 테이블에 사용자 생성 + tenant_user_profiles 생성) * * @param array $data 사원 데이터 * - create_account: bool (true=시스템 계정 생성, false=사원 전용) * - password: string (create_account=true일 때 필수) */ public function store(array $data): TenantUserProfile { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($data, $tenantId, $userId) { // 1. 비밀번호 결정: create_account=false면 NULL (사원 전용, 로그인 불가) $password = null; $createAccount = $data['create_account'] ?? false; if ($createAccount && ! empty($data['password'])) { $password = Hash::make($data['password']); } // 2. users 테이블에 사용자 생성 $user = User::create([ 'user_id' => $data['user_id'] ?? $this->generateUserId($data['email']), 'name' => $data['name'], 'email' => $data['email'], 'phone' => $data['phone'] ?? null, 'password' => $password, 'is_active' => $data['is_active'] ?? true, 'created_by' => $userId, ]); // 3. user_tenants pivot에 관계 추가 $user->tenantsMembership()->attach($tenantId, [ 'is_active' => true, 'is_default' => true, 'joined_at' => now(), ]); // 4. tenant_user_profiles 생성 $profile = TenantUserProfile::create([ 'tenant_id' => $tenantId, 'user_id' => $user->id, 'department_id' => $data['department_id'] ?? null, 'position_key' => $data['position_key'] ?? null, 'job_title_key' => $data['job_title_key'] ?? null, 'work_location_key' => $data['work_location_key'] ?? null, 'employment_type_key' => $data['employment_type_key'] ?? null, 'employee_status' => $data['employee_status'] ?? 'active', 'manager_user_id' => $data['manager_user_id'] ?? null, 'profile_photo_path' => $data['profile_photo_path'] ?? null, 'display_name' => $data['display_name'] ?? null, ]); // 5. json_extra 사원 정보 설정 $profile->updateEmployeeInfo([ 'employee_code' => $data['employee_code'] ?? null, 'resident_number' => $data['resident_number'] ?? null, 'gender' => $data['gender'] ?? null, 'address' => $data['address'] ?? null, 'salary' => $data['salary'] ?? null, 'hire_date' => $data['hire_date'] ?? null, 'rank' => $data['rank'] ?? null, 'bank_account' => $data['bank_account'] ?? null, 'work_type' => $data['work_type'] ?? 'regular', 'contract_info' => $data['contract_info'] ?? null, ]); $profile->save(); return $profile->fresh(['user', 'department', 'manager']); }); } /** * 사원 수정 */ public function update(int $id, array $data): TenantUserProfile { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $profile = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->find($id); if (! $profile) { throw new NotFoundHttpException(__('error.not_found')); } return DB::transaction(function () use ($profile, $data, $userId) { // 1. users 테이블 업데이트 (기본 정보) $user = $profile->user; $userUpdates = []; if (isset($data['name'])) { $userUpdates['name'] = $data['name']; } if (isset($data['email'])) { $userUpdates['email'] = $data['email']; } if (isset($data['phone'])) { $userUpdates['phone'] = $data['phone']; } if (isset($data['is_active'])) { $userUpdates['is_active'] = $data['is_active']; } if (! empty($userUpdates)) { $userUpdates['updated_by'] = $userId; $user->update($userUpdates); } // 2. tenant_user_profiles 업데이트 $profileUpdates = []; $profileFields = [ 'department_id', 'position_key', 'job_title_key', 'work_location_key', 'employment_type_key', 'employee_status', 'manager_user_id', 'profile_photo_path', 'display_name', ]; foreach ($profileFields as $field) { if (array_key_exists($field, $data)) { $profileUpdates[$field] = $data[$field]; } } if (! empty($profileUpdates)) { $profile->update($profileUpdates); } // 3. json_extra 사원 정보 업데이트 $jsonExtraFields = [ 'employee_code', 'resident_number', 'gender', 'address', 'salary', 'hire_date', 'rank', 'bank_account', 'work_type', 'contract_info', ]; $jsonExtraUpdates = []; foreach ($jsonExtraFields as $field) { if (array_key_exists($field, $data)) { $jsonExtraUpdates[$field] = $data[$field]; } } if (! empty($jsonExtraUpdates)) { $profile->updateEmployeeInfo($jsonExtraUpdates); $profile->save(); } return $profile->fresh(['user', 'department', 'manager']); }); } /** * 사원 삭제 (soft delete) */ public function destroy(int $id): array { $tenantId = $this->tenantId(); $profile = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->find($id); if (! $profile) { throw new NotFoundHttpException(__('error.not_found')); } // tenant_user_profiles에는 SoftDeletes가 없으므로 hard delete // 또는 employee_status를 resigned로 변경 $profile->update(['employee_status' => 'resigned']); return [ 'id' => $id, 'deleted_at' => now()->toDateTimeString(), ]; } /** * 일괄 삭제 */ public function bulkDelete(array $ids): array { $tenantId = $this->tenantId(); $updated = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->whereIn('id', $ids) ->update(['employee_status' => 'resigned']); return [ 'processed' => count($ids), 'updated' => $updated, ]; } /** * 사원 통계 */ public function stats(): array { $tenantId = $this->tenantId(); $baseQuery = TenantUserProfile::query()->where('tenant_id', $tenantId); $total = (clone $baseQuery)->count(); $active = (clone $baseQuery)->where('employee_status', 'active')->count(); $leave = (clone $baseQuery)->where('employee_status', 'leave')->count(); $resigned = (clone $baseQuery)->where('employee_status', 'resigned')->count(); return [ 'total' => $total, 'active' => $active, 'leave' => $leave, 'resigned' => $resigned, ]; } /** * 시스템 계정 생성 (비밀번호 설정) */ public function createAccount(int $id, string $password): TenantUserProfile { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $profile = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->with('user') ->find($id); if (! $profile) { throw new NotFoundHttpException(__('error.not_found')); } $profile->user->update([ 'password' => Hash::make($password), 'must_change_password' => true, 'updated_by' => $userId, ]); return $profile->fresh(['user', 'department', 'manager']); } /** * 시스템 계정 해제 (비밀번호 제거 → 로그인 불가, 사원 정보 유지) */ public function revokeAccount(int $id): TenantUserProfile { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $profile = TenantUserProfile::query() ->where('tenant_id', $tenantId) ->with('user') ->find($id); if (! $profile) { throw new NotFoundHttpException(__('error.not_found')); } $user = $profile->user; // 이미 계정이 없는 경우 if (empty($user->password)) { throw new \InvalidArgumentException(__('employee.no_account')); } // 1. 비밀번호 제거 (로그인 불가) $user->update([ 'password' => null, 'must_change_password' => false, 'updated_by' => $userId, ]); // 2. 기존 토큰 무효화 (로그아웃 처리) $user->tokens()->delete(); return $profile->fresh(['user', 'department', 'manager']); } /** * 사용자 ID 자동 생성 */ private function generateUserId(string $email): string { $prefix = explode('@', $email)[0]; $suffix = Str::random(4); return strtolower($prefix.'_'.$suffix); } }