$results * @return array{allocations: array, pool_payout: int, trigger: ?string} */ public function allocate(Draw $draw, JackpotPool $pool, Collection $results): array { $winners = $results->filter( fn (array $r) => ($r['matched_tier'] ?? null) === 'first' && (int) $r['gross_win'] > 0, ); if ($winners->isEmpty()) { return ['allocations' => [], 'pool_payout' => 0, 'trigger' => null]; } $thresholdOk = (int) $pool->current_amount >= (int) $pool->trigger_threshold; $gapOk = $this->gapTriggerMet($pool); if (! $thresholdOk && ! $gapOk) { return ['allocations' => [], 'pool_payout' => 0, 'trigger' => null]; } $trigger = $thresholdOk ? 'threshold' : 'forced_gap'; $poolBefore = (int) $pool->current_amount; $poolPayout = (int) floor($poolBefore * (float) $pool->payout_rate); if ($poolPayout <= 0) { return ['allocations' => [], 'pool_payout' => 0, 'trigger' => null]; } $list = $winners->values()->all(); $weightTotal = 0; foreach ($list as $r) { $weightTotal += (int) $r['item']->total_bet_amount; } if ($weightTotal <= 0) { return ['allocations' => [], 'pool_payout' => 0, 'trigger' => null]; } $allocations = []; $remaining = $poolPayout; $n = count($list); foreach ($list as $idx => $r) { /** @var TicketItem $item */ $item = $r['item']; $w = (int) $item->total_bet_amount; if ($idx === $n - 1) { $share = max(0, $remaining); } else { $share = (int) floor($poolPayout * $w / $weightTotal); $remaining -= $share; } $allocations[(int) $item->id] = $share; } $pool->forceFill([ 'current_amount' => max(0, $poolBefore - $poolPayout), 'last_trigger_draw_id' => $draw->id, ])->save(); JackpotPayoutLog::query()->create([ 'draw_id' => $draw->id, 'jackpot_pool_id' => $pool->id, 'trigger_type' => $trigger, 'total_payout_amount' => $poolPayout, 'winner_count' => count($allocations), 'trigger_snapshot_json' => [ 'threshold_ok' => $thresholdOk, 'gap_ok' => $gapOk, 'pool_amount_before' => $poolBefore, 'payout_rate' => (string) $pool->payout_rate, ], ]); return ['allocations' => $allocations, 'pool_payout' => $poolPayout, 'trigger' => $trigger]; } private function gapTriggerMet(JackpotPool $pool): bool { $gap = (int) $pool->force_trigger_draw_gap; if ($gap <= 0) { return false; } $lastId = (int) ($pool->last_trigger_draw_id ?? 0); $count = Draw::query() ->where('status', DrawStatus::Settled->value) ->when($lastId > 0, fn ($q) => $q->where('id', '>', $lastId)) ->count(); return $count >= $gap; } }