feat: 切换 schema dump 基线并增强返点结算与管理校验

This commit is contained in:
2026-06-08 17:41:41 +08:00
parent 2d32f006c5
commit 8d5d7f5b17
130 changed files with 5746 additions and 6723 deletions

View File

@@ -0,0 +1 @@

View File

@@ -1,49 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@@ -1,35 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->bigInteger('expiration')->index();
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->bigInteger('expiration')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@@ -1,57 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedSmallInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@@ -1,26 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('currencies', function (Blueprint $table) {
$table->id();
$table->string('code', 16)->unique();
$table->string('name', 64);
$table->unsignedTinyInteger('decimal_places')->default(2);
$table->boolean('is_enabled')->default(true);
$table->boolean('is_bettable')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('currencies');
}
};

View File

@@ -1,31 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('players', function (Blueprint $table) {
$table->id();
$table->string('site_code', 64);
$table->string('site_player_id', 128);
$table->string('username', 128)->nullable();
$table->string('nickname', 128)->nullable();
$table->string('default_currency', 16)->default('NPR');
$table->unsignedTinyInteger('status')->default(0)->comment('0=active,1=frozen,2=blocked');
$table->timestamp('last_login_at')->nullable();
$table->timestamps();
$table->unique(['site_code', 'site_player_id'], 'uk_players_site_player');
$table->index('status', 'idx_players_status');
});
}
public function down(): void
{
Schema::dropIfExists('players');
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('admin_users', function (Blueprint $table) {
$table->id();
$table->string('name', 128);
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->unsignedTinyInteger('status')->default(0)->comment('0=active,1=disabled');
$table->timestamp('last_login_at')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('admin_users');
}
};

View File

@@ -1,45 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('admin_roles', function (Blueprint $table) {
$table->id();
$table->string('slug', 64)->unique();
$table->string('name', 128);
$table->timestamps();
});
Schema::create('admin_permissions', function (Blueprint $table) {
$table->id();
$table->string('slug', 128)->unique();
$table->string('name', 128);
$table->timestamps();
});
Schema::create('admin_role_permissions', function (Blueprint $table) {
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->foreignId('permission_id')->constrained('admin_permissions')->cascadeOnDelete();
$table->primary(['role_id', 'permission_id']);
});
Schema::create('admin_user_roles', function (Blueprint $table) {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->primary(['admin_user_id', 'role_id']);
});
}
public function down(): void
{
Schema::dropIfExists('admin_user_roles');
Schema::dropIfExists('admin_role_permissions');
Schema::dropIfExists('admin_permissions');
Schema::dropIfExists('admin_roles');
}
};

View File

@@ -1,30 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('player_wallets', function (Blueprint $table) {
$table->id();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->string('wallet_type', 32)->default('lottery');
$table->string('currency_code', 16);
$table->bigInteger('balance')->default(0);
$table->bigInteger('frozen_balance')->default(0);
$table->unsignedTinyInteger('status')->default(0)->comment('0=active,1=frozen');
$table->unsignedBigInteger('version')->default(0);
$table->timestamps();
$table->unique(['player_id', 'wallet_type', 'currency_code'], 'uk_player_wallets_player_type_currency');
});
}
public function down(): void
{
Schema::dropIfExists('player_wallets');
}
};

View File

@@ -1,38 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('wallet_txns', function (Blueprint $table) {
$table->id();
$table->string('txn_no', 64)->unique();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->foreignId('wallet_id')->constrained('player_wallets')->cascadeOnDelete();
$table->string('biz_type', 32);
$table->string('biz_no', 64)->nullable();
$table->unsignedTinyInteger('direction')->comment('1=in,2=out');
$table->bigInteger('amount');
$table->bigInteger('balance_before');
$table->bigInteger('balance_after');
$table->string('status', 32);
$table->string('external_ref_no', 64)->nullable();
$table->string('idempotent_key', 64)->nullable();
$table->string('remark', 255)->nullable();
$table->timestamps();
$table->index(['player_id', 'created_at'], 'idx_wallet_txns_player_time');
$table->index(['biz_type', 'biz_no'], 'idx_wallet_txns_biz');
$table->unique(['idempotent_key', 'biz_type'], 'uk_wallet_txns_idempotent_biz');
});
}
public function down(): void
{
Schema::dropIfExists('wallet_txns');
}
};

View File

@@ -1,35 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('transfer_orders', function (Blueprint $table) {
$table->id();
$table->string('transfer_no', 64)->unique();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->string('direction', 16);
$table->string('currency_code', 16);
$table->bigInteger('amount');
$table->string('idempotent_key', 64)->unique();
$table->string('status', 32);
$table->json('external_request_payload')->nullable();
$table->json('external_response_payload')->nullable();
$table->string('external_ref_no', 64)->nullable();
$table->string('fail_reason', 255)->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
$table->index(['player_id', 'created_at']);
});
}
public function down(): void
{
Schema::dropIfExists('transfer_orders');
}
};

View File

@@ -1,35 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('draws', function (Blueprint $table) {
$table->id();
$table->string('draw_no', 32)->unique();
$table->date('business_date');
$table->unsignedInteger('sequence_no');
$table->string('status', 32);
$table->timestamp('start_time')->nullable();
$table->timestamp('close_time')->nullable();
$table->timestamp('draw_time')->nullable();
$table->timestamp('cooling_end_time')->nullable();
$table->string('result_source', 16)->nullable()->comment('rng|manual');
$table->unsignedInteger('current_result_version')->default(0);
$table->unsignedInteger('settle_version')->default(0);
$table->boolean('is_reopened')->default(false);
$table->timestamps();
$table->index(['status', 'draw_time'], 'idx_draws_status_draw_time');
});
}
public function down(): void
{
Schema::dropIfExists('draws');
}
};

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('draw_result_batches', function (Blueprint $table) {
$table->id();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->unsignedInteger('result_version');
$table->string('source_type', 16)->comment('rng|manual');
$table->string('rng_seed_hash', 128)->nullable();
$table->text('raw_seed_encrypted')->nullable();
$table->string('status', 32);
$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();
$table->unique(['draw_id', 'result_version'], 'uk_draw_result_batches_draw_version');
});
}
public function down(): void
{
Schema::dropIfExists('draw_result_batches');
}
};

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('draw_result_items', function (Blueprint $table) {
$table->id();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->foreignId('result_batch_id')->constrained('draw_result_batches')->cascadeOnDelete();
$table->string('prize_type', 32);
$table->unsignedInteger('prize_index')->default(0);
$table->char('number_4d', 4);
$table->char('suffix_3d', 3)->nullable();
$table->char('suffix_2d', 2)->nullable();
$table->unsignedTinyInteger('head_digit')->nullable();
$table->unsignedTinyInteger('tail_digit')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index(['draw_id', 'prize_type', 'prize_index'], 'idx_draw_result_items_draw_prize');
$table->index(['draw_id', 'number_4d'], 'idx_draw_result_items_draw_number');
});
}
public function down(): void
{
Schema::dropIfExists('draw_result_items');
}
};

View File

@@ -1,37 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
/**
* 默认 User 登录未使用:业务玩家为 players后台为 admin_users。
* sessions 表仍保留SESSION_DRIVER=database user_id 可为空,不依赖 users。
*/
return new class extends Migration
{
public function up(): void
{
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('users');
}
public function down(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
};

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('play_types', function (Blueprint $table) {
$table->id();
$table->string('play_code', 32)->unique();
$table->string('category', 16);
$table->unsignedTinyInteger('dimension')->nullable()->comment('2/3/4');
$table->string('bet_mode', 32)->nullable();
$table->string('display_name_zh', 64)->nullable();
$table->string('display_name_en', 64)->nullable();
$table->string('display_name_ne', 64)->nullable();
$table->boolean('is_enabled')->default(true);
$table->integer('sort_order')->default(0);
$table->boolean('supports_multi_number')->default(false);
$table->json('reserved_rule_json')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('play_types');
}
};

View File

@@ -1,44 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('play_config_versions', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('version_no');
$table->string('status', 16);
$table->timestamp('effective_at')->nullable();
$table->foreignId('updated_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->string('reason', 255)->nullable();
$table->timestamps();
});
Schema::create('play_config_items', function (Blueprint $table) {
$table->id();
$table->foreignId('version_id')->constrained('play_config_versions')->cascadeOnDelete();
$table->string('play_code', 32);
$table->boolean('is_enabled')->default(true);
$table->bigInteger('min_bet_amount')->default(0);
$table->bigInteger('max_bet_amount')->default(0);
$table->integer('display_order')->default(0);
$table->text('rule_text_zh')->nullable();
$table->text('rule_text_en')->nullable();
$table->text('rule_text_ne')->nullable();
$table->json('extra_config_json')->nullable();
$table->timestamps();
$table->unique(['version_id', 'play_code'], 'uk_play_config_items_version_play');
});
}
public function down(): void
{
Schema::dropIfExists('play_config_items');
Schema::dropIfExists('play_config_versions');
}
};

View File

@@ -1,46 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('odds_versions', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('version_no');
$table->string('status', 16);
$table->timestamp('effective_at')->nullable();
$table->foreignId('updated_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->string('reason', 255)->nullable();
$table->timestamps();
});
Schema::create('odds_items', function (Blueprint $table) {
$table->id();
$table->foreignId('version_id')->constrained('odds_versions')->cascadeOnDelete();
$table->string('play_code', 32);
$table->string('prize_scope', 32);
$table->bigInteger('odds_value')->default(0);
$table->decimal('rebate_rate', 8, 4)->default(0);
$table->decimal('commission_rate', 8, 4)->default(0);
$table->string('currency_code', 16);
$table->json('extra_config_json')->nullable();
$table->timestamps();
$table->index(['version_id', 'play_code'], 'idx_odds_items_version_play');
$table->unique(
['version_id', 'play_code', 'prize_scope', 'currency_code'],
'uk_odds_items_version_play_prize_currency'
);
});
}
public function down(): void
{
Schema::dropIfExists('odds_items');
Schema::dropIfExists('odds_versions');
}
};

View File

@@ -1,39 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('risk_cap_versions', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('version_no');
$table->string('status', 16);
$table->timestamp('effective_at')->nullable();
$table->foreignId('updated_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->string('reason', 255)->nullable();
$table->timestamps();
});
Schema::create('risk_cap_items', function (Blueprint $table) {
$table->id();
$table->foreignId('version_id')->constrained('risk_cap_versions')->cascadeOnDelete();
$table->foreignId('draw_id')->nullable()->constrained('draws')->nullOnDelete();
$table->char('normalized_number', 4);
$table->bigInteger('cap_amount');
$table->string('cap_type', 16);
$table->timestamps();
$table->index(['version_id', 'draw_id', 'normalized_number'], 'idx_risk_cap_items_lookup');
});
}
public function down(): void
{
Schema::dropIfExists('risk_cap_items');
Schema::dropIfExists('risk_cap_versions');
}
};

View File

@@ -1,34 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('ticket_orders', function (Blueprint $table) {
$table->id();
$table->string('order_no', 64)->unique();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->string('currency_code', 16);
$table->bigInteger('total_bet_amount')->default(0);
$table->bigInteger('total_rebate_amount')->default(0);
$table->bigInteger('total_actual_deduct')->default(0);
$table->bigInteger('total_estimated_payout')->default(0);
$table->string('status', 32);
$table->string('submit_source', 16)->default('h5');
$table->string('client_trace_id', 64)->nullable();
$table->timestamps();
$table->index(['player_id', 'draw_id'], 'idx_ticket_orders_player_draw');
});
}
public function down(): void
{
Schema::dropIfExists('ticket_orders');
}
};

View File

@@ -1,51 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('ticket_items', function (Blueprint $table) {
$table->id();
$table->string('ticket_no', 64)->unique();
$table->foreignId('order_id')->constrained('ticket_orders')->cascadeOnDelete();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->string('original_number', 32)->nullable();
$table->char('normalized_number', 4);
$table->string('play_code', 32);
$table->unsignedTinyInteger('dimension')->nullable()->comment('2/3/4');
$table->unsignedTinyInteger('digit_slot')->nullable()->comment('千百十个位,领域字典');
$table->string('bet_mode', 32)->nullable();
$table->bigInteger('unit_bet_amount')->default(0);
$table->bigInteger('total_bet_amount')->default(0);
$table->decimal('rebate_rate_snapshot', 8, 4)->default(0);
$table->decimal('commission_rate_snapshot', 8, 4)->default(0);
$table->bigInteger('actual_deduct_amount')->default(0);
$table->json('odds_snapshot_json')->nullable();
$table->json('rule_snapshot_json')->nullable();
$table->unsignedInteger('combination_count')->default(1);
$table->bigInteger('estimated_max_payout')->default(0);
$table->bigInteger('risk_locked_amount')->default(0);
$table->string('status', 32);
$table->string('fail_reason_code', 32)->nullable();
$table->string('fail_reason_text', 255)->nullable();
$table->bigInteger('win_amount')->default(0);
$table->bigInteger('jackpot_win_amount')->default(0);
$table->timestamp('settled_at')->nullable();
$table->timestamps();
$table->index(['player_id', 'draw_id'], 'idx_ticket_items_player_draw');
$table->index(['draw_id', 'status'], 'idx_ticket_items_draw_status');
$table->index(['draw_id', 'normalized_number'], 'idx_ticket_items_draw_number');
});
}
public function down(): void
{
Schema::dropIfExists('ticket_items');
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('ticket_combinations', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_item_id')->constrained('ticket_items')->cascadeOnDelete();
$table->unsignedInteger('combination_no')->default(0);
$table->char('number_4d', 4);
$table->bigInteger('bet_amount')->default(0);
$table->bigInteger('estimated_payout')->default(0);
$table->timestamp('created_at')->useCurrent();
$table->index('ticket_item_id', 'idx_ticket_combinations_item');
$table->index('number_4d', 'idx_ticket_combinations_number');
});
}
public function down(): void
{
Schema::dropIfExists('ticket_combinations');
}
};

View File

@@ -1,45 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('risk_pools', function (Blueprint $table) {
$table->id();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->char('normalized_number', 4);
$table->bigInteger('total_cap_amount')->default(0);
$table->bigInteger('locked_amount')->default(0);
$table->bigInteger('remaining_amount')->default(0);
$table->unsignedTinyInteger('sold_out_status')->default(0);
$table->unsignedBigInteger('version')->default(0);
$table->timestamps();
$table->unique(['draw_id', 'normalized_number'], 'uk_risk_pools_draw_number');
$table->index(['draw_id', 'sold_out_status'], 'idx_risk_pools_draw_soldout');
});
Schema::create('risk_pool_lock_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->char('normalized_number', 4);
$table->foreignId('ticket_item_id')->nullable()->constrained('ticket_items')->nullOnDelete();
$table->string('action_type', 16);
$table->bigInteger('amount')->default(0);
$table->string('source_reason', 32)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index(['draw_id', 'normalized_number'], 'idx_risk_lock_logs_draw_number');
});
}
public function down(): void
{
Schema::dropIfExists('risk_pool_lock_logs');
Schema::dropIfExists('risk_pools');
}
};

View File

@@ -1,93 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('settlement_batches', function (Blueprint $table) {
$table->id();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->foreignId('result_batch_id')->constrained('draw_result_batches')->cascadeOnDelete();
$table->unsignedInteger('settle_version')->default(1);
$table->string('status', 32);
$table->unsignedInteger('total_ticket_count')->default(0);
$table->unsignedInteger('total_win_count')->default(0);
$table->bigInteger('total_payout_amount')->default(0);
$table->bigInteger('total_jackpot_payout_amount')->default(0);
$table->string('review_status', 32)->default('pending');
$table->foreignId('reviewed_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->timestamp('reviewed_at')->nullable();
$table->string('review_remark', 255)->nullable();
$table->timestamp('paid_at')->nullable();
$table->timestamp('started_at')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
$table->index(['draw_id', 'settle_version'], 'idx_settlement_batches_draw_version');
});
Schema::create('ticket_settlement_details', function (Blueprint $table) {
$table->id();
$table->foreignId('settlement_batch_id')->constrained('settlement_batches')->cascadeOnDelete();
$table->foreignId('ticket_item_id')->constrained('ticket_items')->cascadeOnDelete();
$table->string('matched_prize_tier', 32)->nullable();
$table->bigInteger('win_amount')->default(0);
$table->bigInteger('jackpot_allocation_amount')->default(0);
$table->json('match_detail_json')->nullable();
$table->timestamps();
$table->unique(['settlement_batch_id', 'ticket_item_id'], 'uk_ticket_settlement_batch_ticket');
});
Schema::create('jackpot_pools', function (Blueprint $table) {
$table->id();
$table->string('currency_code', 16)->unique();
$table->bigInteger('current_amount')->default(0);
$table->decimal('contribution_rate', 8, 4)->default(0);
$table->bigInteger('trigger_threshold')->default(0);
$table->decimal('payout_rate', 8, 4)->default(0);
$table->unsignedInteger('force_trigger_draw_gap')->default(0);
$table->bigInteger('min_bet_amount')->default(0);
$table->unsignedTinyInteger('status')->default(0)->comment('0=off,1=on');
$table->foreignId('last_trigger_draw_id')->nullable()->constrained('draws')->nullOnDelete();
$table->timestamps();
});
Schema::create('jackpot_contributions', function (Blueprint $table) {
$table->id();
$table->foreignId('jackpot_pool_id')->constrained('jackpot_pools')->cascadeOnDelete();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->foreignId('ticket_item_id')->nullable()->constrained('ticket_items')->nullOnDelete();
$table->bigInteger('contribution_amount')->default(0);
$table->string('currency_code', 16);
$table->timestamps();
$table->index(['draw_id', 'player_id'], 'idx_jackpot_contrib_draw_player');
});
Schema::create('jackpot_payout_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('draw_id')->constrained('draws')->cascadeOnDelete();
$table->foreignId('jackpot_pool_id')->constrained('jackpot_pools')->cascadeOnDelete();
$table->string('trigger_type', 32);
$table->bigInteger('total_payout_amount')->default(0);
$table->unsignedInteger('winner_count')->default(0);
$table->json('trigger_snapshot_json')->nullable();
$table->timestamp('created_at')->useCurrent();
});
}
public function down(): void
{
Schema::dropIfExists('jackpot_payout_logs');
Schema::dropIfExists('jackpot_contributions');
Schema::dropIfExists('jackpot_pools');
Schema::dropIfExists('ticket_settlement_details');
Schema::dropIfExists('settlement_batches');
}
};

View File

