feat: 更新玩法配置管理,简化字段并增强功能

- 将玩法相关的显示名称字段统一为 `display_name`,移除多语言字段。
- 在 `PlayTypePatchController` 中新增即时切换玩法开关的功能,并推送大厅更新。
- 优化多个控制器和服务中的权限检查与数据处理逻辑,提升代码可读性与维护性。
This commit is contained in:
2026-05-25 14:34:24 +08:00
parent 270d2e9af1
commit e27a00f260
74 changed files with 4469 additions and 280 deletions

View File

@@ -28,6 +28,180 @@ final class AdminReportQueryService
return ['date_from' => $dateFrom, 'date_to' => $dateTo];
}
/**
* @return array{date_from: string, date_to: string}
*/
public function resolveDashboardPeriod(string $period, ?string $dateFrom, ?string $dateTo): array
{
$today = now()->toDateString();
$range = match ($period) {
'today' => ['date_from' => $today, 'date_to' => $today],
'last_7_days' => [
'date_from' => now()->subDays(6)->toDateString(),
'date_to' => $today,
],
'last_30_days' => [
'date_from' => now()->subDays(29)->toDateString(),
'date_to' => $today,
],
'this_month' => [
'date_from' => now()->startOfMonth()->toDateString(),
'date_to' => $today,
],
'lifetime' => $this->lifetimeBusinessDateBounds(),
'custom' => [
'date_from' => $dateFrom !== null && $dateFrom !== '' ? $dateFrom : $today,
'date_to' => $dateTo !== null && $dateTo !== '' ? $dateTo : $today,
],
default => ['date_from' => $today, 'date_to' => $today],
};
$from = $range['date_from'];
$to = $range['date_to'];
if ($from > $to) {
[$from, $to] = [$to, $from];
}
return ['date_from' => $from, 'date_to' => $to];
}
/**
* @return array{date_from: string, date_to: string}
*/
private function lifetimeBusinessDateBounds(): array
{
$today = now()->toDateString();
$bounds = DB::table('draws as d')
->join('ticket_orders as o', 'o.draw_id', '=', 'd.id')
->selectRaw('MIN(d.business_date) as date_from')
->selectRaw('MAX(d.business_date) as date_to')
->first();
$from = $this->formatBusinessDateValue($bounds?->date_from) ?? $today;
$to = $this->formatBusinessDateValue($bounds?->date_to) ?? $today;
return ['date_from' => $from, 'date_to' => $to];
}
/**
* @return array{
* total_bet_minor: int,
* total_payout_minor: int,
* approx_house_gross_minor: int,
* draw_count: int,
* business_day_count: int
* }
*/
public function periodFinanceTotals(string $dateFrom, string $dateTo): array
{
$rows = $this->dailyProfitRows($dateFrom, $dateTo);
$totalBet = 0;
$totalPayout = 0;
$totalGross = 0;
foreach ($rows as $row) {
$totalBet += (int) $row['total_bet_minor'];
$totalPayout += (int) $row['total_payout_minor'];
$totalGross += (int) $row['approx_house_gross_minor'];
}
$activity = DB::table('draws as d')
->join('ticket_orders as o', 'o.draw_id', '=', 'd.id')
->whereBetween('d.business_date', [$dateFrom, $dateTo])
->selectRaw('COUNT(DISTINCT d.id) as draw_count')
->selectRaw('COUNT(DISTINCT d.business_date) as business_day_count')
->first();
return [
'total_bet_minor' => $totalBet,
'total_payout_minor' => $totalPayout,
'approx_house_gross_minor' => $totalGross,
'draw_count' => (int) ($activity->draw_count ?? 0),
'business_day_count' => (int) ($activity->business_day_count ?? 0),
];
}
/**
* 连续业务日序列(无数据日补零),用于趋势图。
*
* @return list<array<string, mixed>>
*/
public function dailyProfitSeriesFilled(string $dateFrom, string $dateTo, int $maxDays = 90): array
{
$from = Carbon::parse($dateFrom)->startOfDay();
$to = Carbon::parse($dateTo)->startOfDay();
$spanDays = (int) $from->diffInDays($to) + 1;
$chartFrom = $dateFrom;
$chartTo = $dateTo;
$truncated = false;
if ($spanDays > $maxDays) {
$chartFrom = $to->copy()->subDays($maxDays - 1)->format('Y-m-d');
$truncated = true;
}
$indexed = collect($this->dailyProfitRows($chartFrom, $chartTo))->keyBy('business_date');
$cursor = Carbon::parse($chartFrom)->startOfDay();
$end = Carbon::parse($chartTo)->startOfDay();
$series = [];
while ($cursor <= $end) {
$key = $cursor->format('Y-m-d');
$series[] = $indexed[$key] ?? [
'business_date' => $key,
'total_bet_minor' => 0,
'total_payout_minor' => 0,
'approx_house_gross_minor' => 0,
];
$cursor->addDay();
}
return [
'series' => $series,
'chart_date_from' => $chartFrom,
'chart_date_to' => $chartTo,
'truncated' => $truncated,
'span_days' => $spanDays,
];
}
/**
* @return list<array<string, mixed>>
*/
public function playDimensionBreakdownRows(
string $dateFrom,
string $dateTo,
?string $playCode = null,
int $limit = 12,
): array {
return $this->playDimensionBaseQuery($playCode, $dateFrom, $dateTo)
->orderByDesc('total_bet_minor')
->limit($limit)
->get()
->map(static function (object $row): array {
return [
'play_code' => (string) $row->play_code,
'dimension' => (int) $row->dimension,
'total_bet_minor' => (int) $row->total_bet_minor,
'total_payout_minor' => (int) $row->total_payout_minor,
'approx_house_gross_minor' => (int) $row->approx_house_gross_minor,
];
})
->values()
->all();
}
public function resolvePeriodCurrencyCode(string $dateFrom, string $dateTo): ?string
{
$currencyCode = (string) (DB::table('ticket_orders as o')
->join('draws as d', 'd.id', '=', 'o.draw_id')
->whereBetween('d.business_date', [$dateFrom, $dateTo])
->orderByDesc('o.id')
->value('o.currency_code') ?? '');
return $currencyCode !== '' ? $currencyCode : null;
}
public function dailyProfitPaginated(string $dateFrom, string $dateTo, int $page, int $perPage): LengthAwarePaginator
{
$rows = $this->dailyProfitRows($dateFrom, $dateTo);
@@ -80,6 +254,57 @@ final class AdminReportQueryService
->all();
}
/**
* 全平台历史累计投注/派彩/盈亏(与 daily-profit 同口径,不限业务日)。
*
* @return array{
* currency_code: ?string,
* total_bet_minor: int,
* total_payout_minor: int,
* approx_house_gross_minor: int,
* draw_count: int,
* business_day_count: int,
* date_from: ?string,
* date_to: ?string
* }
*/
public function platformLifetimeTotals(): array
{
$totalBetMinor = (int) DB::table('ticket_orders')->sum('total_actual_deduct');
$payoutAgg = DB::table('ticket_items')
->selectRaw('COALESCE(SUM(win_amount), 0) as win_minor, COALESCE(SUM(jackpot_win_amount), 0) as jackpot_minor')
->first();
$totalPayoutMinor = (int) ($payoutAgg->win_minor ?? 0) + (int) ($payoutAgg->jackpot_minor ?? 0);
$activity = DB::table('draws as d')
->join('ticket_orders as o', 'o.draw_id', '=', 'd.id')
->selectRaw('COUNT(DISTINCT d.id) as draw_count')
->selectRaw('COUNT(DISTINCT d.business_date) as business_day_count')
->selectRaw('MIN(d.business_date) as date_from')
->selectRaw('MAX(d.business_date) as date_to')
->first();
$drawCount = (int) ($activity->draw_count ?? 0);
$businessDayCount = (int) ($activity->business_day_count ?? 0);
$dateFrom = $this->formatBusinessDateValue($activity?->date_from);
$dateTo = $this->formatBusinessDateValue($activity?->date_to);
$currencyCode = (string) (DB::table('ticket_orders')->orderByDesc('id')->value('currency_code') ?? '');
return [
'currency_code' => $currencyCode !== '' ? $currencyCode : null,
'total_bet_minor' => $totalBetMinor,
'total_payout_minor' => $totalPayoutMinor,
'approx_house_gross_minor' => $totalBetMinor - $totalPayoutMinor,
'draw_count' => $drawCount,
'business_day_count' => $businessDayCount,
'date_from' => $dateFrom,
'date_to' => $dateTo,
];
}
public function playerWinLossPaginated(
?int $playerId,
string $dateFrom,
@@ -340,4 +565,26 @@ final class AdminReportQueryService
return $query;
}
private function formatBusinessDateValue(mixed $value): ?string
{
if ($value === null) {
return null;
}
if ($value instanceof Carbon) {
return $value->format('Y-m-d');
}
$raw = trim((string) $value);
if ($raw === '') {
return null;
}
if (preg_match('/^\d{4}-\d{2}-\d{2}/', $raw, $m) === 1) {
return substr($m[0], 0, 10);
}
return $raw;
}
}