*/ protected array $trackableFields = ['name', 'description']; /** * 다국어 JSON 컬럼 — sub-key dot-path 단위로 user_overrides 보존. * * 사용자가 ko 라벨만 수정하면 user_overrides=['name.ko'] 로 기록되어 * 언어팩 활성/시더 재실행 시 ja/en 키는 자동 동기화 가능. * * @var array */ protected array $translatableTrackableFields = ['name', 'description']; /** @var array 활동 로그 추적 필드 */ public static array $activityLogFields = [ 'identifier' => ['label_key' => 'activity_log.fields.identifier', 'type' => 'text'], 'is_active' => ['label_key' => 'activity_log.fields.is_active', 'type' => 'boolean'], ]; /** * 테이블명 * * @var string */ protected $table = 'roles'; /** * 기본키 * * @var string */ protected $primaryKey = 'id'; /** * 타임스탬프 사용 여부 * * @var bool */ public $timestamps = true; protected $fillable = [ 'identifier', 'name', 'description', 'extension_type', 'extension_identifier', 'user_overrides', 'is_active', ]; /** * 속성 캐스팅 */ protected function casts(): array { return [ 'name' => 'array', 'description' => 'array', 'extension_type' => ExtensionOwnerType::class, 'user_overrides' => 'array', 'is_active' => 'boolean', ]; } /** * 역할에 속한 사용자들과의 관계를 정의합니다. */ public function users(): BelongsToMany { return $this->belongsToMany(User::class, 'user_roles') ->withPivot(['assigned_at', 'assigned_by']) ->withTimestamps(); } /** * 역할이 가진 권한들과의 관계를 정의합니다. */ public function permissions(): BelongsToMany { return $this->belongsToMany(Permission::class, 'role_permissions') ->withPivot(['granted_at', 'granted_by', 'scope_type']) ->withTimestamps(); } /** * 역할이 접근 가능한 메뉴들과의 관계 * * @return BelongsToMany */ public function menus(): BelongsToMany { return $this->belongsToMany(Menu::class, 'role_menus') ->withPivot('permission_type') ->withTimestamps(); } /** * 역할의 메뉴 권한들과의 관계를 정의합니다. * * @deprecated role_menus 피벗 테이블 사용. menus() 메서드를 사용하세요. * @return HasMany */ public function menuPermissions(): HasMany { return $this->hasMany(MenuPermission::class); } /** * 특정 권한을 가지고 있는지 확인합니다. * * @param string $permission 권한 식별자 */ public function hasPermission(string $permission): bool { return $this->permissions()->where('identifier', $permission)->exists(); } /** * 여러 권한을 가지고 있는지 확인합니다. * * @param array $permissions 권한 식별자 배열 * @param bool $requireAll 모든 권한이 필요한지 여부 (true: AND, false: OR) */ public function hasPermissions(array $permissions, bool $requireAll = true): bool { $userPermissions = $this->permissions()->whereIn('identifier', $permissions)->count(); return $requireAll ? $userPermissions === count($permissions) : $userPermissions > 0; } /** * 특정 메뉴에 대한 권한을 가지고 있는지 확인합니다. * * @param int $menuId 메뉴 ID * @param string $permissionType 권한 유형 (read, write, delete) */ public function hasMenuPermission(int $menuId, string $permissionType = 'read'): bool { return $this->menuPermissions() ->where('menu_id', $menuId) ->where('permission_type', $permissionType) ->where('is_allowed', true) ->exists(); } /** * 지정된 로케일의 역할 이름 반환 */ public function getLocalizedName(?string $locale = null): string { $locale = $locale ?? app()->getLocale(); if (! is_array($this->name)) { return (string) $this->name; } return $this->name[$locale] ?? $this->name[config('app.fallback_locale')] ?? (! empty($this->name) ? array_values($this->name)[0] : '') ?? ''; } /** * 지정된 로케일의 역할 설명 반환 */ public function getLocalizedDescription(?string $locale = null): string { $locale = $locale ?? app()->getLocale(); if (! is_array($this->description)) { return (string) $this->description; } return $this->description[$locale] ?? $this->description[config('app.fallback_locale')] ?? (! empty($this->description) ? array_values($this->description)[0] : '') ?? ''; } /** * 현재 로케일의 역할 이름 반환 */ protected function localizedName(): Attribute { return Attribute::make( get: fn () => $this->getLocalizedName() ); } /** * 현재 로케일의 역할 설명 반환 */ protected function localizedDescription(): Attribute { return Attribute::make( get: fn () => $this->getLocalizedDescription() ); } /** * 코어 시스템에서 생성된 역할인지 확인합니다. * * @return bool */ public function isCore(): bool { return $this->extension_type === ExtensionOwnerType::Core; } /** * 삭제 가능한 역할인지 확인합니다. * * extension_type이 null(사용자 직접 생성)인 경우에만 삭제 가능합니다. * * @return bool */ public function isDeletable(): bool { return $this->extension_type === null; } /** * 확장(모듈/플러그인)이 소유한 역할인지 확인합니다. * * @return bool */ public function isExtensionOwned(): bool { return in_array($this->extension_type, [ExtensionOwnerType::Module, ExtensionOwnerType::Plugin], true); } }