feat: 更新玩法配置管理,简化字段并增强功能
- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。 - 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。 - 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
This commit is contained in:
@@ -150,6 +150,84 @@ test('module 6 box family expands combinations and computes amount semantics', f
|
||||
->and($lines[7]['rule_snapshot_json']['rounding_refund_amount'] ?? null)->toBe(17);
|
||||
});
|
||||
|
||||
test('module 6 mbox remainder splits amount evenly across preview and place', function (): void {
|
||||
$player = ticketPlayerWithWallet(500_000);
|
||||
ticketOpenDraw();
|
||||
|
||||
$cases = [
|
||||
[
|
||||
'number' => '1234',
|
||||
'amount' => 10_001,
|
||||
'combination_count' => 24,
|
||||
'unit_bet_amount' => 416,
|
||||
'total_bet_amount' => 9984,
|
||||
'rounding_refund_amount' => 17,
|
||||
],
|
||||
[
|
||||
'number' => '1122',
|
||||
'amount' => 601,
|
||||
'combination_count' => 6,
|
||||
'unit_bet_amount' => 100,
|
||||
'total_bet_amount' => 600,
|
||||
'rounding_refund_amount' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($cases as $index => $case) {
|
||||
$resp = $this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->postJson('/api/v1/ticket/preview', [
|
||||
'draw_id' => '20260511-001',
|
||||
'currency_code' => 'NPR',
|
||||
'client_trace_id' => 'trace-module6-mbox-remainder-'.$index,
|
||||
'lines' => [
|
||||
['number' => $case['number'], 'play_code' => 'mbox', 'amount' => $case['amount']],
|
||||
],
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$line = $resp->json('data.lines.0');
|
||||
expect($line['combination_count'])->toBe($case['combination_count'])
|
||||
->and($line['total_bet_amount'])->toBe($case['total_bet_amount'])
|
||||
->and($line['actual_deduct_amount'])->toBe($case['total_bet_amount'])
|
||||
->and($line['rule_snapshot_json']['rounding_refund_amount'] ?? null)->toBe($case['rounding_refund_amount'])
|
||||
->and(intdiv($case['amount'], $case['combination_count']))->toBe($case['unit_bet_amount']);
|
||||
}
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->postJson('/api/v1/ticket/place', [
|
||||
'draw_id' => '20260511-001',
|
||||
'currency_code' => 'NPR',
|
||||
'client_trace_id' => 'trace-module6-mbox-place-remainder',
|
||||
'lines' => [
|
||||
['number' => '1234', 'play_code' => 'mbox', 'amount' => 10_001],
|
||||
],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.summary.total_bet_amount', 9984)
|
||||
->assertJsonPath('data.summary.total_actual_deduct', 9984);
|
||||
|
||||
$item = TicketItem::query()->where('play_code', 'mbox')->firstOrFail();
|
||||
$ruleSnapshot = is_array($item->rule_snapshot_json) ? $item->rule_snapshot_json : [];
|
||||
|
||||
expect((int) $item->unit_bet_amount)->toBe(416)
|
||||
->and((int) $item->total_bet_amount)->toBe(9984)
|
||||
->and((int) $item->actual_deduct_amount)->toBe(9984)
|
||||
->and($ruleSnapshot['rounding_refund_amount'] ?? null)->toBe(17);
|
||||
|
||||
$comboAmounts = TicketCombination::query()
|
||||
->where('ticket_item_id', $item->id)
|
||||
->pluck('bet_amount')
|
||||
->map(fn ($amount) => (int) $amount)
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
expect($comboAmounts)->toBe([416]);
|
||||
|
||||
$wallet = PlayerWallet::query()->where('player_id', $player->id)->firstOrFail();
|
||||
expect((int) $wallet->balance)->toBe(500_000 - 9984);
|
||||
});
|
||||
|
||||
test('module 6 roll expands each R position and charges per expanded combination', function (): void {
|
||||
$player = ticketPlayerWithWallet(500_000);
|
||||
ticketOpenDraw();
|
||||
@@ -537,6 +615,104 @@ test('ticket place rejects bet amount below configured minimum', function (): vo
|
||||
expect(TicketOrder::query()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('ticket preview reports high risk warning without deducting wallet or creating order', function (): void {
|
||||
$player = ticketPlayerWithWallet();
|
||||
$draw = ticketOpenDraw();
|
||||
|
||||
RiskPool::query()->create([
|
||||
'draw_id' => $draw->id,
|
||||
'normalized_number' => '1234',
|
||||
'total_cap_amount' => 4000,
|
||||
'locked_amount' => 0,
|
||||
'remaining_amount' => 4000,
|
||||
'sold_out_status' => 0,
|
||||
'version' => 0,
|
||||
]);
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->postJson('/api/v1/ticket/preview', [
|
||||
'draw_id' => '20260511-001',
|
||||
'currency_code' => 'NPR',
|
||||
'client_trace_id' => 'trace-preview-risk-warning',
|
||||
'lines' => [
|
||||
['number' => '1234', 'play_code' => 'big', 'amount' => 160],
|
||||
],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.lines.0.risk_status', 'ok')
|
||||
->assertJsonPath('data.warnings.0.number_4d', '1234');
|
||||
|
||||
$wallet = PlayerWallet::query()->where('player_id', $player->id)->firstOrFail();
|
||||
$pool = RiskPool::query()->where('draw_id', $draw->id)->where('normalized_number', '1234')->firstOrFail();
|
||||
|
||||
expect((int) $wallet->balance)->toBe(200_000)
|
||||
->and((int) $pool->locked_amount)->toBe(0)
|
||||
->and((int) $pool->remaining_amount)->toBe(4000)
|
||||
->and(TicketOrder::query()->count())->toBe(0)
|
||||
->and(WalletTxn::query()->where('biz_type', 'bet_deduct')->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('ticket preview validates digit size dimension and slot rules', function (): void {
|
||||
$player = ticketPlayerWithWallet();
|
||||
ticketOpenDraw();
|
||||
|
||||
$base = [
|
||||
'draw_id' => '20260511-001',
|
||||
'currency_code' => 'NPR',
|
||||
'client_trace_id' => 'trace-digit-validation',
|
||||
];
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->postJson('/api/v1/ticket/preview', $base + [
|
||||
'lines' => [
|
||||
['number' => '9', 'play_code' => 'digit_big', 'amount' => 100, 'digit_slot' => 3],
|
||||
],
|
||||
])
|
||||
->assertStatus(400)
|
||||
->assertJsonPath('code', ErrorCode::BetInvalidPlayInput->value);
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->postJson('/api/v1/ticket/preview', $base + [
|
||||
'client_trace_id' => 'trace-digit-invalid-slot',
|
||||
'lines' => [
|
||||
['number' => '9', 'play_code' => 'digit_big', 'amount' => 100, 'dimension' => 'D2', 'digit_slot' => 1],
|
||||
],
|
||||
])
|
||||
->assertStatus(400)
|
||||
->assertJsonPath('code', ErrorCode::BetInvalidPlayInput->value);
|
||||
|
||||
expect(TicketOrder::query()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('ticket place persists valid 2d digit size slot snapshot', function (): void {
|
||||
$player = ticketPlayerWithWallet(500_000);
|
||||
ticketOpenDraw();
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
->postJson('/api/v1/ticket/place', [
|
||||
'draw_id' => '20260511-001',
|
||||
'currency_code' => 'NPR',
|
||||
'client_trace_id' => 'trace-digit-d2-slot',
|
||||
'lines' => [
|
||||
['number' => '9', 'play_code' => 'digit_big', 'amount' => 100, 'dimension' => 'D2', 'digit_slot' => 3],
|
||||
],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.summary.success_count', 1)
|
||||
->assertJsonPath('data.items.0.status', 'pending_draw');
|
||||
|
||||
$item = TicketItem::query()->where('play_code', 'digit_big')->firstOrFail();
|
||||
$ruleSnapshot = is_array($item->rule_snapshot_json) ? $item->rule_snapshot_json : [];
|
||||
|
||||
expect((int) $item->dimension)->toBe(2)
|
||||
->and((int) $item->digit_slot)->toBe(3)
|
||||
->and((int) $item->combination_count)->toBe(5000)
|
||||
->and($ruleSnapshot['dimension'] ?? null)->toBe('D2')
|
||||
->and($ruleSnapshot['digit_slot'] ?? null)->toBe(3)
|
||||
->and(TicketCombination::query()->where('ticket_item_id', $item->id)->where('number_4d', '0009')->exists())->toBeTrue()
|
||||
->and(TicketCombination::query()->where('ticket_item_id', $item->id)->where('number_4d', '0004')->exists())->toBeFalse();
|
||||
});
|
||||
|
||||
test('ticket preview rejects invalid line amount per validation rules', function (): void {
|
||||
$player = ticketPlayerWithWallet();
|
||||
ticketOpenDraw();
|
||||
@@ -833,16 +1009,18 @@ test('ticket place reverses wallet and releases risk when post deduction confirm
|
||||
$player = ticketPlayerWithWallet(20_000);
|
||||
$draw = ticketOpenDraw();
|
||||
|
||||
JackpotPool::query()->create([
|
||||
'currency_code' => 'NPR',
|
||||
'current_amount' => 0,
|
||||
'contribution_rate' => 1,
|
||||
'trigger_threshold' => 0,
|
||||
'payout_rate' => 0,
|
||||
'force_trigger_draw_gap' => 0,
|
||||
'min_bet_amount' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
JackpotPool::query()->updateOrCreate(
|
||||
['currency_code' => 'NPR'],
|
||||
[
|
||||
'current_amount' => 0,
|
||||
'contribution_rate' => 1,
|
||||
'trigger_threshold' => 0,
|
||||
'payout_rate' => 0,
|
||||
'force_trigger_draw_gap' => 0,
|
||||
'min_bet_amount' => 0,
|
||||
'status' => 1,
|
||||
],
|
||||
);
|
||||
DB::statement("CREATE TRIGGER fail_jackpot_contribution_insert BEFORE INSERT ON jackpot_contributions BEGIN SELECT RAISE(ABORT, 'forced_confirmation_failure'); END");
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer dev:'.$player->id)
|
||||
|
||||
Reference in New Issue
Block a user