code ?? '') === '' && is_string($role->slug) && $role->slug !== '') { $role->code = $role->slug; } }); } protected $fillable = [ 'slug', 'name', 'code', 'description', 'status', 'is_system', 'sort_order', 'owner_agent_id', 'delegated_from_role_id', 'scope_type', ]; protected function casts(): array { return [ 'owner_agent_id' => 'integer', 'delegated_from_role_id' => 'integer', 'status' => 'integer', 'is_system' => 'boolean', 'sort_order' => 'integer', ]; } public function isAgentScoped(): bool { return $this->scope_type === self::SCOPE_AGENT && $this->owner_agent_id !== null; } public function isReadOnlyTemplate(): bool { return $this->delegated_from_role_id !== null; } /** * @return BelongsToMany */ public function menuActions(): BelongsToMany { return $this->belongsToMany( AdminMenuAction::class, 'admin_role_menu_actions', 'role_id', 'menu_action_id', ); } /** @return BelongsToMany */ public function users(): BelongsToMany { return $this->belongsToMany( AdminUser::class, 'admin_user_site_roles', 'role_id', 'admin_user_id', )->withPivot(['site_id', 'granted_at']); } /** * 由已授权的 menu_action 反推 `prd.*`(与 Registry 映射一致)。 * * @return list */ public function legacyPermissionSlugs(): array { $codes = DB::table('admin_role_menu_actions as rma') ->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id') ->where('rma.role_id', $this->id) ->where('ma.status', 1) ->pluck('ma.permission_code') ->all(); return AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes($codes); } /** 授予当前库中全部启用的 menu_action(用于超级管理员)。 */ public function syncAllActiveMenuActions(): void { $ids = DB::table('admin_menu_actions') ->where('status', 1) ->pluck('id') ->map(static fn ($id): int => (int) $id) ->all(); DB::table('admin_role_menu_actions')->where('role_id', $this->id)->delete(); foreach ($ids as $mid) { DB::table('admin_role_menu_actions')->insert([ 'role_id' => $this->id, 'menu_action_id' => $mid, ]); } } /** * @param list $slugs */ public function syncLegacyPermissionSlugs(array $slugs): void { $legacySlugs = AdminPermissionInheritance::expand( AdminPermissionBridge::normalizeCanonicalLegacySlugs($slugs), ); $codes = []; foreach ($legacySlugs as $slug) { $codes = array_merge($codes, AdminPermissionBridge::menuActionCodesForLegacy($slug)); } $codes = array_values(array_unique($codes)); $ids = DB::table('admin_menu_actions') ->whereIn('permission_code', $codes) ->where('status', 1) ->pluck('id') ->all(); DB::table('admin_role_menu_actions')->where('role_id', $this->id)->delete(); foreach ($ids as $mid) { DB::table('admin_role_menu_actions')->insert([ 'role_id' => $this->id, 'menu_action_id' => (int) $mid, ]); } $granted = $this->legacyPermissionSlugs(); $missing = array_values(array_diff($legacySlugs, $granted)); if ($missing !== []) { throw ValidationException::withMessages([ 'permission_slugs' => [ 'permission_catalog_incomplete: '.implode(', ', $missing) .' (run: php artisan migrate && php artisan lottery:admin-auth-sync --audit)', ], ]); } } public function assignedUserCount(): int { $agentCount = (int) DB::table('admin_user_agent_roles') ->where('role_id', $this->id) ->distinct() ->count('admin_user_id'); if ($this->isAgentScoped()) { return $agentCount; } return (int) DB::table('admin_user_site_roles') ->where('role_id', $this->id) ->distinct() ->count('admin_user_id'); } }