feat(lottery): 扩展传统马来赔率、玩法结算与开注商短码支持
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
lotterLaravel E2E / e2e-api (push) Has been cancelled

This commit is contained in:
2026-07-10 17:04:28 +08:00
parent 2ea3e810a0
commit cbb18b976f
24 changed files with 320 additions and 45 deletions

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('bet_providers', function (Blueprint $table): void {
$table->string('short_code', 1)->nullable()->after('name');
});
DB::table('bet_providers')->orderBy('id')->get(['id', 'code'])->each(function (object $provider): void {
DB::table('bet_providers')
->where('id', $provider->id)
->update(['short_code' => strtoupper(substr((string) $provider->code, 0, 1))]);
});
}
public function down(): void
{
Schema::table('bet_providers', function (Blueprint $table): void {
$table->dropColumn('short_code');
});
}
};

View File

@@ -10,9 +10,9 @@ final class BetProviderSeeder extends Seeder
public function run(): void
{
$rows = [
['code' => 'SG', 'name' => 'Singapore', 'sort_order' => 10],
['code' => 'MY', 'name' => 'Malaysia', 'sort_order' => 20],
['code' => 'TH', 'name' => 'Thailand', 'sort_order' => 30],
['code' => 'SG', 'name' => 'Singapore', 'short_code' => 'S', 'sort_order' => 10],
['code' => 'MY', 'name' => 'Malaysia', 'short_code' => 'M', 'sort_order' => 20],
['code' => 'TH', 'name' => 'Thailand', 'short_code' => 'T', 'sort_order' => 30],
];
foreach ($rows as $row) {
@@ -20,6 +20,7 @@ final class BetProviderSeeder extends Seeder
['code' => $row['code']],
[
'name' => $row['name'],
'short_code' => $row['short_code'],
'is_enabled' => true,
'sort_order' => $row['sort_order'],
],

View File

@@ -98,7 +98,7 @@ final class OperationalConfigV1Seeder extends Seeder
/** 对齐界面文档 §5.5:头/二/三/特别/安慰odds_value = 乘数×10000NPR 基准展示口径) */
foreach (PlayType::query()->orderBy('sort_order')->orderBy('play_code')->get() as $pt) {
foreach (OddsStandardScopes::PRESET_ODDS_BY_SCOPE as $scope => $oddsValue) {
foreach (OddsStandardScopes::presetOddsForPlay((string) $pt->play_code) as $scope => $oddsValue) {
OddsItem::query()->create([
'version_id' => $oddsVersion->id,
'play_code' => $pt->play_code,

View File

@@ -118,7 +118,7 @@ final class PlayOperationalAlignmentSeeder extends Seeder
$rebate = (float) ($anchor?->rebate_rate ?? 0);
$commission = (float) ($anchor?->commission_rate ?? 0);
foreach (OddsStandardScopes::PRESET_ODDS_BY_SCOPE as $scope => $oddsValue) {
foreach (OddsStandardScopes::presetOddsForPlay((string) $playCode) as $scope => $oddsValue) {
$exists = OddsItem::query()
->where('version_id', $vid)
->where('play_code', $playCode)

View File

@@ -17,6 +17,7 @@ final class PlayTypeSeeder extends Seeder
array_merge($row, $defaults),
);
}
}
/**
@@ -25,24 +26,36 @@ final class PlayTypeSeeder extends Seeder
private function rows(): array
{
return [
$this->row('big', 'standard', 4, 'single', 'Big', 10),
$this->row('small', 'standard', 4, 'single', 'Small', 20),
$this->row('big', 'standard', 4, 'single', 'B', 10),
$this->row('small', 'standard', 4, 'single', 'S', 20),
$this->row('pos_4a', 'position', 4, 'single', '4A', 30, false, ['prize_scope' => ['first']]),
$this->row('pos_4b', 'position', 4, 'single', '4B', 40, false, ['prize_scope' => ['second']]),
$this->row('pos_4c', 'position', 4, 'single', '4C', 50, false, ['prize_scope' => ['third']]),
$this->row('pos_4d', 'position', 4, 'single', '4D', 60, false, ['prize_scope' => ['starter']]),
$this->row('pos_4e', 'position', 4, 'single', '4E', 70, false, ['prize_scope' => ['consolation']]),
$this->row('four_any', 'position', 4, 'single', '4N', 75, false, ['prize_scope' => ['first', 'second', 'third', 'starter', 'consolation']]),
$this->row('four_top', 'position', 4, 'single', 'A', 76, false, ['prize_scope' => ['first', 'second', 'third']]),
$this->row('four_lower', 'position', 4, 'single', 'C', 77, false, ['prize_scope' => ['starter', 'consolation']]),
$this->row('pos_3a', 'position', 3, 'single', '3A', 80, false, ['prize_scope' => ['first']]),
$this->row('pos_3a', 'position', 3, 'single', 'A', 80, false, ['prize_scope' => ['first']]),
$this->row('pos_3b', 'position', 3, 'single', '3B', 90, false, ['prize_scope' => ['second']]),
$this->row('pos_3c', 'position', 3, 'single', '3C', 100, false, ['prize_scope' => ['third']]),
$this->row('pos_3abc', 'position', 3, 'single', '3ABC', 110, false, ['prize_scope' => ['first', 'second', 'third']]),
$this->row('pos_3d', 'position', 3, 'single', '3D', 110, false, ['prize_scope' => ['starter']]),
$this->row('pos_3e', 'position', 3, 'single', '3E', 115, false, ['prize_scope' => ['consolation']]),
$this->row('pos_3lower', 'position', 3, 'single', 'C', 117, false, ['prize_scope' => ['starter', 'consolation']]),
$this->row('pos_2a', 'position', 2, 'single', '2A', 120, false, ['prize_scope' => ['first']]),
$this->row('pos_2b', 'position', 2, 'single', '2B', 130, false, ['prize_scope' => ['second']]),
$this->row('pos_2c', 'position', 2, 'single', '2C', 140, false, ['prize_scope' => ['third']]),
$this->row('pos_2abc', 'position', 2, 'single', '2ABC', 150, false, ['prize_scope' => ['first', 'second', 'third']]),
$this->row('pos_2d', 'position', 2, 'single', '2D', 150, false, ['prize_scope' => ['starter']]),
$this->row('pos_2e', 'position', 2, 'single', '2E', 155, false, ['prize_scope' => ['consolation']]),
$this->row('pos_2any', 'position', 2, 'single', '2N', 157, false, ['prize_scope' => ['first', 'second', 'third', 'starter', 'consolation']]),
// 5D / 6D require their own published result source. Keep them in the catalog,
// but disabled until that independent draw pipeline is enabled.
$this->row('five_d', 'position', 5, 'single', '5D', 158, false, ['requires_independent_draw' => true], false),
$this->row('six_d', 'position', 6, 'single', '6D', 159, false, ['requires_independent_draw' => true], false),
$this->row('straight', 'box', 4, 'single', 'Straight', 160, false, ['expand_mode' => 'straight']),
$this->row('box', 'box', 4, 'single', 'Box', 170, false, ['expand_mode' => 'box']),

View File

@@ -0,0 +1,38 @@
<?php
namespace Database\Seeders;
use App\Models\OddsItem;
use App\Models\OddsVersion;
use App\Models\RiskCapItem;
use App\Support\OddsStandardScopes;
use App\Lottery\ConfigVersionStatus;
use Illuminate\Database\Seeder;
/** Applies the traditional Malaysia baseline to active and draft odds versions. */
final class TraditionalMalaysiaOddsSeeder extends Seeder
{
public function run(): void
{
$codes = [
'big', 'small', 'pos_4a', 'pos_4b', 'pos_4c', 'pos_4d', 'pos_4e', 'four_any', 'four_top', 'four_lower',
'pos_3a', 'pos_3b', 'pos_3c', 'pos_3d', 'pos_3e', 'pos_3lower',
'pos_2a', 'pos_2b', 'pos_2c', 'pos_2d', 'pos_2e', 'pos_2any',
];
foreach (OddsVersion::query()->whereIn('status', [ConfigVersionStatus::Active->value, ConfigVersionStatus::Draft->value])->cursor() as $version) {
foreach ($codes as $code) {
foreach (OddsStandardScopes::presetOddsForPlay($code) as $scope => $oddsValue) {
OddsItem::query()
->where('version_id', $version->id)
->where('play_code', $code)
->where('prize_scope', $scope)
->update(['odds_value' => $oddsValue]);
}
}
}
// Existing default cap was calibrated for the prior 25x test multiplier.
RiskCapItem::query()->where('cap_type', 'default')->update(['cap_amount' => 5_000_000_000_000]);
}
}