- Menu 모델에 options JSON 필드 및 헬퍼 메서드 추가 - getOption(), setOption() - getRouteName(), getSection(), getMenuType() - requiresRole(), getBladeComponent(), getCssClass() - getMeta(), setMeta() - MngMenuSeeder 추가 (tenant_id=1 기본 메뉴 29개) - main 섹션: 대시보드, PM, 시스템, 권한, 콘텐츠, 시스템설정 - tools 섹션: 개발 도구 - labs 섹션: 실험실 (S/A/M 탭)
248 lines
5.8 KiB
PHP
248 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Models\Commons;
|
|
|
|
use App\Models\Scopes\TenantScope;
|
|
use App\Traits\BelongsToTenant;
|
|
use App\Traits\ModelTrait;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
/**
|
|
* @mixin IdeHelperMenu
|
|
*/
|
|
class Menu extends Model
|
|
{
|
|
use BelongsToTenant, ModelTrait, SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'tenant_id', 'parent_id', 'global_menu_id', 'name', 'url', 'is_active', 'sort_order',
|
|
'hidden', 'is_external', 'external_url', 'icon', 'is_customized', 'options',
|
|
'created_by', 'updated_by', 'deleted_by',
|
|
];
|
|
|
|
protected $hidden = [
|
|
'created_by',
|
|
'updated_by',
|
|
'deleted_by',
|
|
'deleted_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_active' => 'boolean',
|
|
'hidden' => 'boolean',
|
|
'is_external' => 'boolean',
|
|
'is_customized' => 'boolean',
|
|
'options' => 'array',
|
|
];
|
|
|
|
/**
|
|
* 동기화 비교 대상 필드
|
|
*/
|
|
public static function getSyncFields(): array
|
|
{
|
|
return ['name', 'url', 'icon', 'sort_order', 'is_active', 'hidden', 'is_external', 'external_url', 'options'];
|
|
}
|
|
|
|
// ============================================================
|
|
// Options JSON 헬퍼 메서드
|
|
// ============================================================
|
|
|
|
/**
|
|
* options에서 특정 키 값 조회
|
|
*/
|
|
public function getOption(string $key, mixed $default = null): mixed
|
|
{
|
|
return data_get($this->options, $key, $default);
|
|
}
|
|
|
|
/**
|
|
* options에 특정 키 값 설정
|
|
*/
|
|
public function setOption(string $key, mixed $value): static
|
|
{
|
|
$options = $this->options ?? [];
|
|
data_set($options, $key, $value);
|
|
$this->options = $options;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 라우트명 조회 (mng, api, react 등에서 사용)
|
|
*/
|
|
public function getRouteName(): ?string
|
|
{
|
|
return $this->getOption('route_name');
|
|
}
|
|
|
|
/**
|
|
* 메뉴 섹션 조회 (main, tools, labs 등)
|
|
*/
|
|
public function getSection(): string
|
|
{
|
|
return $this->getOption('section', 'main');
|
|
}
|
|
|
|
/**
|
|
* 메뉴 타입 조회 (normal, tool, lab 등)
|
|
*/
|
|
public function getMenuType(): string
|
|
{
|
|
return $this->getOption('menu_type', 'normal');
|
|
}
|
|
|
|
/**
|
|
* 필요 역할 조회
|
|
*/
|
|
public function getRequiresRole(): ?string
|
|
{
|
|
return $this->getOption('requires_role');
|
|
}
|
|
|
|
/**
|
|
* 특정 역할이 필요한지 확인
|
|
*/
|
|
public function requiresRole(?string $role = null): bool
|
|
{
|
|
$requiredRole = $this->getRequiresRole();
|
|
|
|
if ($requiredRole === null) {
|
|
return false;
|
|
}
|
|
|
|
if ($role === null) {
|
|
return true;
|
|
}
|
|
|
|
return $requiredRole === $role;
|
|
}
|
|
|
|
/**
|
|
* Blade 컴포넌트명 조회
|
|
*/
|
|
public function getBladeComponent(): ?string
|
|
{
|
|
return $this->getOption('blade_component');
|
|
}
|
|
|
|
/**
|
|
* CSS 클래스 조회
|
|
*/
|
|
public function getCssClass(): ?string
|
|
{
|
|
return $this->getOption('css_class');
|
|
}
|
|
|
|
/**
|
|
* meta 데이터 조회 (앱별 커스텀 데이터)
|
|
*/
|
|
public function getMeta(?string $key = null, mixed $default = null): mixed
|
|
{
|
|
if ($key === null) {
|
|
return $this->getOption('meta', []);
|
|
}
|
|
|
|
return $this->getOption("meta.{$key}", $default);
|
|
}
|
|
|
|
/**
|
|
* meta 데이터 설정
|
|
*/
|
|
public function setMeta(string $key, mixed $value): static
|
|
{
|
|
return $this->setOption("meta.{$key}", $value);
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
}
|