feat: 增强代理和玩家管理功能
- 在 SyncAdminAuthorizationCommand 中新增对代理线路和结算菜单操作的同步功能,确保缺失的菜单操作行能够被创建。 - 更新多个控制器中的权限检查逻辑,使用 hasPermissionCode 替代原有的权限验证方式,提升权限管理的灵活性。 - 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。 - 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。 - 在 AdminUser 和 AgentNode 模型中增强角色与用户的权限管理功能,支持更细粒度的权限控制。
This commit is contained in:
28
app/Support/AdminAccountScopeGuard.php
Normal file
28
app/Support/AdminAccountScopeGuard.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
final class AdminAccountScopeGuard
|
||||
{
|
||||
public static function assertPlatformAccount(AdminUser $user): void
|
||||
{
|
||||
if ($user->isAgentAccount()) {
|
||||
throw ValidationException::withMessages([
|
||||
'admin_user' => [trans('admin.agent_account_managed_in_agents')],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function assertSystemRole(AdminRole $role): void
|
||||
{
|
||||
if (($role->scope_type ?? AdminRole::SCOPE_SYSTEM) !== AdminRole::SCOPE_SYSTEM) {
|
||||
throw ValidationException::withMessages([
|
||||
'admin_role' => [trans('admin.agent_role_managed_in_agents')],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 代理线路开通、占成授信、代理账单等 permission_code 的 menu_action 补救(auth-sync 前须存在)。
|
||||
*/
|
||||
final class AdminAgentLineSettlementPermissionMenuActionSync
|
||||
{
|
||||
public static function syncMissing(): int
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$viewActionId = DB::table('admin_action_catalog')->where('code', 'view')->value('id');
|
||||
$manageActionId = DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
|
||||
if ($viewActionId === null || $manageActionId === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
|
||||
$agentMenuId = (int) DB::table('admin_menus')->where('code', 'system.agents')->value('id');
|
||||
if ($agentMenuId > 0) {
|
||||
$lineMenuId = self::ensureChildMenu($agentMenuId, 'system.agents.line', '代理线路开通', $now, 'system.agents');
|
||||
$profileMenuId = self::ensureChildMenu($agentMenuId, 'system.agents.profile', '代理占成授信', $now, 'system.agents');
|
||||
$created += self::ensureMenuAction($lineMenuId, (int) $manageActionId, 'agent.line.provision', '代理线路开通', $now) ? 1 : 0;
|
||||
$created += self::ensureMenuAction($profileMenuId, (int) $manageActionId, 'agent.profile.manage', '代理占成授信管理', $now) ? 1 : 0;
|
||||
}
|
||||
|
||||
$settlementBatchMenuId = (int) DB::table('admin_menus')->where('code', 'settlement.batch')->value('id');
|
||||
if ($settlementBatchMenuId > 0) {
|
||||
$agentBillMenuId = self::ensureChildMenu(
|
||||
$settlementBatchMenuId,
|
||||
'settlement.batch.agent',
|
||||
'代理账单',
|
||||
$now,
|
||||
'settlement.batch',
|
||||
);
|
||||
$created += self::ensureMenuAction($agentBillMenuId, (int) $viewActionId, 'settlement.agent.view', '代理账单查看', $now) ? 1 : 0;
|
||||
$created += self::ensureMenuAction($agentBillMenuId, (int) $manageActionId, 'settlement.agent.manage', '代理账单管理', $now) ? 1 : 0;
|
||||
}
|
||||
|
||||
return $created;
|
||||
}
|
||||
|
||||
private static function ensureChildMenu(
|
||||
int $parentId,
|
||||
string $code,
|
||||
string $name,
|
||||
Carbon $now,
|
||||
string $activeMenuCode = 'system.agents',
|
||||
): int {
|
||||
$existing = DB::table('admin_menus')->where('code', $code)->value('id');
|
||||
if ($existing !== null) {
|
||||
return (int) $existing;
|
||||
}
|
||||
|
||||
return (int) DB::table('admin_menus')->insertGetId([
|
||||
'parent_id' => $parentId,
|
||||
'menu_type' => 'button',
|
||||
'code' => $code,
|
||||
'name' => $name,
|
||||
'path' => null,
|
||||
'route_name' => null,
|
||||
'component' => null,
|
||||
'icon' => null,
|
||||
'active_menu_code' => $activeMenuCode,
|
||||
'sort_order' => 0,
|
||||
'is_visible' => false,
|
||||
'is_cache' => false,
|
||||
'is_external' => false,
|
||||
'status' => 1,
|
||||
'meta_json' => null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function ensureMenuAction(int $menuId, int $actionId, string $permissionCode, string $name, Carbon $now): bool
|
||||
{
|
||||
if (DB::table('admin_menu_actions')->where('permission_code', $permissionCode)->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DB::table('admin_menu_actions')->insert([
|
||||
'menu_id' => $menuId,
|
||||
'action_id' => $actionId,
|
||||
'permission_code' => $permissionCode,
|
||||
'name' => $name,
|
||||
'status' => 1,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ final class AdminAgentScope
|
||||
|
||||
$actor = self::primaryAgentNode($admin);
|
||||
if ($actor === null) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($player->agent_node_id === null) {
|
||||
|
||||
76
app/Support/AdminAgentSettlementScope.php
Normal file
76
app/Support/AdminAgentSettlementScope.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
/** 代理账单按管理员可访问站点过滤。 */
|
||||
final class AdminAgentSettlementScope
|
||||
{
|
||||
public static function applyToBillsQuery(Builder $query, AdminUser $admin, string $billsAlias = 'settlement_bills'): void
|
||||
{
|
||||
$siteIds = $admin->accessibleAdminSiteIds();
|
||||
if ($siteIds === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($siteIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query->whereExists(function (Builder $sub) use ($siteIds, $billsAlias): void {
|
||||
$sub->selectRaw('1')
|
||||
->from('settlement_periods')
|
||||
->whereColumn('settlement_periods.id', $billsAlias.'.settlement_period_id')
|
||||
->whereIn('settlement_periods.admin_site_id', $siteIds);
|
||||
});
|
||||
}
|
||||
|
||||
public static function periodAccessible(AdminUser $admin, int $settlementPeriodId): bool
|
||||
{
|
||||
$siteIds = $admin->accessibleAdminSiteIds();
|
||||
if ($siteIds === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($siteIds === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \Illuminate\Support\Facades\DB::table('settlement_periods')
|
||||
->where('id', $settlementPeriodId)
|
||||
->whereIn('admin_site_id', $siteIds)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public static function siteAccessible(AdminUser $admin, int $adminSiteId): bool
|
||||
{
|
||||
$siteIds = $admin->accessibleAdminSiteIds();
|
||||
if ($siteIds === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($adminSiteId, $siteIds, true);
|
||||
}
|
||||
|
||||
public static function billAccessible(AdminUser $admin, int $settlementBillId): bool
|
||||
{
|
||||
$siteIds = $admin->accessibleAdminSiteIds();
|
||||
if ($siteIds === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($siteIds === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \Illuminate\Support\Facades\DB::table('settlement_bills')
|
||||
->join('settlement_periods', 'settlement_periods.id', '=', 'settlement_bills.settlement_period_id')
|
||||
->where('settlement_bills.id', $settlementBillId)
|
||||
->whereIn('settlement_periods.admin_site_id', $siteIds)
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminSite;
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\AgentNode;
|
||||
use App\Models\AgentProfile;
|
||||
|
||||
final class AdminAuthProfile
|
||||
{
|
||||
@@ -26,10 +28,13 @@ final class AdminAuthProfile
|
||||
* agent: ?array{
|
||||
* id: int,
|
||||
* admin_site_id: int,
|
||||
* site_code: string,
|
||||
* path: string,
|
||||
* code: string,
|
||||
* name: string,
|
||||
* depth: int
|
||||
* depth: int,
|
||||
* can_create_child_agent: bool,
|
||||
* can_create_player: bool
|
||||
* },
|
||||
* is_super_admin: bool,
|
||||
* operational_permissions: list<string>,
|
||||
@@ -56,7 +61,17 @@ final class AdminAuthProfile
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, admin_site_id: int, path: string, code: string, name: string, depth: int}|null
|
||||
* @return array{
|
||||
* id: int,
|
||||
* admin_site_id: int,
|
||||
* site_code: string,
|
||||
* path: string,
|
||||
* code: string,
|
||||
* name: string,
|
||||
* depth: int,
|
||||
* can_create_child_agent: bool,
|
||||
* can_create_player: bool
|
||||
* }|null
|
||||
*/
|
||||
private static function agentContext(AdminUser $admin): ?array
|
||||
{
|
||||
@@ -69,13 +84,19 @@ final class AdminAuthProfile
|
||||
return null;
|
||||
}
|
||||
|
||||
$siteCode = AdminSite::query()->where('id', (int) $node->admin_site_id)->value('code');
|
||||
$profile = AgentProfile::query()->where('agent_node_id', $node->id)->first();
|
||||
|
||||
return [
|
||||
'id' => (int) $node->id,
|
||||
'admin_site_id' => (int) $node->admin_site_id,
|
||||
'site_code' => is_string($siteCode) && $siteCode !== '' ? $siteCode : '',
|
||||
'path' => (string) $node->path,
|
||||
'code' => (string) $node->code,
|
||||
'name' => (string) $node->name,
|
||||
'depth' => (int) $node->depth,
|
||||
'can_create_child_agent' => $profile === null || $profile->can_create_child_agent,
|
||||
'can_create_player' => $profile === null || $profile->can_create_player,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ final class AdminAuthorizationRegistry
|
||||
['slug' => 'prd.agent.role.manage', 'name' => '代理角色·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.role.manage']],
|
||||
['slug' => 'prd.agent.user.view', 'name' => '代理账号·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.user.view', 'agent.node.view']],
|
||||
['slug' => 'prd.agent.user.manage', 'name' => '代理账号·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.user.manage']],
|
||||
['slug' => 'prd.agent-line.provision', 'name' => '代理线路·开通', 'nav_segment' => 'agents', 'permission_codes' => ['agent.line.provision']],
|
||||
['slug' => 'prd.agent.profile.manage', 'name' => '代理占成授信·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.profile.manage']],
|
||||
['slug' => 'prd.settlement.agent.view', 'name' => '代理账单·查看', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.agent.view']],
|
||||
['slug' => 'prd.settlement.agent.manage', 'name' => '代理账单·可管理', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.agent.manage']],
|
||||
|
||||
['slug' => 'prd.users.manage', 'name' => '用户管理·可管理', 'nav_segment' => 'players', 'permission_codes' => ['service.players.manage']],
|
||||
['slug' => 'prd.users.view_finance', 'name' => '用户管理·财务查看', 'nav_segment' => 'players', 'permission_codes' => ['service.players.view', 'service.wallet.view']],
|
||||
@@ -106,7 +110,7 @@ final class AdminAuthorizationRegistry
|
||||
'audit' => '审计日志',
|
||||
'settings' => '系统设置',
|
||||
'integration' => '接入站点',
|
||||
'agents' => '代理管理',
|
||||
'agents' => '代理线路',
|
||||
];
|
||||
|
||||
return array_map(
|
||||
@@ -140,7 +144,7 @@ final class AdminAuthorizationRegistry
|
||||
{
|
||||
return [
|
||||
['segment' => 'dashboard', 'label' => 'Dashboard', 'href' => '/admin', 'nav_group' => 'overview', 'requiredAny' => ['prd.dashboard.view']],
|
||||
['segment' => 'agents', 'label' => 'Agents', 'href' => '/admin/agents', 'nav_group' => 'agent', 'activeMatchPrefix' => '/admin/agents', 'requiredAny' => ['prd.agent.view', 'prd.agent.manage', 'prd.agent.role.view', 'prd.agent.role.manage', 'prd.agent.user.view', 'prd.agent.user.manage']],
|
||||
['segment' => 'agents', 'label' => 'Agent lines', 'href' => '/admin/agents', 'nav_group' => 'agent', 'activeMatchPrefix' => '/admin/agents', 'requiredAny' => array_values(array_unique(array_merge(['prd.agent.view', 'prd.agent.manage', 'prd.agent.role.view', 'prd.agent.role.manage', 'prd.agent.user.view', 'prd.agent.user.manage', 'prd.agent-line.provision', 'prd.agent.profile.manage', 'prd.settlement.agent.view', 'prd.settlement.agent.manage'], AdminPermissionLanguage::requiredAnyPrdSlugs('integration-sites'))))],
|
||||
['segment' => 'draws', 'label' => 'Draws', 'href' => '/admin/draws', 'nav_group' => 'operations', 'requiredAny' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.draw_reopen.manage']],
|
||||
['segment' => 'tickets', 'label' => 'Tickets', 'href' => '/admin/tickets', 'nav_group' => 'operations', 'requiredAny' => ['prd.tickets.view']],
|
||||
['segment' => 'players', 'label' => 'Players', 'href' => '/admin/players', 'nav_group' => 'operations', 'requiredAny' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.player_freeze.manage']],
|
||||
@@ -152,7 +156,6 @@ final class AdminAuthorizationRegistry
|
||||
['segment' => 'rules_odds', 'label' => 'Odds & rebate', 'href' => '/admin/rules/odds', 'nav_group' => 'rules', 'platform_only' => true, 'requiredAny' => ['prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view']],
|
||||
['segment' => 'jackpot', 'label' => 'Jackpot', 'href' => '/admin/jackpot', 'nav_group' => 'rules', 'platform_only' => true, 'activeMatchPrefix' => '/admin/jackpot', 'requiredAny' => ['prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
['segment' => 'risk_cap', 'label' => 'Risk cap rules', 'href' => '/admin/risk/cap', 'nav_group' => 'rules', 'platform_only' => true, 'activeMatchPrefix' => '/admin/risk/cap', 'requiredAny' => ['prd.risk_cap.manage', 'prd.risk_cap.view']],
|
||||
['segment' => 'integration', 'label' => 'Integration sites', 'href' => '/admin/config/integration-sites', 'nav_group' => 'platform', 'platform_only' => true, 'activeMatchPrefix' => '/admin/config/integration-sites', 'requiredAny' => AdminPermissionLanguage::requiredAnyPrdSlugs('integration-sites')],
|
||||
['segment' => 'currencies', 'label' => 'Currencies', 'href' => '/admin/currencies', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.currency.manage']],
|
||||
['segment' => 'admin_users', 'label' => 'Admin Users', 'href' => '/admin/admin-users', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.admin_user.manage']],
|
||||
['segment' => 'admin_roles', 'label' => 'Admin Roles', 'href' => '/admin/admin-roles', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.admin_role.manage']],
|
||||
@@ -215,7 +218,16 @@ final class AdminAuthorizationRegistry
|
||||
'dashboard' => ['prd.dashboard.view'],
|
||||
'admin_users' => ['prd.admin_user.manage'],
|
||||
'admin_roles' => ['prd.admin_role.manage'],
|
||||
'agents' => ['prd.agent.view', 'prd.agent.manage', 'prd.agent.role.view', 'prd.agent.role.manage', 'prd.agent.user.view', 'prd.agent.user.manage'],
|
||||
'agents' => [
|
||||
'prd.agent.view',
|
||||
'prd.agent.manage',
|
||||
'prd.agent.role.view',
|
||||
'prd.agent.role.manage',
|
||||
'prd.agent.user.view',
|
||||
'prd.agent.user.manage',
|
||||
'prd.agent-line.provision',
|
||||
'prd.agent.profile.manage',
|
||||
],
|
||||
'players' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.player_freeze.manage'],
|
||||
'currencies' => ['prd.currency.manage'],
|
||||
'wallet' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.wallet_adjust.manage', 'prd.users.view_finance'],
|
||||
@@ -225,7 +237,13 @@ final class AdminAuthorizationRegistry
|
||||
'jackpot' => ['prd.jackpot.manage', 'prd.jackpot.view'],
|
||||
'risk_cap' => ['prd.risk_cap.manage', 'prd.risk_cap.view'],
|
||||
'risk' => ['prd.risk.view', 'prd.risk.manage'],
|
||||
'settlement' => ['prd.payout.manage', 'prd.payout.review', 'prd.payout.view'],
|
||||
'settlement' => [
|
||||
'prd.payout.manage',
|
||||
'prd.payout.review',
|
||||
'prd.payout.view',
|
||||
'prd.settlement.agent.view',
|
||||
'prd.settlement.agent.manage',
|
||||
],
|
||||
'reconcile' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs'],
|
||||
'reports' => ['prd.report.view', 'prd.report.export'],
|
||||
'tickets' => ['prd.tickets.view'],
|
||||
@@ -386,6 +404,8 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.admin-roles.destroy', 'module_code' => 'system', 'name' => '删除角色', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/admin-roles/{admin_role}', 'route_name' => 'api.v1.admin.admin-roles.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
['code' => 'admin.admin-roles.permissions.sync', 'module_code' => 'system', 'name' => '角色权限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-roles/{admin_role}/permissions', 'route_name' => 'api.v1.admin.admin-roles.permissions.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
|
||||
['code' => 'admin.agent-lines.store', 'module_code' => 'agent', 'name' => '开通代理线路', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-lines', 'route_name' => 'api.v1.admin.agent-lines.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.line.provision'], 'legacy_permission_slugs' => ['prd.agent-line.provision', 'prd.agent.manage']],
|
||||
['code' => 'admin.agent-lines.show', 'module_code' => 'agent', 'name' => '代理线路详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-lines/{admin_site}', 'route_name' => 'api.v1.admin.agent-lines.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.line.provision', 'agent.node.view', 'agent.node.manage'], 'legacy_permission_slugs' => ['prd.agent-line.provision', 'prd.agent.view', 'prd.agent.manage']],
|
||||
['code' => 'admin.agent-nodes.tree', 'module_code' => 'agent', 'name' => '代理树', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/tree', 'route_name' => 'api.v1.admin.agent-nodes.tree', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage', 'agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage']],
|
||||
['code' => 'admin.agent-nodes.store', 'module_code' => 'agent', 'name' => '创建下级代理', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes', 'route_name' => 'api.v1.admin.agent-nodes.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||
['code' => 'admin.agent-nodes.show', 'module_code' => 'agent', 'name' => '代理节点详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
||||
@@ -406,6 +426,12 @@ final class AdminAuthorizationRegistry
|
||||
|
||||
['code' => 'admin.agent-delegation-grants.index', 'module_code' => 'agent', 'name' => '代理下放上限查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/delegation-grants', 'route_name' => 'api.v1.admin.agent-delegation-grants.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
||||
['code' => 'admin.agent-delegation-grants.sync', 'module_code' => 'agent', 'name' => '代理下放上限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/delegation-grants', 'route_name' => 'api.v1.admin.agent-delegation-grants.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||
['code' => 'admin.agent-nodes.profile.show', 'module_code' => 'agent', 'name' => '代理占成授信查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/profile', 'route_name' => 'api.v1.admin.agent-nodes.profile.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.profile.manage', 'agent.node.manage'], 'legacy_permission_slugs' => ['prd.agent.profile.manage', 'prd.agent.manage']],
|
||||
['code' => 'admin.agent-nodes.profile.update', 'module_code' => 'agent', 'name' => '代理占成授信更新', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/profile', 'route_name' => 'api.v1.admin.agent-nodes.profile.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.profile.manage'], 'legacy_permission_slugs' => ['prd.agent.profile.manage']],
|
||||
['code' => 'admin.settlement-periods.store', 'module_code' => 'settlement', 'name' => '创建代理账期', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-periods', 'route_name' => 'api.v1.admin.settlement-periods.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['settlement.agent.manage'], 'legacy_permission_slugs' => ['prd.settlement.agent.manage']],
|
||||
['code' => 'admin.settlement-periods.close', 'module_code' => 'settlement', 'name' => '关闭代理账期', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-periods/{settlement_period}/close', 'route_name' => 'api.v1.admin.settlement-periods.close', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['settlement.agent.manage'], 'legacy_permission_slugs' => ['prd.settlement.agent.manage']],
|
||||
['code' => 'admin.settlement-bills.index', 'module_code' => 'settlement', 'name' => '代理账单列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/settlement-bills', 'route_name' => 'api.v1.admin.settlement-bills.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['settlement.agent.view', 'settlement.agent.manage'], 'legacy_permission_slugs' => ['prd.settlement.agent.view', 'prd.settlement.agent.manage']],
|
||||
['code' => 'admin.settlement-bills.confirm', 'module_code' => 'settlement', 'name' => '确认代理账单', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-bills/{settlement_bill}/confirm', 'route_name' => 'api.v1.admin.settlement-bills.confirm', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['settlement.agent.manage'], 'legacy_permission_slugs' => ['prd.settlement.agent.manage']],
|
||||
|
||||
['code' => 'admin.play-types.index', 'module_code' => 'config', 'name' => '玩法类型列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/play-types', 'route_name' => 'api.v1.admin.play-types.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.play_switch.manage', 'prd.odds.manage', 'prd.odds.view', 'prd.rebate.manage', 'prd.rebate.view']],
|
||||
['code' => 'admin.play-types.patch', 'module_code' => 'config', 'name' => '玩法类型切换', 'http_method' => 'PATCH', 'uri_pattern' => '/api/v1/admin/play-types/{play_code}', 'route_name' => 'api.v1.admin.play-types.patch', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.play_switch.manage']],
|
||||
@@ -435,7 +461,7 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.currencies.update', 'module_code' => 'settings', 'name' => '更新币种', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/currencies/{currency}', 'route_name' => 'api.v1.admin.currencies.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.currency.manage']],
|
||||
['code' => 'admin.currencies.destroy', 'module_code' => 'settings', 'name' => '删除币种', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/currencies/{currency}', 'route_name' => 'api.v1.admin.currencies.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.currency.manage']],
|
||||
|
||||
['code' => 'admin.integration-sites.index', 'module_code' => 'integration', 'name' => '接入站点列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/integration-sites', 'route_name' => 'api.v1.admin.integration-sites.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['integration.site.view', 'integration.site.manage']],
|
||||
['code' => 'admin.integration-sites.index', 'module_code' => 'integration', 'name' => '接入站点列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/integration-sites', 'route_name' => 'api.v1.admin.integration-sites.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['integration.site.view', 'integration.site.manage', 'service.players.manage']],
|
||||
['code' => 'admin.integration-sites.store', 'module_code' => 'integration', 'name' => '创建接入站点', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/integration-sites', 'route_name' => 'api.v1.admin.integration-sites.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['integration.site.manage']],
|
||||
['code' => 'admin.integration-sites.show', 'module_code' => 'integration', 'name' => '接入站点详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/integration-sites/{admin_site}', 'route_name' => 'api.v1.admin.integration-sites.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['integration.site.view', 'integration.site.manage']],
|
||||
['code' => 'admin.integration-sites.update', 'module_code' => 'integration', 'name' => '更新接入站点', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/integration-sites/{admin_site}', 'route_name' => 'api.v1.admin.integration-sites.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['integration.site.manage']],
|
||||
|
||||
@@ -51,6 +51,16 @@ final class AdminSiteScope
|
||||
return in_array($siteCode, $allowed, true);
|
||||
}
|
||||
|
||||
public static function siteIdAllowed(AdminUser $admin, int $siteId): bool
|
||||
{
|
||||
$siteIds = $admin->accessibleAdminSiteIds();
|
||||
if ($siteIds === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($siteId, $siteIds, true);
|
||||
}
|
||||
|
||||
public static function playerAccessible(AdminUser $admin, Player $player): bool
|
||||
{
|
||||
if (! self::siteCodeAllowed($admin, (string) $player->site_code)) {
|
||||
@@ -79,7 +89,10 @@ final class AdminSiteScope
|
||||
}
|
||||
|
||||
$query->whereIn('site_code', $codes);
|
||||
AdminAgentScope::applyToPlayerQuery($query, $admin);
|
||||
|
||||
if (AdminAgentScope::primaryAgentNode($admin) !== null) {
|
||||
AdminAgentScope::applyToPlayerQuery($query, $admin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ final class AdminUserApiPresenter
|
||||
'nickname' => $user->name,
|
||||
'email' => $user->email,
|
||||
'status' => (int) $user->status,
|
||||
'account_kind' => $user->isPlatformAccount() ? 'platform' : 'agent',
|
||||
'roles' => $user->adminRoleSlugs(),
|
||||
'direct_permissions' => $user->directLegacyPermissionSlugs(),
|
||||
'effective_permissions' => $user->adminPermissionSlugs(),
|
||||
|
||||
30
app/Support/AgentLinePresenter.php
Normal file
30
app/Support/AgentLinePresenter.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminSite;
|
||||
use App\Models\AgentNode;
|
||||
|
||||
final class AgentLinePresenter
|
||||
{
|
||||
/**
|
||||
* @param array{sso_jwt_secret: string, wallet_api_key: string} $secrets
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function provisioned(AdminSite $site, AgentNode $root, array $secrets): array
|
||||
{
|
||||
$sitePayload = AdminIntegrationSitePresenter::withPlainSecretsOnce(
|
||||
AdminIntegrationSitePresenter::detail($site),
|
||||
$secrets,
|
||||
);
|
||||
|
||||
return array_merge($sitePayload, [
|
||||
'agent_node' => AgentNodePresenter::item($root),
|
||||
'line_root' => [
|
||||
'agent_node_id' => (int) $root->id,
|
||||
'site_code' => (string) $site->code,
|
||||
'is_line_root' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminSite;
|
||||
use App\Models\AgentNode;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class AgentNodePresenter
|
||||
{
|
||||
@@ -16,14 +18,27 @@ final class AgentNodePresenter
|
||||
* code: string,
|
||||
* name: string,
|
||||
* status: int,
|
||||
* is_root: bool
|
||||
* is_root: bool,
|
||||
* username: ?string,
|
||||
* email: ?string
|
||||
* }
|
||||
*/
|
||||
public static function item(AgentNode $node): array
|
||||
{
|
||||
$account = DB::table('admin_user_agents as aua')
|
||||
->join('admin_users as au', 'au.id', '=', 'aua.admin_user_id')
|
||||
->where('aua.agent_node_id', $node->id)
|
||||
->orderByDesc('aua.is_primary')
|
||||
->orderBy('aua.admin_user_id')
|
||||
->select('au.username', 'au.email')
|
||||
->first();
|
||||
|
||||
$siteCode = AdminSite::query()->where('id', $node->admin_site_id)->value('code');
|
||||
|
||||
return [
|
||||
'id' => (int) $node->id,
|
||||
'admin_site_id' => (int) $node->admin_site_id,
|
||||
'site_code' => $siteCode !== null ? (string) $siteCode : null,
|
||||
'parent_id' => $node->parent_id !== null ? (int) $node->parent_id : null,
|
||||
'path' => (string) $node->path,
|
||||
'depth' => (int) $node->depth,
|
||||
@@ -31,6 +46,9 @@ final class AgentNodePresenter
|
||||
'name' => (string) $node->name,
|
||||
'status' => (int) $node->status,
|
||||
'is_root' => $node->isRoot(),
|
||||
'is_line_root' => $node->isRoot(),
|
||||
'username' => $account?->username !== null ? (string) $account->username : null,
|
||||
'email' => $account?->email !== null ? (string) $account->email : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
25
app/Support/CreditLineMode.php
Normal file
25
app/Support/CreditLineMode.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminSite;
|
||||
use App\Services\LotterySettings;
|
||||
|
||||
final class CreditLineMode
|
||||
{
|
||||
public static function isEnabledForSiteCode(string $siteCode): bool
|
||||
{
|
||||
if ((bool) LotterySettings::get('settlement.credit_line_disable_instant_rebate', false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$site = AdminSite::query()->where('code', $siteCode)->first(['extra_json']);
|
||||
if ($site === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$extra = is_array($site->extra_json) ? $site->extra_json : [];
|
||||
|
||||
return (bool) ($extra['credit_line_mode'] ?? false);
|
||||
}
|
||||
}
|
||||
72
app/Support/Settlement/DesignDocExample12.php
Normal file
72
app/Support/Settlement/DesignDocExample12.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support\Settlement;
|
||||
|
||||
/**
|
||||
* 设计文档 §12.1~§12.4 验收常量(平台视角:玩家输为正)。
|
||||
*
|
||||
* @see docs/信用占成盘代理系统设计说明文档.md
|
||||
*/
|
||||
final class DesignDocExample12
|
||||
{
|
||||
public const GAME_WIN_LOSS = 1000;
|
||||
|
||||
public const BASIC_REBATE = 50;
|
||||
|
||||
public const EXTRA_REBATE_BY_C = 20;
|
||||
|
||||
/** 玩家净结算 P→C:1000 - 50 - 20 */
|
||||
public const PLAYER_NET_SETTLEMENT = 930;
|
||||
|
||||
/** 共享净输赢:1000 - 50 */
|
||||
public const SHARED_NET_WIN_LOSS = 950;
|
||||
|
||||
/** 总占成:A=60%, B=40%, C=25% */
|
||||
public const TOTAL_SHARE_A = 60;
|
||||
|
||||
public const TOTAL_SHARE_B = 40;
|
||||
|
||||
public const TOTAL_SHARE_C = 25;
|
||||
|
||||
/** 实际占成收益(共享池) */
|
||||
public const SHARE_PROFIT_C = 237.5;
|
||||
|
||||
public const SHARE_PROFIT_B = 142.5;
|
||||
|
||||
public const SHARE_PROFIT_A = 190.0;
|
||||
|
||||
public const SHARE_PROFIT_PLATFORM = 380.0;
|
||||
|
||||
/** C 最终收益:237.5 - 20 */
|
||||
public const FINAL_PROFIT_C = 217.5;
|
||||
|
||||
/** 逐级结算应付 */
|
||||
public const TIER_P_TO_C = 930.0;
|
||||
|
||||
public const TIER_C_TO_B = 712.5;
|
||||
|
||||
public const TIER_B_TO_A = 570.0;
|
||||
|
||||
public const TIER_A_TO_PLATFORM = 380.0;
|
||||
|
||||
/**
|
||||
* @return array{actual: array<string, float>, total: float}
|
||||
*/
|
||||
public static function actualShareRates(): array
|
||||
{
|
||||
$c = self::TOTAL_SHARE_C;
|
||||
$b = self::TOTAL_SHARE_B - self::TOTAL_SHARE_C;
|
||||
$a = self::TOTAL_SHARE_A - self::TOTAL_SHARE_B;
|
||||
$platform = 100 - self::TOTAL_SHARE_A;
|
||||
|
||||
return [
|
||||
'actual' => [
|
||||
'C' => $c,
|
||||
'B' => $b,
|
||||
'A' => $a,
|
||||
'platform' => $platform,
|
||||
],
|
||||
'total' => $c + $b + $a + $platform,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user