- 更新多个控制器和服务,使用 LotterySettings 服务获取彩票相关配置,如默认币种、开奖间隔、下注窗口等,提升代码一致性与可维护性。 - 移除 .env.example 中不再使用的配置项,建议通过后台管理进行设置。
114 lines
4.0 KiB
PHP
114 lines
4.0 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Draw;
|
||
|
||
use Carbon\Carbon;
|
||
use App\Models\Draw;
|
||
use App\Models\TicketOrder;
|
||
use App\Lottery\DrawStatus;
|
||
use App\Services\LotterySettings;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
/**
|
||
* 管理员修改未开奖期号的时间轴(仅 pending / 无注单的 open)。
|
||
*/
|
||
final class DrawManualUpdateService
|
||
{
|
||
public function __construct(
|
||
private readonly DrawTimelineBuilder $timeline,
|
||
) {}
|
||
|
||
/**
|
||
* @param array{
|
||
* draw_time: string,
|
||
* start_time?: string|null,
|
||
* close_time?: string|null,
|
||
* draw_no?: string|null,
|
||
* business_date?: string|null,
|
||
* sequence_no?: int|null,
|
||
* } $input
|
||
*/
|
||
public function update(Draw $draw, array $input, ?Carbon $now = null): Draw
|
||
{
|
||
$tz = LotterySettings::drawTimezone();
|
||
$nowUtc = ($now ?? Carbon::now())->utc();
|
||
|
||
return DB::transaction(function () use ($draw, $input, $tz, $nowUtc): Draw {
|
||
/** @var Draw $locked */
|
||
$locked = Draw::query()->whereKey($draw->id)->lockForUpdate()->firstOrFail();
|
||
$this->assertEditable($locked);
|
||
|
||
$drawLocal = Carbon::parse((string) $input['draw_time'], $tz);
|
||
$startLocal = isset($input['start_time']) && $input['start_time'] !== null && $input['start_time'] !== ''
|
||
? Carbon::parse((string) $input['start_time'], $tz)
|
||
: null;
|
||
$closeLocal = isset($input['close_time']) && $input['close_time'] !== null && $input['close_time'] !== ''
|
||
? Carbon::parse((string) $input['close_time'], $tz)
|
||
: null;
|
||
|
||
if ($startLocal === null || $closeLocal === null) {
|
||
$defaults = $this->timeline->windowsFromDrawLocal($drawLocal);
|
||
$startLocal ??= $defaults['start_local'];
|
||
$closeLocal ??= $defaults['close_local'];
|
||
}
|
||
|
||
if (! $startLocal->lt($closeLocal) || ! $closeLocal->lt($drawLocal)) {
|
||
throw new \RuntimeException('draw_timeline_invalid');
|
||
}
|
||
|
||
$businessDate = isset($input['business_date']) && $input['business_date'] !== ''
|
||
? (string) $input['business_date']
|
||
: $drawLocal->format('Y-m-d');
|
||
|
||
$sequenceNo = isset($input['sequence_no']) && $input['sequence_no'] !== null
|
||
? max(1, (int) $input['sequence_no'])
|
||
: (int) $locked->sequence_no;
|
||
|
||
$drawNo = isset($input['draw_no']) && trim((string) $input['draw_no']) !== ''
|
||
? trim((string) $input['draw_no'])
|
||
: (string) $locked->draw_no;
|
||
|
||
if (
|
||
Draw::query()
|
||
->where('draw_no', $drawNo)
|
||
->where('id', '!=', $locked->id)
|
||
->exists()
|
||
) {
|
||
throw new \RuntimeException('draw_no_exists');
|
||
}
|
||
|
||
$built = $this->timeline->buildFromLocals($startLocal, $closeLocal, $drawLocal, $nowUtc);
|
||
|
||
$locked->forceFill([
|
||
'draw_no' => $drawNo,
|
||
'business_date' => $businessDate,
|
||
'sequence_no' => $sequenceNo,
|
||
'status' => $built['status'],
|
||
'start_time' => $built['start_utc'],
|
||
'close_time' => $built['close_utc'],
|
||
'draw_time' => $built['draw_utc'],
|
||
])->save();
|
||
|
||
return $locked->refresh();
|
||
});
|
||
}
|
||
|
||
private function assertEditable(Draw $draw): void
|
||
{
|
||
if ($draw->resultBatches()->exists()) {
|
||
throw new \RuntimeException('draw_result_exists');
|
||
}
|
||
|
||
if (! in_array($draw->status, [DrawStatus::Pending->value, DrawStatus::Open->value], true)) {
|
||
throw new \RuntimeException('draw_not_editable');
|
||
}
|
||
|
||
if ($draw->status === DrawStatus::Open->value) {
|
||
$betTotal = (int) TicketOrder::query()->where('draw_id', $draw->id)->sum('total_actual_deduct');
|
||
if ($betTotal > 0) {
|
||
throw new \RuntimeException('draw_has_bets');
|
||
}
|
||
}
|
||
}
|
||
}
|