@@ -1,87 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('report_jobs', function (Blueprint $table) {
$table->id();
$table->string('job_no', 64)->unique();
$table->foreignId('admin_user_id')->nullable()->constrained('admin_users')->nullOnDelete();
$table->string('report_type', 64);
$table->string('export_format', 16)->default('csv');
$table->json('filter_json')->nullable();
$table->string('status', 32);
$table->string('output_path', 512)->nullable();
$table->text('error_message')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
});
Schema::create('audit_logs', function (Blueprint $table) {
$table->id();
$table->string('operator_type', 16);
$table->unsignedBigInteger('operator_id')->default(0);
$table->string('module_code', 32)->nullable();
$table->string('action_code', 32)->nullable();
$table->string('target_type', 32)->nullable();
$table->string('target_id', 64)->nullable();
$table->json('before_json')->nullable();
$table->json('after_json')->nullable();
$table->string('ip', 64)->nullable();
$table->string('user_agent', 255)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index(['operator_type', 'operator_id', 'created_at'], 'idx_audit_logs_operator_time');
$table->index(['module_code', 'action_code'], 'idx_audit_logs_module_action');
});
Schema::create('system_jobs', function (Blueprint $table) {
$table->id();
$table->string('job_key', 128)->unique();
$table->string('name', 128);
$table->string('schedule_cron', 64)->nullable();
$table->boolean('is_enabled')->default(true);
$table->timestamp('last_started_at')->nullable();
$table->timestamp('last_finished_at')->nullable();
$table->string('last_status', 32)->nullable();
$table->timestamps();
});
Schema::create('reconcile_jobs', function (Blueprint $table) {
$table->id();
$table->string('job_no', 64)->unique();
$table->string('reconcile_type', 32);
$table->string('status', 32);
$table->timestamp('period_start')->nullable();
$table->timestamp('period_end')->nullable();
$table->json('summary_json')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
});
Schema::create('reconcile_items', function (Blueprint $table) {
$table->id();
$table->foreignId('reconcile_job_id')->constrained('reconcile_jobs')->cascadeOnDelete();
$table->string('side_a_ref', 128)->nullable();
$table->string('side_b_ref', 128)->nullable();
$table->bigInteger('difference_amount')->default(0);
$table->string('status', 32);
$table->timestamp('resolved_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('reconcile_items');
Schema::dropIfExists('reconcile_jobs');
Schema::dropIfExists('system_jobs');
Schema::dropIfExists('audit_logs');
Schema::dropIfExists('report_jobs');
}
};

View File

@@ -1,34 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
/**
* 【任务 3 · 配置中心】键值运行时配置。
*
* `config/*.php` / `.env` 分工:
* - 密钥、站点 URL 等仍以 env 为准,不进库或不在此表明文扩写密钥;
* - 可随时由运营调整的开关、限额类配置放本表,读时走 LotterySettings 带缓存。
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('lottery_settings', function (Blueprint $table) {
$table->id();
$table->string('setting_key', 160)->unique();
$table->json('value_json');
$table->string('group_name', 64)->default('general')->comment('控制台分组展示用');
$table->string('description_zh')->nullable()->comment('运维说明');
$table->timestamps();
$table->index('group_name', 'idx_lottery_settings_group');
});
}
public function down(): void
{
Schema::dropIfExists('lottery_settings');
}
};

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@@ -1,40 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* 将历史上重复时间戳的 migration 文件名同步到 migrations 表,避免重命名后被视为未执行。
*
* 新装库UPDATE 命中 0 行,无影响。
* 已有库:把旧文件名改为新文件名,与重命名后的 PHP 文件一致。
*
* 若从旧文件名升级且 `php artisan migrate` 报「列已存在」等重复执行错误,请先单独执行本文件再全量 migrate
* php artisan migrate --path=database/migrations/2026_05_09_119999_rename_duplicate_migration_filenames_in_table.php
* php artisan migrate
*/
return new class extends Migration
{
/** @var array<string, string> */
private const RENAMES = [
'2026_05_09_120000_add_username_and_nullable_email_to_admin_users' => '2026_05_09_120001_add_username_and_nullable_email_to_admin_users',
'2026_05_09_120000_migrate_draw_status_to_domain_dict' => '2026_05_09_120002_migrate_draw_status_to_domain_dict',
'2026_05_25_120000_consolidate_play_display_name_columns' => '2026_05_25_120001_consolidate_play_display_name_columns',
'2026_05_25_120000_expand_audit_logs_target_type' => '2026_05_25_120002_expand_audit_logs_target_type',
'2026_05_25_120000_refine_admin_permission_granularity' => '2026_05_25_120003_refine_admin_permission_granularity',
];
public function up(): void
{
foreach (self::RENAMES as $from => $to) {
DB::table('migrations')->where('migration', $from)->update(['migration' => $to]);
}
}
public function down(): void
{
foreach (self::RENAMES as $from => $to) {
DB::table('migrations')->where('migration', $to)->update(['migration' => $from]);
}
}
};

View File

@@ -1,79 +0,0 @@
<?php
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('admin_users', function (Blueprint $table) {
$table->string('username', 64)->nullable()->after('id');
});
$this->backfillUsernames();
Schema::table('admin_users', function (Blueprint $table) {
$table->string('username', 64)->nullable(false)->change();
$table->unique('username');
});
Schema::table('admin_users', function (Blueprint $table) {
$table->dropUnique(['email']);
});
Schema::table('admin_users', function (Blueprint $table) {
$table->string('email')->nullable()->change();
});
}
public function down(): void
{
Schema::table('admin_users', function (Blueprint $table) {
$table->dropUnique(['username']);
});
Schema::table('admin_users', function (Blueprint $table) {
$table->dropColumn('username');
});
Schema::table('admin_users', function (Blueprint $table) {
$table->string('email')->nullable(false)->change();
});
Schema::table('admin_users', function (Blueprint $table) {
$table->unique('email');
});
}
private function backfillUsernames(): void
{
$reserved = [];
foreach (DB::table('admin_users')->orderBy('id')->cursor() as $row) {
$email = (string) $row->email;
$local = Str::lower(Str::before($email, '@'));
$slug = preg_replace('/[^a-z0-9._-]/', '', $local);
$base = Str::substr($slug !== '' ? $slug : 'admin'.(string) $row->id, 0, 50);
if ($base === '') {
$base = 'admin'.(string) $row->id;
}
$candidate = $base;
$n = 0;
while (in_array($candidate, $reserved, true)
|| DB::table('admin_users')->where('username', $candidate)->where('id', '!=', $row->id)->exists()) {
$n++;
$suffix = '_'.$n;
$candidate = Str::substr($base, 0, 64 - strlen($suffix)).$suffix;
}
$reserved[] = $candidate;
DB::table('admin_users')->where('id', $row->id)->update(['username' => $candidate]);
}
}
};

View File

@@ -1,22 +0,0 @@
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
/**
* 将旧版期号状态值迁移为《04-领域字典》约定。
*/
return new class extends Migration
{
public function up(): void
{
DB::table('draws')->where('status', 'pending_review')->update(['status' => 'review']);
DB::table('draws')->where('status', 'published')->update(['status' => 'cooldown']);
}
public function down(): void
{
DB::table('draws')->where('status', 'review')->update(['status' => 'pending_review']);
DB::table('draws')->where('status', 'cooldown')->update(['status' => 'published']);
}
};

View File

@@ -1,25 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('reconcile_jobs', function (Blueprint $table): void {
$table->foreignId('admin_user_id')
->nullable()
->constrained('admin_users')
->nullOnDelete();
});
}
public function down(): void
{
Schema::table('reconcile_jobs', function (Blueprint $table): void {
$table->dropConstrainedForeignId('admin_user_id');
});
}
};

View File

@@ -1,22 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('admin_user_permissions', function (Blueprint $table) {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('permission_id')->constrained('admin_permissions')->cascadeOnDelete();
$table->primary(['admin_user_id', 'permission_id']);
});
}
public function down(): void
{
Schema::dropIfExists('admin_user_permissions');
}
};

View File

