environment('production')) { return; } $hall = app(DrawHallSnapshotBuilder::class); $draw = $hall->resolveHallTarget(); if ($draw === null) { $draw = $this->createFallbackOpenDraw(); } $player = Player::query() ->where('site_code', 'demo') ->where('site_player_id', 'demo-player-001') ->first() ?? Player::query()->orderBy('id')->first(); if ($player === null) { $this->command?->warn('DashboardHallFixtureSeeder: 无 players 行,请先执行 DevPlayerAndWalletSeeder。'); return; } DB::transaction(function () use ($draw, $player): void { $this->seedRiskPools($draw); $this->seedTicketOrders($draw, $player); $this->seedPendingReviewBatch($draw); $this->seedAbnormalTransfers($player); }); $this->command?->info(sprintf( 'DashboardHallFixtureSeeder: 已写入 hall 当期 draw_no=%s id=%d', $draw->draw_no, $draw->id, )); } private function createFallbackOpenDraw(): Draw { $now = Carbon::now()->utc(); $biz = $now->format('Y-m-d'); $ymd = str_replace('-', '', $biz); return Draw::query()->updateOrCreate( ['draw_no' => $ymd.'-997'], [ 'business_date' => $biz, 'sequence_no' => 997, 'status' => DrawStatus::Open->value, 'start_time' => $now->copy()->subMinutes(5), 'close_time' => $now->copy()->addHours(2), 'draw_time' => $now->copy()->addHours(2)->addSeconds(30), 'cooling_end_time' => null, 'result_source' => null, 'current_result_version' => 0, 'settle_version' => 0, 'is_reopened' => false, ], ); } private function seedRiskPools(Draw $draw): void { $rows = [ // 4D 纯数字 ['1234', 5_000_000, 3_200_000, 0], ['2387', 2_000_000, 1_800_000, 0], ['4752', 1_500_000, 1_200_000, 0], ['8899', 800_000, 720_000, 0], ['5678', 3_000_000, 900_000, 0], ['3456', 1_200_000, 600_000, 0], ['1111', 2_000_000, 400_000, 0], ['2222', 1_000_000, 250_000, 0], ['3333', 900_000, 450_000, 0], ['4444', 700_000, 350_000, 0], ['0999', 300_000, 180_000, 0], ['0099', 200_000, 50_000, 0], // 3D:占位字母去掉后剩 3 位数字(如 909) ['90X9', 1_200_000, 960_000, 0], ['80Y0', 900_000, 720_000, 0], ['Z999', 700_000, 560_000, 0], // 2D:剩 2 位数字 ['9X9X', 650_000, 520_000, 0], ['8Y8Z', 550_000, 330_000, 0], // 特别号:含字母且有效数字不足 3 位 ['12AB', 600_000, 120_000, 0], ['ABCD', 420_000, 210_000, 0], ['1A2B', 380_000, 95_000, 0], // 售罄 —— 覆盖 4D / 3D / 2D / 特别 ['0001', 500_000, 500_000, 1], ['9999', 400_000, 400_000, 1], ['9Y90', 350_000, 350_000, 1], ['9A9B', 280_000, 280_000, 1], ['AB12', 220_000, 220_000, 1], ]; foreach ($rows as [$num, $cap, $locked, $sold]) { $remaining = max(0, $cap - $locked); RiskPool::query()->updateOrCreate( ['draw_id' => $draw->id, 'normalized_number' => $num], [ 'total_cap_amount' => $cap, 'locked_amount' => $locked, 'remaining_amount' => $remaining, 'sold_out_status' => $sold, 'version' => 1, ], ); } } private function seedTicketOrders(Draw $draw, Player $player): void { $suffix = (string) $draw->id; $o1 = TicketOrder::query()->firstOrCreate( ['order_no' => 'ORD-HALL-'.$suffix.'-A'], [ 'player_id' => $player->id, 'draw_id' => $draw->id, 'currency_code' => strtoupper((string) ($player->default_currency ?? 'NPR')), 'total_bet_amount' => 280_000, 'total_rebate_amount' => 0, 'total_actual_deduct' => 280_000, 'total_estimated_payout' => 0, 'status' => 'placed', 'submit_source' => 'h5', 'client_trace_id' => 'hall-fixture-a', ], ); TicketItem::query()->firstOrCreate( ['ticket_no' => 'TK-HALL-'.$suffix.'-001'], [ 'order_id' => $o1->id, 'player_id' => $player->id, 'draw_id' => $draw->id, 'original_number' => '1234', 'normalized_number' => '1234', 'play_code' => 'straight', 'dimension' => 4, 'digit_slot' => null, 'bet_mode' => null, 'unit_bet_amount' => 200_000, 'total_bet_amount' => 200_000, 'rebate_rate_snapshot' => 0, 'commission_rate_snapshot' => 0, 'actual_deduct_amount' => 200_000, 'odds_snapshot_json' => null, 'rule_snapshot_json' => null, 'combination_count' => 1, 'estimated_max_payout' => 0, 'risk_locked_amount' => 0, 'status' => 'success', 'fail_reason_code' => null, 'fail_reason_text' => null, 'win_amount' => 15_000, 'jackpot_win_amount' => 0, 'settled_at' => null, ], ); TicketItem::query()->firstOrCreate( ['ticket_no' => 'TK-HALL-'.$suffix.'-002'], [ 'order_id' => $o1->id, 'player_id' => $player->id, 'draw_id' => $draw->id, 'original_number' => '5678', 'normalized_number' => '5678', 'play_code' => 'straight', 'dimension' => 4, 'digit_slot' => null, 'bet_mode' => null, 'unit_bet_amount' => 80_000, 'total_bet_amount' => 80_000, 'rebate_rate_snapshot' => 0, 'commission_rate_snapshot' => 0, 'actual_deduct_amount' => 80_000, 'odds_snapshot_json' => null, 'rule_snapshot_json' => null, 'combination_count' => 1, 'estimated_max_payout' => 0, 'risk_locked_amount' => 0, 'status' => 'success', 'fail_reason_code' => null, 'fail_reason_text' => null, 'win_amount' => 0, 'jackpot_win_amount' => 5_000, 'settled_at' => null, ], ); $o2 = TicketOrder::query()->firstOrCreate( ['order_no' => 'ORD-HALL-'.$suffix.'-B'], [ 'player_id' => $player->id, 'draw_id' => $draw->id, 'currency_code' => strtoupper((string) ($player->default_currency ?? 'NPR')), 'total_bet_amount' => 120_000, 'total_rebate_amount' => 0, 'total_actual_deduct' => 120_000, 'total_estimated_payout' => 0, 'status' => 'placed', 'submit_source' => 'h5', 'client_trace_id' => 'hall-fixture-b', ], ); TicketItem::query()->firstOrCreate( ['ticket_no' => 'TK-HALL-'.$suffix.'-003'], [ 'order_id' => $o2->id, 'player_id' => $player->id, 'draw_id' => $draw->id, 'original_number' => '2387', 'normalized_number' => '2387', 'play_code' => 'straight', 'dimension' => 4, 'digit_slot' => null, 'bet_mode' => null, 'unit_bet_amount' => 120_000, 'total_bet_amount' => 120_000, 'rebate_rate_snapshot' => 0, 'commission_rate_snapshot' => 0, 'actual_deduct_amount' => 120_000, 'odds_snapshot_json' => null, 'rule_snapshot_json' => null, 'combination_count' => 1, 'estimated_max_payout' => 0, 'risk_locked_amount' => 0, 'status' => 'success', 'fail_reason_code' => null, 'fail_reason_text' => null, 'win_amount' => 0, 'jackpot_win_amount' => 0, 'settled_at' => null, ], ); } private function seedPendingReviewBatch(Draw $draw): void { $max = (int) DrawResultBatch::query()->where('draw_id', $draw->id)->max('result_version'); $next = max(1, $max + 1); DrawResultBatch::query()->firstOrCreate( [ 'draw_id' => $draw->id, 'result_version' => $next, ], [ 'source_type' => 'manual', 'rng_seed_hash' => null, 'raw_seed_encrypted' => null, 'status' => DrawResultBatchStatus::PendingReview->value, 'created_by' => null, 'confirmed_by' => null, 'confirmed_at' => null, ], ); } private function seedAbnormalTransfers(Player $player): void { $rows = [ ['TR-HALL-DEMO-F1', 'failed', 'idem-hall-f1'], ['TR-HALL-DEMO-P1', 'processing', 'idem-hall-p1'], ['TR-HALL-DEMO-R1', 'pending_reconcile', 'idem-hall-r1'], ]; foreach ($rows as [$no, $status, $idem]) { TransferOrder::query()->firstOrCreate( ['transfer_no' => $no], [ 'player_id' => $player->id, 'direction' => 'in', 'currency_code' => strtoupper((string) ($player->default_currency ?? 'NPR')), 'amount' => 10_000, 'idempotent_key' => $idem, 'status' => $status, 'external_request_payload' => null, 'external_response_payload' => null, 'external_ref_no' => null, 'fail_reason' => $status === 'failed' ? 'demo seed' : null, 'finished_at' => $status === 'failed' ? now() : null, ], ); } } }