forceFill([ 'status' => DrawStatus::Drawing->value, ])->save(); $manualReview = LotterySettings::drawRequireManualReview(); $providers = $this->enabledProvidersForRng(); $batches = collect(); foreach ($providers as $provider) { $seedHex = DrawRngSeedDerivation::generateSeedHex(); $rngSeedHash = DrawRngSeedDerivation::hashSeedHex($seedHex); $rawSeedEncrypted = DrawRngSeedDerivation::encryptSeedHex($seedHex); $derivedRows = DrawRngSeedDerivation::deriveAllSlotRows($seedHex, (int) $draw->id); $versionQuery = DrawResultBatch::query()->where('draw_id', $draw->id); if ($provider['code'] === BetProvider::DEFAULT_CODE) { $versionQuery->where(function ($q) use ($provider): void { $q->where('provider_code', $provider['code']) ->orWhereNull('provider_code'); }); } else { $versionQuery->where('provider_code', $provider['code']); } $nextVersion = max(1, (int) $versionQuery->max('result_version') + 1); $batch = DrawResultBatch::query()->create([ 'draw_id' => $draw->id, 'provider_code' => $provider['code'], 'provider_name' => $provider['name'], 'result_version' => $nextVersion, 'source_type' => DrawResultSourceType::Rng->value, 'rng_seed_hash' => $rngSeedHash, 'raw_seed_encrypted' => $rawSeedEncrypted, 'status' => $manualReview ? DrawResultBatchStatus::PendingReview->value : DrawResultBatchStatus::Published->value, 'created_by' => null, 'confirmed_by' => null, 'confirmed_at' => $manualReview ? null : now(), ]); foreach ($derivedRows as $row) { DrawResultItem::query()->create([ 'draw_id' => $draw->id, 'result_batch_id' => $batch->id, 'prize_type' => $row['prize_type'], 'prize_index' => $row['prize_index'], 'number_4d' => $row['number_4d'], 'suffix_3d' => $row['suffix_3d'], 'suffix_2d' => $row['suffix_2d'], 'head_digit' => $row['head_digit'], 'tail_digit' => $row['tail_digit'], ]); } $batches->push($batch); } /** @var DrawResultBatch $primaryBatch */ $primaryBatch = $batches->firstWhere('provider_code', BetProvider::DEFAULT_CODE) ?? $batches->first(); if ($manualReview) { $draw->forceFill([ 'status' => DrawStatus::Review->value, 'result_source' => DrawResultSourceType::Rng->value, ])->save(); } else { foreach ($batches as $batch) { $this->publisher->markPublishedInTransaction($draw->fresh(), $batch->fresh()); } } return $primaryBatch->fresh(); } /** * @return array{rung: int, errors: array} */ public function runDue(?Carbon $now = null): array { $nowUtc = ($now ?? Carbon::now())->utc(); $rung = 0; $errors = []; $ids = Draw::query() ->where('status', DrawStatus::Closed->value) ->whereNotNull('draw_time') ->where('draw_time', '<=', $nowUtc) ->where('settle_version', 0) ->where(function ($q): void { $q->where('is_reopened', true) ->orWhereDoesntHave('resultBatches'); }) ->orderBy('draw_time') ->limit((int) config('lottery.draw_tick_rng_limit', 10)) ->pluck('id'); foreach ($ids as $drawId) { try { DB::transaction(function () use ($drawId, &$rung): void { /** @var Draw|null $locked */ $locked = Draw::query()->whereKey($drawId)->lockForUpdate()->first(); if ($locked === null || $locked->status !== DrawStatus::Closed->value) { return; } if ((int) $locked->settle_version > 0) { return; } if (! (bool) $locked->is_reopened && $locked->resultBatches()->exists()) { return; } $this->executeLocked($locked); $rung++; }); } catch (\Throwable $e) { $errors[] = (string) $drawId.': '.$e->getMessage(); } } return ['rung' => $rung, 'errors' => $errors]; } /** * @return Collection */ private function enabledProvidersForRng(): Collection { $providers = BetProvider::query() ->where('is_enabled', true) ->orderByRaw('case when code = ? then 0 else 1 end', [BetProvider::DEFAULT_CODE]) ->orderBy('sort_order') ->orderBy('id') ->get(['code', 'name']) ->map(fn (BetProvider $provider): array => [ 'code' => (string) $provider->code, 'name' => (string) $provider->name, ]); if ($providers->isNotEmpty()) { return $providers->values(); } return collect([[ 'code' => BetProvider::DEFAULT_CODE, 'name' => 'Singapore', ]]); } }