@@ -1,728 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$this->createTables();
$this->seedInitialData();
$this->migrateLegacyAssignments();
$this->dropLegacyTables();
}
public function down(): void
{
$this->recreateLegacyTables();
$this->migrateBackToLegacyTables();
Schema::dropIfExists('admin_user_site_roles');
Schema::dropIfExists('admin_user_menu_actions');
Schema::dropIfExists('admin_user_data_scopes');
Schema::dropIfExists('admin_role_menu_actions');
Schema::dropIfExists('admin_role_api_resources');
Schema::dropIfExists('admin_role_menus');
Schema::dropIfExists('admin_role_data_scopes');
Schema::dropIfExists('admin_api_resource_bindings');
Schema::dropIfExists('admin_api_resources');
Schema::dropIfExists('admin_menu_actions');
Schema::dropIfExists('admin_action_catalog');
Schema::dropIfExists('admin_menus');
Schema::dropIfExists('admin_data_scopes');
Schema::dropIfExists('admin_sites');
}
private function createTables(): void
{
Schema::create('admin_sites', function (Blueprint $table): void {
$table->id();
$table->string('code', 64)->unique();
$table->string('name', 128);
$table->string('currency_code', 16)->default('NPR');
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->boolean('is_default')->default(false);
$table->json('extra_json')->nullable();
$table->timestamps();
});
Schema::table('admin_roles', function (Blueprint $table): void {
$table->string('code', 64)->nullable()->after('id');
$table->text('description')->nullable()->after('name');
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled')->after('description');
$table->boolean('is_system')->default(false)->after('status');
$table->unsignedInteger('sort_order')->default(0)->after('is_system');
});
DB::table('admin_roles')->update([
'code' => DB::raw('slug'),
'status' => 1,
'is_system' => true,
'sort_order' => 0,
]);
Schema::table('admin_roles', function (Blueprint $table): void {
$table->string('code', 64)->nullable(false)->change();
$table->unique('code');
});
Schema::create('admin_menus', function (Blueprint $table): void {
$table->id();
$table->foreignId('parent_id')->nullable()->constrained('admin_menus')->nullOnDelete();
$table->string('menu_type', 24)->comment('directory|menu|page');
$table->string('code', 128)->unique();
$table->string('name', 128);
$table->string('path', 255)->nullable();
$table->string('route_name', 255)->nullable();
$table->string('component', 255)->nullable();
$table->string('icon', 128)->nullable();
$table->string('active_menu_code', 128)->nullable();
$table->unsignedInteger('sort_order')->default(0);
$table->boolean('is_visible')->default(true);
$table->boolean('is_cache')->default(false);
$table->boolean('is_external')->default(false);
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->json('meta_json')->nullable();
$table->timestamps();
$table->index(['parent_id', 'sort_order'], 'idx_admin_menus_parent_sort');
});
Schema::create('admin_action_catalog', function (Blueprint $table): void {
$table->id();
$table->string('code', 64)->unique();
$table->string('name', 64);
$table->unsignedInteger('sort_order')->default(0);
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->timestamps();
});
Schema::create('admin_menu_actions', function (Blueprint $table): void {
$table->id();
$table->foreignId('menu_id')->constrained('admin_menus')->cascadeOnDelete();
$table->foreignId('action_id')->constrained('admin_action_catalog')->cascadeOnDelete();
$table->string('permission_code', 128)->unique();
$table->string('name', 128);
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->timestamps();
$table->unique(['menu_id', 'action_id'], 'uk_admin_menu_actions_menu_action');
$table->index(['menu_id', 'status'], 'idx_admin_menu_actions_menu_status');
});
Schema::create('admin_api_resources', function (Blueprint $table): void {
$table->id();
$table->string('code', 128)->unique();
$table->string('module_code', 64);
$table->string('name', 128);
$table->string('http_method', 16);
$table->string('uri_pattern', 255);
$table->string('route_name', 255)->nullable();
$table->string('auth_mode', 24)->default('permission_required')->comment('login_only|permission_required|internal_only');
$table->boolean('is_audit_required')->default(false);
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->json('meta_json')->nullable();
$table->timestamps();
$table->index(['module_code', 'status'], 'idx_admin_api_resources_module_status');
});
Schema::create('admin_api_resource_bindings', function (Blueprint $table): void {
$table->id();
$table->foreignId('api_resource_id')->constrained('admin_api_resources')->cascadeOnDelete();
$table->foreignId('menu_action_id')->constrained('admin_menu_actions')->cascadeOnDelete();
$table->timestamps();
$table->unique(['api_resource_id', 'menu_action_id'], 'uk_admin_api_bindings_api_action');
});
Schema::create('admin_data_scopes', function (Blueprint $table): void {
$table->id();
$table->string('code', 64)->unique();
$table->string('name', 128);
$table->string('scope_type', 32)->comment('all_sites|site_only|site_all_data|site_single_player|self_only');
$table->string('module_code', 64)->nullable();
$table->text('description')->nullable();
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->timestamps();
});
Schema::create('admin_role_menus', function (Blueprint $table): void {
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->foreignId('menu_id')->constrained('admin_menus')->cascadeOnDelete();
$table->primary(['role_id', 'menu_id']);
});
Schema::create('admin_role_menu_actions', function (Blueprint $table): void {
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->foreignId('menu_action_id')->constrained('admin_menu_actions')->cascadeOnDelete();
$table->primary(['role_id', 'menu_action_id']);
});
Schema::create('admin_role_api_resources', function (Blueprint $table): void {
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->foreignId('api_resource_id')->constrained('admin_api_resources')->cascadeOnDelete();
$table->primary(['role_id', 'api_resource_id']);
});
Schema::create('admin_role_data_scopes', function (Blueprint $table): void {
$table->id();
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->foreignId('site_id')->nullable()->constrained('admin_sites')->nullOnDelete();
$table->foreignId('data_scope_id')->constrained('admin_data_scopes')->cascadeOnDelete();
$table->string('module_code', 64)->nullable();
$table->json('constraint_json')->nullable();
$table->timestamps();
$table->unique(['role_id', 'site_id', 'data_scope_id', 'module_code'], 'uk_admin_role_data_scopes');
});
Schema::create('admin_user_site_roles', function (Blueprint $table): void {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('site_id')->constrained('admin_sites')->cascadeOnDelete();
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->timestamp('granted_at')->nullable();
$table->primary(['admin_user_id', 'site_id', 'role_id'], 'pk_admin_user_site_roles');
});
Schema::create('admin_user_menu_actions', function (Blueprint $table): void {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('site_id')->nullable()->constrained('admin_sites')->nullOnDelete();
$table->foreignId('menu_action_id')->constrained('admin_menu_actions')->cascadeOnDelete();
$table->timestamp('granted_at')->nullable();
$table->primary(['admin_user_id', 'site_id', 'menu_action_id'], 'pk_admin_user_menu_actions');
});
Schema::create('admin_user_data_scopes', function (Blueprint $table): void {
$table->id();
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('site_id')->nullable()->constrained('admin_sites')->nullOnDelete();
$table->foreignId('data_scope_id')->constrained('admin_data_scopes')->cascadeOnDelete();
$table->string('module_code', 64)->nullable();
$table->json('constraint_json')->nullable();
$table->timestamps();
$table->unique(['admin_user_id', 'site_id', 'data_scope_id', 'module_code'], 'uk_admin_user_data_scopes');
});
}
private function seedInitialData(): void
{
$now = Carbon::now();
DB::table('admin_sites')->insert([
'code' => 'default_site',
'name' => '默认站点',
'currency_code' => 'NPR',
'status' => 1,
'is_default' => true,
'extra_json' => json_encode(['source' => 'migration'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'created_at' => $now,
'updated_at' => $now,
]);
DB::table('admin_action_catalog')->insert([
['code' => 'view', 'name' => '查看', 'sort_order' => 10, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'create', 'name' => '新增', 'sort_order' => 20, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'update', 'name' => '编辑', 'sort_order' => 30, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'delete', 'name' => '删除', 'sort_order' => 40, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'review', 'name' => '审核', 'sort_order' => 50, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'publish', 'name' => '发布', 'sort_order' => 60, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'export', 'name' => '导出', 'sort_order' => 70, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'manage', 'name' => '管理', 'sort_order' => 80, 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
]);
DB::table('admin_data_scopes')->insert([
['code' => 'all_sites', 'name' => '全站点', 'scope_type' => 'all_sites', 'module_code' => null, 'description' => '可访问所有站点数据', 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'site_only', 'name' => '指定站点', 'scope_type' => 'site_only', 'module_code' => null, 'description' => '仅限授权站点登录和访问', 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'site_all_data', 'name' => '站点内全部数据', 'scope_type' => 'site_all_data', 'module_code' => null, 'description' => '可访问站点内全部业务数据', 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'site_single_player', 'name' => '站点内单玩家', 'scope_type' => 'site_single_player', 'module_code' => 'player_service', 'description' => '仅限按指定玩家处理客诉与查单', 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
['code' => 'self_only', 'name' => '仅本人相关', 'scope_type' => 'self_only', 'module_code' => 'audit', 'description' => '仅可查看与自身相关的数据', 'status' => 1, 'created_at' => $now, 'updated_at' => $now],
]);
$this->seedMenuTree($now);
$this->seedApiResources($now);
}
private function seedMenuTree(Carbon $now): void
{
$menus = [
['parent_code' => null, 'menu_type' => 'menu', 'code' => 'dashboard', 'name' => '仪表盘', 'path' => '/admin', 'route_name' => 'admin.dashboard', 'component' => 'dashboard/index', 'icon' => 'layout-dashboard', 'sort_order' => 10],
['parent_code' => null, 'menu_type' => 'directory', 'code' => 'draw', 'name' => '开奖管理', 'path' => '/admin/draws', 'route_name' => null, 'component' => null, 'icon' => 'dice-5', 'sort_order' => 20],
['parent_code' => 'draw', 'menu_type' => 'page', 'code' => 'draw.results', 'name' => '开奖结果', 'path' => '/admin/draws', 'route_name' => 'admin.draws.index', 'component' => 'draw/results', 'icon' => null, 'sort_order' => 10],
['parent_code' => 'draw', 'menu_type' => 'page', 'code' => 'draw.review', 'name' => '开奖审核', 'path' => '/admin/draws/review', 'route_name' => 'admin.draws.review', 'component' => 'draw/review', 'icon' => null, 'sort_order' => 20],
['parent_code' => null, 'menu_type' => 'directory', 'code' => 'config', 'name' => '运营配置', 'path' => '/admin/config', 'route_name' => null, 'component' => null, 'icon' => 'sliders-horizontal', 'sort_order' => 30],
['parent_code' => 'config', 'menu_type' => 'page', 'code' => 'config.play', 'name' => '玩法开关', 'path' => '/admin/config/play-switches', 'route_name' => 'admin.config.play', 'component' => 'config/play', 'icon' => null, 'sort_order' => 10],
['parent_code' => 'config', 'menu_type' => 'page', 'code' => 'config.odds', 'name' => '赔率配置', 'path' => '/admin/config/odds', 'route_name' => 'admin.config.odds', 'component' => 'config/odds', 'icon' => null, 'sort_order' => 20],
['parent_code' => 'config', 'menu_type' => 'page', 'code' => 'config.risk_cap', 'name' => '封顶配置', 'path' => '/admin/config/play-limits', 'route_name' => 'admin.config.risk_cap', 'component' => 'config/risk-cap', 'icon' => null, 'sort_order' => 30],
['parent_code' => 'config', 'menu_type' => 'page', 'code' => 'config.jackpot', 'name' => 'Jackpot 配置', 'path' => '/admin/jackpot/pools', 'route_name' => 'admin.jackpot.pools', 'component' => 'config/jackpot', 'icon' => null, 'sort_order' => 40],
['parent_code' => null, 'menu_type' => 'page', 'code' => 'risk.monitor', 'name' => '风控监控', 'path' => '/admin/risk', 'route_name' => 'admin.risk.monitor', 'component' => 'risk/monitor', 'icon' => 'shield-alert', 'sort_order' => 40],
['parent_code' => null, 'menu_type' => 'page', 'code' => 'settlement.batch', 'name' => '结算批次', 'path' => '/admin/settlement-batches', 'route_name' => 'admin.settlement.batches', 'component' => 'settlement/batches', 'icon' => 'receipt-text', 'sort_order' => 50],
['parent_code' => null, 'menu_type' => 'directory', 'code' => 'service', 'name' => '客服财务', 'path' => '/admin/service-desk', 'route_name' => null, 'component' => null, 'icon' => 'hand-helping', 'sort_order' => 60],
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.players', 'name' => '玩家查询', 'path' => '/admin/players', 'route_name' => 'admin.players.index', 'component' => 'service/players', 'icon' => null, 'sort_order' => 10],
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.tickets', 'name' => '玩家注单', 'path' => '/admin/tickets', 'route_name' => 'admin.tickets.index', 'component' => 'service/tickets', 'icon' => null, 'sort_order' => 20],
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.wallet', 'name' => '钱包流水', 'path' => '/admin/wallet/transactions', 'route_name' => 'admin.wallet.transactions', 'component' => 'service/wallet', 'icon' => null, 'sort_order' => 30],
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.reconcile', 'name' => '对账管理', 'path' => '/admin/reconcile', 'route_name' => 'admin.reconcile.index', 'component' => 'service/reconcile', 'icon' => null, 'sort_order' => 40],
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.audit', 'name' => '审计日志', 'path' => '/admin/audit-logs', 'route_name' => 'admin.audit.index', 'component' => 'service/audit', 'icon' => null, 'sort_order' => 60],
['parent_code' => null, 'menu_type' => 'page', 'code' => 'system.admin_user', 'name' => '管理员权限', 'path' => '/admin/admin-users', 'route_name' => 'admin.system.admin-users', 'component' => 'system/admin-users', 'icon' => 'users-round', 'sort_order' => 70],
['parent_code' => null, 'menu_type' => 'page', 'code' => 'system.admin_role', 'name' => '角色管理', 'path' => '/admin/admin-roles', 'route_name' => 'admin.system.admin-roles', 'component' => 'system/admin-roles', 'icon' => 'shield-check', 'sort_order' => 71],
];
$menuIds = [];
foreach ($menus as $menu) {
$menuIds[$menu['code']] = DB::table('admin_menus')->insertGetId([
'parent_id' => $menu['parent_code'] === null ? null : $menuIds[$menu['parent_code']],
'menu_type' => $menu['menu_type'],
'code' => $menu['code'],
'name' => $menu['name'],
'path' => $menu['path'],
'route_name' => $menu['route_name'],
'component' => $menu['component'],
'icon' => $menu['icon'],
'active_menu_code' => null,
'sort_order' => $menu['sort_order'],
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
$actionIds = DB::table('admin_action_catalog')->pluck('id', 'code');
$menuActions = [
['menu_code' => 'draw.results', 'action_code' => 'view', 'permission_code' => 'draw.results.view', 'name' => '开奖结果查看'],
['menu_code' => 'draw.review', 'action_code' => 'review', 'permission_code' => 'draw.review.review', 'name' => '开奖审核'],
['menu_code' => 'draw.review', 'action_code' => 'publish', 'permission_code' => 'draw.review.publish', 'name' => '开奖发布'],
['menu_code' => 'config.play', 'action_code' => 'manage', 'permission_code' => 'config.play.manage', 'name' => '玩法开关管理'],
['menu_code' => 'config.odds', 'action_code' => 'manage', 'permission_code' => 'config.odds.manage', 'name' => '赔率配置管理'],
['menu_code' => 'config.risk_cap', 'action_code' => 'view', 'permission_code' => 'config.risk_cap.view', 'name' => '封顶配置查看'],
['menu_code' => 'config.risk_cap', 'action_code' => 'manage', 'permission_code' => 'config.risk_cap.manage', 'name' => '封顶配置管理'],
['menu_code' => 'config.jackpot', 'action_code' => 'view', 'permission_code' => 'config.jackpot.view', 'name' => 'Jackpot 查看'],
['menu_code' => 'config.jackpot', 'action_code' => 'manage', 'permission_code' => 'config.jackpot.manage', 'name' => 'Jackpot 管理'],
['menu_code' => 'risk.monitor', 'action_code' => 'view', 'permission_code' => 'risk.monitor.view', 'name' => '风控监控查看'],
['menu_code' => 'settlement.batch', 'action_code' => 'view', 'permission_code' => 'settlement.batch.view', 'name' => '结算查看'],
['menu_code' => 'settlement.batch', 'action_code' => 'review', 'permission_code' => 'settlement.batch.review', 'name' => '结算审核'],
['menu_code' => 'settlement.batch', 'action_code' => 'manage', 'permission_code' => 'settlement.batch.manage', 'name' => '结算执行'],
['menu_code' => 'service.players', 'action_code' => 'view', 'permission_code' => 'service.players.view', 'name' => '玩家查询查看'],
['menu_code' => 'service.players', 'action_code' => 'manage', 'permission_code' => 'service.players.manage', 'name' => '玩家查询管理'],
['menu_code' => 'service.players', 'action_code' => 'update', 'permission_code' => 'service.players.freeze', 'name' => '冻结解冻玩家'],
['menu_code' => 'service.tickets', 'action_code' => 'view', 'permission_code' => 'service.tickets.view', 'name' => '玩家注单查看'],
['menu_code' => 'service.wallet', 'action_code' => 'view', 'permission_code' => 'service.wallet.view', 'name' => '钱包流水查看'],
['menu_code' => 'service.wallet', 'action_code' => 'manage', 'permission_code' => 'service.wallet.manage', 'name' => '钱包流水管理'],
['menu_code' => 'service.reconcile', 'action_code' => 'view', 'permission_code' => 'service.reconcile.view', 'name' => '对账查看'],
['menu_code' => 'service.reconcile', 'action_code' => 'manage', 'permission_code' => 'service.reconcile.manage', 'name' => '对账管理'],
['menu_code' => 'service.audit', 'action_code' => 'view', 'permission_code' => 'service.audit.view', 'name' => '审计查看'],
['menu_code' => 'system.admin_user', 'action_code' => 'manage', 'permission_code' => 'system.admin_user.manage', 'name' => '管理员权限管理'],
['menu_code' => 'system.admin_role', 'action_code' => 'manage', 'permission_code' => 'system.admin_role.manage', 'name' => '角色权限管理'],
];
foreach ($menuActions as $row) {
DB::table('admin_menu_actions')->insert([
'menu_id' => $menuIds[$row['menu_code']],
'action_id' => $actionIds[$row['action_code']],
'permission_code' => $row['permission_code'],
'name' => $row['name'],
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
private function seedApiResources(Carbon $now): void
{
$resources = [
['code' => 'admin.dashboard', 'module_code' => 'dashboard', 'name' => '后台仪表盘', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/dashboard', 'route_name' => 'api.v1.admin.dashboard', 'auth_mode' => 'login_only', 'is_audit_required' => false, 'permission_codes' => []],
['code' => 'admin.draws.index', 'module_code' => 'draw', 'name' => '期开奖列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/draws', 'route_name' => 'api.v1.admin.draws.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['draw.results.view']],
['code' => 'admin.draws.show', 'module_code' => 'draw', 'name' => '期开奖详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/draws/{draw}', 'route_name' => 'api.v1.admin.draws.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['draw.results.view']],
['code' => 'admin.draws.publish', 'module_code' => 'draw', 'name' => '开奖发布', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/result-batches/{batch}/publish', 'route_name' => 'api.v1.admin.draws.result-batches.publish', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['draw.review.publish']],
['code' => 'admin.draws.settlement.run', 'module_code' => 'settlement', 'name' => '执行结算', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/settlement/run', 'route_name' => 'api.v1.admin.draws.settlement.run', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['settlement.batch.manage', 'settlement.batch.review']],
['code' => 'admin.wallet.transactions', 'module_code' => 'wallet', 'name' => '钱包流水查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/wallet/transactions', 'route_name' => 'api.v1.admin.wallet.transactions', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.wallet.view', 'service.wallet.manage']],
['code' => 'admin.wallet.transfer-orders', 'module_code' => 'wallet', 'name' => '转账单查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/wallet/transfer-orders', 'route_name' => 'api.v1.admin.wallet.transfer-orders', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.wallet.view', 'service.wallet.manage']],
['code' => 'admin.players.wallets', 'module_code' => 'player_service', 'name' => '玩家钱包查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/wallets', 'route_name' => 'api.v1.admin.players.wallets', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.wallet.view']],
['code' => 'admin.players.ticket-items', 'module_code' => 'player_service', 'name' => '玩家注单查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/ticket-items', 'route_name' => 'api.v1.admin.players.ticket-items.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.tickets.view']],
['code' => 'admin.reconcile.index', 'module_code' => 'reconcile', 'name' => '对账任务列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reconcile-jobs', 'route_name' => 'api.v1.admin.reconcile-jobs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.reconcile.view', 'service.reconcile.manage']],
['code' => 'admin.reconcile.store', 'module_code' => 'reconcile', 'name' => '创建对账任务', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/reconcile-jobs', 'route_name' => 'api.v1.admin.reconcile-jobs.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.reconcile.manage']],
['code' => 'admin.audit.index', 'module_code' => 'audit', 'name' => '审计日志查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/audit-logs', 'route_name' => 'api.v1.admin.audit-logs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.audit.view']],
['code' => 'admin.admin-users.index', 'module_code' => 'system', 'name' => '管理员列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-users', 'route_name' => 'api.v1.admin.admin-users.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['system.admin_user.manage']],
['code' => 'admin.admin-users.permission-catalog', 'module_code' => 'system', 'name' => '管理员权限目录', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-user-permission-catalog', 'route_name' => 'api.v1.admin.admin-users.permission-catalog', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['system.admin_user.manage']],
['code' => 'admin.admin-users.permissions.sync', 'module_code' => 'system', 'name' => '管理员权限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-users/{admin_user}/permissions', 'route_name' => 'api.v1.admin.admin-users.permissions.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['system.admin_user.manage']],
];
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
foreach ($resources as $resource) {
$resourceId = DB::table('admin_api_resources')->insertGetId([
'code' => $resource['code'],
'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,
'created_at' => $now,
'updated_at' => $now,
]);
foreach ($resource['permission_codes'] as $permissionCode) {
if (! isset($menuActionIds[$permissionCode])) {
continue;
}
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => $resourceId,
'menu_action_id' => $menuActionIds[$permissionCode],
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
private function migrateLegacyAssignments(): void
{
$now = Carbon::now();
$defaultSiteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
$legacyRoles = DB::table('admin_roles')
->select('id', 'code', 'slug', 'name')
->get();
$legacyRoleAssignments = DB::table('admin_user_roles')->get();
foreach ($legacyRoleAssignments as $row) {
$legacyRoleId = (int) $row->role_id;
DB::table('admin_user_site_roles')->insert([
'admin_user_id' => (int) $row->admin_user_id,
'site_id' => $defaultSiteId,
'role_id' => $legacyRoleId,
'granted_at' => $now,
]);
}
$legacyPermissionById = DB::table('admin_permissions')->pluck('slug', 'id');
$legacyRolePermissions = DB::table('admin_role_permissions')->get()->groupBy('role_id');
$legacyUserPermissions = DB::table('admin_user_permissions')->get()->groupBy('admin_user_id');
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$apiResourceIdsByPermission = DB::table('admin_api_resource_bindings')
->join('admin_menu_actions', 'admin_menu_actions.id', '=', 'admin_api_resource_bindings.menu_action_id')
->select('admin_menu_actions.permission_code', 'admin_api_resource_bindings.api_resource_id')
->get()
->groupBy('permission_code')
->map(static fn ($rows) => $rows->pluck('api_resource_id')->all());
$menuIdsByPermission = DB::table('admin_menu_actions')
->join('admin_menus', 'admin_menus.id', '=', 'admin_menu_actions.menu_id')
->pluck('admin_menus.id', 'admin_menu_actions.permission_code');
$legacyToNewPermissionMap = [
'prd.users.manage' => ['service.players.manage'],
'prd.users.view_finance' => ['service.players.view', 'service.wallet.view'],
'prd.users.view_cs' => ['service.players.view', 'service.tickets.view'],
'prd.play_switch.manage' => ['config.play.manage'],
'prd.odds.manage' => ['config.odds.manage'],
'prd.risk_cap.manage' => ['config.risk_cap.manage'],
'prd.risk_cap.view' => ['config.risk_cap.view'],
'prd.rebate.manage' => ['config.odds.manage'],
'prd.rebate.view' => ['config.odds.manage'],
'prd.jackpot.manage' => ['config.jackpot.manage'],
'prd.jackpot.view' => ['config.jackpot.view'],
'prd.draw_result.manage' => ['draw.results.view', 'draw.review.review', 'draw.review.publish', 'risk.monitor.view'],
'prd.draw_result.view' => ['draw.results.view', 'risk.monitor.view'],
'prd.payout.manage' => ['settlement.batch.manage', 'settlement.batch.view'],
'prd.payout.review' => ['settlement.batch.review', 'settlement.batch.view'],
'prd.payout.view' => ['settlement.batch.view'],
'prd.wallet_reconcile.manage' => ['service.wallet.manage', 'service.reconcile.manage'],
'prd.wallet_reconcile.view' => ['service.wallet.view', 'service.reconcile.view'],
'prd.wallet_reconcile.view_cs' => ['service.wallet.view', 'service.reconcile.view'],
'prd.audit.all' => ['service.audit.view'],
'prd.audit.self' => ['service.audit.view'],
'prd.audit.finance' => ['service.audit.view'],
'prd.admin_user.manage' => ['system.admin_user.manage'],
'prd.player_freeze.manage' => ['service.players.freeze'],
'prd.wallet_adjust.manage' => ['service.wallet.manage'],
'prd.draw_reopen.manage' => ['draw.review.publish'],
];
foreach ($legacyRoles as $role) {
$roleId = (int) $role->id;
$grantedPermissions = [];
foreach ($legacyRolePermissions->get((int) $role->id, collect()) as $pivot) {
$permissionId = (int) $pivot->permission_id;
$legacySlug = $legacyPermissionById[$permissionId] ?? null;
if (! is_string($legacySlug)) {
continue;
}
foreach ($legacyToNewPermissionMap[$legacySlug] ?? [] as $permissionCode) {
$grantedPermissions[$permissionCode] = true;
}
}
foreach (array_keys($grantedPermissions) as $permissionCode) {
if (isset($menuIdsByPermission[$permissionCode])) {
$this->grantMenuWithAncestors($roleId, (int) $menuIdsByPermission[$permissionCode]);
}
if (isset($menuActionIds[$permissionCode])) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => $roleId,
'menu_action_id' => (int) $menuActionIds[$permissionCode],
]);
}
foreach ($apiResourceIdsByPermission[$permissionCode] ?? [] as $apiResourceId) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => $roleId,
'api_resource_id' => (int) $apiResourceId,
]);
}
}
$roleCode = (string) ($role->code ?: $role->slug);
$this->assignRoleDataScopes($roleId, $roleCode, $defaultSiteId, $now);
}
foreach ($legacyUserPermissions as $adminUserId => $pivots) {
$grantedPermissions = [];
foreach ($pivots as $pivot) {
$permissionId = (int) $pivot->permission_id;
$legacySlug = $legacyPermissionById[$permissionId] ?? null;
if (! is_string($legacySlug)) {
continue;
}
foreach ($legacyToNewPermissionMap[$legacySlug] ?? [] as $permissionCode) {
$grantedPermissions[$permissionCode] = true;
}
}
foreach (array_keys($grantedPermissions) as $permissionCode) {
if (! isset($menuActionIds[$permissionCode])) {
continue;
}
DB::table('admin_user_menu_actions')->updateOrInsert([
'admin_user_id' => (int) $adminUserId,
'site_id' => $defaultSiteId,
'menu_action_id' => (int) $menuActionIds[$permissionCode],
], [
'granted_at' => $now,
]);
}
}
}
private function assignRoleDataScopes(int $roleId, string $roleCode, int $siteId, Carbon $now): void
{
$dataScopeIds = DB::table('admin_data_scopes')->pluck('id', 'code');
$rows = match ($roleCode) {
'super_admin' => [
['scope_code' => 'all_sites', 'module_code' => null],
['scope_code' => 'site_all_data', 'module_code' => null],
],
'risk_operator' => [
['scope_code' => 'site_only', 'module_code' => null],
['scope_code' => 'site_all_data', 'module_code' => 'risk'],
['scope_code' => 'self_only', 'module_code' => 'audit'],
],
'finance' => [
['scope_code' => 'site_only', 'module_code' => null],
['scope_code' => 'site_all_data', 'module_code' => 'wallet'],
['scope_code' => 'site_all_data', 'module_code' => 'settlement'],
['scope_code' => 'site_all_data', 'module_code' => 'report'],
['scope_code' => 'site_all_data', 'module_code' => 'reconcile'],
],
'customer_service' => [
['scope_code' => 'site_only', 'module_code' => null],
['scope_code' => 'site_single_player', 'module_code' => 'player_service'],
],
default => [
['scope_code' => 'site_only', 'module_code' => null],
],
};
foreach ($rows as $row) {
$scopeId = $dataScopeIds[$row['scope_code']] ?? null;
if ($scopeId === null) {
continue;
}
DB::table('admin_role_data_scopes')->insert([
'role_id' => $roleId,
'site_id' => $row['scope_code'] === 'all_sites' ? null : $siteId,
'data_scope_id' => (int) $scopeId,
'module_code' => $row['module_code'],
'constraint_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
private function grantMenuWithAncestors(int $roleId, int $menuId): void
{
$currentMenuId = $menuId;
while ($currentMenuId > 0) {
DB::table('admin_role_menus')->updateOrInsert([
'role_id' => $roleId,
'menu_id' => $currentMenuId,
]);
$parentId = DB::table('admin_menus')->where('id', $currentMenuId)->value('parent_id');
$currentMenuId = $parentId === null ? 0 : (int) $parentId;
}
}
private function dropLegacyTables(): void
{
Schema::dropIfExists('admin_user_permissions');
Schema::dropIfExists('admin_user_roles');
Schema::dropIfExists('admin_role_permissions');
Schema::dropIfExists('admin_permissions');
}
private function recreateLegacyTables(): void
{
Schema::table('admin_roles', function (Blueprint $table): void {
$table->dropUnique(['code']);
$table->dropColumn(['code', 'description', 'status', 'is_system', 'sort_order']);
});
Schema::create('admin_permissions', function (Blueprint $table): void {
$table->id();
$table->string('slug', 128)->unique();
$table->string('name', 128);
$table->timestamps();
});
Schema::create('admin_role_permissions', function (Blueprint $table): void {
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->foreignId('permission_id')->constrained('admin_permissions')->cascadeOnDelete();
$table->primary(['role_id', 'permission_id']);
});
Schema::create('admin_user_roles', function (Blueprint $table): void {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->primary(['admin_user_id', 'role_id']);
});
Schema::create('admin_user_permissions', function (Blueprint $table): void {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('permission_id')->constrained('admin_permissions')->cascadeOnDelete();
$table->primary(['admin_user_id', 'permission_id']);
});
}
private function migrateBackToLegacyTables(): void
{
$now = Carbon::now();
$legacyPermissions = [
['slug' => 'prd.users.manage', 'name' => '用户管理·可管理'],
['slug' => 'prd.users.view_finance', 'name' => '用户管理·财务查看'],
['slug' => 'prd.users.view_cs', 'name' => '用户管理·客服单用户'],
['slug' => 'prd.play_switch.manage', 'name' => '玩法开关·可管理'],
['slug' => 'prd.odds.manage', 'name' => '赔率配置·可管理'],
['slug' => 'prd.risk_cap.manage', 'name' => '封顶配置·可管理'],
['slug' => 'prd.risk_cap.view', 'name' => '封顶配置·查看'],
['slug' => 'prd.rebate.manage', 'name' => '佣金/回水·可管理'],
['slug' => 'prd.rebate.view', 'name' => '佣金/回水·查看'],
['slug' => 'prd.jackpot.manage', 'name' => 'Jackpot 配置·可管理'],
['slug' => 'prd.jackpot.view', 'name' => 'Jackpot 配置·查看'],
['slug' => 'prd.draw_result.manage', 'name' => '开奖结果录入·可管理'],
['slug' => 'prd.draw_result.view', 'name' => '开奖结果·查看'],
['slug' => 'prd.draw_reopen.manage', 'name' => '开奖结果重开·可管理'],
['slug' => 'prd.payout.manage', 'name' => '派彩确认·可管理'],
['slug' => 'prd.payout.review', 'name' => '派彩确认·可审核'],
['slug' => 'prd.payout.view', 'name' => '派彩确认·查看'],
['slug' => 'prd.wallet_reconcile.manage', 'name' => '钱包对账·可管理'],
['slug' => 'prd.wallet_reconcile.view', 'name' => '钱包对账·查看'],
['slug' => 'prd.wallet_reconcile.view_cs', 'name' => '钱包对账·客服单用户'],
['slug' => 'prd.wallet_adjust.manage', 'name' => '补单/冲正·可管理'],
['slug' => 'prd.audit.all', 'name' => '审计日志·全部'],
['slug' => 'prd.audit.self', 'name' => '审计日志·自身相关'],
['slug' => 'prd.audit.finance', 'name' => '审计日志·资金相关'],
['slug' => 'prd.player_freeze.manage', 'name' => '冻结/解冻玩家·可管理'],
['slug' => 'prd.admin_user.manage', 'name' => '后台用户权限管理·可管理'],
];
foreach ($legacyPermissions as $permission) {
DB::table('admin_permissions')->insert([
'slug' => $permission['slug'],
'name' => $permission['name'],
'created_at' => $now,
'updated_at' => $now,
]);
}
$permissionIds = DB::table('admin_permissions')->pluck('id', 'slug');
$roleCodeMap = DB::table('admin_roles')->pluck('id', 'slug');
$rolePermissionMap = [
'super_admin' => array_keys($permissionIds->all()),
'risk_operator' => [
'prd.play_switch.manage',
'prd.odds.manage',
'prd.risk_cap.manage',
'prd.rebate.manage',
'prd.jackpot.manage',
'prd.draw_result.manage',
'prd.payout.review',
'prd.wallet_reconcile.view',
'prd.audit.self',
'prd.player_freeze.manage',
],
'finance' => [
'prd.users.view_finance',
'prd.risk_cap.view',
'prd.rebate.view',
'prd.jackpot.view',
'prd.draw_result.view',
'prd.payout.view',
'prd.wallet_reconcile.manage',
'prd.wallet_adjust.manage',
'prd.audit.finance',
],
'customer_service' => [
'prd.users.view_cs',
'prd.draw_result.view',
'prd.wallet_reconcile.view_cs',
],
];
foreach ($rolePermissionMap as $roleCode => $permissionSlugs) {
$roleId = $roleCodeMap[$roleCode] ?? null;
if ($roleId === null) {
continue;
}
foreach ($permissionSlugs as $slug) {
$permissionId = $permissionIds[$slug] ?? null;
if ($permissionId === null) {
continue;
}
DB::table('admin_role_permissions')->insert([
'role_id' => (int) $roleId,
'permission_id' => (int) $permissionId,
]);
}
}
$userRoles = DB::table('admin_user_site_roles')
->select('admin_user_id', 'role_id')
->distinct()
->get();
foreach ($userRoles as $row) {
DB::table('admin_user_roles')->insert([
'admin_user_id' => (int) $row->admin_user_id,
'role_id' => (int) $row->role_id,
]);
}
}
};

View File

@@ -1,79 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('play_config_items', function (Blueprint $table): void {
$table->string('category', 16)->nullable()->after('play_code');
$table->unsignedTinyInteger('dimension')->nullable()->after('category');
$table->string('bet_mode', 32)->nullable()->after('dimension');
$table->string('display_name_zh', 64)->nullable()->after('bet_mode');
$table->string('display_name_en', 64)->nullable()->after('display_name_zh');
$table->string('display_name_ne', 64)->nullable()->after('display_name_en');
$table->boolean('supports_multi_number')->default(false)->after('display_name_ne');
$table->json('reserved_rule_json')->nullable()->after('supports_multi_number');
});
$playTypes = DB::table('play_types')
->select([
'play_code',
'category',
'dimension',
'bet_mode',
'display_name_zh',
'display_name_en',
'display_name_ne',
'supports_multi_number',
'reserved_rule_json',
])
->get()
->keyBy('play_code');
DB::table('play_config_items')
->select(['id', 'play_code'])
->orderBy('id')
->chunkById(200, function ($rows) use ($playTypes): void {
foreach ($rows as $row) {
$pt = $playTypes->get($row->play_code);
if ($pt === null) {
continue;
}
DB::table('play_config_items')
->where('id', $row->id)
->update([
'category' => $pt->category,
'dimension' => $pt->dimension,
'bet_mode' => $pt->bet_mode,
'display_name_zh' => $pt->display_name_zh,
'display_name_en' => $pt->display_name_en,
'display_name_ne' => $pt->display_name_ne,
'supports_multi_number' => (bool) $pt->supports_multi_number,
'reserved_rule_json' => $pt->reserved_rule_json,
]);
}
}, 'id');
}
public function down(): void
{
Schema::table('play_config_items', function (Blueprint $table): void {
$table->dropColumn([
'category',
'dimension',
'bet_mode',
'display_name_zh',
'display_name_en',
'display_name_ne',
'supports_multi_number',
'reserved_rule_json',
]);
});
}
};

View File

@@ -1,22 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('jackpot_pools', function (Blueprint $table): void {
$table->json('combo_trigger_play_codes')->nullable()->after('min_bet_amount');
});
}
public function down(): void
{
Schema::table('jackpot_pools', function (Blueprint $table): void {
$table->dropColumn('combo_trigger_play_codes');
});
}
};

View File

@@ -1,28 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('ticket_orders', function (Blueprint $table): void {
$table->unsignedInteger('play_config_version_no')->default(0)->after('client_trace_id');
$table->unsignedInteger('odds_version_no')->default(0)->after('play_config_version_no');
$table->unsignedInteger('risk_cap_version_no')->default(0)->after('odds_version_no');
});
}
public function down(): void
{
Schema::table('ticket_orders', function (Blueprint $table): void {
$table->dropColumn([
'play_config_version_no',
'odds_version_no',
'risk_cap_version_no',
]);
});
}
};

View File

@@ -1,83 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
use App\Support\AdminAuthorizationRegistry;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
foreach (AdminAuthorizationRegistry::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,
]);
}
}
DB::table('admin_role_api_resources')->delete();
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->select('rma.role_id', 'arb.api_resource_id')
->distinct()
->get();
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->insert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $row->api_resource_id,
]);
}
}
public function down(): void
{
// 保持数据升级可逆风险最低:不在 down 中尝试删除资源,避免误删线上已使用授权关系。
}
};

View File

@@ -1,51 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$currencyCodes = DB::table('currencies')
->where('is_enabled', true)
->where('is_bettable', true)
->pluck('code')
->filter(static fn ($code): bool => is_string($code) && trim($code) !== '')
->map(static fn (string $code): string => strtoupper($code))
->unique()
->values();
if ($currencyCodes->isEmpty()) {
$currencyCodes = collect([strtoupper((string) config('lottery.default_currency', 'NPR'))]);
}
foreach ($currencyCodes as $currencyCode) {
$exists = DB::table('jackpot_pools')->where('currency_code', $currencyCode)->exists();
if ($exists) {
continue;
}
DB::table('jackpot_pools')->insert([
'currency_code' => $currencyCode,
'current_amount' => 0,
'contribution_rate' => '0.0200',
'trigger_threshold' => 100_000_000,
'payout_rate' => '0.5000',
'force_trigger_draw_gap' => 100,
'min_bet_amount' => 100,
'status' => 0,
'last_trigger_draw_id' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
public function down(): void
{
// 保留奖池配置与水位,避免回滚误删运营数据。
}
};

View File

@@ -1,48 +0,0 @@
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Support\AdminPermissionBridge;
return new class extends Migration
{
public function up(): void
{
Schema::create('admin_role_legacy_permissions', function (Blueprint $table): void {
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->string('permission_slug', 128);
$table->timestamps();
$table->primary(['role_id', 'permission_slug'], 'pk_admin_role_legacy_permissions');
});
$now = now();
$roleCodes = DB::table('admin_role_menu_actions as rma')
->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id')
->where('ma.status', 1)
->select('rma.role_id', 'ma.permission_code')
->get()
->groupBy('role_id');
foreach ($roleCodes as $roleId => $rows) {
$slugs = AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes(
$rows->pluck('permission_code')->all(),
);
foreach ($slugs as $slug) {
DB::table('admin_role_legacy_permissions')->insert([
'role_id' => (int) $roleId,
'permission_slug' => $slug,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
public function down(): void
{
Schema::dropIfExists('admin_role_legacy_permissions');
}
};

View File

@@ -1,124 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
use App\Support\AdminAuthorizationRegistry;
use App\Support\AdminPermissionBridge;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$actionCatalogId = (int) DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
$adminUserMenuId = (int) DB::table('admin_menus')->where('code', 'system.admin_user')->value('id');
if ($adminUserMenuId > 0) {
$adminUserMenu = DB::table('admin_menus')->where('id', $adminUserMenuId)->first();
DB::table('admin_menus')->updateOrInsert(
['code' => 'system.admin_role'],
[
'parent_id' => $adminUserMenu->parent_id,
'menu_type' => 'page',
'name' => '角色管理',
'path' => '/admin/admin-roles',
'route_name' => 'admin.system.admin-roles',
'component' => 'system/admin-roles',
'icon' => 'shield-check',
'active_menu_code' => null,
'sort_order' => ((int) $adminUserMenu->sort_order) + 1,
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
],
);
}
$menuId = (int) DB::table('admin_menus')->where('code', 'system.admin_role')->value('id');
if ($actionCatalogId > 0 && $menuId > 0) {
DB::table('admin_menu_actions')->updateOrInsert(
['permission_code' => 'system.admin_role.manage'],
[
'menu_id' => $menuId,
'action_id' => $actionCatalogId,
'name' => '角色权限管理',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
],
);
}
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
foreach (AdminAuthorizationRegistry::resources() as $resource) {
$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();
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,
]);
}
}
$adminRoleSlug = 'prd.admin_role.manage';
$adminUserSlug = 'prd.admin_user.manage';
$roleIds = DB::table('admin_role_legacy_permissions')
->where('permission_slug', $adminUserSlug)
->pluck('role_id')
->all();
foreach ($roleIds as $roleId) {
DB::table('admin_role_legacy_permissions')->updateOrInsert(
[
'role_id' => (int) $roleId,
'permission_slug' => $adminRoleSlug,
],
[
'created_at' => $now,
'updated_at' => $now,
],
);
foreach (AdminPermissionBridge::menuActionCodesForLegacy($adminRoleSlug) as $permissionCode) {
$menuActionId = $menuActionIds[$permissionCode] ?? null;
if ($menuActionId === null) {
continue;
}
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => (int) $menuActionId,
]);
}
}
}
public function down(): void
{
// 不回滚授权数据,避免删除线上已经显式授予的角色管理权限。
}
};

View File

@@ -1,82 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
use App\Support\AdminAuthorizationRegistry;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$playersMenuId = (int) DB::table('admin_menus')->where('code', 'service.players')->value('id');
$updateActionId = (int) DB::table('admin_action_catalog')->where('code', 'update')->value('id');
if ($playersMenuId > 0 && $updateActionId > 0) {
DB::table('admin_menu_actions')->updateOrInsert(
['permission_code' => 'service.players.freeze'],
[
'menu_id' => $playersMenuId,
'action_id' => $updateActionId,
'name' => '冻结解冻玩家',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
],
);
}
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$playerResourceBindings = [
'admin.players.index' => ['service.players.manage', 'service.players.view'],
'admin.players.store' => ['service.players.manage'],
'admin.players.show' => ['service.players.manage', 'service.players.view'],
'admin.players.update' => ['service.players.manage'],
'admin.players.destroy' => ['service.players.manage'],
'admin.players.freeze' => ['service.players.freeze'],
'admin.players.unfreeze' => ['service.players.freeze'],
'admin.players.wallets' => ['service.players.manage', 'service.wallet.view'],
'admin.players.ticket-items' => ['service.players.manage', 'service.tickets.view'],
];
foreach (AdminAuthorizationRegistry::resources() as $resource) {
if (($resource['module_code'] ?? null) !== 'player_service') {
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();
$permissionCodes = $playerResourceBindings[$resource['code']] ?? $resource['permission_codes'];
foreach ($permissionCodes 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,
]);
}
}
}
public function down(): void
{
// 不回滚授权绑定,避免误删线上已调整的资源权限关系。
}
};

View File

@@ -1,71 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$resourceId = DB::table('admin_api_resources')
->where('code', 'admin.tickets.index')
->value('id');
if ($resourceId === null) {
$resourceId = DB::table('admin_api_resources')->insertGetId([
'code' => 'admin.tickets.index',
'module_code' => 'ticket',
'name' => '后台注单列表',
'http_method' => 'GET',
'uri_pattern' => '/api/v1/admin/tickets',
'route_name' => 'api.v1.admin.tickets.index',
'auth_mode' => 'permission_required',
'is_audit_required' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
$menuActionId = DB::table('admin_menu_actions')
->where('permission_code', 'service.tickets.view')
->value('id');
if ($menuActionId !== null) {
$exists = DB::table('admin_api_resource_bindings')
->where('api_resource_id', $resourceId)
->where('menu_action_id', $menuActionId)
->exists();
if (! $exists) {
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => $resourceId,
'menu_action_id' => $menuActionId,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
public function down(): void
{
$resourceId = DB::table('admin_api_resources')
->where('code', 'admin.tickets.index')
->value('id');
if ($resourceId !== null) {
DB::table('admin_api_resource_bindings')
->where('api_resource_id', $resourceId)
->delete();
DB::table('admin_api_resources')
->where('id', $resourceId)
->delete();
}
}
};

View File

@@ -1,109 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$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 => str_starts_with((string) $resource['code'], 'admin.currencies.'),
));
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,
]);
}
}
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->join('admin_api_resources as ar', 'ar.id', '=', 'arb.api_resource_id')
->whereIn('ar.code', array_column($resources, 'code'))
->select('rma.role_id', 'arb.api_resource_id')
->distinct()
->get();
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $row->api_resource_id,
], []);
}
}
public function down(): void
{
$resourceCodes = ['admin.currencies.index', 'admin.currencies.store', 'admin.currencies.update'];
$resourceIds = DB::table('admin_api_resources')
->whereIn('code', $resourceCodes)
->pluck('id')
->all();
if ($resourceIds === []) {
return;
}
DB::table('admin_role_api_resources')
->whereIn('api_resource_id', $resourceIds)
->delete();
DB::table('admin_api_resource_bindings')
->whereIn('api_resource_id', $resourceIds)
->delete();
DB::table('admin_api_resources')
->whereIn('id', $resourceIds)
->delete();
}
};

