From 1e70d2edbf718d7e76dcbf30ca30da577753c4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 29 Jan 2026 01:06:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EB=8F=99=EA=B8=B0=ED=99=94=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=20=EA=B8=80=EB=A1=9C=EB=B2=8C/?= =?UTF-8?q?=ED=85=8C=EB=84=8C=ED=8A=B8=20=ED=95=84=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 환경 탭 앞에 글로벌/테넌트 토글 버튼 추가 - 글로벌: tenant_id가 NULL인 코드/카테고리만 표시 - 테넌트: 현재 선택된 테넌트의 데이터만 표시 - Push/Pull API에 type 파라미터 추가 Co-Authored-By: Claude Opus 4.5 --- .../Controllers/CategorySyncController.php | 124 ++++++++++-------- .../Controllers/CommonCodeSyncController.php | 109 ++++++++------- resources/views/categories/sync.blade.php | 42 +++++- resources/views/common-codes/sync.blade.php | 42 +++++- 4 files changed, 198 insertions(+), 119 deletions(-) diff --git a/app/Http/Controllers/CategorySyncController.php b/app/Http/Controllers/CategorySyncController.php index fc05cd42..16eab094 100644 --- a/app/Http/Controllers/CategorySyncController.php +++ b/app/Http/Controllers/CategorySyncController.php @@ -50,9 +50,10 @@ public function index(Request $request): View|Response $environments = $this->getEnvironments(); $selectedEnv = $request->get('env', 'dev'); + $selectedType = $request->get('type', 'global'); // global or tenant - // 로컬 카테고리 조회 - $localCategories = $this->getCategoryList(); + // 로컬 카테고리 조회 (타입 필터 적용) + $localCategories = $this->getCategoryList($selectedType); // 원격 카테고리 조회 $remoteCategories = []; @@ -60,7 +61,7 @@ public function index(Request $request): View|Response if (! empty($environments[$selectedEnv]['url'])) { try { - $remoteCategories = $this->fetchRemoteCategories($environments[$selectedEnv]); + $remoteCategories = $this->fetchRemoteCategories($environments[$selectedEnv], $selectedType); } catch (\Exception $e) { $remoteError = $e->getMessage(); } @@ -72,6 +73,7 @@ public function index(Request $request): View|Response return view('categories.sync', [ 'environments' => $environments, 'selectedEnv' => $selectedEnv, + 'selectedType' => $selectedType, 'localCategories' => $localCategories, 'remoteCategories' => $remoteCategories, 'remoteError' => $remoteError, @@ -92,7 +94,8 @@ public function export(Request $request): JsonResponse return response()->json(['error' => 'Unauthorized'], 401); } - $categories = $this->getCategoryList(); + $type = $request->get('type', 'all'); // global, tenant, or all + $categories = $this->getCategoryList($type); return response()->json([ 'success' => true, @@ -216,6 +219,7 @@ public function push(Request $request): JsonResponse { $validated = $request->validate([ 'env' => 'required|string|in:dev,prod', + 'type' => 'required|string|in:global,tenant', 'category_keys' => 'required|array|min:1', 'category_keys.*' => 'string', ]); @@ -227,8 +231,8 @@ public function push(Request $request): JsonResponse return response()->json(['error' => '환경 설정이 없습니다.'], 400); } - // 선택된 카테고리 조회 - $localCategories = $this->getCategoryList(); + // 선택된 카테고리 조회 (타입 필터 적용) + $localCategories = $this->getCategoryList($validated['type']); $selectedCategories = array_filter($localCategories, function ($cat) use ($validated) { $key = $this->makeCategoryKey($cat); return in_array($key, $validated['category_keys']); @@ -271,6 +275,7 @@ public function pull(Request $request): JsonResponse { $validated = $request->validate([ 'env' => 'required|string|in:dev,prod', + 'type' => 'required|string|in:global,tenant', 'category_keys' => 'required|array|min:1', 'category_keys.*' => 'string', ]); @@ -282,9 +287,9 @@ public function pull(Request $request): JsonResponse return response()->json(['error' => '환경 설정이 없습니다.'], 400); } - // 원격 카테고리 조회 + // 원격 카테고리 조회 (타입 필터 적용) try { - $remoteCategories = $this->fetchRemoteCategories($env); + $remoteCategories = $this->fetchRemoteCategories($env, $validated['type']); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } @@ -378,65 +383,70 @@ public function pull(Request $request): JsonResponse } /** - * 카테고리 목록 조회 (글로벌 + 테넌트) + * 카테고리 목록 조회 + * @param string $type 'global', 'tenant', or 'all' */ - private function getCategoryList(): array + private function getCategoryList(string $type = 'all'): array { $tenantId = $this->getTenantId(); $categories = []; // 글로벌 카테고리 - $globalCategories = GlobalCategory::whereNull('deleted_at') - ->orderBy('code_group') - ->orderBy('sort_order') - ->get(); + if ($type === 'global' || $type === 'all') { + $globalCategories = GlobalCategory::whereNull('deleted_at') + ->orderBy('code_group') + ->orderBy('sort_order') + ->get(); - foreach ($globalCategories as $cat) { - $parentCode = null; - if ($cat->parent_id) { - $parent = GlobalCategory::find($cat->parent_id); - $parentCode = $parent?->code; + foreach ($globalCategories as $cat) { + $parentCode = null; + if ($cat->parent_id) { + $parent = GlobalCategory::find($cat->parent_id); + $parentCode = $parent?->code; + } + + $categories[] = [ + 'is_global' => true, + 'tenant_id' => null, + 'code_group' => $cat->code_group, + 'code' => $cat->code, + 'name' => $cat->name, + 'parent_code' => $parentCode, + 'sort_order' => $cat->sort_order, + 'description' => $cat->description, + 'is_active' => $cat->is_active, + ]; } - - $categories[] = [ - 'is_global' => true, - 'tenant_id' => null, - 'code_group' => $cat->code_group, - 'code' => $cat->code, - 'name' => $cat->name, - 'parent_code' => $parentCode, - 'sort_order' => $cat->sort_order, - 'description' => $cat->description, - 'is_active' => $cat->is_active, - ]; } // 테넌트 카테고리 - $tenantCategories = Category::withoutGlobalScopes() - ->where('tenant_id', $tenantId) - ->whereNull('deleted_at') - ->orderBy('code_group') - ->orderBy('sort_order') - ->get(); + if ($type === 'tenant' || $type === 'all') { + $tenantCategories = Category::withoutGlobalScopes() + ->where('tenant_id', $tenantId) + ->whereNull('deleted_at') + ->orderBy('code_group') + ->orderBy('sort_order') + ->get(); - foreach ($tenantCategories as $cat) { - $parentCode = null; - if ($cat->parent_id) { - $parent = Category::withoutGlobalScopes()->find($cat->parent_id); - $parentCode = $parent?->code; + foreach ($tenantCategories as $cat) { + $parentCode = null; + if ($cat->parent_id) { + $parent = Category::withoutGlobalScopes()->find($cat->parent_id); + $parentCode = $parent?->code; + } + + $categories[] = [ + 'is_global' => false, + 'tenant_id' => $cat->tenant_id, + 'code_group' => $cat->code_group, + 'code' => $cat->code, + 'name' => $cat->name, + 'parent_code' => $parentCode, + 'sort_order' => $cat->sort_order, + 'description' => $cat->description, + 'is_active' => $cat->is_active, + ]; } - - $categories[] = [ - 'is_global' => false, - 'tenant_id' => $cat->tenant_id, - 'code_group' => $cat->code_group, - 'code' => $cat->code, - 'name' => $cat->name, - 'parent_code' => $parentCode, - 'sort_order' => $cat->sort_order, - 'description' => $cat->description, - 'is_active' => $cat->is_active, - ]; } return $categories; @@ -445,12 +455,14 @@ private function getCategoryList(): array /** * 원격 카테고리 조회 */ - private function fetchRemoteCategories(array $env): array + private function fetchRemoteCategories(array $env, string $type = 'all'): array { $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->timeout(10)->get(rtrim($env['url'], '/') . '/category-sync/export'); + ])->timeout(10)->get(rtrim($env['url'], '/') . '/category-sync/export', [ + 'type' => $type, + ]); if (! $response->successful()) { throw new \Exception('API 오류: HTTP ' . $response->status()); diff --git a/app/Http/Controllers/CommonCodeSyncController.php b/app/Http/Controllers/CommonCodeSyncController.php index ba1b8f92..3623bade 100644 --- a/app/Http/Controllers/CommonCodeSyncController.php +++ b/app/Http/Controllers/CommonCodeSyncController.php @@ -49,9 +49,10 @@ public function index(Request $request): View|Response $environments = $this->getEnvironments(); $selectedEnv = $request->get('env', 'dev'); + $selectedType = $request->get('type', 'global'); // global or tenant - // 로컬 코드 조회 - $localCodes = $this->getCodeList(); + // 로컬 코드 조회 (타입 필터 적용) + $localCodes = $this->getCodeList($selectedType); // 원격 코드 조회 $remoteCodes = []; @@ -59,7 +60,7 @@ public function index(Request $request): View|Response if (! empty($environments[$selectedEnv]['url'])) { try { - $remoteCodes = $this->fetchRemoteCodes($environments[$selectedEnv]); + $remoteCodes = $this->fetchRemoteCodes($environments[$selectedEnv], $selectedType); } catch (\Exception $e) { $remoteError = $e->getMessage(); } @@ -71,6 +72,7 @@ public function index(Request $request): View|Response return view('common-codes.sync', [ 'environments' => $environments, 'selectedEnv' => $selectedEnv, + 'selectedType' => $selectedType, 'localCodes' => $localCodes, 'remoteCodes' => $remoteCodes, 'remoteError' => $remoteError, @@ -91,7 +93,8 @@ public function export(Request $request): JsonResponse return response()->json(['error' => 'Unauthorized'], 401); } - $codes = $this->getCodeList(); + $type = $request->get('type', 'all'); // global, tenant, or all + $codes = $this->getCodeList($type); return response()->json([ 'success' => true, @@ -168,6 +171,7 @@ public function push(Request $request): JsonResponse { $validated = $request->validate([ 'env' => 'required|string|in:dev,prod', + 'type' => 'required|string|in:global,tenant', 'code_keys' => 'required|array|min:1', 'code_keys.*' => 'string', ]); @@ -179,8 +183,8 @@ public function push(Request $request): JsonResponse return response()->json(['error' => '환경 설정이 없습니다.'], 400); } - // 선택된 코드 조회 - $localCodes = $this->getCodeList(); + // 선택된 코드 조회 (타입 필터 적용) + $localCodes = $this->getCodeList($validated['type']); $selectedCodes = array_filter($localCodes, function ($code) use ($validated) { $key = $this->makeCodeKey($code); return in_array($key, $validated['code_keys']); @@ -223,6 +227,7 @@ public function pull(Request $request): JsonResponse { $validated = $request->validate([ 'env' => 'required|string|in:dev,prod', + 'type' => 'required|string|in:global,tenant', 'code_keys' => 'required|array|min:1', 'code_keys.*' => 'string', ]); @@ -234,9 +239,9 @@ public function pull(Request $request): JsonResponse return response()->json(['error' => '환경 설정이 없습니다.'], 400); } - // 원격 코드 조회 + // 원격 코드 조회 (타입 필터 적용) try { - $remoteCodes = $this->fetchRemoteCodes($env); + $remoteCodes = $this->fetchRemoteCodes($env, $validated['type']); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } @@ -289,52 +294,56 @@ public function pull(Request $request): JsonResponse } /** - * 코드 목록 조회 (글로벌 + 테넌트) + * 코드 목록 조회 + * @param string $type 'global', 'tenant', or 'all' */ - private function getCodeList(): array + private function getCodeList(string $type = 'all'): array { $tenantId = $this->getTenantId(); - - // 글로벌 코드 (tenant_id IS NULL) - $globalCodes = CommonCode::query() - ->whereNull('tenant_id') - ->orderBy('code_group') - ->orderBy('sort_order') - ->get(); - - // 테넌트 코드 - $tenantCodes = CommonCode::query() - ->where('tenant_id', $tenantId) - ->orderBy('code_group') - ->orderBy('sort_order') - ->get(); - $codes = []; - foreach ($globalCodes as $code) { - $codes[] = [ - 'tenant_id' => null, - 'code_group' => $code->code_group, - 'code' => $code->code, - 'name' => $code->name, - 'sort_order' => $code->sort_order, - 'attributes' => $code->attributes, - 'is_active' => $code->is_active, - 'is_global' => true, - ]; + // 글로벌 코드 (tenant_id IS NULL) + if ($type === 'global' || $type === 'all') { + $globalCodes = CommonCode::query() + ->whereNull('tenant_id') + ->orderBy('code_group') + ->orderBy('sort_order') + ->get(); + + foreach ($globalCodes as $code) { + $codes[] = [ + 'tenant_id' => null, + 'code_group' => $code->code_group, + 'code' => $code->code, + 'name' => $code->name, + 'sort_order' => $code->sort_order, + 'attributes' => $code->attributes, + 'is_active' => $code->is_active, + 'is_global' => true, + ]; + } } - foreach ($tenantCodes as $code) { - $codes[] = [ - 'tenant_id' => $code->tenant_id, - 'code_group' => $code->code_group, - 'code' => $code->code, - 'name' => $code->name, - 'sort_order' => $code->sort_order, - 'attributes' => $code->attributes, - 'is_active' => $code->is_active, - 'is_global' => false, - ]; + // 테넌트 코드 + if ($type === 'tenant' || $type === 'all') { + $tenantCodes = CommonCode::query() + ->where('tenant_id', $tenantId) + ->orderBy('code_group') + ->orderBy('sort_order') + ->get(); + + foreach ($tenantCodes as $code) { + $codes[] = [ + 'tenant_id' => $code->tenant_id, + 'code_group' => $code->code_group, + 'code' => $code->code, + 'name' => $code->name, + 'sort_order' => $code->sort_order, + 'attributes' => $code->attributes, + 'is_active' => $code->is_active, + 'is_global' => false, + ]; + } } return $codes; @@ -343,12 +352,14 @@ private function getCodeList(): array /** * 원격 코드 조회 */ - private function fetchRemoteCodes(array $env): array + private function fetchRemoteCodes(array $env, string $type = 'all'): array { $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->timeout(10)->get(rtrim($env['url'], '/') . '/common-code-sync/export'); + ])->timeout(10)->get(rtrim($env['url'], '/') . '/common-code-sync/export', [ + 'type' => $type, + ]); if (! $response->successful()) { throw new \Exception('API 오류: HTTP ' . $response->status()); diff --git a/resources/views/categories/sync.blade.php b/resources/views/categories/sync.blade.php index ab1415a9..cf906050 100644 --- a/resources/views/categories/sync.blade.php +++ b/resources/views/categories/sync.blade.php @@ -29,12 +29,37 @@ class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg - +
-
- +
-