user(); $allowedVenueIds = $user->isSuperAdmin() ? Venue::query()->pluck('id') : $user->venues()->pluck('venues.id'); $selectedVenueId = $request->filled('venue_id') ? (int) $request->input('venue_id') : null; $venueIds = $allowedVenueIds; if ($selectedVenueId) { $venueIds = $venueIds->filter(fn ($id) => (int) $id === $selectedVenueId)->values(); } $datesBlank = ! $request->filled('start_date') || ! $request->filled('end_date'); $startDate = $datesBlank ? null : trim((string) $request->input('start_date')); $endDate = $datesBlank ? null : trim((string) $request->input('end_date')); $activityId = null; if (! $datesBlank && $request->filled('activity_id')) { $activityId = (int) $request->input('activity_id'); $allowed = Activity::query() ->whereKey($activityId) ->whereNull('deleted_at') ->whereIn('venue_id', $venueIds) ->exists(); if (! $allowed) { return response()->json(['message' => '活动不存在或无权查看'], 422); } } /** 顶部五项:账号可见场馆范围内的全量累计,不受日期与「筛选场馆」影响 */ $scopeVenueIds = $allowedVenueIds; $activeVenueCount = (int) DB::table('reservations') ->whereIn('venue_id', $scopeVenueIds) ->distinct('venue_id') ->count('venue_id'); $userCount = (int) DB::table('reservations') ->whereIn('venue_id', $scopeVenueIds) ->whereNotNull('wechat_user_id') ->selectRaw('COUNT(DISTINCT wechat_user_id) as c') ->value('c'); $activitySessions = Activity::query() ->whereIn('venue_id', $scopeVenueIds) ->whereNull('deleted_at') ->count(); $ticketGrabSessions = TicketGrabEvent::query() ->whereHas('venues', fn ($vq) => $vq->whereIn('venues.id', $scopeVenueIds)) ->count(); $blacklistedUniqueQuery = Blacklist::query() ->whereIn('venue_id', $scopeVenueIds) ->whereNotNull('visitor_phone') ->whereRaw("TRIM(visitor_phone) <> ''") ->whereExists(function ($q) use ($scopeVenueIds, $user) { $q->selectRaw('1') ->from('reservations') ->whereColumn('reservations.visitor_phone', 'blacklists.visitor_phone') ->whereIn('reservations.venue_id', $scopeVenueIds); if ($user->isSuperAdmin() && Schema::hasTable('wechat_users')) { $q->whereNotNull('reservations.wechat_user_id') ->whereExists(function ($wq) { $wq->selectRaw('1') ->from('wechat_users') ->whereColumn('wechat_users.id', 'reservations.wechat_user_id'); }); } }); $blacklistedUnique = $blacklistedUniqueQuery ->distinct('visitor_phone') ->count('visitor_phone'); $activitySums = null; $activityStatsByVenue = []; if (! $datesBlank) { $activityOverlap = function ($q) use ($startDate, $endDate) { $q->where(function ($w) use ($startDate, $endDate) { $w->whereNotNull('start_at') ->whereNotNull('end_at') ->whereDate('start_at', '<=', $endDate) ->whereDate('end_at', '>=', $startDate); })->orWhere(function ($w) use ($startDate, $endDate) { $w->whereNull('start_at') ->whereNull('end_at') ->whereDate('created_at', '>=', $startDate) ->whereDate('created_at', '<=', $endDate); }); }; $statsActivityIdsQuery = Activity::query() ->whereIn('venue_id', $venueIds) ->whereNull('deleted_at'); if ($activityId !== null) { $statsActivityIdsQuery->whereKey($activityId); } else { $statsActivityIdsQuery->where($activityOverlap); } $statsActivityIds = $statsActivityIdsQuery->pluck('id'); $activitySums = $statsActivityIds->isEmpty() ? null : Activity::query() ->whereIn('id', $statsActivityIds) ->selectRaw('COALESCE(SUM(view_count),0) as v') ->first(); if ($statsActivityIds->isNotEmpty()) { $actByVenueRows = Activity::query() ->whereIn('id', $statsActivityIds) ->selectRaw('venue_id') ->selectRaw('COALESCE(SUM(view_count), 0) as total_view_count') ->groupBy('venue_id') ->get(); $names = Venue::query()->whereIn('id', $actByVenueRows->pluck('venue_id'))->pluck('name', 'id'); foreach ($actByVenueRows as $row) { $vid = (int) $row->venue_id; $activityStatsByVenue[] = [ 'venue_id' => $vid, 'venue_name' => (string) ($names[$vid] ?? ('#'.$vid)), 'total_view_count' => (int) ($row->total_view_count ?? 0), ]; } usort($activityStatsByVenue, fn (array $x, array $y) => strcmp($x['venue_name'], $y['venue_name'])); } } return response()->json([ 'scope' => [ 'role' => $user->role, 'venue_id' => $selectedVenueId, 'start_date' => $startDate, 'end_date' => $endDate, 'activity_id' => $activityId, 'dates_applied' => ! $datesBlank, ], 'summary' => [ 'active_venue_count' => (int) $activeVenueCount, 'activity_sessions' => (int) $activitySessions, 'ticket_grab_sessions' => (int) $ticketGrabSessions, 'user_count' => (int) $userCount, 'blacklisted_unique' => (int) $blacklistedUnique, ], 'activity_stats' => [ 'total_view_count' => (int) ($activitySums->v ?? 0), ], 'activity_stats_venues' => $activityStatsByVenue, ]); } public function ticketGrabStats(Request $request, DashboardTicketGrabStatsService $ticketGrabStats): JsonResponse { $validated = $request->validate([ 'ticket_grab_event_id' => ['required', 'integer', 'exists:ticket_grab_events,id'], 'date' => ['required', 'date_format:Y-m-d'], ]); $user = $request->user(); $allowedVenueIds = $user->isSuperAdmin() ? Venue::query()->pluck('id') : $user->venues()->pluck('venues.id'); $eventId = (int) $validated['ticket_grab_event_id']; $date = $validated['date']; $pivotVenues = TicketGrabEventVenue::query() ->where('ticket_grab_event_id', $eventId) ->pluck('venue_id'); if ($pivotVenues->isNotEmpty() && ! $user->isSuperAdmin()) { $allow = $allowedVenueIds->map(fn ($id) => (int) $id); $ok = false; foreach ($pivotVenues as $vid) { if ($allow->contains((int) $vid)) { $ok = true; break; } } if (! $ok) { return response()->json(['message' => '无权查看该抢票活动统计'], 403); } } $scoped = DashboardTicketGrabStatsService::scopedVenueIdsForEvent($eventId, $allowedVenueIds); if ($scoped->isEmpty()) { return response()->json(['message' => '当前账号下无可统计的场馆或未配置参与场馆'], 403); } $payload = $ticketGrabStats->build($eventId, $date, $scoped); if (isset($payload['error'])) { return response()->json(['message' => $payload['error']], 404); } $payload['data_updated_at'] = $date; return response()->json($payload); } }