View File

@@ -1,49 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('odds_items', function (Blueprint $table) {
// 添加 dimension 字段用于按 2D/3D/4D 配置佣金
$table->unsignedTinyInteger('dimension')->nullable()->after('prize_scope')->comment('2/3/4 维度,佣金按维度配置');
// 删除旧的唯一约束
$table->dropUnique('uk_odds_items_version_play_prize_currency');
// 添加新的唯一约束:佣金按 dimension + currency_code 配置
// 赔率仍按 play_code + prize_scope + currency_code 配置
$table->unique(
['version_id', 'play_code', 'prize_scope', 'currency_code', 'dimension'],
'uk_odds_items_version_play_prize_currency_dimension'
);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('odds_items', function (Blueprint $table) {
// 删除新的唯一约束
$table->dropUnique('uk_odds_items_version_play_prize_currency_dimension');
// 恢复旧的唯一约束
$table->unique(
['version_id', 'play_code', 'prize_scope', 'currency_code'],
'uk_odds_items_version_play_prize_currency'
);
// 删除 dimension 字段
$table->dropColumn('dimension');
});
}
};

View File

@@ -1,105 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$resource = collect(AdminAuthorizationRegistry::resources())
->first(static fn (array $item): bool => $item['code'] === 'admin.currencies.destroy');
if ($resource === null) {
return;
}
$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,
]);
}
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->where('arb.api_resource_id', (int) $resourceId)
->select('rma.role_id')
->distinct()
->get();
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $resourceId,
], []);
}
}
public function down(): void
{
$resourceId = DB::table('admin_api_resources')
->where('code', 'admin.currencies.destroy')
->value('id');
if ($resourceId === null) {
return;
}
DB::table('admin_role_api_resources')
->where('api_resource_id', (int) $resourceId)
->delete();
DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $resourceId)
->delete();
DB::table('admin_api_resources')
->where('id', (int) $resourceId)
->delete();
}
};

