*/ public function build(?string $currencyCode = null): array { $currency = $this->resolveBettableCurrency($currencyCode); $playVersion = PlayConfigVersion::query() ->where('status', ConfigVersionStatus::Active->value) ->firstOrFail(); $oddsVersion = OddsVersion::query() ->where('status', ConfigVersionStatus::Active->value) ->firstOrFail(); $riskVersion = RiskCapVersion::query() ->where('status', ConfigVersionStatus::Active->value) ->firstOrFail(); $playTypes = PlayType::query()->orderBy('sort_order')->orderBy('play_code')->get(); /** @var Collection $configByCode */ $configByCode = PlayConfigItem::query() ->where('version_id', $playVersion->id) ->get() ->keyBy('play_code'); $oddsRows = OddsItem::query() ->where('version_id', $oddsVersion->id) ->where('currency_code', $currency->code) ->get(); /** @var Collection> */ $oddsByPlay = $oddsRows->groupBy('play_code'); $riskItems = RiskCapItem::query() ->where('version_id', $riskVersion->id) ->orderBy('normalized_number') ->get(); $plays = $playTypes->map(function (PlayType $pt) use ($configByCode, $oddsByPlay): array { $c = $configByCode->get($pt->play_code); $items = $oddsByPlay->get($pt->play_code, collect()); $o = $this->pickPrimaryOddsItem($items); return [ 'play_code' => $pt->play_code, '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, 'sort_order' => (int) $pt->sort_order, 'supports_multi_number' => (bool) $pt->supports_multi_number, 'master_enabled' => (bool) $pt->is_enabled, 'config' => $c === null ? null : $this->serializePlayConfigItem($c), 'odds' => $o === null ? null : $this->serializeOddsItem($o), ]; })->values()->all(); return [ 'currency_code' => $currency->code, 'effective_versions' => [ 'play_config' => $this->serializeVersionHead($playVersion), 'odds' => $this->serializeVersionHead($oddsVersion), 'risk_cap' => $this->serializeVersionHead($riskVersion), ], 'plays' => $plays, 'risk_cap_items' => $riskItems->map(fn (RiskCapItem $r) => $this->serializeRiskItem($r))->all(), ]; } /** * 大厅列表展示单档赔率:优先头奖档 {@see first},兼容历史 {@see default}。 * * @param Collection $items */ private function pickPrimaryOddsItem(Collection $items): ?OddsItem { if ($items->isEmpty()) { return null; } foreach (['first', 'default', 'second', 'third', 'starter', 'consolation'] as $scope) { $hit = $items->firstWhere('prize_scope', $scope); if ($hit !== null) { return $hit; } } return $items->first(); } private function resolveBettableCurrency(?string $currencyCode): Currency { if ($currencyCode !== null && $currencyCode !== '') { $row = Currency::query()->where('code', strtoupper($currencyCode))->first(); if ($row === null || ! $row->is_enabled || ! $row->is_bettable) { throw new \InvalidArgumentException('currency'); } return $row; } return Currency::query() ->where('is_enabled', true) ->where('is_bettable', true) ->orderBy('code') ->firstOrFail(); } /** @return array */ private function serializeVersionHead(PlayConfigVersion|OddsVersion|RiskCapVersion $v): array { return [ 'id' => (int) $v->getKey(), 'version_no' => (int) $v->version_no, 'effective_at' => $v->effective_at?->toIso8601String(), ]; } /** @return array */ private function serializePlayConfigItem(PlayConfigItem $r): array { return [ 'is_enabled' => (bool) $r->is_enabled, 'min_bet_amount' => (int) $r->min_bet_amount, 'max_bet_amount' => (int) $r->max_bet_amount, 'display_order' => (int) $r->display_order, 'rule_text_zh' => $r->rule_text_zh, 'rule_text_en' => $r->rule_text_en, 'rule_text_ne' => $r->rule_text_ne, 'extra_config_json' => $r->extra_config_json, ]; } /** @return array */ private function serializeOddsItem(OddsItem $r): array { return [ 'prize_scope' => $r->prize_scope, 'odds_value' => (int) $r->odds_value, 'rebate_rate' => (string) $r->rebate_rate, 'commission_rate' => (string) $r->commission_rate, 'currency_code' => $r->currency_code, 'extra_config_json' => $r->extra_config_json, /** 赔率乘数小数位 = odds_value / 10000 */ 'odds_multiplier' => round($r->odds_value / 10000, 4), ]; } /** @return array */ private function serializeRiskItem(RiskCapItem $r): array { return [ 'draw_id' => $r->draw_id, 'normalized_number' => $r->normalized_number, 'cap_amount' => (int) $r->cap_amount, 'cap_type' => $r->cap_type, ]; } }