feat: 扩展奖池、风控与报表能力,新增对账补偿、广播和人工操作接口

This commit is contained in:
2026-05-18 15:09:10 +08:00
parent 9157dcb6a1
commit 6ef41cee76
46 changed files with 1889 additions and 98 deletions

View File

@@ -30,11 +30,12 @@ final class JackpotBurstAllocator
$thresholdOk = (int) $pool->current_amount >= (int) $pool->trigger_threshold;
$gapOk = $this->gapTriggerMet($pool);
if (! $thresholdOk && ! $gapOk) {
$comboOk = $this->comboTriggerMet($pool, $winners);
if (! $thresholdOk && ! $gapOk && ! $comboOk) {
return ['allocations' => [], 'pool_payout' => 0, 'trigger' => null];
}
$trigger = $thresholdOk ? 'threshold' : 'forced_gap';
$trigger = $thresholdOk ? 'threshold' : ($gapOk ? 'forced_gap' : 'play_combo');
$poolBefore = (int) $pool->current_amount;
$poolPayout = (int) floor($poolBefore * (float) $pool->payout_rate);
@@ -81,6 +82,8 @@ final class JackpotBurstAllocator
'trigger_snapshot_json' => [
'threshold_ok' => $thresholdOk,
'gap_ok' => $gapOk,
'combo_ok' => $comboOk,
'combo_trigger_play_codes' => $this->comboTriggerPlayCodes($pool),
'pool_amount_before' => $poolBefore,
'payout_rate' => (string) $pool->payout_rate,
],
@@ -104,4 +107,35 @@ final class JackpotBurstAllocator
return $count >= $gap;
}
/**
* @param Collection<int, array{item: TicketItem, matched_tier: ?string, gross_win: int}> $winners
*/
private function comboTriggerMet(JackpotPool $pool, Collection $winners): bool
{
$codes = $this->comboTriggerPlayCodes($pool);
if ($codes === []) {
return false;
}
return $winners->contains(
fn (array $r): bool => in_array((string) $r['item']->play_code, $codes, true),
);
}
/**
* @return list<string>
*/
private function comboTriggerPlayCodes(JackpotPool $pool): array
{
$raw = $pool->combo_trigger_play_codes;
if (! is_array($raw)) {
return [];
}
return array_values(array_filter(
array_map(fn ($v): string => strtolower(trim((string) $v)), $raw),
fn (string $v): bool => $v !== '',
));
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Services\Jackpot;
use App\Models\Draw;
use App\Models\JackpotPool;
use App\Support\CurrencyFormatter;
use App\Lottery\DrawStatus;
final class JackpotSummaryService
{
/**
* @return array<string, mixed>
*/
public function summary(string $currencyCode): array
{
$currency = strtoupper(trim($currencyCode));
if ($currency === '' || strlen($currency) > 16) {
$currency = 'NPR';
}
$pool = JackpotPool::query()
->where('currency_code', $currency)
->where('status', 1)
->first();
$amountMinor = $pool !== null ? (int) $pool->current_amount : 0;
return [
'currency_code' => $currency,
'enabled' => $pool !== null,
'current_amount_minor' => $amountMinor,
'current_amount_formatted' => CurrencyFormatter::fromMinor($amountMinor),
'draws_since_last_burst' => $pool !== null ? $this->drawsSinceLastBurst($pool) : null,
'last_trigger_draw_id' => $pool?->last_trigger_draw_id !== null ? (int) $pool->last_trigger_draw_id : null,
];
}
private function drawsSinceLastBurst(JackpotPool $pool): int
{
$lastId = (int) ($pool->last_trigger_draw_id ?? 0);
return (int) Draw::query()
->where('status', DrawStatus::Settled->value)
->when($lastId > 0, fn ($q) => $q->where('id', '>', $lastId))
->count();
}
}