View File

@@ -1,143 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$serviceMenuId = (int) DB::table('admin_menus')->where('code', 'service')->value('id');
$manageActionId = (int) DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
if ($serviceMenuId > 0) {
DB::table('admin_menus')->updateOrInsert(
['code' => 'service.currency'],
[
'parent_id' => $serviceMenuId,
'menu_type' => 'page',
'name' => '币种管理',
'path' => '/admin/settings/currencies',
'route_name' => 'admin.settings.currencies',
'component' => 'settings/currencies',
'icon' => null,
'active_menu_code' => null,
'sort_order' => 70,
'is_visible' => false,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
],
);
}
$currencyMenuId = (int) DB::table('admin_menus')->where('code', 'service.currency')->value('id');
if ($currencyMenuId > 0 && $manageActionId > 0) {
DB::table('admin_menu_actions')->updateOrInsert(
['permission_code' => 'service.currency.manage'],
[
'menu_id' => $currencyMenuId,
'action_id' => $manageActionId,
'name' => '币种管理',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
],
);
}
if (Schema::hasTable('admin_permissions')) {
DB::table('admin_permissions')->updateOrInsert(
['slug' => 'prd.currency.manage'],
[
'name' => '币种管理·可管理',
'updated_at' => $now,
'created_at' => $now,
],
);
}
$currencyActionId = DB::table('admin_menu_actions')
->where('permission_code', 'service.currency.manage')
->value('id');
if ($currencyActionId === null) {
return;
}
$roleIds = DB::table('admin_role_legacy_permissions')
->where('permission_slug', 'prd.users.manage')
->pluck('role_id')
->all();
foreach ($roleIds as $roleId) {
DB::table('admin_role_legacy_permissions')->updateOrInsert(
[
'role_id' => (int) $roleId,
'permission_slug' => 'prd.currency.manage',
],
[
'created_at' => $now,
'updated_at' => $now,
],
);
DB::table('admin_role_menu_actions')->updateOrInsert(
[
'role_id' => (int) $roleId,
'menu_action_id' => (int) $currencyActionId,
],
[],
);
}
$currencyResourceIds = DB::table('admin_api_resources')
->whereIn('code', [
'admin.currencies.index',
'admin.currencies.store',
'admin.currencies.update',
'admin.currencies.destroy',
])
->pluck('id')
->all();
foreach ($currencyResourceIds as $resourceId) {
DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $resourceId)
->delete();
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => (int) $resourceId,
'menu_action_id' => (int) $currencyActionId,
'created_at' => $now,
'updated_at' => $now,
]);
}
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->whereIn('arb.api_resource_id', $currencyResourceIds)
->select('rma.role_id', 'arb.api_resource_id')
->distinct()
->get();
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $row->api_resource_id,
], []);
}
}
public function down(): void
{
// 不自动回滚线上角色与资源绑定,避免误删已调整的授权。
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
DB::table('admin_menus')
->where('code', 'service.currency')
->update([
'path' => '/admin/currencies',
'route_name' => 'admin.currencies',
'updated_at' => now(),
]);
}
public function down(): void
{
DB::table('admin_menus')
->where('code', 'service.currency')
->update([
'path' => '/admin/settings/currencies',
'route_name' => 'admin.settings.currencies',
'updated_at' => now(),
]);
}
};

View File

@@ -1,160 +0,0 @@
<?php
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$actionViewId = DB::table('admin_action_catalog')->where('code', 'view')->value('id');
if ($actionViewId === null) {
return;
}
$serviceMenuId = DB::table('admin_menus')->where('code', 'service')->value('id');
if ($serviceMenuId === null) {
return;
}
$reportMenuId = DB::table('admin_menus')->where('code', 'service.report')->value('id');
if ($reportMenuId === null) {
$reportMenuId = DB::table('admin_menus')->insertGetId([
'parent_id' => $serviceMenuId,
'menu_type' => 'page',
'code' => 'service.report',
'name' => '报表中心',
'path' => '/admin/reports',
'route_name' => 'admin.reports.index',
'component' => 'service/reports',
'icon' => null,
'active_menu_code' => null,
'sort_order' => 50,
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
if (! isset($menuActionIds['service.report.view'])) {
DB::table('admin_menu_actions')->insert([
'menu_id' => (int) $reportMenuId,
'action_id' => (int) $actionViewId,
'permission_code' => 'service.report.view',
'name' => '报表中心查看',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
}
$reportResourceCodes = [
'admin.reports.daily-profit',
'admin.reports.player-win-loss',
'admin.reports.play-dimension',
'admin.reports.rebate-commission',
'admin.report-jobs.index',
'admin.report-jobs.store',
'admin.report-jobs.show',
'admin.report-jobs.download',
];
foreach (AdminAuthorizationRegistry::resources() as $resource) {
if (! in_array($resource['code'], $reportResourceCodes, true)) {
continue;
}
$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,
]);
}
}
$superRoleId = DB::table('admin_roles')->where('slug', 'super_admin')->value('id');
if ($superRoleId !== null && isset($menuActionIds['service.report.view'])) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $superRoleId,
'menu_action_id' => (int) $menuActionIds['service.report.view'],
]);
}
}
public function down(): void
{
$codes = [
'admin.reports.daily-profit',
'admin.reports.player-win-loss',
'admin.reports.play-dimension',
'admin.reports.rebate-commission',
'admin.report-jobs.index',
'admin.report-jobs.store',
'admin.report-jobs.show',
'admin.report-jobs.download',
];
$resourceIds = DB::table('admin_api_resources')->whereIn('code', $codes)->pluck('id');
foreach ($resourceIds as $resourceId) {
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}
$menuActionId = DB::table('admin_menu_actions')->where('permission_code', 'service.report.view')->value('id');
if ($menuActionId !== null) {
DB::table('admin_role_menu_actions')->where('menu_action_id', (int) $menuActionId)->delete();
DB::table('admin_menu_actions')->where('id', (int) $menuActionId)->delete();
}
DB::table('admin_menus')->where('code', 'service.report')->delete();
}
};

View File

@@ -1,122 +0,0 @@
<?php
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/** @var list<string> */
private const STALE_RESOURCE_CODES = [
'admin.reports.index',
'admin.reports.store',
'admin.reconcile.index',
'admin.reconcile.store',
'admin.draws.publish',
];
/** @var list<string> */
private const REPORT_RESOURCE_CODES = [
'admin.reports.daily-profit',
'admin.reports.player-win-loss',
'admin.reports.play-dimension',
'admin.reports.rebate-commission',
'admin.report-jobs.index',
'admin.report-jobs.store',
'admin.report-jobs.show',
'admin.report-jobs.download',
];
public function up(): void
{
$now = Carbon::now();
$this->deleteStaleApiResources();
$this->ensureReportViewOnRolesWithReportLegacy();
$this->syncReportResourceBindings($now);
}
public function down(): void
{
// 绑定收紧与角色补权为数据修复,不回滚以免再现漂移。
}
private function deleteStaleApiResources(): void
{
$resourceIds = DB::table('admin_api_resources')
->whereIn('code', self::STALE_RESOURCE_CODES)
->pluck('id');
foreach ($resourceIds as $resourceId) {
$id = (int) $resourceId;
DB::table('admin_api_resource_bindings')->where('api_resource_id', $id)->delete();
DB::table('admin_api_resources')->where('id', $id)->delete();
}
}
private function ensureReportViewOnRolesWithReportLegacy(): void
{
$menuActionId = DB::table('admin_menu_actions')
->where('permission_code', 'service.report.view')
->where('status', 1)
->value('id');
if ($menuActionId === null) {
return;
}
$reportSlugs = ['prd.report.view', 'prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player'];
$roleIds = DB::table('admin_role_legacy_permissions')
->whereIn('permission_slug', $reportSlugs)
->distinct()
->pluck('role_id');
foreach ($roleIds as $roleId) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => (int) $menuActionId,
]);
}
}
private function syncReportResourceBindings(Carbon $now): void
{
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$registryByCode = [];
foreach (AdminAuthorizationRegistry::resources() as $resource) {
$registryByCode[$resource['code']] = $resource;
}
foreach (self::REPORT_RESOURCE_CODES as $code) {
$resource = $registryByCode[$code] ?? null;
if ($resource === null) {
continue;
}
$resourceId = DB::table('admin_api_resources')->where('code', $code)->value('id');
if ($resourceId === null) {
continue;
}
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,
]);
}
}
}
};

View File

@@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
/**
* 移除未接入业务或已由其它表替代的冗余表:
* - system_jobs从未使用
* - admin_role_menus侧栏改由 prd.* + Registry 驱动
* - admin_role_api_resources鉴权由 role_menu_actions + bindings 实时推导
* - admin_*_data_scopes数据范围未落地
*/
return new class extends Migration
{
public function up(): void
{
Schema::dropIfExists('admin_user_data_scopes');
Schema::dropIfExists('admin_role_data_scopes');
Schema::dropIfExists('admin_data_scopes');
Schema::dropIfExists('admin_role_api_resources');
Schema::dropIfExists('admin_role_menus');
Schema::dropIfExists('system_jobs');
}
public function down(): void
{
// 冗余表删除为单向清理,不回滚。
}
};

View File

@@ -1,46 +0,0 @@
<?php
use App\Models\AdminRole;
use App\Support\AdminPermissionBridge;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('admin_role_legacy_permissions')) {
return;
}
$roleIds = DB::table('admin_roles')->pluck('id');
foreach ($roleIds as $roleId) {
$legacySlugs = DB::table('admin_role_legacy_permissions')
->where('role_id', (int) $roleId)
->pluck('permission_slug')
->all();
$slugs = AdminPermissionBridge::normalizeCanonicalLegacySlugs(
is_array($legacySlugs) ? $legacySlugs : [],
);
if ($slugs === []) {
continue;
}
$role = AdminRole::query()->find((int) $roleId);
if ($role !== null) {
$role->syncLegacyPermissionSlugs($slugs);
}
}
Schema::dropIfExists('admin_role_legacy_permissions');
}
public function down(): void
{
// 单向清理slug 已合并,权限以 admin_role_menu_actions 为准。
}
};

View File

@@ -1,50 +0,0 @@
<?php
use App\Models\LotterySetting;
use Illuminate\Database\Migrations\Migration;
/**
* 玩法规则 HTML由单键 frontend.play_rules_html 拆分为 zh/en/ne 三语键。
* 已有内容迁移到 frontend.play_rules_html_zh。
*/
return new class extends Migration
{
private const LEGACY_KEY = 'frontend.play_rules_html';
private const I18N_KEYS = [
'frontend.play_rules_html_zh' => '玩家端玩法规则页 HTML中文',
'frontend.play_rules_html_en' => '玩家端玩法规则页 HTMLEnglish',
'frontend.play_rules_html_ne' => '玩家端玩法规则页 HTMLनेपाली',
];
public function up(): void
{
$legacyRow = LotterySetting::query()->where('setting_key', self::LEGACY_KEY)->first();
$legacyValue = $legacyRow?->value_json;
foreach (self::I18N_KEYS as $key => $description) {
if (LotterySetting::query()->where('setting_key', $key)->exists()) {
continue;
}
$value = '';
if ($key === 'frontend.play_rules_html_zh' && is_string($legacyValue) && trim($legacyValue) !== '') {
$value = $legacyValue;
}
LotterySetting::query()->create([
'setting_key' => $key,
'value_json' => $value,
'group_name' => 'frontend',
'description_zh' => $description,
]);
}
}
public function down(): void
{
LotterySetting::query()
->whereIn('setting_key', array_keys(self::I18N_KEYS))
->delete();
}
};

View File

@@ -1,54 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
foreach (['play_types', 'play_config_items'] as $table) {
Schema::table($table, function (Blueprint $blueprint): void {
$blueprint->string('display_name', 64)->nullable()->after('bet_mode');
});
DB::table($table)->update([
'display_name' => DB::raw(
"COALESCE(
NULLIF(TRIM(display_name_zh), ''),
NULLIF(TRIM(display_name_en), ''),
NULLIF(TRIM(display_name_ne), ''),
play_code
)",
),
]);
Schema::table($table, function (Blueprint $blueprint): void {
$blueprint->dropColumn(['display_name_zh', 'display_name_en', 'display_name_ne']);
});
}
}
public function down(): void
{
foreach (['play_types', 'play_config_items'] as $table) {
Schema::table($table, function (Blueprint $blueprint): void {
$blueprint->string('display_name_zh', 64)->nullable()->after('bet_mode');
$blueprint->string('display_name_en', 64)->nullable()->after('display_name_zh');
$blueprint->string('display_name_ne', 64)->nullable()->after('display_name_en');
});
DB::table($table)->update([
'display_name_zh' => DB::raw('display_name'),
'display_name_en' => DB::raw('display_name'),
'display_name_ne' => DB::raw('display_name'),
]);
Schema::table($table, function (Blueprint $blueprint): void {
$blueprint->dropColumn('display_name');
});
}
}
};

View File

@@ -1,25 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
/**
* admin_api_resources.code 最长可达 128;中间件将完整 code 写入 audit_logs.target_type。
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('audit_logs', function (Blueprint $table): void {
$table->string('target_type', 128)->nullable()->change();
});
}
public function down(): void
{
Schema::table('audit_logs', function (Blueprint $table): void {
$table->string('target_type', 32)->nullable()->change();
});
}
};

View File

@@ -1,62 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$actionIds = DB::table('admin_action_catalog')->pluck('id', 'code');
$menuIds = DB::table('admin_menus')->pluck('id', 'code');
$walletMenuId = $menuIds['service.wallet'] ?? null;
$updateActionId = $actionIds['update'] ?? null;
if ($walletMenuId !== null && $updateActionId !== null) {
$exists = DB::table('admin_menu_actions')
->where('permission_code', 'service.wallet.adjust')
->exists();
if (! $exists) {
DB::table('admin_menu_actions')->insert([
'menu_id' => $walletMenuId,
'action_id' => $updateActionId,
'permission_code' => 'service.wallet.adjust',
'name' => '钱包补单/冲正',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
$oddsMenuId = $menuIds['config.odds'] ?? null;
$viewActionId = $actionIds['view'] ?? null;
if ($oddsMenuId !== null && $viewActionId !== null) {
$exists = DB::table('admin_menu_actions')
->where('permission_code', 'config.odds.view')
->exists();
if (! $exists) {
DB::table('admin_menu_actions')->insert([
'menu_id' => $oddsMenuId,
'action_id' => $viewActionId,
'permission_code' => 'config.odds.view',
'name' => '赔率配置查看',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
public function down(): void
{
DB::table('admin_menu_actions')
->whereIn('permission_code', ['service.wallet.adjust', 'config.odds.view'])
->delete();
}
};

View File

@@ -1,48 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/** @var list<string> */
private const STALE_PERMISSION_CODES = [
'dashboard.view',
'service.reports.view',
'service.reports.export',
];
public function up(): void
{
$menuActionIds = DB::table('admin_menu_actions')
->whereIn('permission_code', self::STALE_PERMISSION_CODES)
->pluck('id');
if ($menuActionIds->isNotEmpty()) {
DB::table('admin_menu_actions')
->whereIn('id', $menuActionIds->all())
->delete();
}
$staleReportMenuId = DB::table('admin_menus')
->where('code', 'service.reports')
->value('id');
if ($staleReportMenuId !== null) {
$hasActions = DB::table('admin_menu_actions')
->where('menu_id', (int) $staleReportMenuId)
->exists();
if (! $hasActions) {
DB::table('admin_menus')
->where('id', (int) $staleReportMenuId)
->delete();
}
}
}
public function down(): void
{
// 数据清理迁移,不回滚以免再现僵尸 permission_code。
}
};

View File

@@ -1,87 +0,0 @@
<?php
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$resource = collect(AdminAuthorizationRegistry::resources())
->firstWhere('code', 'admin.dashboard.analytics');
if ($resource === null) {
return;
}
$resourceId = DB::table('admin_api_resources')
->where('code', 'admin.dashboard.analytics')
->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' => 'admin.dashboard.analytics',
'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) {
$actionId = DB::table('admin_menu_actions')
->where('permission_code', $permissionCode)
->value('id');
if ($actionId === null) {
continue;
}
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => (int) $resourceId,
'menu_action_id' => (int) $actionId,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
public function down(): void
{
$resourceId = DB::table('admin_api_resources')
->where('code', 'admin.dashboard.analytics')
->value('id');
if ($resourceId === null) {
return;
}
DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $resourceId)
->delete();
DB::table('admin_api_resources')
->where('id', (int) $resourceId)
->delete();
}
};

View File

