'boolean', 'hidden' => 'boolean', 'is_external' => 'boolean', 'is_customized' => 'boolean', ]; /** * 동기화 비교 대상 필드 */ public static function getSyncFields(): array { return ['name', 'url', 'icon', 'sort_order', 'is_active', 'hidden', 'is_external', 'external_url']; } public function parent() { return $this->belongsTo(Menu::class, 'parent_id'); } public function children() { return $this->hasMany(Menu::class, 'parent_id'); } public function tenant() { return $this->belongsTo(\App\Models\Tenants\Tenant::class, 'tenant_id'); } /** * 원본 글로벌 메뉴 (복제된 메뉴인 경우) */ public function globalMenu() { return $this->belongsTo(GlobalMenu::class, 'global_menu_id'); } /** * 글로벌 메뉴에서 복제된 메뉴인지 확인 */ public function isClonedFromGlobal(): bool { return ! is_null($this->global_menu_id); } /** * 테넌트가 커스터마이징 했는지 확인 */ public function isCustomized(): bool { return (bool) $this->is_customized; } /** * 글로벌 메뉴 스코프 (tenant_id가 NULL인 것) */ public function scopeGlobal($query) { return $query->withoutGlobalScope(TenantScope::class) ->whereNull('tenant_id'); } /** * 활성 메뉴 스코프 */ public function scopeActive($query) { return $query->where('is_active', true); } /** * 표시되는 메뉴 스코프 (hidden=false) */ public function scopeVisible($query) { return $query->where('hidden', false); } /** * 최상위 메뉴 스코프 (parent_id가 NULL인 것) */ public function scopeRoots($query) { return $query->whereNull('parent_id'); } /** * 공유(NULL) + 현재 테넌트 모두 포함해서 조회 * (SoftDeletes 글로벌 스코프는 그대로 유지) */ public function scopeWithShared($query, ?int $tenantId = null) { $tenantId = $tenantId ?? app('tenant_id'); return $query ->withoutGlobalScope(TenantScope::class) ->where(function ($w) use ($tenantId) { if (is_null($tenantId)) { $w->whereNull('tenant_id'); } else { $w->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); } }); } }