You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
7.4 KiB

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Activity;
use App\Models\Blacklist;
use App\Models\TicketGrabEvent;
use App\Models\TicketGrabEventVenue;
use App\Models\Venue;
use App\Services\DashboardTicketGrabStatsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class DashboardController extends Controller
{
public function stats(Request $request): JsonResponse
{
$user = $request->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();
}
$activityId = $request->filled('activity_id') ? (int) $request->input('activity_id') : null;
if ($activityId) {
$allowed = Activity::query()
->whereKey($activityId)
->whereNull('deleted_at')
->whereIn('venue_id', $venueIds)
->exists();
if (! $allowed) {
return response()->json(['message' => '活动不存在或无权查看'], 422);
}
}
/** 顶部指标:账号可见场馆范围内的全量累计(不受「筛选场馆」影响) */
$scopeVenueIds = $allowedVenueIds;
$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()
->active()
->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');
$baseActivityQuery = Activity::query()
->whereIn('venue_id', $venueIds)
->whereNull('deleted_at');
if ($activityId !== null) {
$baseActivityQuery->whereKey($activityId);
}
$totalViews = (int) (clone $baseActivityQuery)->sum('view_count');
$page = max(1, (int) $request->input('activity_stats_page', 1));
$pageSize = max(1, min(2000, (int) $request->input('activity_stats_page_size', 500)));
$totalActivities = (clone $baseActivityQuery)->count();
$activityRows = (clone $baseActivityQuery)
->orderByDesc('view_count')
->orderByDesc('id')
->forPage($page, $pageSize)
->get(['id', 'title', 'venue_id', 'start_at', 'end_at', 'view_count']);
$venueIdsForNames = $activityRows->pluck('venue_id')->unique()->filter()->values()->all();
$venueNameMap = $venueIdsForNames === []
? collect()
: Venue::query()->whereIn('id', $venueIdsForNames)->pluck('name', 'id');
$activityStatsActivities = $activityRows->map(function ($a) use ($venueNameMap) {
$vid = (int) $a->venue_id;
return [
'id' => (int) $a->id,
'title' => (string) $a->title,
'venue_id' => $vid,
'venue_name' => (string) ($venueNameMap[$vid] ?? ('#'.$vid)),
'view_count' => (int) ($a->view_count ?? 0),
'start_at' => $a->start_at,
'end_at' => $a->end_at,
];
})->values()->all();
return response()->json([
'scope' => [
'role' => $user->role,
'venue_id' => $selectedVenueId,
'activity_id' => $activityId,
],
'summary' => [
'activity_sessions' => (int) $activitySessions,
'ticket_grab_sessions' => (int) $ticketGrabSessions,
'user_count' => (int) $userCount,
'blacklisted_unique' => (int) $blacklistedUnique,
],
'activity_stats' => [
'total_view_count' => $totalViews,
],
'activity_stats_activities' => [
'data' => $activityStatsActivities,
'total' => $totalActivities,
'page' => $page,
'page_size' => $pageSize,
],
]);
}
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);
}
}