*/ 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(); /** @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 = $configByCode->values() ->sortBy([ ['display_order', 'asc'], ['play_code', 'asc'], ]) ->map(function (PlayConfigItem $c) use ($oddsByPlay): array { $items = $oddsByPlay->get($c->play_code, collect()); $o = $this->pickPrimaryOddsItem($items); return [ 'play_code' => $c->play_code, 'category' => $c->category, 'dimension' => $c->dimension === null ? null : (int) $c->dimension, 'bet_mode' => $c->bet_mode, 'display_name' => $c->display_name, 'sort_order' => (int) $c->display_order, 'supports_multi_number' => (bool) $c->supports_multi_number, 'master_enabled' => (bool) $c->is_enabled, 'config' => $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 [ 'category' => $r->category, 'dimension' => $r->dimension === null ? null : (int) $r->dimension, 'bet_mode' => $r->bet_mode, 'display_name' => $r->display_name, '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, 'supports_multi_number' => (bool) $r->supports_multi_number, 'reserved_rule_json' => $r->reserved_rule_json, '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, ]; } }