feat: 增强代理和玩家管理功能
- 在 SyncAdminAuthorizationCommand 中新增对代理线路和结算菜单操作的同步功能,确保缺失的菜单操作行能够被创建。 - 更新多个控制器中的权限检查逻辑,使用 hasPermissionCode 替代原有的权限验证方式,提升权限管理的灵活性。 - 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。 - 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。 - 在 AdminUser 和 AgentNode 模型中增强角色与用户的权限管理功能,支持更细粒度的权限控制。
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$sites = DB::table('admin_sites')->orderBy('id')->get(['id', 'code']);
|
||||
|
||||
foreach ($sites as $site) {
|
||||
$siteId = (int) $site->id;
|
||||
$code = (string) $site->code;
|
||||
$legacyCode = 'root-'.$code;
|
||||
|
||||
$root = DB::table('agent_nodes')
|
||||
->where('admin_site_id', $siteId)
|
||||
->where('depth', 0)
|
||||
->first(['id', 'code']);
|
||||
|
||||
if ($root === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((string) $root->code === $legacyCode) {
|
||||
$conflict = DB::table('agent_nodes')
|
||||
->where('admin_site_id', $siteId)
|
||||
->where('code', $code)
|
||||
->where('id', '!=', (int) $root->id)
|
||||
->exists();
|
||||
if (! $conflict) {
|
||||
DB::table('agent_nodes')->where('id', (int) $root->id)->update([
|
||||
'code' => $code,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$sites = DB::table('admin_sites')->orderBy('id')->get(['id', 'code']);
|
||||
|
||||
foreach ($sites as $site) {
|
||||
$siteId = (int) $site->id;
|
||||
$code = (string) $site->code;
|
||||
|
||||
DB::table('agent_nodes')
|
||||
->where('admin_site_id', $siteId)
|
||||
->where('depth', 0)
|
||||
->where('code', $code)
|
||||
->update([
|
||||
'code' => 'root-'.$code,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('agent_profiles', function (Blueprint $table): void {
|
||||
$table->foreignId('agent_node_id')->primary()->constrained('agent_nodes')->cascadeOnDelete();
|
||||
$table->decimal('total_share_rate', 5, 2)->default(0)->comment('总占成 0-100');
|
||||
$table->unsignedBigInteger('credit_limit')->default(0);
|
||||
$table->unsignedBigInteger('allocated_credit')->default(0);
|
||||
$table->unsignedBigInteger('used_credit')->default(0);
|
||||
$table->decimal('rebate_limit', 8, 4)->default(0);
|
||||
$table->decimal('default_player_rebate', 8, 4)->default(0);
|
||||
$table->string('settlement_cycle', 16)->default('weekly');
|
||||
$table->boolean('can_grant_extra_rebate')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('player_credit_accounts', function (Blueprint $table): void {
|
||||
$table->foreignId('player_id')->primary()->constrained('players')->cascadeOnDelete();
|
||||
$table->unsignedBigInteger('credit_limit')->default(0);
|
||||
$table->unsignedBigInteger('used_credit')->default(0);
|
||||
$table->unsignedBigInteger('frozen_credit')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('player_rebate_profiles', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
|
||||
$table->string('game_type', 32)->default('*');
|
||||
$table->boolean('inherit_from_agent')->default(true);
|
||||
$table->decimal('rebate_rate', 8, 4)->default(0);
|
||||
$table->decimal('extra_rebate_rate', 8, 4)->default(0);
|
||||
$table->timestamps();
|
||||
$table->unique(['player_id', 'game_type']);
|
||||
});
|
||||
|
||||
Schema::create('settlement_periods', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('admin_site_id')->constrained('admin_sites')->cascadeOnDelete();
|
||||
$table->timestamp('period_start');
|
||||
$table->timestamp('period_end');
|
||||
$table->string('status', 16)->default('open');
|
||||
$table->timestamps();
|
||||
$table->index(['admin_site_id', 'status']);
|
||||
});
|
||||
|
||||
Schema::create('settlement_bills', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('settlement_period_id')->constrained('settlement_periods')->cascadeOnDelete();
|
||||
$table->string('bill_type', 16);
|
||||
$table->string('owner_type', 16);
|
||||
$table->unsignedBigInteger('owner_id');
|
||||
$table->string('counterparty_type', 16);
|
||||
$table->unsignedBigInteger('counterparty_id');
|
||||
$table->bigInteger('gross_win_loss')->default(0);
|
||||
$table->bigInteger('rebate_amount')->default(0);
|
||||
$table->bigInteger('adjustment_amount')->default(0);
|
||||
$table->bigInteger('net_amount')->default(0);
|
||||
$table->bigInteger('paid_amount')->default(0);
|
||||
$table->bigInteger('unpaid_amount')->default(0);
|
||||
$table->string('status', 16)->default('pending');
|
||||
$table->timestamp('confirmed_at')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['settlement_period_id', 'bill_type']);
|
||||
});
|
||||
|
||||
Schema::create('rebate_records', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
|
||||
$table->foreignId('settlement_period_id')->nullable()->constrained('settlement_periods')->nullOnDelete();
|
||||
$table->string('game_type', 32)->default('*');
|
||||
$table->unsignedBigInteger('valid_bet_amount')->default(0);
|
||||
$table->decimal('rebate_rate', 8, 4)->default(0);
|
||||
$table->unsignedBigInteger('rebate_amount')->default(0);
|
||||
$table->string('rebate_type', 16)->default('basic');
|
||||
$table->foreignId('owner_agent_id')->nullable()->constrained('agent_nodes')->nullOnDelete();
|
||||
$table->string('status', 16)->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('rebate_allocations', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('rebate_record_id')->constrained('rebate_records')->cascadeOnDelete();
|
||||
$table->foreignId('settlement_bill_id')->nullable()->constrained('settlement_bills')->nullOnDelete();
|
||||
$table->string('participant_type', 16);
|
||||
$table->unsignedBigInteger('participant_id')->default(0);
|
||||
$table->decimal('actual_share_rate', 5, 2)->default(0);
|
||||
$table->bigInteger('allocated_amount')->default(0);
|
||||
$table->string('allocation_rule', 32)->default('share');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('payment_records', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('settlement_bill_id')->constrained('settlement_bills')->cascadeOnDelete();
|
||||
$table->string('payer_type', 16);
|
||||
$table->unsignedBigInteger('payer_id');
|
||||
$table->string('payee_type', 16);
|
||||
$table->unsignedBigInteger('payee_id');
|
||||
$table->bigInteger('amount');
|
||||
$table->string('method', 32)->nullable();
|
||||
$table->string('status', 16)->default('pending');
|
||||
$table->foreignId('created_by')->nullable()->constrained('admin_users')->nullOnDelete();
|
||||
$table->foreignId('confirmed_by')->nullable()->constrained('admin_users')->nullOnDelete();
|
||||
$table->timestamp('confirmed_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('credit_ledger', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('owner_type', 16);
|
||||
$table->unsignedBigInteger('owner_id');
|
||||
$table->bigInteger('amount');
|
||||
$table->string('reason', 64);
|
||||
$table->string('ref_type', 32)->nullable();
|
||||
$table->unsignedBigInteger('ref_id')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['owner_type', 'owner_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('credit_ledger');
|
||||
Schema::dropIfExists('payment_records');
|
||||
Schema::dropIfExists('rebate_allocations');
|
||||
Schema::dropIfExists('rebate_records');
|
||||
Schema::dropIfExists('settlement_bills');
|
||||
Schema::dropIfExists('settlement_periods');
|
||||
Schema::dropIfExists('player_rebate_profiles');
|
||||
Schema::dropIfExists('player_credit_accounts');
|
||||
Schema::dropIfExists('agent_profiles');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
use App\Support\AdminAgentLineSettlementPermissionMenuActionSync;
|
||||
use App\Support\AdminAuthorizationRegistry;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* 代理账单 / 线路开通等 API 写入 admin_api_resources,并补齐 settlement.agent.* menu_action。
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/** @var list<string> */
|
||||
private const RESOURCE_CODE_PREFIXES = [
|
||||
'admin.settlement-bills.',
|
||||
'admin.settlement-periods.',
|
||||
'admin.agent-lines.',
|
||||
'admin.agent-nodes.profile.',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private const MENU_ACTION_CODES = [
|
||||
'settlement.agent.view',
|
||||
'settlement.agent.manage',
|
||||
'agent.line.provision',
|
||||
'agent.profile.manage',
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
AdminAgentLineSettlementPermissionMenuActionSync::syncMissing();
|
||||
|
||||
$now = Carbon::now();
|
||||
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
||||
|
||||
$resources = array_values(array_filter(
|
||||
AdminAuthorizationRegistry::resources(),
|
||||
static fn (array $resource): bool => self::matchesResourceCode((string) $resource['code']),
|
||||
));
|
||||
|
||||
foreach ($resources as $resource) {
|
||||
$resourceId = DB::table('admin_api_resources')
|
||||
->where('code', $resource['code'])
|
||||
->value('id');
|
||||
|
||||
$payload = [
|
||||
'module_code' => $resource['module_code'],
|
||||
'name' => $resource['name'],
|
||||
'http_method' => $resource['http_method'],
|
||||
'uri_pattern' => $resource['uri_pattern'],
|
||||
'route_name' => $resource['route_name'],
|
||||
'auth_mode' => $resource['auth_mode'],
|
||||
'is_audit_required' => $resource['is_audit_required'],
|
||||
'status' => 1,
|
||||
'meta_json' => null,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
if ($resourceId === null) {
|
||||
$resourceId = DB::table('admin_api_resources')->insertGetId($payload + [
|
||||
'code' => $resource['code'],
|
||||
'created_at' => $now,
|
||||
]);
|
||||
} else {
|
||||
DB::table('admin_api_resources')
|
||||
->where('id', (int) $resourceId)
|
||||
->update($payload);
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')
|
||||
->where('api_resource_id', (int) $resourceId)
|
||||
->delete();
|
||||
|
||||
foreach ($resource['permission_codes'] as $permissionCode) {
|
||||
$menuActionId = $menuActionIds[$permissionCode] ?? null;
|
||||
if ($menuActionId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')->insert([
|
||||
'api_resource_id' => (int) $resourceId,
|
||||
'menu_action_id' => (int) $menuActionId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->grantSuperAdminMenuActions();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach (AdminAuthorizationRegistry::resources() as $resource) {
|
||||
if (! self::matchesResourceCode((string) $resource['code'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resourceId = DB::table('admin_api_resources')->where('code', $resource['code'])->value('id');
|
||||
if ($resourceId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
|
||||
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static function matchesResourceCode(string $code): bool
|
||||
{
|
||||
foreach (self::RESOURCE_CODE_PREFIXES as $prefix) {
|
||||
if (str_starts_with($code, $prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function grantSuperAdminMenuActions(): void
|
||||
{
|
||||
$superRoleId = DB::table('admin_roles')->where('slug', 'super_admin')->value('id');
|
||||
if ($superRoleId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menuActionIds = DB::table('admin_menu_actions')
|
||||
->whereIn('permission_code', self::MENU_ACTION_CODES)
|
||||
->pluck('id');
|
||||
|
||||
foreach ($menuActionIds as $menuActionId) {
|
||||
DB::table('admin_role_menu_actions')->updateOrInsert([
|
||||
'role_id' => (int) $superRoleId,
|
||||
'menu_action_id' => (int) $menuActionId,
|
||||
]);
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('admin_role_legacy_permissions')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (['prd.settlement.agent.view', 'prd.settlement.agent.manage', 'prd.agent-line.provision', 'prd.agent.profile.manage'] as $slug) {
|
||||
DB::table('admin_role_legacy_permissions')->updateOrInsert([
|
||||
'role_id' => (int) $superRoleId,
|
||||
'permission_slug' => $slug,
|
||||
], []);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('agent_profiles', function (Blueprint $table): void {
|
||||
$table->boolean('can_create_child_agent')->default(false)->after('can_grant_extra_rebate');
|
||||
$table->boolean('can_create_player')->default(true)->after('can_create_child_agent');
|
||||
});
|
||||
|
||||
\Illuminate\Support\Facades\DB::table('agent_profiles')->update([
|
||||
'can_create_child_agent' => true,
|
||||
'can_create_player' => true,
|
||||
]);
|
||||
|
||||
$nodeService = app(\App\Services\Agent\AgentNodeService::class);
|
||||
\App\Models\AgentNode::query()->each(static function (\App\Models\AgentNode $node) use ($nodeService): void {
|
||||
$nodeService->syncPrimaryOwnerRoleFromProfile($node);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('agent_profiles', function (Blueprint $table): void {
|
||||
$table->dropColumn(['can_create_child_agent', 'can_create_player']);
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user