whereKey($draw->id)->lockForUpdate()->firstOrFail(); if ($locked->status === DrawStatus::Settled->value) { return false; } if ($locked->status !== DrawStatus::Settling->value) { return false; } $publishedBatch = DrawResultBatch::query() ->where('draw_id', $locked->id) ->where('status', DrawResultBatchStatus::Published->value) ->where('result_version', (int) $locked->current_result_version) ->orderByDesc('id') ->first(); if ($publishedBatch === null) { return false; } $existingDone = SettlementBatch::query() ->where('draw_id', $locked->id) ->where('result_batch_id', $publishedBatch->id) ->whereIn('status', [ SettlementBatchStatus::PendingReview->value, SettlementBatchStatus::Approved->value, SettlementBatchStatus::Paid->value, SettlementBatchStatus::Completed->value, ]) ->first(); if ($existingDone !== null) { $locked->forceFill([ 'settle_version' => (int) $existingDone->settle_version, ])->save(); return true; } $items = DrawResultItem::query() ->where('result_batch_id', $publishedBatch->id) ->orderBy('id') ->get(); $board = new PublishedDrawResultBoard($items); $nextSettleVersion = (int) $locked->settle_version + 1; $batchRow = SettlementBatch::query()->create([ 'draw_id' => $locked->id, 'result_batch_id' => $publishedBatch->id, 'settle_version' => $nextSettleVersion, 'status' => SettlementBatchStatus::Running->value, 'review_status' => 'pending', 'started_at' => now(), ]); $ticketItems = TicketItem::query() ->where('draw_id', $locked->id) ->where('status', 'success') ->with(['combinations', 'order']) ->orderBy('id') ->get(); /** @var list $prepared */ $prepared = []; foreach ($ticketItems as $item) { $matcher = $this->matchers->for((string) $item->play_code); $result = $matcher->match($item, $board, $item->combinations); $gross = max(0, (int) $result['win_amount']); $tier = $result['matched_prize_tier'] ?? null; $tier = is_string($tier) ? $tier : null; $net = $this->payoutAdjuster->adjustGrossWin($gross, $item); $prepared[] = [ 'item' => $item, 'gross_win' => $gross, 'matched_tier' => $tier, 'net_win' => $net, 'match_detail' => $result['match_detail'], ]; } $currency = strtoupper((string) ($ticketItems->first()?->order?->currency_code ?? 'NPR')); $pool = JackpotPool::query() ->where('currency_code', $currency) ->where('status', 1) ->lockForUpdate() ->first(); $allocations = []; $totalJackpotPayout = 0; if ($pool !== null) { $burstInput = collect($prepared)->map(fn (array $p): array => [ 'item' => $p['item'], 'matched_tier' => $p['matched_tier'], 'gross_win' => $p['gross_win'], ]); $burstOut = $this->jackpotBurst->allocate($locked, $pool, $burstInput); $allocations = $burstOut['allocations']; $totalJackpotPayout = (int) $burstOut['pool_payout']; } $ticketCount = 0; $winCount = 0; $totalPayout = 0; foreach ($prepared as $p) { /** @var TicketItem $item */ $item = $p['item']; $ticketCount++; $net = (int) $p['net_win']; $jackpotShare = (int) ($allocations[(int) $item->id] ?? 0); $finalCredit = $net + $jackpotShare; TicketSettlementDetail::query()->create([ 'settlement_batch_id' => $batchRow->id, 'ticket_item_id' => $item->id, 'matched_prize_tier' => $p['matched_tier'], 'win_amount' => $net, 'jackpot_allocation_amount' => $jackpotShare, 'match_detail_json' => $p['match_detail'], ]); $item->forceFill([ 'win_amount' => $net, 'jackpot_win_amount' => $jackpotShare, 'settled_at' => null, 'status' => $finalCredit > 0 ? 'pending_payout' : 'settled_lose', ])->save(); if ($finalCredit > 0) { $winCount++; } $totalPayout += $finalCredit; $locks = []; foreach ($item->combinations as $c) { $locks[] = [ 'number_4d' => (string) $c->number_4d, 'amount' => (int) $c->estimated_payout, ]; } $this->riskPool->release((int) $locked->id, $item, $locks); } $batchRow->forceFill([ 'status' => SettlementBatchStatus::PendingReview->value, 'total_ticket_count' => $ticketCount, 'total_win_count' => $winCount, 'total_payout_amount' => $totalPayout, 'total_jackpot_payout_amount' => $totalJackpotPayout, 'finished_at' => now(), ])->save(); $locked->forceFill([ 'status' => DrawStatus::Settling->value, 'settle_version' => $nextSettleVersion, ])->save(); return true; }); } }