toDateString()); $dateTo = (string) ($filters['date_to'] ?? $dateFrom); if ($dateFrom > $dateTo) { [$dateFrom, $dateTo] = [$dateTo, $dateFrom]; } return ['date_from' => $dateFrom, 'date_to' => $dateTo]; } public function dailyProfitPaginated(string $dateFrom, string $dateTo, int $page, int $perPage): LengthAwarePaginator { $rows = $this->dailyProfitRows($dateFrom, $dateTo); $total = count($rows); $offset = max(0, ($page - 1) * $perPage); $items = array_slice($rows, $offset, $perPage); return new PaginatorInstance($items, $total, $perPage, $page, [ 'path' => PaginatorInstance::resolveCurrentPath(), ]); } /** * @return list> */ public function dailyProfitRows(string $dateFrom, string $dateTo): array { $betSub = DB::table('ticket_orders') ->selectRaw('draw_id, SUM(total_actual_deduct) as total_bet_minor') ->groupBy('draw_id'); $payoutSub = DB::table('ticket_items') ->selectRaw('draw_id, SUM(win_amount + jackpot_win_amount) as total_payout_minor') ->groupBy('draw_id'); return DB::table('draws as d') ->whereBetween('d.business_date', [$dateFrom, $dateTo]) ->leftJoinSub($betSub, 'b', 'b.draw_id', '=', 'd.id') ->leftJoinSub($payoutSub, 'p', 'p.draw_id', '=', 'd.id') ->groupBy('d.business_date') ->orderBy('d.business_date') ->get([ 'd.business_date', DB::raw('COALESCE(SUM(b.total_bet_minor), 0) as total_bet_minor'), DB::raw('COALESCE(SUM(p.total_payout_minor), 0) as total_payout_minor'), DB::raw('COALESCE(SUM(b.total_bet_minor), 0) - COALESCE(SUM(p.total_payout_minor), 0) as approx_house_gross_minor'), ]) ->map(static function (object $row): array { $businessDate = $row->business_date instanceof Carbon ? $row->business_date->format('Y-m-d') : (string) $row->business_date; return [ 'business_date' => $businessDate, '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 playerWinLossPaginated( ?int $playerId, string $dateFrom, string $dateTo, int $page, int $perPage, ): LengthAwarePaginator { $query = $this->playerWinLossBaseQuery($playerId, $dateFrom, $dateTo); return $query->paginate($perPage, ['*'], 'page', $page); } public function playDimensionPaginated( ?string $playCode, string $dateFrom, string $dateTo, int $page, int $perPage, ): LengthAwarePaginator { $query = $this->playDimensionBaseQuery($playCode, $dateFrom, $dateTo); return $query->paginate($perPage, ['*'], 'page', $page); } public function rebateCommissionPaginated( ?string $playCode, string $dateFrom, string $dateTo, int $page, int $perPage, ): LengthAwarePaginator { $query = $this->rebateCommissionBaseQuery($playCode, $dateFrom, $dateTo); return $query->paginate($perPage, ['*'], 'page', $page); } /** * @return list> */ public function reportRows(string $reportType, ?array $filterJson): array { $range = $this->resolveDateRange($filterJson); $dateFrom = $range['date_from']; $dateTo = $range['date_to']; return match ($reportType) { 'daily_profit_summary' => $this->dailyProfitExportRows($dateFrom, $dateTo), 'player_win_loss' => $this->playerWinLossExportRows($filterJson, $dateFrom, $dateTo), 'play_dimension_report' => $this->playDimensionExportRows($filterJson, $dateFrom, $dateTo), 'rebate_commission_report' => $this->rebateCommissionExportRows($filterJson, $dateFrom, $dateTo), 'audit_operation_report' => $this->auditExportRows($filterJson, $dateFrom, $dateTo), default => [ ['报表类型', '开始日期', '结束日期'], [$this->reportLabel($reportType), $dateFrom, $dateTo], ], }; } public function reportLabel(string $reportType): string { return match ($reportType) { 'draw_profit_summary' => '期号盈亏', 'daily_profit_summary' => '每日盈亏汇总', 'player_win_loss' => '玩家输赢报表', 'wallet_transfer_report', 'wallet_txns_daily', 'transfer_orders_daily' => '玩家转入转出报表', 'hot_number_risk_report' => '热门号码风险报表', 'play_dimension_report' => '玩法维度报表', 'sold_out_number_report' => '售罄号码报表', 'rebate_commission_report' => '佣金回水报表', 'audit_operation_report' => '后台操作审计报表', default => $reportType, }; } /** * @return list> */ private function dailyProfitExportRows(string $dateFrom, string $dateTo): array { $rows = [ ['日期', '下注', '派彩', '盈亏'], ]; foreach ($this->dailyProfitRows($dateFrom, $dateTo) as $row) { $rows[] = [ $row['business_date'], $row['total_bet_minor'], $row['total_payout_minor'], $row['approx_house_gross_minor'], ]; } return $rows; } /** * @return list> */ private function playerWinLossExportRows(?array $filterJson, string $dateFrom, string $dateTo): array { $playerId = isset($filterJson['player_id']) ? (int) $filterJson['player_id'] : null; $rows = [ ['玩家ID', '用户名', '下注', '派彩', '净输赢'], ]; $items = $this->playerWinLossBaseQuery($playerId, $dateFrom, $dateTo)->get(); foreach ($items as $row) { $rows[] = [ (int) $row->player_id, (string) $row->username, (int) $row->total_bet_minor, (int) $row->total_payout_minor, (int) $row->net_win_loss_minor, ]; } return $rows; } /** * @return list> */ private function playDimensionExportRows(?array $filterJson, string $dateFrom, string $dateTo): array { $playCode = isset($filterJson['play_code']) ? trim((string) $filterJson['play_code']) : null; $rows = [ ['玩法', '维度', '下注', '派彩', '盈亏'], ]; $items = $this->playDimensionBaseQuery($playCode !== '' ? $playCode : null, $dateFrom, $dateTo)->get(); foreach ($items as $row) { $rows[] = [ (string) $row->play_code, (int) $row->dimension, (int) $row->total_bet_minor, (int) $row->total_payout_minor, (int) $row->approx_house_gross_minor, ]; } return $rows; } /** * @return list> */ private function rebateCommissionExportRows(?array $filterJson, string $dateFrom, string $dateTo): array { $playCode = isset($filterJson['play_code']) ? trim((string) $filterJson['play_code']) : null; $rows = [ ['玩法', '回水', '订单数', '注单数'], ]; $items = $this->rebateCommissionBaseQuery($playCode !== '' ? $playCode : null, $dateFrom, $dateTo)->get(); foreach ($items as $row) { $rows[] = [ (string) $row->play_code, (int) $row->total_rebate_minor, (int) $row->order_count, (int) $row->ticket_item_count, ]; } return $rows; } /** * @return list> */ private function auditExportRows(?array $filterJson, string $dateFrom, string $dateTo): array { $operatorId = isset($filterJson['operator_id']) ? (int) $filterJson['operator_id'] : null; $rows = [ ['ID', '操作者类型', '操作者ID', '模块', '操作', 'IP', '时间'], ]; $q = AuditLog::query()->orderByDesc('id'); if ($operatorId !== null && $operatorId > 0) { $q->where('operator_id', $operatorId); } $q->whereDate('created_at', '>=', $dateFrom) ->whereDate('created_at', '<=', $dateTo); foreach ($q->limit(5000)->get() as $log) { $rows[] = [ (int) $log->id, (string) $log->operator_type, (int) $log->operator_id, (string) $log->module_code, (string) $log->action_code, (string) ($log->ip ?? ''), $log->created_at?->toIso8601String() ?? '', ]; } return $rows; } /** @return \Illuminate\Database\Query\Builder */ private function playerWinLossBaseQuery(?int $playerId, string $dateFrom, string $dateTo) { $query = DB::table('ticket_items as ti') ->join('ticket_orders as o', 'o.id', '=', 'ti.order_id') ->leftJoin('players as p', 'p.id', '=', 'ti.player_id') ->selectRaw('ti.player_id') ->selectRaw('p.username as username') ->selectRaw('SUM(ti.actual_deduct_amount) as total_bet_minor') ->selectRaw('SUM(ti.win_amount + ti.jackpot_win_amount) as total_payout_minor') ->selectRaw('SUM(ti.actual_deduct_amount) - SUM(ti.win_amount + ti.jackpot_win_amount) as net_win_loss_minor') ->whereDate('o.created_at', '>=', $dateFrom) ->whereDate('o.created_at', '<=', $dateTo) ->groupBy('ti.player_id', 'p.username') ->orderByDesc('net_win_loss_minor'); if ($playerId !== null && $playerId > 0) { $query->where('ti.player_id', $playerId); } return $query; } /** @return \Illuminate\Database\Query\Builder */ private function playDimensionBaseQuery(?string $playCode, string $dateFrom, string $dateTo) { $query = DB::table('ticket_items as ti') ->join('ticket_orders as o', 'o.id', '=', 'ti.order_id') ->selectRaw('ti.play_code') ->selectRaw('ti.dimension') ->selectRaw('SUM(ti.actual_deduct_amount) as total_bet_minor') ->selectRaw('SUM(ti.win_amount + ti.jackpot_win_amount) as total_payout_minor') ->selectRaw('SUM(ti.actual_deduct_amount) - SUM(ti.win_amount + ti.jackpot_win_amount) as approx_house_gross_minor') ->whereDate('o.created_at', '>=', $dateFrom) ->whereDate('o.created_at', '<=', $dateTo) ->groupBy('ti.play_code', 'ti.dimension') ->orderBy('ti.play_code') ->orderBy('ti.dimension'); if ($playCode !== null && $playCode !== '') { $query->where('ti.play_code', $playCode); } return $query; } /** @return \Illuminate\Database\Query\Builder */ private function rebateCommissionBaseQuery(?string $playCode, string $dateFrom, string $dateTo) { $query = DB::table('ticket_items as ti') ->join('ticket_orders as o', 'o.id', '=', 'ti.order_id') ->selectRaw('ti.play_code') ->selectRaw('SUM(ti.total_bet_amount - ti.actual_deduct_amount) as total_rebate_minor') ->selectRaw('COUNT(DISTINCT o.id) as order_count') ->selectRaw('COUNT(ti.id) as ticket_item_count') ->whereDate('o.created_at', '>=', $dateFrom) ->whereDate('o.created_at', '<=', $dateTo) ->groupBy('ti.play_code') ->orderBy('ti.play_code'); if ($playCode !== null && $playCode !== '') { $query->where('ti.play_code', $playCode); } return $query; } }