feat: 拆分开奖与结算审核流程,新增手动结果录入、重开和派彩审批接口

This commit is contained in:
2026-05-16 18:01:06 +08:00
parent 83046b402d
commit 4f143c7cb1
38 changed files with 1992 additions and 170 deletions

View File

@@ -3,11 +3,9 @@
namespace App\Services\Settlement;
use App\Models\Draw;
use App\Models\Player;
use App\Models\TicketItem;
use App\Lottery\DrawStatus;
use App\Models\JackpotPool;
use App\Models\TicketOrder;
use App\Models\DrawResultItem;
use App\Models\DrawResultBatch;
use App\Models\SettlementBatch;
@@ -16,13 +14,12 @@ use App\Lottery\DrawResultBatchStatus;
use App\Lottery\SettlementBatchStatus;
use App\Models\TicketSettlementDetail;
use App\Services\Ticket\RiskPoolService;
use App\Services\Ticket\TicketWalletService;
use App\Services\Jackpot\JackpotBurstAllocator;
/**
* 阶段 6:对已发布开奖、处于 `settling` 的期号执行结算(匹配 回水派彩调整 Jackpot 爆池分配 明细 风险池释放 入账)。
* 阶段 6:对已发布开奖、处于 `settling` 的期号执行结算(匹配 回水派彩调整 Jackpot 爆池分配 明细 风险池释放 待审核)。
*
* 幂等:同一 `draw` + 已发布 `result_batch` 若已有 `completed` 批次,则仅推进期号状态为 `settled`
* 派彩入账由审核通过后的独立 payout 动作执行,避免未确认结果直接入账
*/
final class SettlementOrchestrator
{
@@ -30,7 +27,6 @@ final class SettlementOrchestrator
private readonly SettlementMatcherRegistry $matchers,
private readonly SettlementPayoutAdjuster $payoutAdjuster,
private readonly JackpotBurstAllocator $jackpotBurst,
private readonly TicketWalletService $wallet,
private readonly RiskPoolService $riskPool,
) {}
@@ -65,12 +61,16 @@ final class SettlementOrchestrator
$existingDone = SettlementBatch::query()
->where('draw_id', $locked->id)
->where('result_batch_id', $publishedBatch->id)
->where('status', SettlementBatchStatus::Completed->value)
->whereIn('status', [
SettlementBatchStatus::PendingReview->value,
SettlementBatchStatus::Approved->value,
SettlementBatchStatus::Paid->value,
SettlementBatchStatus::Completed->value,
])
->first();
if ($existingDone !== null) {
$locked->forceFill([
'status' => DrawStatus::Settled->value,
'settle_version' => (int) $existingDone->settle_version,
])->save();
@@ -91,6 +91,7 @@ final class SettlementOrchestrator
'result_batch_id' => $publishedBatch->id,
'settle_version' => $nextSettleVersion,
'status' => SettlementBatchStatus::Running->value,
'review_status' => 'pending',
'started_at' => now(),
]);
@@ -139,7 +140,6 @@ final class SettlementOrchestrator
$totalJackpotPayout = (int) $burstOut['pool_payout'];
}
$playerTotals = [];
$ticketCount = 0;
$winCount = 0;
$totalPayout = 0;
@@ -164,8 +164,8 @@ final class SettlementOrchestrator
$item->forceFill([
'win_amount' => $net,
'jackpot_win_amount' => $jackpotShare,
'settled_at' => now(),
'status' => $finalCredit > 0 ? 'settled_win' : 'settled_lose',
'settled_at' => null,
'status' => $finalCredit > 0 ? 'pending_payout' : 'settled_lose',
])->save();
if ($finalCredit > 0) {
@@ -173,9 +173,6 @@ final class SettlementOrchestrator
}
$totalPayout += $finalCredit;
$pid = (int) $item->player_id;
$playerTotals[$pid] = ($playerTotals[$pid] ?? 0) + $finalCredit;
$locks = [];
foreach ($item->combinations as $c) {
$locks[] = [
@@ -186,16 +183,8 @@ final class SettlementOrchestrator
$this->riskPool->release((int) $locked->id, $item, $locks);
}
foreach ($playerTotals as $playerId => $amount) {
if ($amount <= 0) {
continue;
}
$player = Player::query()->whereKey($playerId)->firstOrFail();
$this->wallet->creditSettlementPayout($player, $currency, $amount, (int) $batchRow->id);
}
$batchRow->forceFill([
'status' => SettlementBatchStatus::Completed->value,
'status' => SettlementBatchStatus::PendingReview->value,
'total_ticket_count' => $ticketCount,
'total_win_count' => $winCount,
'total_payout_amount' => $totalPayout,
@@ -204,20 +193,10 @@ final class SettlementOrchestrator
])->save();
$locked->forceFill([
'status' => DrawStatus::Settled->value,
'status' => DrawStatus::Settling->value,
'settle_version' => $nextSettleVersion,
])->save();
foreach ($ticketItems->pluck('order_id')->unique()->all() as $orderId) {
$pending = TicketItem::query()
->where('order_id', $orderId)
->whereNotIn('status', ['settled_win', 'settled_lose'])
->exists();
if (! $pending) {
TicketOrder::query()->whereKey($orderId)->update(['status' => 'settled']);
}
}
return true;
});
}