*/ private const BASE_AGENT_ROLE_SLUGS = [ 'prd.agent.view', 'prd.tickets.view', 'prd.report.view', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', ]; /** @var list */ private const CHILD_AGENT_MANAGE_SLUGS = ['prd.agent.manage', 'prd.agent.profile.manage']; /** @var list */ private const PLAYER_MANAGE_SLUGS = [ 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', ]; /** * @param array{ * parent_id: int, * code?: ?string, * name: string, * username: string, * password: string, * email?: ?string, * status?: int, * total_share_rate?: float|int, * credit_limit?: int, * rebate_limit?: float|int, * default_player_rebate?: float|int, * settlement_cycle?: string, * can_grant_extra_rebate?: bool, * can_create_child_agent?: bool, * can_create_player?: bool * } $payload */ public function createChild(AdminUser $actor, array $payload): AgentNode { if (! $actor->isSuperAdmin()) { $this->agentProfileService->assertActorMayCreateChildAgent($actor); } $parent = AgentNode::query()->findOrFail((int) $payload['parent_id']); $this->agentProfileService->assertChildCapabilityGrantsWithinParent($parent, $payload, $actor); $name = trim((string) $payload['name']); $code = $this->resolveCodeForCreate($parent, $payload['code'] ?? null, (string) ($payload['username'] ?? '')); $username = trim((string) ($payload['username'] ?? '')); if ($username === '') { $username = 'agent_'.$code; } $password = (string) ($payload['password'] ?? ''); if ($password === '') { if (app()->environment('testing')) { $password = 'TestPass1!'; } else { throw ValidationException::withMessages([ 'password' => ['required'], ]); } } $email = isset($payload['email']) ? trim((string) $payload['email']) : null; $status = (int) ($payload['status'] ?? 1); if ($name === '') { throw ValidationException::withMessages([ 'name' => ['required'], ]); } if (AgentNode::query()->where('admin_site_id', $parent->admin_site_id)->where('code', $code)->exists()) { throw ValidationException::withMessages([ 'code' => ['unique'], ]); } if (AdminUser::query()->where('username', $username)->exists()) { throw ValidationException::withMessages([ 'username' => ['unique'], ]); } if ($email !== null && $email !== '' && AdminUser::query()->where('email', $email)->exists()) { throw ValidationException::withMessages([ 'email' => ['unique'], ]); } return DB::transaction(function () use ($actor, $parent, $code, $name, $username, $password, $email, $status, $payload): AgentNode { $node = AgentNode::query()->create([ 'admin_site_id' => $parent->admin_site_id, 'parent_id' => $parent->id, 'path' => '/', 'depth' => (int) $parent->depth + 1, 'code' => $code, 'name' => $name, 'status' => $status === 0 ? 0 : 1, 'created_by' => $actor->id, 'extra_json' => null, ]); $node->path = (string) $parent->path.$node->id.'/'; $node->save(); $role = AdminRole::query()->create([ 'slug' => 'agent_owner_'.$node->id, 'code' => 'agent_owner_'.$node->id, 'name' => '代理账号', 'description' => '系统自动生成的一代理一账号默认角色', 'status' => $status === 0 ? 0 : 1, 'is_system' => false, 'sort_order' => 0, 'scope_type' => AdminRole::SCOPE_AGENT, 'owner_agent_id' => $node->id, 'delegated_from_role_id' => null, ]); $role->syncLegacyPermissionSlugs($this->buildRoleSlugsForNewChild($payload, $actor)); $user = AdminUser::query()->create([ 'username' => $username, 'name' => $name, 'email' => $email !== '' ? $email : null, 'password' => $password, 'status' => AdminUserStatus::fromAgentNodeStatus($status === 0 ? 0 : 1), ]); DB::table('admin_user_agents')->insert([ 'admin_user_id' => $user->id, 'agent_node_id' => $node->id, 'is_primary' => true, 'granted_at' => now(), ]); $user->syncAgentRoleIds((int) $node->id, [(int) $role->id]); $profile = $this->agentProfileService->upsertForNode($node, [ 'total_share_rate' => (float) ($payload['total_share_rate'] ?? 0), 'credit_limit' => (int) ($payload['credit_limit'] ?? 0), 'rebate_limit' => (float) ($payload['rebate_limit'] ?? 0), 'default_player_rebate' => (float) ($payload['default_player_rebate'] ?? 0), 'settlement_cycle' => (string) ($payload['settlement_cycle'] ?? 'weekly'), 'can_grant_extra_rebate' => (bool) ($payload['can_grant_extra_rebate'] ?? false), 'can_create_child_agent' => (bool) ($payload['can_create_child_agent'] ?? false), 'can_create_player' => (bool) ($payload['can_create_player'] ?? true), ], $parent); $this->syncPrimaryOwnerRoleFromProfile($node, $profile); return $node->fresh(['adminSite']); }); } /** * @param array{ * name?: string, * username?: string, * email?: ?string, * password?: ?string, * status?: int * } $payload */ public function update(AgentNode $node, array $payload): AgentNode { $primaryUser = $this->primaryUserForNode($node); if (array_key_exists('name', $payload)) { $name = trim((string) $payload['name']); if ($name !== '') { $node->name = $name; if ($primaryUser instanceof AdminUser) { $primaryUser->name = $name; } } } if (array_key_exists('username', $payload)) { $username = trim((string) $payload['username']); if ($username === '') { throw ValidationException::withMessages([ 'username' => ['required'], ]); } if ($primaryUser instanceof AdminUser) { if ($username !== $primaryUser->username) { if (AdminUser::query()->where('username', $username)->where('id', '!=', $primaryUser->id)->exists()) { throw ValidationException::withMessages(['username' => ['unique']]); } $primaryUser->username = $username; } } else { $password = (string) ($payload['password'] ?? ''); if ($password === '') { if (app()->environment('testing')) { $password = 'TestPass1!'; } else { throw ValidationException::withMessages([ 'password' => ['required'], ]); } } $email = array_key_exists('email', $payload) ? ($payload['email'] !== null ? trim((string) $payload['email']) : null) : null; $status = array_key_exists('status', $payload) ? ((int) $payload['status'] === 0 ? 0 : 1) : ((int) $node->status === 0 ? 0 : 1); $primaryUser = $this->provisionPrimaryOwnerAccount( $node, $username, $password, $email, $status, ); if ($node->name !== '') { $primaryUser->name = $node->name; } } } if (array_key_exists('email', $payload) && $primaryUser instanceof AdminUser) { $email = $payload['email'] !== null ? trim((string) $payload['email']) : null; if ($email !== null && $email !== '' && AdminUser::query()->where('email', $email)->where('id', '!=', $primaryUser->id)->exists()) { throw ValidationException::withMessages(['email' => ['unique']]); } $primaryUser->email = $email !== '' ? $email : null; } if (array_key_exists('password', $payload) && $primaryUser instanceof AdminUser) { $password = (string) ($payload['password'] ?? ''); if ($password !== '') { $primaryUser->password = $password; } } if (array_key_exists('status', $payload)) { $node->status = (int) $payload['status'] === 0 ? 0 : 1; if ($primaryUser instanceof AdminUser) { $primaryUser->status = AdminUserStatus::fromAgentNodeStatus($node->status); } AdminRole::query() ->where('owner_agent_id', $node->id) ->update(['status' => $node->status]); } $node->save(); if ($primaryUser instanceof AdminUser) { $primaryUser->save(); } return $node->fresh(['adminSite']); } public function destroy(AgentNode $node): void { DB::transaction(static function () use ($node): void { $userIds = DB::table('admin_user_agents') ->where('agent_node_id', $node->id) ->pluck('admin_user_id') ->map(static fn ($id): int => (int) $id) ->all(); if ($userIds !== []) { DB::table('admin_user_agent_roles') ->where('agent_node_id', $node->id) ->whereIn('admin_user_id', $userIds) ->delete(); DB::table('admin_user_agents') ->where('agent_node_id', $node->id) ->whereIn('admin_user_id', $userIds) ->delete(); DB::table('admin_user_site_roles') ->whereIn('admin_user_id', $userIds) ->where('site_id', $node->admin_site_id) ->delete(); AdminUser::query()->whereIn('id', $userIds)->delete(); } DB::table('admin_role_menu_actions') ->whereIn( 'role_id', AdminRole::query()->where('owner_agent_id', $node->id)->pluck('id')->all(), ) ->delete(); AdminRole::query() ->where('owner_agent_id', $node->id) ->delete(); $node->delete(); }); } public function hasBlockingCustomRoles(AgentNode $node): bool { return AdminRole::query() ->where('owner_agent_id', $node->id) ->whereNull('delegated_from_role_id') ->exists(); } private function primaryUserForNode(AgentNode $node): ?AdminUser { $userId = DB::table('admin_user_agents') ->where('agent_node_id', $node->id) ->orderByDesc('is_primary') ->orderBy('admin_user_id') ->value('admin_user_id'); if ($userId === null) { return null; } return AdminUser::query()->find((int) $userId); } private function provisionPrimaryOwnerAccount( AgentNode $node, string $username, string $password, ?string $email, int $status, ): AdminUser { if (AdminUser::query()->where('username', $username)->exists()) { throw ValidationException::withMessages(['username' => ['unique']]); } if ($email !== null && $email !== '' && AdminUser::query()->where('email', $email)->exists()) { throw ValidationException::withMessages(['email' => ['unique']]); } return DB::transaction(function () use ($node, $username, $password, $email, $status): AdminUser { $role = AdminRole::query() ->where('owner_agent_id', $node->id) ->where('slug', 'agent_owner_'.$node->id) ->first(); if ($role === null) { $role = AdminRole::query()->create([ 'slug' => 'agent_owner_'.$node->id, 'code' => 'agent_owner_'.$node->id, 'name' => '代理账号', 'description' => '系统自动生成的一代理一账号默认角色', 'status' => $status === 0 ? 0 : 1, 'is_system' => false, 'sort_order' => 0, 'scope_type' => AdminRole::SCOPE_AGENT, 'owner_agent_id' => $node->id, 'delegated_from_role_id' => null, ]); } $profile = AgentProfile::query()->where('agent_node_id', $node->id)->first(); $role->syncLegacyPermissionSlugs( $profile !== null ? $this->roleSlugsFromProfile($profile) : $this->defaultOwnerRoleSlugs(), ); $user = AdminUser::query()->create([ 'username' => $username, 'name' => $node->name !== '' ? $node->name : $username, 'email' => $email !== null && $email !== '' ? $email : null, 'password' => $password, 'status' => AdminUserStatus::fromAgentNodeStatus($status), ]); DB::table('admin_user_agents')->insert([ 'admin_user_id' => $user->id, 'agent_node_id' => $node->id, 'is_primary' => true, 'granted_at' => now(), ]); $user->syncAgentRoleIds((int) $node->id, [(int) $role->id]); return $user; }); } /** * @return list */ private function defaultOwnerRoleSlugs(): array { return array_values(array_unique(array_merge( self::BASE_AGENT_ROLE_SLUGS, self::PLAYER_MANAGE_SLUGS, ))); } public function syncPrimaryOwnerRoleFromProfile(AgentNode $node, ?AgentProfile $profile = null): void { $profile ??= AgentProfile::query()->where('agent_node_id', $node->id)->first(); if ($profile === null) { return; } $role = AdminRole::query() ->where('owner_agent_id', $node->id) ->where('slug', 'agent_owner_'.$node->id) ->first(); if ($role === null) { return; } $role->syncLegacyPermissionSlugs($this->roleSlugsFromProfile($profile)); } /** * @param array $payload * @return list */ private function buildRoleSlugsForNewChild(array $payload, AdminUser $actor): array { $slugs = self::BASE_AGENT_ROLE_SLUGS; if ((bool) ($payload['can_create_child_agent'] ?? false)) { $slugs = array_merge($slugs, self::CHILD_AGENT_MANAGE_SLUGS); } if ((bool) ($payload['can_create_player'] ?? true)) { $slugs = array_merge($slugs, self::PLAYER_MANAGE_SLUGS); } return $this->filterSlugsByActor($actor, $slugs); } /** * @return list */ private function roleSlugsFromProfile(AgentProfile $profile): array { $slugs = self::BASE_AGENT_ROLE_SLUGS; if ($profile->can_create_child_agent) { $slugs = array_merge($slugs, self::CHILD_AGENT_MANAGE_SLUGS); } if ($profile->can_create_player) { $slugs = array_merge($slugs, self::PLAYER_MANAGE_SLUGS); } return array_values(array_unique($slugs)); } /** * @param list $slugs * @return list */ private function filterSlugsByActor(AdminUser $actor, array $slugs): array { if ($actor->isSuperAdmin()) { return array_values(array_unique($slugs)); } $mine = array_fill_keys($actor->adminPermissionSlugs(), true); return array_values(array_filter( $slugs, static fn (string $slug): bool => isset($mine[$slug]), )); } private function resolveCodeForCreate(AgentNode $parent, mixed $rawCode, string $username): string { $preferred = trim((string) $rawCode); if ($preferred !== '') { if (AgentNode::query()->where('admin_site_id', $parent->admin_site_id)->where('code', $preferred)->exists()) { throw ValidationException::withMessages([ 'code' => ['unique'], ]); } return $preferred; } $base = preg_replace('/[^a-zA-Z0-9_-]+/', '_', $username) ?? ''; $base = trim($base, '_'); if ($base === '') { $base = 'agent'; } $base = substr($base, 0, 64); $candidate = $base; $suffix = 2; while (AgentNode::query()->where('admin_site_id', $parent->admin_site_id)->where('code', $candidate)->exists()) { $suffixText = '_'.$suffix; $candidate = substr($base, 0, max(1, 64 - strlen($suffixText))).$suffixText; $suffix++; } return $candidate; } }