*/ public const PRESET_ODDS_BY_SCOPE = [ 'first' => 25_000_000, 'second' => 10_000_000, 'third' => 5_000_000, 'starter' => 1_800_000, 'consolation' => 600_000, ]; /** Traditional Malaysia 4D defaults, in multiplier x 10,000. */ public static function presetOddsForPlay(string $playCode): array { if ($playCode === 'small') { return [ 'first' => 35_000_000, 'second' => 20_000_000, 'third' => 10_000_000, 'starter' => 0, 'consolation' => 0, ]; } if (str_starts_with($playCode, 'pos_3')) { return self::scale(self::PRESET_ODDS_BY_SCOPE, 10); } if (str_starts_with($playCode, 'pos_2')) { return self::scale(self::PRESET_ODDS_BY_SCOPE, 100); } return self::PRESET_ODDS_BY_SCOPE; } /** @param array $odds @return array */ private static function scale(array $odds, int $divisor): array { return array_map(static fn (int $value): int => (int) floor($value / $divisor), $odds); } /** @var list */ public const SCOPE_KEYS = ['first', 'second', 'third', 'starter', 'consolation']; /** * 为指定赔率版本补全缺失的标准五档行(幂等)。 * * 仅对「该版本里已出现过的 (play_code, currency_code)」补全,避免给从未配置过的玩法凭空加行。 * 若版本下无任何 odds_items,则用当前全部玩法 × 首个可下注币种补全。 * * 佣金按维度(2D/3D/4D)配置:同一维度的所有玩法共享佣金率。 */ public static function syncMissingForVersion(OddsVersion $version): void { DB::transaction(function () use ($version): void { $vid = (int) $version->id; $pairs = OddsItem::query() ->where('version_id', $vid) ->select(['provider_code', 'play_code', 'currency_code']) ->distinct() ->get(); if ($pairs->isEmpty()) { $currencyCode = Currency::query() ->where('is_bettable', true) ->where('is_enabled', true) ->orderBy('code') ->value('code'); if ($currencyCode === null || $currencyCode === '') { return; } $pairs = PlayType::query() ->orderBy('sort_order') ->orderBy('play_code') ->get(['play_code', 'dimension']) ->map(fn (PlayType $pt) => (object) [ 'provider_code' => 'GLOBAL', 'play_code' => $pt->play_code, 'dimension' => $pt->dimension, 'currency_code' => $currencyCode, ]); } // 按维度分组,获取每个维度的佣金率 $dimensionCommissions = []; foreach ($pairs as $pair) { $providerCode = strtoupper((string) ($pair->provider_code ?? 'GLOBAL')); $dimension = $pair->dimension ?? null; $currencyCode = strtoupper((string) $pair->currency_code); $key = $providerCode.'|'.$dimension.'|'.$currencyCode; if (!isset($dimensionCommissions[$key])) { // 从现有记录中获取该维度的佣金率 $anchor = OddsItem::query() ->where('version_id', $vid) ->where('provider_code', $providerCode) ->where('currency_code', $currencyCode) ->where('dimension', $dimension) ->orderByDesc('id') ->first(); $dimensionCommissions[$key] = [ 'rebate' => (float) ($anchor?->rebate_rate ?? 0), 'commission' => (float) ($anchor?->commission_rate ?? 0), ]; } } foreach ($pairs as $pair) { $providerCode = strtoupper((string) ($pair->provider_code ?? 'GLOBAL')); $playCode = (string) $pair->play_code; $dimension = $pair->dimension ?? null; $currencyCode = strtoupper((string) $pair->currency_code); $key = $providerCode.'|'.$dimension.'|'.$currencyCode; $rebate = $dimensionCommissions[$key]['rebate'] ?? 0; $commission = $dimensionCommissions[$key]['commission'] ?? 0; foreach (self::presetOddsForPlay($playCode) as $scope => $oddsValue) { $exists = OddsItem::query() ->where('version_id', $vid) ->where('provider_code', $providerCode) ->where('play_code', $playCode) ->where('currency_code', $currencyCode) ->where('prize_scope', $scope) ->exists(); if ($exists) { continue; } OddsItem::query()->create([ 'version_id' => $vid, 'provider_code' => $providerCode, 'play_code' => $playCode, 'prize_scope' => $scope, 'dimension' => $dimension, 'odds_value' => $oddsValue, 'rebate_rate' => $rebate, 'commission_rate' => $commission, 'currency_code' => $currencyCode, 'extra_config_json' => null, ]); } } }); } }