@@ -1,51 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('settlement_batches', function (Blueprint $table): void {
if (! Schema::hasColumn('settlement_batches', 'review_status')) {
$table->string('review_status', 32)->default('pending')->after('total_jackpot_payout_amount');
}
if (! Schema::hasColumn('settlement_batches', 'reviewed_by')) {
$table->foreignId('reviewed_by')->nullable()->after('review_status')->constrained('admin_users')->nullOnDelete();
}
if (! Schema::hasColumn('settlement_batches', 'reviewed_at')) {
$table->timestamp('reviewed_at')->nullable()->after('reviewed_by');
}
if (! Schema::hasColumn('settlement_batches', 'review_remark')) {
$table->string('review_remark', 255)->nullable()->after('reviewed_at');
}
if (! Schema::hasColumn('settlement_batches', 'paid_at')) {
$table->timestamp('paid_at')->nullable()->after('review_remark');
}
});
}
public function down(): void
{
Schema::table('settlement_batches', function (Blueprint $table): void {
if (Schema::hasColumn('settlement_batches', 'paid_at')) {
$table->dropColumn('paid_at');
}
if (Schema::hasColumn('settlement_batches', 'review_remark')) {
$table->dropColumn('review_remark');
}
if (Schema::hasColumn('settlement_batches', 'reviewed_at')) {
$table->dropColumn('reviewed_at');
}
if (Schema::hasColumn('settlement_batches', 'reviewed_by')) {
$table->dropForeign(['reviewed_by']);
$table->dropColumn('reviewed_by');
}
if (Schema::hasColumn('settlement_batches', 'review_status')) {
$table->dropColumn('review_status');
}
});
}
};

View File

@@ -1,216 +0,0 @@
<?php
use App\Support\AdminPermissionBridge;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/** @var list<array{menu_code: string, action_code: string, permission_code: string, name: string}> */
private const NEW_MENU_ACTIONS = [
['menu_code' => 'dashboard', 'action_code' => 'view', 'permission_code' => 'dashboard.view', 'name' => '仪表盘查看'],
['menu_code' => 'service.report', 'action_code' => 'export', 'permission_code' => 'service.report.export', 'name' => '报表导出'],
['menu_code' => 'risk.monitor', 'action_code' => 'manage', 'permission_code' => 'risk.monitor.manage', 'name' => '风控监控管理'],
];
public function up(): void
{
$now = Carbon::now();
$menuIds = DB::table('admin_menus')->pluck('id', 'code');
$actionIds = DB::table('admin_action_catalog')->pluck('id', 'code');
$menuActionIds = [];
foreach (self::NEW_MENU_ACTIONS as $row) {
$menuId = $menuIds[$row['menu_code']] ?? null;
$actionId = $actionIds[$row['action_code']] ?? null;
if ($menuId === null || $actionId === null) {
continue;
}
$exists = DB::table('admin_menu_actions')
->where('permission_code', $row['permission_code'])
->exists();
if ($exists) {
$menuActionIds[$row['permission_code']] = (int) DB::table('admin_menu_actions')
->where('permission_code', $row['permission_code'])
->value('id');
continue;
}
$menuActionIds[$row['permission_code']] = (int) DB::table('admin_menu_actions')->insertGetId([
'menu_id' => (int) $menuId,
'action_id' => (int) $actionId,
'permission_code' => $row['permission_code'],
'name' => $row['name'],
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
$this->grantMenuActionsToAllRoles($menuActionIds, $now);
$this->grantReportExportToReportViewRoles($menuActionIds['service.report.export'] ?? null, $now);
$this->grantTicketsViewToLegacyRoles($menuActionIds, $now);
}
public function down(): void
{
$codes = array_column(self::NEW_MENU_ACTIONS, 'permission_code');
$ids = DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->pluck('id');
foreach ($ids as $id) {
DB::table('admin_role_menu_actions')->where('menu_action_id', (int) $id)->delete();
DB::table('admin_user_menu_actions')->where('menu_action_id', (int) $id)->delete();
DB::table('admin_api_resource_bindings')->where('menu_action_id', (int) $id)->delete();
DB::table('admin_menu_actions')->where('id', (int) $id)->delete();
}
}
/**
* @param array<string, int> $menuActionIds
*/
private function grantMenuActionsToAllRoles(array $menuActionIds, Carbon $now): void
{
$dashboardId = $menuActionIds['dashboard.view'] ?? null;
if ($dashboardId === null) {
return;
}
$roleIds = DB::table('admin_roles')->pluck('id');
foreach ($roleIds as $roleId) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => $dashboardId,
]);
}
}
private function grantReportExportToReportViewRoles(?int $exportMenuActionId, Carbon $now): void
{
if ($exportMenuActionId === null) {
return;
}
$viewMenuActionId = DB::table('admin_menu_actions')
->where('permission_code', 'service.report.view')
->value('id');
if ($viewMenuActionId === null) {
return;
}
$roleIds = DB::table('admin_role_menu_actions')
->where('menu_action_id', (int) $viewMenuActionId)
->distinct()
->pluck('role_id');
foreach ($roleIds as $roleId) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => $exportMenuActionId,
]);
}
}
/**
* 原注单入口依赖多种 prd.*;迁移为独立的 prd.tickets.view。
*
* @param array<string, int> $menuActionIds
*/
private function grantTicketsViewToLegacyRoles(array $menuActionIds, Carbon $now): void
{
$ticketsViewId = DB::table('admin_menu_actions')
->where('permission_code', 'service.tickets.view')
->value('id');
if ($ticketsViewId === null) {
return;
}
$legacySlugs = [
'prd.users.view_cs',
'prd.users.manage',
'prd.users.view_finance',
'prd.draw_result.view',
'prd.draw_result.manage',
'prd.payout.view',
'prd.payout.review',
'prd.payout.manage',
];
$roleIds = $this->roleIdsWithAnyLegacySlug($legacySlugs);
foreach ($roleIds as $roleId) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => (int) $ticketsViewId,
]);
}
$riskViewId = DB::table('admin_menu_actions')
->where('permission_code', 'risk.monitor.view')
->value('id');
$riskManageId = $menuActionIds['risk.monitor.manage'] ?? null;
if ($riskManageId === null) {
return;
}
$riskRoleIds = $this->roleIdsWithAnyLegacySlug([
'prd.draw_result.manage',
'prd.draw_result.view',
'prd.risk.manage',
'prd.risk.view',
]);
foreach ($riskRoleIds as $roleId) {
if ($riskViewId !== null) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => (int) $riskViewId,
]);
}
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $roleId,
'menu_action_id' => (int) $riskManageId,
]);
}
}
/**
* 通过角色已授权的 menu_action 反推曾拥有指定 prd.* 的角色legacy 表已废弃)。
*
* @param list<string> $legacySlugs
* @return list<int>
*/
private function roleIdsWithAnyLegacySlug(array $legacySlugs): array
{
$codes = [];
foreach ($legacySlugs as $slug) {
$codes = array_merge($codes, AdminPermissionBridge::menuActionCodesForLegacy($slug));
}
$codes = array_values(array_unique($codes));
if ($codes === []) {
return [];
}
$menuActionIds = DB::table('admin_menu_actions')
->whereIn('permission_code', $codes)
->where('status', 1)
->pluck('id');
if ($menuActionIds->isEmpty()) {
return [];
}
return DB::table('admin_role_menu_actions')
->whereIn('menu_action_id', $menuActionIds->map(fn ($id) => (int) $id)->all())
->distinct()
->pluck('role_id')
->map(fn ($id) => (int) $id)
->all();
}
};

View File

@@ -1,25 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('ticket_orders', function (Blueprint $table): void {
$table->unique(
['player_id', 'draw_id', 'client_trace_id'],
'uniq_ticket_orders_player_draw_trace',
);
});
}
public function down(): void
{
Schema::table('ticket_orders', function (Blueprint $table): void {
$table->dropUnique('uniq_ticket_orders_player_draw_trace');
});
}
};

View File

@@ -1,112 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
if (! DB::table('admin_action_catalog')->where('code', 'manual_burst')->exists()) {
DB::table('admin_action_catalog')->insert([
'code' => 'manual_burst',
'name' => '手动爆池',
'sort_order' => 85,
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
$jackpotMenuId = (int) DB::table('admin_menus')->where('code', 'config.jackpot')->value('id');
$manualBurstActionId = (int) DB::table('admin_action_catalog')->where('code', 'manual_burst')->value('id');
if ($jackpotMenuId <= 0 || $manualBurstActionId <= 0) {
return;
}
DB::table('admin_menu_actions')->updateOrInsert(
['permission_code' => 'jackpot.pool.manual_burst'],
[
'menu_id' => $jackpotMenuId,
'action_id' => $manualBurstActionId,
'name' => 'Jackpot 手动爆池',
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
],
);
if (Schema::hasTable('admin_permissions')) {
DB::table('admin_permissions')->updateOrInsert(
['slug' => 'prd.jackpot.manual_burst'],
[
'name' => 'Jackpot 手动爆池·仅超管',
'created_at' => $now,
'updated_at' => $now,
],
);
}
$menuActionId = (int) DB::table('admin_menu_actions')
->where('permission_code', 'jackpot.pool.manual_burst')
->value('id');
$superRoleId = (int) DB::table('admin_roles')->where('slug', 'super_admin')->value('id');
if ($superRoleId > 0 && $menuActionId > 0) {
if (Schema::hasTable('admin_role_legacy_permissions')) {
DB::table('admin_role_legacy_permissions')->updateOrInsert(
[
'role_id' => $superRoleId,
'permission_slug' => 'prd.jackpot.manual_burst',
],
[
'created_at' => $now,
'updated_at' => $now,
],
);
}
DB::table('admin_role_menu_actions')->updateOrInsert(
[
'role_id' => $superRoleId,
'menu_action_id' => $menuActionId,
],
[],
);
}
$resourceId = DB::table('admin_api_resources')
->where('code', 'admin.jackpot.pools.manual-burst')
->value('id');
if ($resourceId !== null && $menuActionId > 0) {
DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $resourceId)
->delete();
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => (int) $resourceId,
'menu_action_id' => $menuActionId,
'created_at' => $now,
'updated_at' => $now,
]);
if ($superRoleId > 0 && Schema::hasTable('admin_role_api_resources')) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => $superRoleId,
'api_resource_id' => (int) $resourceId,
], []);
}
}
}
public function down(): void
{
// 避免误删线上已调整的授权绑定。
}
};

View File

@@ -1,237 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::table('admin_sites', function (Blueprint $table): void {
$table->string('wallet_api_url', 512)->nullable()->after('extra_json');
$table->string('wallet_debit_path', 128)->default('/wallet/debit-for-lottery')->after('wallet_api_url');
$table->string('wallet_credit_path', 128)->default('/wallet/credit-from-lottery')->after('wallet_debit_path');
$table->string('wallet_balance_path', 128)->default('/wallet/balance')->after('wallet_credit_path');
$table->text('wallet_api_key_encrypted')->nullable()->after('wallet_balance_path');
$table->text('sso_jwt_secret_encrypted')->nullable()->after('wallet_api_key_encrypted');
$table->unsignedSmallInteger('wallet_timeout_seconds')->default(10)->after('sso_jwt_secret_encrypted');
$table->json('iframe_allowed_origins')->nullable()->after('wallet_timeout_seconds');
$table->string('lottery_h5_base_url', 512)->nullable()->after('iframe_allowed_origins');
$table->text('notes')->nullable()->after('lottery_h5_base_url');
});
$this->seedIntegrationMenuActions();
$this->backfillDefaultSiteFromEnv();
$this->syncIntegrationApiResources();
}
public function down(): void
{
$resourceIds = DB::table('admin_api_resources')
->where('code', 'like', 'admin.integration-sites.%')
->pluck('id')
->all();
if ($resourceIds !== []) {
DB::table('admin_role_api_resources')->whereIn('api_resource_id', $resourceIds)->delete();
DB::table('admin_api_resource_bindings')->whereIn('api_resource_id', $resourceIds)->delete();
DB::table('admin_api_resources')->whereIn('id', $resourceIds)->delete();
}
Schema::table('admin_sites', function (Blueprint $table): void {
$table->dropColumn([
'wallet_api_url',
'wallet_debit_path',
'wallet_credit_path',
'wallet_balance_path',
'wallet_api_key_encrypted',
'sso_jwt_secret_encrypted',
'wallet_timeout_seconds',
'iframe_allowed_origins',
'lottery_h5_base_url',
'notes',
]);
});
}
private function seedIntegrationMenuActions(): void
{
$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;
}
$configMenuId = DB::table('admin_menus')->where('code', 'config')->value('id');
if ($configMenuId === null) {
return;
}
$integrationMenuId = DB::table('admin_menus')->where('code', 'config.integration')->value('id');
if ($integrationMenuId === null) {
$integrationMenuId = DB::table('admin_menus')->insertGetId([
'parent_id' => (int) $configMenuId,
'menu_type' => 'page',
'code' => 'config.integration',
'name' => '主站接入站点',
'path' => '/admin/config/integration-sites',
'route_name' => 'admin.config.integration',
'component' => 'config/integration',
'icon' => null,
'active_menu_code' => null,
'sort_order' => 45,
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
foreach ([
['permission_code' => 'integration.site.view', 'action_id' => (int) $viewActionId, 'name' => '接入站点查看'],
['permission_code' => 'integration.site.manage', 'action_id' => (int) $manageActionId, 'name' => '接入站点管理'],
] as $row) {
$exists = DB::table('admin_menu_actions')
->where('permission_code', $row['permission_code'])
->exists();
if ($exists) {
continue;
}
DB::table('admin_menu_actions')->insert([
'menu_id' => (int) $integrationMenuId,
'action_id' => $row['action_id'],
'permission_code' => $row['permission_code'],
'name' => $row['name'],
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
private function backfillDefaultSiteFromEnv(): void
{
$siteId = DB::table('admin_sites')->where('is_default', true)->value('id')
?? DB::table('admin_sites')->orderBy('id')->value('id');
if ($siteId === null) {
return;
}
$walletUrl = env('MAIN_SITE_WALLET_API_URL');
$ssoSecret = env('MAIN_SITE_SSO_JWT_SECRET');
$walletKey = env('MAIN_SITE_WALLET_API_KEY');
$payload = [
'updated_at' => Carbon::now(),
];
if (is_string($walletUrl) && trim($walletUrl) !== '') {
$payload['wallet_api_url'] = rtrim(trim($walletUrl), '/');
}
$debitPath = env('MAIN_SITE_WALLET_DEBIT_PATH');
if (is_string($debitPath) && $debitPath !== '') {
$payload['wallet_debit_path'] = $debitPath;
}
$creditPath = env('MAIN_SITE_WALLET_CREDIT_PATH');
if (is_string($creditPath) && $creditPath !== '') {
$payload['wallet_credit_path'] = $creditPath;
}
$balancePath = env('MAIN_SITE_WALLET_BALANCE_PATH');
if (is_string($balancePath) && $balancePath !== '') {
$payload['wallet_balance_path'] = $balancePath;
}
$timeout = env('MAIN_SITE_WALLET_TIMEOUT');
if (is_numeric($timeout)) {
$payload['wallet_timeout_seconds'] = max(1, (int) $timeout);
}
if (is_string($ssoSecret) && $ssoSecret !== '') {
$payload['sso_jwt_secret_encrypted'] = encrypt($ssoSecret);
}
if (is_string($walletKey) && $walletKey !== '') {
$payload['wallet_api_key_encrypted'] = encrypt($walletKey);
}
if (count($payload) > 1) {
DB::table('admin_sites')->where('id', (int) $siteId)->update($payload);
}
}
private function syncIntegrationApiResources(): void
{
if (! Schema::hasTable('admin_api_resources')) {
return;
}
$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 => str_starts_with((string) $resource['code'], 'admin.integration-sites.'),
));
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,
]);
}
}
}
};

View File

@@ -1,105 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
/**
* 为已执行 140000 的环境补种 integration 权限动作并同步 API 绑定。
*/
return new class extends Migration
{
public function up(): void
{
$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;
}
$configMenuId = DB::table('admin_menus')->where('code', 'config')->value('id');
if ($configMenuId === null) {
return;
}
$integrationMenuId = DB::table('admin_menus')->where('code', 'config.integration')->value('id');
if ($integrationMenuId === null) {
$integrationMenuId = DB::table('admin_menus')->insertGetId([
'parent_id' => (int) $configMenuId,
'menu_type' => 'page',
'code' => 'config.integration',
'name' => '主站接入站点',
'path' => '/admin/config/integration-sites',
'route_name' => 'admin.config.integration',
'component' => 'config/integration',
'icon' => null,
'active_menu_code' => null,
'sort_order' => 45,
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
foreach ([
['permission_code' => 'integration.site.view', 'action_id' => (int) $viewActionId, 'name' => '接入站点查看'],
['permission_code' => 'integration.site.manage', 'action_id' => (int) $manageActionId, 'name' => '接入站点管理'],
] as $row) {
if (DB::table('admin_menu_actions')->where('permission_code', $row['permission_code'])->exists()) {
continue;
}
DB::table('admin_menu_actions')->insert([
'menu_id' => (int) $integrationMenuId,
'action_id' => $row['action_id'],
'permission_code' => $row['permission_code'],
'name' => $row['name'],
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$resources = array_values(array_filter(
AdminAuthorizationRegistry::resources(),
static fn (array $resource): bool => str_starts_with((string) $resource['code'], 'admin.integration-sites.'),
));
foreach ($resources as $resource) {
$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();
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,
]);
}
}
}
public function down(): void
{
// 保留 menu_actions / bindings避免回滚后超管无法管理已创建的接入站点。
}
};

View File

@@ -1,72 +0,0 @@
<?php
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
/**
* `dashboard.view` 2026_05_26 才写入 admin_menu_actions此前 sync 无法绑定
* admin.dashboard / admin.dashboard.analytics导致 permission_required 资源无 bindings。
*/
return new class extends Migration
{
public function up(): void
{
$now = Carbon::now();
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
foreach (AdminAuthorizationRegistry::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,
]);
}
}
}
public function down(): void
{
// 数据修复迁移:不在 down 中回滚 bindings避免误删线上授权关系。
}
};

View File

@@ -1,41 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
$duplicateIds = DB::table('jackpot_contributions')
->select('ticket_item_id')
->whereNotNull('ticket_item_id')
->groupBy('ticket_item_id')
->havingRaw('count(*) > 1')
->pluck('ticket_item_id');
foreach ($duplicateIds as $ticketItemId) {
$rows = DB::table('jackpot_contributions')
->where('ticket_item_id', $ticketItemId)
->orderByDesc('id')
->pluck('id');
$keep = $rows->shift();
if ($keep !== null && $rows->isNotEmpty()) {
DB::table('jackpot_contributions')->whereIn('id', $rows->all())->delete();
}
}
Schema::table('jackpot_contributions', function (Blueprint $table): void {
$table->unique('ticket_item_id', 'uk_jackpot_contributions_ticket_item');
});
}
public function down(): void
{
Schema::table('jackpot_contributions', function (Blueprint $table): void {
$table->dropUnique('uk_jackpot_contributions_ticket_item');
});
}
};

