|
|
|
|
<?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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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);
|
|
|
|
|
}
|
|
|
|
|
}
|