id ?? null) : ($user['id'] ?? null); } /** * 메뉴 목록 조회 */ public static function index(array $params) { $tenantId = self::tenantId(); $q = Menu::query()->withShared($tenantId); if (array_key_exists('parent_id', $params)) { $q->where('parent_id', $params['parent_id']); } if (array_key_exists('is_active', $params)) { $q->where('is_active', (int) $params['is_active']); } if (array_key_exists('hidden', $params)) { $q->where('hidden', (int) $params['hidden']); } $q->orderBy('parent_id')->orderBy('sort_order'); // Builder 그대로 전달해야 쿼리로그/표준응답 형식 유지 return $q->get(); } /** * 메뉴 단건 조회 */ public static function show(array $params) { $id = (int) ($params['id'] ?? 0); $tenantId = self::tenantId(); if (! $id) { return ['error' => 'id가 필요합니다.', 'code' => 400]; } $res = Menu::withShared($tenantId)->find($id); if (empty($res['data'])) { return ['error' => 'Menu not found', 'code' => 404]; } return $res; } /** * 메뉴 생성 */ public static function store(array $params) { $tenantId = self::tenantId(); $userId = self::actorId(); $v = Validator::make($params, [ 'parent_id' => ['nullable', 'integer'], 'name' => ['required', 'string', 'max:100'], 'url' => ['nullable', 'string', 'max:255'], 'is_active' => ['nullable', 'boolean'], 'sort_order' => ['nullable', 'integer'], 'hidden' => ['nullable', 'boolean'], 'is_external' => ['nullable', 'boolean'], 'external_url' => ['nullable', 'string', 'max:255'], 'icon' => ['nullable', 'string', 'max:50'], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } $data = $v->validated(); $menu = new Menu; $menu->tenant_id = $tenantId; $menu->parent_id = $data['parent_id'] ?? null; $menu->name = $data['name']; $menu->url = $data['url'] ?? null; $menu->is_active = (int) ($data['is_active'] ?? 1); $menu->sort_order = (int) ($data['sort_order'] ?? 0); $menu->hidden = (int) ($data['hidden'] ?? 0); $menu->is_external = (int) ($data['is_external'] ?? 0); $menu->external_url = $data['external_url'] ?? null; $menu->icon = $data['icon'] ?? null; $menu->created_by = $userId; $menu->updated_by = $userId; $menu->save(); // 생성 결과를 그대로 전달 return $menu->fresh(); } /** * 메뉴 수정 * - global_menu_id가 있는 테넌트 메뉴 수정 시 is_customized = true 자동 설정 */ public static function update(array $params) { $id = (int) ($params['id'] ?? 0); $tenantId = self::tenantId(); $userId = self::actorId(); if (! $id) { return ['error' => 'id가 필요합니다.', 'code' => 400]; } $v = Validator::make($params, [ 'parent_id' => ['nullable', 'integer'], 'name' => ['nullable', 'string', 'max:100'], 'url' => ['nullable', 'string', 'max:255'], 'is_active' => ['nullable', 'boolean'], 'sort_order' => ['nullable', 'integer'], 'hidden' => ['nullable', 'boolean'], 'is_external' => ['nullable', 'boolean'], 'external_url' => ['nullable', 'string', 'max:255'], 'icon' => ['nullable', 'string', 'max:50'], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } $data = $v->validated(); $menu = Menu::withShared($tenantId)->where('id', $id)->first(); if (! $menu) { return ['error' => 'Menu not found', 'code' => 404]; } $update = Arr::only($data, [ 'parent_id', 'name', 'url', 'is_active', 'sort_order', 'hidden', 'is_external', 'external_url', 'icon', ]); $update = array_filter($update, fn ($v) => ! is_null($v)); if (empty($update)) { return ['error' => '수정할 데이터가 없습니다.', 'code' => 400]; } // 글로벌 메뉴에서 복제된 테넌트 메뉴 수정 시 커스터마이징 플래그 설정 if ($menu->global_menu_id && ! $menu->is_customized) { $update['is_customized'] = true; } $update['updated_by'] = $userId; $menu->fill($update)->save(); return $menu->fresh(); } /** * 메뉴 삭제(소프트) */ public static function destroy(array $params) { $id = (int) ($params['id'] ?? 0); $tenantId = self::tenantId(); $userId = self::actorId(); if (! $id) { return ['error' => 'id가 필요합니다.', 'code' => 400]; } $menu = Menu::withShared($tenantId)->where('id', $id)->first(); if (! $menu) { return ['error' => 'Menu not found', 'code' => 404]; } $menu->deleted_by = $userId; $menu->save(); $menu->delete(); return 'success'; } /** * 정렬 일괄 변경 * $params = [ ['id'=>10, 'sort_order'=>1], ... ] */ public static function reorder(array $params) { if (! is_array($params) || empty($params)) { return ['error' => '유효한 정렬 목록이 필요합니다.', 'code' => 422]; } $tenantId = self::tenantId(); DB::transaction(function () use ($params, $tenantId) { foreach ($params as $it) { if (! isset($it['id'], $it['sort_order'])) { continue; } $menu = Menu::withShared($tenantId)->find((int) $it['id']); if ($menu) { $menu->sort_order = (int) $it['sort_order']; $menu->save(); } } }); return 'success'; } /** * 상태 토글: is_active / hidden / is_external */ public static function toggle(array $params) { $id = (int) ($params['id'] ?? 0); $tenantId = self::tenantId(); $userId = self::actorId(); if (! $id) { return ['error' => 'id가 필요합니다.', 'code' => 400]; } $payload = array_filter([ 'is_active' => array_key_exists('is_active', $params) ? (int) $params['is_active'] : null, 'hidden' => array_key_exists('hidden', $params) ? (int) $params['hidden'] : null, 'is_external' => array_key_exists('is_external', $params) ? (int) $params['is_external'] : null, ], fn ($v) => ! is_null($v)); if (empty($payload)) { return ['error' => '변경할 필드가 없습니다.', 'code' => 422]; } $menu = Menu::withShared($tenantId)->find($id); if (! $menu) { return ['error' => 'Menu not found', 'code' => 404]; } $payload['updated_by'] = $userId; $menu->fill($payload)->save(); return $menu->fresh(); } /** * 삭제된 메뉴 복원 */ public static function restore(array $params) { $id = (int) ($params['id'] ?? 0); $tenantId = self::tenantId(); $userId = self::actorId(); if (! $id) { return ['error' => 'id가 필요합니다.', 'code' => 400]; } // 삭제된 메뉴 포함하여 조회 $menu = Menu::withTrashed() ->withoutGlobalScopes() ->where(function ($q) use ($tenantId) { $q->whereNull('tenant_id') ->orWhere('tenant_id', $tenantId); }) ->where('id', $id) ->first(); if (! $menu) { return ['error' => 'Menu not found', 'code' => 404]; } if (! $menu->trashed()) { return ['error' => '삭제되지 않은 메뉴입니다.', 'code' => 400]; } $menu->restore(); $menu->deleted_by = null; $menu->updated_by = $userId; $menu->save(); return $menu->fresh(); } /** * 삭제된 메뉴 목록 조회 */ public static function trashedList(array $params = []) { $tenantId = self::tenantId(); return Menu::onlyTrashed() ->withoutGlobalScopes() ->where(function ($q) use ($tenantId) { $q->whereNull('tenant_id') ->orWhere('tenant_id', $tenantId); }) ->orderBy('deleted_at', 'desc') ->get(); } }