View File

@@ -1,30 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
Schema::create('jackpot_pool_adjustments', function (Blueprint $table): void {
$table->id();
$table->string('adjustment_no', 32)->unique();
$table->foreignId('jackpot_pool_id')->constrained('jackpot_pools')->cascadeOnDelete();
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->bigInteger('amount_delta')->comment('signed minor units; + increase pool');
$table->bigInteger('balance_before');
$table->bigInteger('balance_after');
$table->string('reason', 500);
$table->timestamps();
$table->index(['jackpot_pool_id', 'created_at'], 'idx_jackpot_pool_adjustments_pool_created');
});
}
public function down(): void
{
Schema::dropIfExists('jackpot_pool_adjustments');
}
};

View File

@@ -1,105 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/** @var list<string> */
private const RESOURCE_CODES = [
'admin.jackpot.pools.adjustments.index',
'admin.jackpot.pools.adjustments.store',
];
public function up(): void
{
$now = Carbon::now();
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$resources = collect(AdminAuthorizationRegistry::resources())
->filter(fn (array $item): bool => in_array($item['code'], self::RESOURCE_CODES, true))
->values();
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,
]);
}
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->where('arb.api_resource_id', (int) $resourceId)
->select('rma.role_id')
->distinct()
->get();
if (Schema::hasTable('admin_role_api_resources')) {
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $resourceId,
], []);
}
}
}
}
public function down(): void
{
foreach (self::RESOURCE_CODES as $code) {
$resourceId = DB::table('admin_api_resources')->where('code', $code)->value('id');
if ($resourceId === null) {
continue;
}
if (Schema::hasTable('admin_role_api_resources')) {
DB::table('admin_role_api_resources')->where('api_resource_id', (int) $resourceId)->delete();
}
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}
}
};

View File

@@ -1,53 +0,0 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
/**
* 对齐高频查询路径PostgreSQL B-tree
*
* - ticket_orders draw_id 结算/后台汇总
* - wallet_txns / ticket_itemsplayer_id + ORDER BY id DESC 分页
* - drawsbusiness_date 筛选 + draw_time 排序(往期/报表)
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('ticket_orders', function (Blueprint $table): void {
$table->index('draw_id', 'idx_ticket_orders_draw_id');
});
Schema::table('wallet_txns', function (Blueprint $table): void {
$table->index(['player_id', 'id'], 'idx_wallet_txns_player_id');
});
Schema::table('ticket_items', function (Blueprint $table): void {
$table->index(['player_id', 'id'], 'idx_ticket_items_player_id');
});
Schema::table('draws', function (Blueprint $table): void {
$table->index(['business_date', 'draw_time'], 'idx_draws_business_date_draw_time');
});
}
public function down(): void
{
Schema::table('draws', function (Blueprint $table): void {
$table->dropIndex('idx_draws_business_date_draw_time');
});
Schema::table('ticket_items', function (Blueprint $table): void {
$table->dropIndex('idx_ticket_items_player_id');
});
Schema::table('wallet_txns', function (Blueprint $table): void {
$table->dropIndex('idx_wallet_txns_player_id');
});
Schema::table('ticket_orders', function (Blueprint $table): void {
$table->dropIndex('idx_ticket_orders_draw_id');
});
}
};

View File

@@ -1,106 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
private const RESOURCE_CODE = 'admin.settings.batch-update';
private const CLONE_BINDINGS_FROM = 'admin.settings.update';
public function up(): void
{
$now = Carbon::now();
$resource = collect(AdminAuthorizationRegistry::resources())
->first(fn (array $item): bool => $item['code'] === self::RESOURCE_CODE);
if ($resource === null) {
return;
}
$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();
$sourceResourceId = DB::table('admin_api_resources')
->where('code', self::CLONE_BINDINGS_FROM)
->value('id');
if ($sourceResourceId !== null) {
$bindings = DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $sourceResourceId)
->get(['menu_action_id']);
foreach ($bindings as $binding) {
DB::table('admin_api_resource_bindings')->insert([
'api_resource_id' => (int) $resourceId,
'menu_action_id' => (int) $binding->menu_action_id,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
if (Schema::hasTable('admin_role_api_resources')) {
$roleResourceRows = DB::table('admin_role_menu_actions as rma')
->join('admin_api_resource_bindings as arb', 'arb.menu_action_id', '=', 'rma.menu_action_id')
->where('arb.api_resource_id', (int) $resourceId)
->select('rma.role_id')
->distinct()
->get();
foreach ($roleResourceRows as $row) {
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $row->role_id,
'api_resource_id' => (int) $resourceId,
], []);
}
}
}
public function down(): void
{
$resourceId = DB::table('admin_api_resources')->where('code', self::RESOURCE_CODE)->value('id');
if ($resourceId === null) {
return;
}
if (Schema::hasTable('admin_role_api_resources')) {
DB::table('admin_role_api_resources')->where('api_resource_id', (int) $resourceId)->delete();
}
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}
};

View File

@@ -1,132 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
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_nodes', function (Blueprint $table): void {
$table->id();
$table->foreignId('admin_site_id')->constrained('admin_sites')->cascadeOnDelete();
$table->foreignId('parent_id')->nullable()->constrained('agent_nodes')->nullOnDelete();
$table->string('path', 512);
$table->unsignedSmallInteger('depth')->default(0);
$table->string('code', 64);
$table->string('name', 128);
$table->unsignedTinyInteger('status')->default(1)->comment('1=enabled,0=disabled');
$table->foreignId('created_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->json('extra_json')->nullable();
$table->timestamps();
$table->unique(['admin_site_id', 'code'], 'uk_agent_nodes_site_code');
$table->index(['admin_site_id', 'parent_id'], 'idx_agent_nodes_site_parent');
$table->index('path', 'idx_agent_nodes_path');
});
Schema::create('admin_user_agents', function (Blueprint $table): void {
$table->foreignId('admin_user_id')->primary()->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('agent_node_id')->constrained('agent_nodes')->cascadeOnDelete();
$table->boolean('is_primary')->default(true);
$table->timestamp('granted_at')->nullable();
});
$this->seedRootAgentNodes();
$this->backfillAdminUserAgents();
}
public function down(): void
{
Schema::dropIfExists('admin_user_agents');
Schema::dropIfExists('agent_nodes');
}
private function seedRootAgentNodes(): void
{
$now = Carbon::now();
$sites = DB::table('admin_sites')->orderBy('id')->get(['id', 'code', 'name']);
foreach ($sites as $site) {
if (DB::table('agent_nodes')->where('admin_site_id', (int) $site->id)->where('depth', 0)->exists()) {
continue;
}
$code = 'root-'.(string) $site->code;
$nodeId = DB::table('agent_nodes')->insertGetId([
'admin_site_id' => (int) $site->id,
'parent_id' => null,
'path' => '/',
'depth' => 0,
'code' => $code,
'name' => (string) $site->name,
'status' => 1,
'created_by' => null,
'extra_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
DB::table('agent_nodes')->where('id', $nodeId)->update([
'path' => '/'.$nodeId.'/',
]);
}
}
private function backfillAdminUserAgents(): void
{
$superRoleId = DB::table('admin_roles')->where('slug', 'super_admin')->value('id');
$now = Carbon::now();
$userIds = DB::table('admin_users')->pluck('id');
foreach ($userIds as $userId) {
$userId = (int) $userId;
if (DB::table('admin_user_agents')->where('admin_user_id', $userId)->exists()) {
continue;
}
if ($superRoleId !== null) {
$isSuper = DB::table('admin_user_site_roles')
->where('admin_user_id', $userId)
->where('role_id', (int) $superRoleId)
->exists();
if ($isSuper) {
continue;
}
}
$siteId = DB::table('admin_user_site_roles')
->where('admin_user_id', $userId)
->orderBy('site_id')
->value('site_id');
if ($siteId === null) {
$siteId = DB::table('admin_sites')->where('is_default', true)->value('id')
?? DB::table('admin_sites')->orderBy('id')->value('id');
}
if ($siteId === null) {
continue;
}
$rootId = DB::table('agent_nodes')
->where('admin_site_id', (int) $siteId)
->where('depth', 0)
->value('id');
if ($rootId === null) {
continue;
}
DB::table('admin_user_agents')->insert([
'admin_user_id' => $userId,
'agent_node_id' => (int) $rootId,
'is_primary' => true,
'granted_at' => $now,
]);
}
}
};

View File

@@ -1,195 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
$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;
}
$systemMenuId = DB::table('admin_menus')->where('code', 'system')->value('id');
if ($systemMenuId === null) {
$systemMenuId = DB::table('admin_menus')->insertGetId([
'parent_id' => null,
'menu_type' => 'directory',
'code' => 'system',
'name' => '系统',
'path' => null,
'route_name' => null,
'component' => null,
'icon' => null,
'active_menu_code' => null,
'sort_order' => 90,
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
$agentMenuId = DB::table('admin_menus')->where('code', 'system.agents')->value('id');
if ($agentMenuId === null) {
$agentMenuId = DB::table('admin_menus')->insertGetId([
'parent_id' => (int) $systemMenuId,
'menu_type' => 'page',
'code' => 'system.agents',
'name' => '代理管理',
'path' => '/admin/agents',
'route_name' => 'admin.agents',
'component' => 'agents/index',
'icon' => null,
'active_menu_code' => null,
'sort_order' => 25,
'is_visible' => true,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
foreach ([
['permission_code' => 'agent.node.view', 'action_id' => (int) $viewActionId, 'name' => '代理节点查看'],
['permission_code' => 'agent.node.manage', 'action_id' => (int) $manageActionId, 'name' => '代理节点管理'],
] as $row) {
if (DB::table('admin_menu_actions')->where('permission_code', $row['permission_code'])->exists()) {
continue;
}
DB::table('admin_menu_actions')->insert([
'menu_id' => (int) $agentMenuId,
'action_id' => $row['action_id'],
'permission_code' => $row['permission_code'],
'name' => $row['name'],
'status' => 1,
'created_at' => $now,
'updated_at' => $now,
]);
}
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$resources = array_values(array_filter(
AdminAuthorizationRegistry::resources(),
static fn (array $resource): bool => str_starts_with((string) $resource['code'], 'admin.agent-nodes.'),
));
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();
$permissionCodes = $resource['permission_codes'] ?? [];
foreach ($permissionCodes 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,
]);
}
}
if (! Schema::hasTable('admin_role_api_resources')) {
return;
}
$superRoleId = DB::table('admin_roles')->where('slug', 'super_admin')->value('id');
if ($superRoleId === null) {
return;
}
foreach ($resources as $resource) {
$resourceId = DB::table('admin_api_resources')->where('code', $resource['code'])->value('id');
if ($resourceId === null) {
continue;
}
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $superRoleId,
'api_resource_id' => (int) $resourceId,
], []);
}
$menuActionIdList = DB::table('admin_menu_actions')
->whereIn('permission_code', ['agent.node.view', 'agent.node.manage'])
->pluck('id');
foreach ($menuActionIdList as $menuActionId) {
DB::table('admin_role_menu_actions')->updateOrInsert([
'role_id' => (int) $superRoleId,
'menu_action_id' => (int) $menuActionId,
], []);
}
}
public function down(): void
{
$codes = [
'admin.agent-nodes.tree',
'admin.agent-nodes.store',
'admin.agent-nodes.show',
'admin.agent-nodes.update',
'admin.agent-nodes.children',
];
foreach ($codes as $code) {
$resourceId = DB::table('admin_api_resources')->where('code', $code)->value('id');
if ($resourceId === null) {
continue;
}
if (Schema::hasTable('admin_role_api_resources')) {
DB::table('admin_role_api_resources')->where('api_resource_id', (int) $resourceId)->delete();
}
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}
DB::table('admin_menu_actions')
->whereIn('permission_code', ['agent.node.view', 'agent.node.manage'])
->delete();
}
};

View File

@@ -1,78 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
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('admin_roles', function (Blueprint $table): void {
$table->foreignId('owner_agent_id')->nullable()->after('sort_order')->constrained('agent_nodes')->nullOnDelete();
$table->foreignId('delegated_from_role_id')->nullable()->after('owner_agent_id')->constrained('admin_roles')->nullOnDelete();
$table->string('scope_type', 16)->default('system')->after('delegated_from_role_id');
});
Schema::create('admin_user_agent_roles', function (Blueprint $table): void {
$table->foreignId('admin_user_id')->constrained('admin_users')->cascadeOnDelete();
$table->foreignId('agent_node_id')->constrained('agent_nodes')->cascadeOnDelete();
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
$table->timestamp('granted_at')->nullable();
$table->primary(['admin_user_id', 'agent_node_id', 'role_id'], 'pk_admin_user_agent_roles');
});
Schema::table('players', function (Blueprint $table): void {
$table->foreignId('agent_node_id')->nullable()->after('site_code')->constrained('agent_nodes')->nullOnDelete();
$table->index(['site_code', 'agent_node_id'], 'idx_players_site_agent');
});
DB::table('admin_roles')->update(['scope_type' => 'system']);
$this->backfillAdminUserAgentRoles();
}
public function down(): void
{
Schema::table('players', function (Blueprint $table): void {
$table->dropIndex('idx_players_site_agent');
$table->dropConstrainedForeignId('agent_node_id');
});
Schema::dropIfExists('admin_user_agent_roles');
Schema::table('admin_roles', function (Blueprint $table): void {
$table->dropConstrainedForeignId('delegated_from_role_id');
$table->dropConstrainedForeignId('owner_agent_id');
$table->dropColumn('scope_type');
});
}
private function backfillAdminUserAgentRoles(): void
{
$now = Carbon::now();
$rows = DB::table('admin_user_site_roles as usr')
->join('admin_user_agents as uaa', 'uaa.admin_user_id', '=', 'usr.admin_user_id')
->join('agent_nodes as an', static function ($join): void {
$join->on('an.id', '=', 'uaa.agent_node_id')
->on('an.admin_site_id', '=', 'usr.site_id');
})
->select(['usr.admin_user_id', 'uaa.agent_node_id', 'usr.role_id', 'usr.granted_at'])
->get();
foreach ($rows as $row) {
DB::table('admin_user_agent_roles')->updateOrInsert(
[
'admin_user_id' => (int) $row->admin_user_id,
'agent_node_id' => (int) $row->agent_node_id,
'role_id' => (int) $row->role_id,
],
[
'granted_at' => $row->granted_at ?? $now,
],
);
}
}
};

View File

@@ -1,116 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
/**
* 代理角色/账号 API 绑定到已有 agent.node.view / agent.node.manage 动作(同菜单下 action 唯一)。
*/
return new class extends Migration
{
public function up(): void
{
$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 => str_starts_with((string) $resource['code'], 'admin.agent-roles.')
|| str_starts_with((string) $resource['code'], 'admin.agent-admin-users.'),
));
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,
]);
}
}
if (! Schema::hasTable('admin_role_api_resources')) {
return;
}
$superRoleId = DB::table('admin_roles')->where('slug', 'super_admin')->value('id');
if ($superRoleId === null) {
return;
}
foreach ($resources as $resource) {
$resourceId = DB::table('admin_api_resources')->where('code', $resource['code'])->value('id');
if ($resourceId === null) {
continue;
}
DB::table('admin_role_api_resources')->updateOrInsert([
'role_id' => (int) $superRoleId,
'api_resource_id' => (int) $resourceId,
], []);
}
}
public function down(): void
{
$codes = [
'admin.agent-roles.update',
'admin.agent-roles.destroy',
'admin.agent-roles.permissions.sync',
'admin.agent-roles.index',
'admin.agent-roles.store',
'admin.agent-admin-users.index',
'admin.agent-admin-users.store',
'admin.agent-admin-users.roles.sync',
];
foreach ($codes as $code) {
$resourceId = DB::table('admin_api_resources')->where('code', $code)->value('id');
if ($resourceId === null) {
continue;
}
if (Schema::hasTable('admin_role_api_resources')) {
DB::table('admin_role_api_resources')->where('api_resource_id', (int) $resourceId)->delete();
}
DB::table('admin_api_resource_bindings')->where('api_resource_id', (int) $resourceId)->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}
}
};

View File

@@ -1,30 +0,0 @@
<?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_delegation_grants', function (Blueprint $table): void {
$table->id();
$table->foreignId('parent_agent_id')->constrained('agent_nodes')->cascadeOnDelete();
$table->foreignId('child_agent_id')->constrained('agent_nodes')->cascadeOnDelete();
$table->foreignId('menu_action_id')->constrained('admin_menu_actions')->cascadeOnDelete();
$table->boolean('can_delegate')->default(false);
$table->foreignId('granted_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->timestamp('granted_at')->nullable();
$table->timestamps();
$table->unique(['child_agent_id', 'menu_action_id'], 'uk_agent_delegation_child_action');
$table->index(['parent_agent_id', 'child_agent_id'], 'idx_agent_delegation_parent_child');
});
}
public function down(): void
{
Schema::dropIfExists('agent_delegation_grants');
}
};

View File

@@ -1,34 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* 将未归属玩家挂到对应主站根代理(与 agent_nodes depth=0 1:1)。
*/
return new class extends Migration
{
public function up(): void
{
if (! \Illuminate\Support\Facades\Schema::hasColumn('players', 'agent_node_id')) {
return;
}
$roots = DB::table('agent_nodes as an')
->join('admin_sites as s', 's.id', '=', 'an.admin_site_id')
->where('an.depth', 0)
->get(['an.id as root_id', 's.code as site_code']);
foreach ($roots as $root) {
DB::table('players')
->where('site_code', (string) $root->site_code)
->whereNull('agent_node_id')
->update(['agent_node_id' => (int) $root->root_id]);
}
}
public function down(): void
{
// 不回滚归属,避免误清空业务绑定。
}
};

View File

