[def, setting] */ protected static function effectiveFieldMap(int $tenantId): array { $defs = SettingFieldDef::all()->keyBy('field_key'); $settings = TenantFieldSetting::where('tenant_id', $tenantId)->get()->keyBy('field_key'); $map = []; foreach ($defs as $key => $def) { $s = $settings->get($key); $enabled = $s ? (bool) $s->enabled : (bool) $def->is_core; if (! $enabled) { continue; } $map[$key] = ['def' => $def, 'setting' => $s]; } return $map; } /** * [GET] 프로필 목록 (페이징) * params: per_page?, q? */ public static function index(array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } $per = (int) ($params['per_page'] ?? $params['size'] ?? 20); $q = TenantUserProfile::where('tenant_id', $tenantId) ->with(['user:id,name,email']); if (! empty($params['q'])) { $kw = $params['q']; $q->where(function ($w) use ($kw) { $w->where('display_name', 'like', "%{$kw}%") ->orWhere('json_extra->employee_no', 'like', "%{$kw}%"); }); } $page = isset($params['page']) ? (int) $params['page'] : null; $data = $q->orderByDesc('id')->paginate($per, ['*'], 'page', $page); return $data; } /** * [GET] 프로필 단건 조회 */ public static function show(int $userId) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } if (! $userId) { return ['error' => 'userId가 올바르지 않습니다.', 'code' => 422]; } $item = TenantUserProfile::where('tenant_id', $tenantId) ->where('user_id', $userId) ->with(['user:id,name,email']) ->first(); if (! $item) { return ['error' => '프로필을 찾을 수 없습니다.', 'code' => 404]; } return $item->toArray(); } /** * [PATCH] 관리자 수정: enabled 필드만 write-back * - 입력 payload는 field_key: value 포맷(예: {"position":"manager","employee_no":"A-001"}) */ public static function update(int $userId, array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } if (! $userId) { return ['error' => 'userId가 올바르지 않습니다.', 'code' => 422]; } // (선택) 간단 유효성: 최소 1개 키 존재 if (empty($params) || ! is_array($params)) { return ['error' => '수정할 항목이 없습니다.', 'code' => 422]; } $fields = self::effectiveFieldMap($tenantId); $profile = TenantUserProfile::firstOrCreate(['tenant_id' => $tenantId, 'user_id' => $userId]); try { DB::transaction(function () use ($fields, $profile, $params) { foreach ($fields as $key => $meta) { if (! array_key_exists($key, $params)) { continue; } // 입력이 없으면 스킵 $def = $meta['def']; $value = $params[$key]; switch ($def->storage_area) { case 'tenant_profile': if ($def->storage_key) { $profile->{$def->storage_key} = $value; } break; case 'tenant_profile_json': $jsonKey = $def->storage_key ?: $key; $extra = is_array($profile->json_extra) ? $profile->json_extra : (array) json_decode((string) $profile->json_extra, true); $extra[$jsonKey] = $value; $profile->json_extra = $extra; break; case 'users': // 정책상 기본 비권장: 무시 // 필요 시 UsersService 등으로 화이트리스트 업데이트 구현 break; } } $profile->save(); }); } catch (\Throwable $e) { return ['error' => '프로필 저장 중 오류가 발생했습니다.', 'code' => 500]; } $fresh = TenantUserProfile::where('tenant_id', $tenantId) ->where('user_id', $userId) ->with(['user:id,name,email']) ->first(); return $fresh ? $fresh->toArray() : null; } /** * [GET] 내 프로필 조회 */ public static function me(array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } $userId = auth()->id(); if (! $userId) { return ['error' => '인증 정보가 없습니다.', 'code' => 401]; } $item = TenantUserProfile::where('tenant_id', $tenantId) ->where('user_id', $userId) ->with(['user:id,name,email']) ->first(); // 없으면 null 반환(스펙에 따라 404로 바꾸려면 위와 동일 처리) return $item ? $item->toArray() : null; } /** * [PATCH] 내 프로필 수정 * - 고정 필드(profile_photo_path, display_name)는 직접 처리 * - 동적 필드(enabled 필드)는 기존 update 로직으로 처리 */ public static function updateMe(array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } $userId = auth()->id(); if (! $userId) { return ['error' => '인증 정보가 없습니다.', 'code' => 401]; } // 고정 필드 추출 (동적 필드 처리 전에 직접 저장) $fixedFields = ['profile_photo_path', 'display_name']; $fixedData = []; foreach ($fixedFields as $field) { if (array_key_exists($field, $params)) { $fixedData[$field] = $params[$field]; unset($params[$field]); } } // 고정 필드가 있으면 직접 업데이트 if (! empty($fixedData)) { $profile = TenantUserProfile::firstOrCreate([ 'tenant_id' => $tenantId, 'user_id' => $userId, ]); $profile->fill($fixedData); $profile->save(); } // 동적 필드가 남아있으면 기존 로직으로 처리 if (! empty($params)) { return self::update($userId, $params); } // 고정 필드만 있었으면 새로고침된 프로필 반환 $fresh = TenantUserProfile::where('tenant_id', $tenantId) ->where('user_id', $userId) ->with(['user:id,name,email']) ->first(); return $fresh ? $fresh->toArray() : null; } }