feat: 增强奖池与钱包管理功能

更新 AdminJackpotPoolUpdateController 校验规则,禁止传入 current_amount。
优化 AdminRiskPoolManualStatusController:更新奖池状态后同步 Redis 状态。
在 TransferOrderReconcileController 中新增 completeCredit 方法,用于处理卡住的转账订单对账。
调整 TransferOrderListController:优化转账订单处理条件。
在 TicketItemsIndexController 中实现支持时区的日期筛选,提升日期处理准确性。
扩展 JackpotPool 模型,新增 adjustments 关联关系。
改进票据与钱包相关服务中的错误处理和事务管理。
This commit is contained in:
2026-05-26 14:58:41 +08:00
parent 48349e3302
commit c8c90e3e94
45 changed files with 1877 additions and 104 deletions

View File

@@ -0,0 +1,41 @@
<?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

@@ -0,0 +1,30 @@
<?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

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