whereKey($batch->id)->lockForUpdate()->firstOrFail(); if ($lockedBatch->status !== DrawResultBatchStatus::PendingReview->value) { throw new \RuntimeException('batch_not_pending_review'); } /** @var Draw $draw */ $draw = Draw::query()->whereKey($lockedBatch->draw_id)->lockForUpdate()->firstOrFail(); $this->assertDrawReadyToPublish($draw, $lockedBatch); DrawResultBatch::query() ->where('draw_id', $draw->id) ->where('id', '!=', $lockedBatch->id) ->where('status', DrawResultBatchStatus::Published->value) ->update(['status' => DrawResultBatchStatus::Rejected->value]); $lockedBatch->forceFill([ 'status' => DrawResultBatchStatus::Published->value, 'confirmed_by' => $admin->id, 'confirmed_at' => now(), ])->save(); return $this->applyPublishedToDraw($draw, $lockedBatch); }); $data = $this->snapshot->build(); $this->hallRealtime->notifyResultPublished($data); $this->hallRealtime->notifyStatusChange($data); return $draw; } /** RNG 自动生成且无需审核时在同一事务调用 */ public function markPublishedInTransaction(Draw $draw, DrawResultBatch $batch): Draw { $batch->forceFill([ 'status' => DrawResultBatchStatus::Published->value, 'confirmed_by' => null, 'confirmed_at' => now(), ])->save(); $draw = $this->applyPublishedToDraw($draw, $batch); DB::afterCommit(function (): void { $data = app(DrawHallSnapshotBuilder::class)->build(); app(LotteryHallRealtimeBroadcaster::class)->notifyResultPublished($data); }); return $draw; } private function applyPublishedToDraw(Draw $draw, DrawResultBatch $batch): Draw { $cooldownMinutes = LotterySettings::drawCooldownMinutes(); if ($cooldownMinutes > 0) { $draw->forceFill([ 'status' => DrawStatus::Cooldown->value, 'current_result_version' => (int) $batch->result_version, 'result_source' => $batch->source_type, 'cooling_end_time' => now()->addMinutes($cooldownMinutes), ])->save(); } else { $draw->forceFill([ 'status' => DrawStatus::Settling->value, 'current_result_version' => (int) $batch->result_version, 'result_source' => $batch->source_type, 'cooling_end_time' => null, ])->save(); } return $draw->refresh(); } private function assertDrawReadyToPublish(Draw $draw, DrawResultBatch $batch): void { if ($draw->status === DrawStatus::Cancelled->value || $draw->status === DrawStatus::Settled->value) { throw new \RuntimeException('draw_not_ready_to_publish'); } if ((int) $batch->result_version < (int) $draw->current_result_version) { throw new \RuntimeException('batch_result_version_stale'); } $allowed = [ DrawStatus::Closed->value, DrawStatus::Review->value, DrawStatus::Cooldown->value, ]; if (! in_array($draw->status, $allowed, true)) { throw new \RuntimeException('draw_not_ready_to_publish'); } $this->assertNoActiveSettlementWorkflow($draw); } /** 存在未完结结算批次时不允许改发布结果,避免派彩与大厅展示号码不一致。 */ private function assertNoActiveSettlementWorkflow(Draw $draw): void { $active = SettlementBatch::query() ->where('draw_id', $draw->id) ->whereIn('status', [ SettlementBatchStatus::Running->value, SettlementBatchStatus::PendingReview->value, SettlementBatchStatus::Approved->value, ]) ->exists(); if ($active) { throw new \RuntimeException('draw_settlement_in_progress'); } } }