artisan('lottery:admin-auth-sync')->assertExitCode(0); }); test('severe overdue agent line (7+ days) freezes betting for all players in line', function (): void { $site = DB::table('admin_sites')->where('is_default', true)->first(); $extra = json_decode((string) ($site->extra_json ?? '{}'), true); if (! is_array($extra)) { $extra = []; } $extra['credit_line_mode'] = true; DB::table('admin_sites')->where('id', $site->id)->update([ 'extra_json' => json_encode($extra), 'updated_at' => now(), ]); $siteId = (int) $site->id; $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $super = AdminUser::query()->create([ 'username' => 'severe_super', 'name' => 'Severe', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); // 创建三级代理链: root -> A -> B $agentA = app(AgentNodeService::class)->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'SEV_A', 'name' => 'Agent A', 'username' => 'sev_agent_a', 'total_share_rate' => 60, 'credit_limit' => 100000, 'can_create_player' => true, ])); $agentB = app(AgentNodeService::class)->createChild($super, agentChildPayload([ 'parent_id' => $agentA->id, 'code' => 'SEV_B', 'name' => 'Agent B', 'username' => 'sev_agent_b', 'total_share_rate' => 40, 'credit_limit' => 50000, 'can_create_player' => true, ])); // 创建玩家归属 B $player = Player::query()->create([ 'site_code' => (string) $site->code, 'agent_node_id' => $agentB->id, 'site_player_id' => 'sev-p1', 'auth_source' => 'lottery_native', 'funding_mode' => 'credit', 'username' => 'sev_player', 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); DB::table('player_credit_accounts')->insert([ 'player_id' => $player->id, 'credit_limit' => 10000, 'used_credit' => 0, 'frozen_credit' => 0, 'created_at' => now(), 'updated_at' => now(), ]); // 创建 A 的严重逾期账单(8天前) $periodId = (int) DB::table('settlement_periods')->insertGetId([ 'admin_site_id' => $siteId, 'period_start' => now()->subWeeks(2), 'period_end' => now()->subWeeks(1), 'status' => 'closed', 'created_at' => now(), 'updated_at' => now(), ]); DB::table('settlement_bills')->insert([ 'settlement_period_id' => $periodId, 'bill_type' => 'agent', 'owner_type' => 'agent', 'owner_id' => $agentA->id, 'counterparty_type' => 'agent', 'counterparty_id' => $rootId, 'gross_win_loss' => 0, 'rebate_amount' => 0, 'adjustment_amount' => 0, 'net_amount' => 1000, 'paid_amount' => 0, 'unpaid_amount' => 1000, 'status' => 'overdue', 'updated_at' => now()->subDays(8), // 8天前逾期 'created_at' => now()->subDays(8), ]); // 验证严重逾期检查 expect(AgentOverdueGuard::agentHasSevereOverdueBills($agentA->id, 7))->toBeTrue(); expect(AgentOverdueGuard::agentLineHasSevereOverdueBills($agentB->id, 7))->toBeTrue(); // 玩家下注应该被拒绝 expect(fn () => app(PlayerCreditService::class)->assertMayPlaceBet($player, 100)) ->toThrow(\Illuminate\Validation\ValidationException::class); }); test('normal overdue (less than 7 days) does not freeze line betting', function (): void { $site = DB::table('admin_sites')->where('is_default', true)->first(); $extra = json_decode((string) ($site->extra_json ?? '{}'), true); if (! is_array($extra)) { $extra = []; } $extra['credit_line_mode'] = true; DB::table('admin_sites')->where('id', $site->id)->update([ 'extra_json' => json_encode($extra), 'updated_at' => now(), ]); $siteId = (int) $site->id; $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $super = AdminUser::query()->create([ 'username' => 'normal_super', 'name' => 'Normal', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $agentA = app(AgentNodeService::class)->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'NORM_A', 'name' => 'Agent A', 'username' => 'norm_agent_a', 'total_share_rate' => 60, 'credit_limit' => 100000, 'can_create_player' => true, ])); $agentB = app(AgentNodeService::class)->createChild($super, agentChildPayload([ 'parent_id' => $agentA->id, 'code' => 'NORM_B', 'name' => 'Agent B', 'username' => 'norm_agent_b', 'total_share_rate' => 40, 'credit_limit' => 50000, 'can_create_player' => true, ])); $player = Player::query()->create([ 'site_code' => (string) $site->code, 'agent_node_id' => $agentB->id, 'site_player_id' => 'norm-p1', 'auth_source' => 'lottery_native', 'funding_mode' => 'credit', 'username' => 'norm_player', 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); DB::table('player_credit_accounts')->insert([ 'player_id' => $player->id, 'credit_limit' => 10000, 'used_credit' => 0, 'frozen_credit' => 0, 'created_at' => now(), 'updated_at' => now(), ]); // 创建 A 的普通逾期账单(3天前) $periodId = (int) DB::table('settlement_periods')->insertGetId([ 'admin_site_id' => $siteId, 'period_start' => now()->subWeeks(2), 'period_end' => now()->subWeeks(1), 'status' => 'closed', 'created_at' => now(), 'updated_at' => now(), ]); DB::table('settlement_bills')->insert([ 'settlement_period_id' => $periodId, 'bill_type' => 'agent', 'owner_type' => 'agent', 'owner_id' => $agentA->id, 'counterparty_type' => 'agent', 'counterparty_id' => $rootId, 'gross_win_loss' => 0, 'rebate_amount' => 0, 'adjustment_amount' => 0, 'net_amount' => 1000, 'paid_amount' => 0, 'unpaid_amount' => 1000, 'status' => 'overdue', 'updated_at' => now()->subDays(3), // 3天前逾期 'created_at' => now()->subDays(3), ]); // 验证普通逾期检查 expect(AgentOverdueGuard::agentHasSevereOverdueBills($agentA->id, 7))->toBeFalse(); expect(AgentOverdueGuard::agentLineHasSevereOverdueBills($agentB->id, 7))->toBeFalse(); // 玩家下注应该成功(普通逾期不冻结整条线) expect(fn () => app(PlayerCreditService::class)->assertMayPlaceBet($player, 100)) ->not->toThrow(\Illuminate\Validation\ValidationException::class); }); test('severe overdue check respects configurable days threshold', function (): void { $site = DB::table('admin_sites')->where('is_default', true)->first(); $siteId = (int) $site->id; $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $super = AdminUser::query()->create([ 'username' => 'config_super', 'name' => 'Config', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $agentA = app(AgentNodeService::class)->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'CFG_A', 'name' => 'Agent A', 'username' => 'cfg_agent_a', 'total_share_rate' => 60, 'credit_limit' => 100000, ])); // 创建 5 天前的逾期账单 $periodId = (int) DB::table('settlement_periods')->insertGetId([ 'admin_site_id' => $siteId, 'period_start' => now()->subWeeks(2), 'period_end' => now()->subWeeks(1), 'status' => 'closed', 'created_at' => now(), 'updated_at' => now(), ]); DB::table('settlement_bills')->insert([ 'settlement_period_id' => $periodId, 'bill_type' => 'agent', 'owner_type' => 'agent', 'owner_id' => $agentA->id, 'counterparty_type' => 'agent', 'counterparty_id' => $rootId, 'gross_win_loss' => 0, 'rebate_amount' => 0, 'adjustment_amount' => 0, 'net_amount' => 1000, 'paid_amount' => 0, 'unpaid_amount' => 1000, 'status' => 'overdue', 'updated_at' => now()->subDays(5), 'created_at' => now()->subDays(5), ]); // 7天阈值:5天不算严重逾期 expect(AgentOverdueGuard::agentHasSevereOverdueBills($agentA->id, 7))->toBeFalse(); // 3天阈值:5天算严重逾期 expect(AgentOverdueGuard::agentHasSevereOverdueBills($agentA->id, 3))->toBeTrue(); });