@@ -1,177 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
/**
* 代理「节点 / 角色 / 账号」查看与管理拆分为独立 permission_code避免只勾一项却获得全部管理能力。
*/
return new class extends Migration
{
public function up(): void
{
$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;
}
$agentMenuId = (int) DB::table('admin_menus')->where('code', 'system.agents')->value('id');
if ($agentMenuId === 0) {
return;
}
$rolesMenuId = $this->ensureChildMenu($agentMenuId, 'system.agents.roles', '代理角色', $now);
$usersMenuId = $this->ensureChildMenu($agentMenuId, 'system.agents.users', '代理账号', $now);
$this->ensureMenuAction((int) $rolesMenuId, (int) $viewActionId, 'agent.role.view', '代理角色查看', $now);
$this->ensureMenuAction((int) $rolesMenuId, (int) $manageActionId, 'agent.role.manage', '代理角色管理', $now);
$this->ensureMenuAction((int) $usersMenuId, (int) $viewActionId, 'agent.user.view', '代理账号查看', $now);
$this->ensureMenuAction((int) $usersMenuId, (int) $manageActionId, 'agent.user.manage', '代理账号管理', $now);
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$nodeViewId = $menuActionIds['agent.node.view'] ?? null;
$nodeManageId = $menuActionIds['agent.node.manage'] ?? null;
$roleViewId = $menuActionIds['agent.role.view'] ?? null;
$roleManageId = $menuActionIds['agent.role.manage'] ?? null;
$userViewId = $menuActionIds['agent.user.view'] ?? null;
$userManageId = $menuActionIds['agent.user.manage'] ?? null;
if ($nodeViewId !== null && $roleViewId !== null && $userViewId !== null) {
$roleIdsWithNodeView = DB::table('admin_role_menu_actions')
->where('menu_action_id', (int) $nodeViewId)
->pluck('role_id')
->unique()
->all();
foreach ($roleIdsWithNodeView as $roleId) {
foreach ([$roleViewId, $userViewId] as $actionId) {
$this->attachRoleMenuAction((int) $roleId, (int) $actionId, $now);
}
}
}
if ($nodeManageId !== null && $roleManageId !== null && $userManageId !== null) {
$roleIdsWithNodeManage = DB::table('admin_role_menu_actions')
->where('menu_action_id', (int) $nodeManageId)
->pluck('role_id')
->unique()
->all();
foreach ($roleIdsWithNodeManage as $roleId) {
foreach ([$roleManageId, $userManageId] as $actionId) {
$this->attachRoleMenuAction((int) $roleId, (int) $actionId, $now);
}
}
}
$resources = array_values(array_filter(
AdminAuthorizationRegistry::resources(),
static fn (array $resource): bool => str_starts_with((string) $resource['code'], 'admin.agent-')
));
foreach ($resources as $resource) {
$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();
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,
]);
}
}
}
private function ensureChildMenu(int $parentId, string $code, string $name, Carbon $now): 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' => 'system.agents',
'sort_order' => 0,
'is_visible' => false,
'is_cache' => false,
'is_external' => false,
'status' => 1,
'meta_json' => null,
'created_at' => $now,
'updated_at' => $now,
]);
}
private function ensureMenuAction(int $menuId, int $actionId, string $permissionCode, string $name, Carbon $now): void
{
if (DB::table('admin_menu_actions')->where('permission_code', $permissionCode)->exists()) {
return;
}
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,
]);
}
private function attachRoleMenuAction(int $roleId, int $menuActionId, Carbon $now): void
{
$exists = DB::table('admin_role_menu_actions')
->where('role_id', $roleId)
->where('menu_action_id', $menuActionId)
->exists();
if ($exists) {
return;
}
DB::table('admin_role_menu_actions')->insert([
'role_id' => $roleId,
'menu_action_id' => $menuActionId,
]);
}
public function down(): void
{
$codes = ['agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage'];
$actionIds = DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->pluck('id')->all();
if ($actionIds !== []) {
DB::table('admin_role_menu_actions')->whereIn('menu_action_id', $actionIds)->delete();
DB::table('admin_api_resource_bindings')->whereIn('menu_action_id', $actionIds)->delete();
DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->delete();
}
DB::table('admin_menus')->whereIn('code', ['system.agents.roles', 'system.agents.users'])->delete();
}
};

View File

@@ -1,87 +0,0 @@
<?php
use App\Support\AdminAuthorizationRegistry;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
/** 补齐代理账号删除 API 资源admin.agent-admin-users.destroy。 */
return new class extends Migration
{
private const RESOURCE_CODE = 'admin.agent-admin-users.destroy';
public function up(): void
{
$resource = collect(AdminAuthorizationRegistry::resources())
->firstWhere('code', self::RESOURCE_CODE);
if ($resource === null) {
return;
}
$now = Carbon::now();
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$resourceId = DB::table('admin_api_resources')
->where('code', self::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' => self::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,
]);
}
}
public function down(): void
{
$resourceId = DB::table('admin_api_resources')
->where('code', self::RESOURCE_CODE)
->value('id');
if ($resourceId === null) {
return;
}
DB::table('admin_api_resource_bindings')
->where('api_resource_id', (int) $resourceId)
->delete();
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
}
};

View File

@@ -1,60 +0,0 @@
<?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(),
]);
}
}
};

View File

@@ -1,140 +0,0 @@
<?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');
}
};

View File

@@ -1,155 +0,0 @@
<?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.settlement-payments.',
'admin.settlement-adjustments.',
'admin.settlement-reports.',
'admin.credit-ledger.',
'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,
], []);
}
}
};

View File

@@ -1,38 +0,0 @@
<?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,
]);
\App\Support\AgentDefaultRolePermissions::ensurePlatformAgentRole();
\App\Models\AdminUser::query()
->whereIn('id', \Illuminate\Support\Facades\DB::table('admin_user_agents')->pluck('admin_user_id'))
->each(static function (\App\Models\AdminUser $user): void {
$agentNodeId = $user->primaryAgentNodeId();
if ($agentNodeId !== null) {
$user->syncPrimaryPlatformAgentRole($agentNodeId);
}
});
}
public function down(): void
{
Schema::table('agent_profiles', function (Blueprint $table): void {
$table->dropColumn(['can_create_child_agent', 'can_create_player']);
});
}
};

View File

@@ -1,51 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* 修正代理主账号 admin_users.status agent_nodes.status 语义不一致的历史数据。
*
* admin_users: 0=可登录, 1=禁用
* agent_nodes: 1=启用, 0=停用
*/
return new class extends Migration
{
public function up(): void
{
$now = now();
$enabledUserIds = DB::table('admin_user_agents as aua')
->join('agent_nodes as an', 'an.id', '=', 'aua.agent_node_id')
->join('admin_users as au', 'au.id', '=', 'aua.admin_user_id')
->where('aua.is_primary', true)
->where('an.status', 1)
->where('au.status', 1)
->pluck('au.id');
if ($enabledUserIds->isNotEmpty()) {
DB::table('admin_users')
->whereIn('id', $enabledUserIds->all())
->update(['status' => 0, 'updated_at' => $now]);
}
$disabledUserIds = DB::table('admin_user_agents as aua')
->join('agent_nodes as an', 'an.id', '=', 'aua.agent_node_id')
->join('admin_users as au', 'au.id', '=', 'aua.admin_user_id')
->where('aua.is_primary', true)
->where('an.status', 0)
->where('au.status', 0)
->pluck('au.id');
if ($disabledUserIds->isNotEmpty()) {
DB::table('admin_users')
->whereIn('id', $disabledUserIds->all())
->update(['status' => 1, 'updated_at' => $now]);
}
}
public function down(): void
{
// 不可逆:无法可靠还原误写前的 admin_users.status
}
};

View File

@@ -1,88 +0,0 @@
<?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('ticket_items', function (Blueprint $table): void {
$table->foreignId('agent_node_id')->nullable()->after('player_id')->constrained('agent_nodes')->nullOnDelete();
$table->json('share_snapshot')->nullable()->after('rule_snapshot_json');
$table->decimal('agent_rebate_rate_snapshot', 8, 4)->nullable()->after('share_snapshot');
$table->timestamp('agent_settled_at')->nullable()->after('settled_at');
$table->foreignId('agent_settlement_reversal_of_id')->nullable()->after('agent_settled_at')
->constrained('ticket_items')->nullOnDelete();
});
Schema::create('share_ledger', function (Blueprint $table): void {
$table->id();
$table->foreignId('ticket_item_id')->constrained('ticket_items')->cascadeOnDelete();
$table->foreignId('player_id')->constrained('players')->cascadeOnDelete();
$table->foreignId('agent_node_id')->nullable()->constrained('agent_nodes')->nullOnDelete();
$table->json('agent_path')->nullable();
$table->json('share_snapshot')->nullable();
$table->bigInteger('game_win_loss')->default(0);
$table->bigInteger('basic_rebate')->default(0);
$table->bigInteger('shared_net_win_loss')->default(0);
$table->json('allocations_json')->nullable();
$table->foreignId('settlement_period_id')->nullable()->constrained('settlement_periods')->nullOnDelete();
$table->unsignedBigInteger('reversal_of_id')->nullable();
$table->timestamp('settled_at');
$table->timestamps();
$table->index(['settled_at', 'player_id']);
$table->index(['settlement_period_id']);
});
Schema::table('share_ledger', function (Blueprint $table): void {
$table->foreign('reversal_of_id')->references('id')->on('share_ledger')->nullOnDelete();
});
Schema::table('rebate_records', function (Blueprint $table): void {
$table->foreignId('ticket_item_id')->nullable()->after('player_id')->constrained('ticket_items')->nullOnDelete();
$table->foreignId('reversal_of_id')->nullable()->after('ticket_item_id')->constrained('rebate_records')->nullOnDelete();
});
Schema::table('settlement_bills', function (Blueprint $table): void {
$table->timestamp('locked_at')->nullable()->after('confirmed_at');
$table->foreignId('reversed_bill_id')->nullable()->after('locked_at')->constrained('settlement_bills')->nullOnDelete();
$table->json('meta_json')->nullable()->after('reversed_bill_id');
});
Schema::create('settlement_adjustments', function (Blueprint $table): void {
$table->id();
$table->foreignId('settlement_period_id')->nullable()->constrained('settlement_periods')->nullOnDelete();
$table->foreignId('original_bill_id')->nullable()->constrained('settlement_bills')->nullOnDelete();
$table->string('adjustment_type', 32);
$table->bigInteger('amount');
$table->string('reason', 255)->nullable();
$table->foreignId('created_by')->nullable()->constrained('admin_users')->nullOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('settlement_adjustments');
Schema::table('settlement_bills', function (Blueprint $table): void {
$table->dropConstrainedForeignId('reversed_bill_id');
$table->dropColumn(['locked_at', 'meta_json']);
});
Schema::table('rebate_records', function (Blueprint $table): void {
$table->dropConstrainedForeignId('reversal_of_id');
$table->dropConstrainedForeignId('ticket_item_id');
});
Schema::dropIfExists('share_ledger');
Schema::table('ticket_items', function (Blueprint $table): void {
$table->dropConstrainedForeignId('agent_settlement_reversal_of_id');
$table->dropConstrainedForeignId('agent_node_id');
$table->dropColumn(['share_snapshot', 'agent_rebate_rate_snapshot', 'agent_settled_at']);
});
}
};

View File

@@ -1,43 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('players', function (Blueprint $table): void {
$table->string('auth_source', 16)->default('main_site_sso')->after('site_player_id');
$table->string('funding_mode', 16)->default('wallet')->after('auth_source');
$table->string('password_hash', 255)->nullable()->after('username');
$table->unsignedSmallInteger('login_failed_count')->default(0)->after('last_login_at');
$table->timestamp('login_locked_until')->nullable()->after('login_failed_count');
});
DB::table('players')->update([
'auth_source' => 'main_site_sso',
'funding_mode' => 'wallet',
]);
Schema::table('players', function (Blueprint $table): void {
$table->index(['site_code', 'auth_source', 'username'], 'idx_players_site_auth_username');
});
}
public function down(): void
{
Schema::table('players', function (Blueprint $table): void {
$table->dropIndex('idx_players_site_auth_username');
$table->dropColumn([
'auth_source',
'funding_mode',
'password_hash',
'login_failed_count',
'login_locked_until',
]);
});
}
};

View File

@@ -1,23 +0,0 @@
<?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('payment_records', function (Blueprint $table): void {
$table->text('proof')->nullable()->after('method');
$table->string('remark', 255)->nullable()->after('proof');
});
}
public function down(): void
{
Schema::table('payment_records', function (Blueprint $table): void {
$table->dropColumn(['proof', 'remark']);
});
}
};

View File

@@ -1,30 +0,0 @@
<?php
use App\Models\AdminRole;
use App\Models\AgentNode;
use App\Support\AgentDefaultRolePermissions;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
AgentNode::query()->each(static function (AgentNode $node): void {
$role = AdminRole::query()
->where('owner_agent_id', $node->id)
->where('slug', 'agent_owner_'.$node->id)
->first();
if ($role === null) {
return;
}
$role->syncLegacyPermissionSlugs(AgentDefaultRolePermissions::ownerSlugsForNode($node));
});
}
public function down(): void
{
// 权限包为产品策略,回滚不恢复旧 slug 集合。
}
};

View File

@@ -1,63 +0,0 @@
<?php
use App\Models\AdminRole;
use App\Models\AdminUser;
use App\Models\AgentNode;
use App\Support\AgentDefaultRolePermissions;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$platformRole = AgentDefaultRolePermissions::ensurePlatformAgentRole();
AgentNode::query()->each(static function (AgentNode $node): void {
$ownerRole = AdminRole::query()
->where('owner_agent_id', $node->id)
->where('slug', 'agent_owner_'.$node->id)
->first();
if ($ownerRole !== null) {
$ownerRole->syncLegacyPermissionSlugs(AgentDefaultRolePermissions::ownerSlugsForNode($node));
}
});
$bindings = DB::table('admin_user_agents')->get(['admin_user_id', 'agent_node_id']);
foreach ($bindings as $binding) {
$adminUserId = (int) $binding->admin_user_id;
$agentNodeId = (int) $binding->agent_node_id;
$user = AdminUser::query()->find($adminUserId);
if ($user === null) {
continue;
}
$agentRoleIds = DB::table('admin_user_agent_roles')
->where('admin_user_id', $adminUserId)
->where('agent_node_id', $agentNodeId)
->pluck('role_id')
->map(static fn ($id): int => (int) $id)
->all();
if ($agentRoleIds === []) {
$ownerId = (int) (AdminRole::query()
->where('owner_agent_id', $agentNodeId)
->where('slug', 'agent_owner_'.$agentNodeId)
->value('id') ?? 0);
if ($ownerId > 0) {
$agentRoleIds = [$ownerId];
}
}
if ($agentRoleIds !== []) {
$user->syncAgentRoleIds($agentNodeId, $agentRoleIds);
}
}
}
public function down(): void
{
// 不回滚权限与 pivot避免经营账号失权。
}
};

View File

@@ -1,38 +0,0 @@
<?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('settlement_bills', function (Blueprint $table): void {
$table->bigInteger('platform_rounding_adjustment')->default(0)->after('adjustment_amount');
});
Schema::table('players', function (Blueprint $table): void {
$table->json('risk_tags')->nullable()->after('status');
});
Schema::table('agent_nodes', function (Blueprint $table): void {
$table->json('risk_tags')->nullable()->after('status');
});
}
public function down(): void
{
Schema::table('agent_nodes', function (Blueprint $table): void {
$table->dropColumn('risk_tags');
});
Schema::table('players', function (Blueprint $table): void {
$table->dropColumn('risk_tags');
});
Schema::table('settlement_bills', function (Blueprint $table): void {
$table->dropColumn('platform_rounding_adjustment');
});
}
};

View File

@@ -1,44 +0,0 @@
<?php
use App\Models\AdminRole;
use App\Models\AdminUser;
use App\Support\AgentDefaultRolePermissions;
use App\Support\AgentPlatformRole;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
AgentDefaultRolePermissions::ensurePlatformAgentRole();
$platformRoleId = AgentPlatformRole::id();
foreach (DB::table('admin_user_agents')->get(['admin_user_id', 'agent_node_id']) as $binding) {
$user = AdminUser::query()->find((int) $binding->admin_user_id);
if ($user === null) {
continue;
}
$user->syncPrimaryPlatformAgentRole((int) $binding->agent_node_id);
}
$ownerRoleIds = AdminRole::query()
->where('scope_type', AdminRole::SCOPE_AGENT)
->where('slug', 'like', 'agent_owner_%')
->pluck('id')
->all();
if ($ownerRoleIds !== []) {
DB::table('admin_user_agent_roles')->whereIn('role_id', $ownerRoleIds)->delete();
DB::table('admin_user_site_roles')->whereIn('role_id', $ownerRoleIds)->delete();
DB::table('admin_role_menu_actions')->whereIn('role_id', $ownerRoleIds)->delete();
AdminRole::query()->whereIn('id', $ownerRoleIds)->delete();
}
}
public function down(): void
{
// 不回滚:避免经营账号失权。
}
};

View File

@@ -1,17 +0,0 @@
<?php
use App\Support\PlatformSystemRoles;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
PlatformSystemRoles::ensureAll();
}
public function down(): void
{
// 不回滚内置角色与权限,避免平台/代理账号失权。
}
};

View File

@@ -1,94 +0,0 @@
<?php
use Illuminate\Support\Carbon;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use App\Support\AdminAuthorizationRegistry;
/**
* 信用流水 API 注册到 admin_api_resources已有库增量同步避免 500 api_resource_not_configured
*/
return new class extends Migration
{
/** @var list<string> */
private const RESOURCE_CODES = [
'admin.credit-ledger.index',
'admin.settlement-payments.index',
'admin.settlement-adjustments.index',
'admin.settlement-reports.summary',
'admin.settlement-reports.show',
];
public function up(): void
{
$now = Carbon::now();
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
$byCode = collect(AdminAuthorizationRegistry::resources())->keyBy('code');
foreach (self::RESOURCE_CODES as $code) {
$resource = $byCode->get($code);
if (! is_array($resource)) {
continue;
}
$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,
]);
}
}
}
public function down(): void
{
foreach (self::RESOURCE_CODES as $code) {
$resourceId = DB::table('admin_api_resources')->where('code', $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();
}
}
};

View File

@@ -0,0 +1,18 @@
# 迁移目录说明
当前项目已切换为 **schema dump 作为数据库基线** 的维护方式。
- 最终版 PostgreSQL 结构:[`../schema/pgsql-schema.sql`](../schema/pgsql-schema.sql)
- 新环境初始化:优先加载 schema dump再执行后续新增 migration
- 旧的历史 migration 已清理,不再作为基线结构来源
后续规则:
1. 新增数据库结构变更时,继续正常创建新的 migration 文件放在本目录。
2. 当结构进入一个新的稳定阶段后,可重新执行:
```bash
php artisan schema:dump --database=pgsql --prune
```
3. 执行 `--prune` 前,确认团队已接受“历史迁移链不再保留”的方式。