loadMissing('activityDay'); $today = now()->toDateString(); if (!$reservation->activity_day_id || !$reservation->activityDay) { return '该预约未关联活动场次,仅支持核销「活动日为今日」的预约'; } $ad = $reservation->activityDay->activity_date; $dateStr = $ad instanceof CarbonInterface ? $ad->format('Y-m-d') : (string) $ad; if ($dateStr !== $today) { return '仅可核销活动日为今日的预约。该预约活动日为:'.$dateStr; } return null; } /** * 扫码后先查询预约信息,不修改状态(用于移动端「确认后再核销」)。 */ public function preview(Request $request): JsonResponse { $data = $request->validate([ 'qr_token' => ['required', 'string'], ]); $reservation = Reservation::with([ 'venue:id,name,reservation_notice', 'activity:id,title', 'activityDay:id,activity_id,activity_date', ])->where('qr_token', $data['qr_token'])->first(); if (!$reservation) { return response()->json(['message' => '无效的二维码或预约不存在'], 404); } $this->ensureVenuePermission($request, $reservation->venue_id); $blockReason = null; if ($reservation->status === 'verified') { $blockReason = '该预约已核销'; } elseif ($reservation->status === 'cancelled') { $blockReason = '该预约已取消,不能核销'; } $canVerify = $reservation->status === 'pending'; if ($canVerify) { $dayMsg = $this->activityDayNotTodayMessage($reservation); if ($dayMsg !== null) { $canVerify = false; $blockReason = $dayMsg; } } return response()->json([ 'reservation' => $reservation, 'can_verify' => $canVerify, 'verify_block_reason' => $blockReason, ]); } public function index(Request $request): JsonResponse { $user = $request->user(); $query = Reservation::with([ 'venue:id,name,reservation_notice', 'activity:id,title', 'activityDay:id,activity_id,activity_date', ]) // 核销端列表:待核销在前,同状态内按 id 倒序 ->orderByRaw("CASE WHEN status = 'pending' THEN 0 ELSE 1 END") ->orderByDesc('id'); if (!$user->isSuperAdmin()) { $venueIds = $user->venues()->pluck('venues.id'); $query->whereIn('venue_id', $venueIds); } if ($request->filled('status') && $request->string('status')->toString() !== 'all') { $query->where('status', (string) $request->string('status')); } if ($request->filled('keyword')) { $keyword = trim((string) $request->input('keyword')); $query->where(function ($q) use ($keyword) { $q->where('visitor_name', 'like', "%{$keyword}%") ->orWhere('visitor_phone', 'like', "%{$keyword}%") ->orWhere('id_card', 'like', "%{$keyword}%") ->orWhere('qr_token', 'like', "%{$keyword}%"); }); } $dateField = (string) $request->input('date_field', 'created_at'); if ($request->filled('start_date') || $request->filled('end_date')) { if ($dateField === 'activity_day') { $start = $request->filled('start_date') ? (string) $request->input('start_date') : null; $end = $request->filled('end_date') ? (string) $request->input('end_date') : $start; $query->whereHas('activityDay', function ($q) use ($start, $end) { if ($start !== null && $start !== '') { $q->whereDate('activity_date', '>=', $start); } if ($end !== null && $end !== '') { $q->whereDate('activity_date', '<=', $end); } }); } else { if ($request->filled('start_date')) { $query->whereDate('created_at', '>=', (string) $request->input('start_date')); } if ($request->filled('end_date')) { $query->whereDate('created_at', '<=', (string) $request->input('end_date')); } } } return response()->json($query->limit(500)->get()); } /** * 今日(活动日为当天)预约单数、已核销单数,与移动端「今日报名」同权限与场馆范围。 * 一大一小按 1 单计算(按预约订单数统计)。 */ public function todaySummary(Request $request): JsonResponse { $user = $request->user(); $today = now()->toDateString(); $base = Reservation::query() ->whereHas('activityDay', function ($q) use ($today) { $q->whereDate('activity_date', $today); }); if (!$user->isSuperAdmin()) { $venueIds = $user->venues()->pluck('venues.id'); $base->whereIn('venue_id', $venueIds); } $totalOrders = (clone $base)->whereNotIn('status', ['cancelled'])->count(); $verifiedOrders = (clone $base)->where('status', 'verified')->count(); return response()->json([ 'total_orders' => (int) $totalOrders, 'verified_orders' => (int) $verifiedOrders, ]); } public function verifyByToken(Request $request): JsonResponse { $data = $request->validate([ 'qr_token' => ['required', 'string'], ]); $reservation = Reservation::with('venue')->where('qr_token', $data['qr_token'])->firstOrFail(); $this->ensureVenuePermission($request, $reservation->venue_id); if ($reservation->status === 'verified') { return response()->json(['message' => '该预约已核销'], 422); } if ($reservation->status === 'cancelled') { return response()->json(['message' => '该预约已取消,不能核销'], 422); } $dayBlock = $this->activityDayNotTodayMessage($reservation); if ($dayBlock !== null) { return response()->json(['message' => $dayBlock], 422); } $reservation->update([ 'status' => 'verified', 'verified_by' => $request->user()->id, 'verified_at' => now(), ]); return response()->json([ 'message' => '核销成功', 'reservation' => $reservation->fresh()->load([ 'venue:id,name,reservation_notice', 'activity:id,title', 'activityDay:id,activity_id,activity_date', 'verifier:id,name,username', ]), ]); } private function ensureVenuePermission(Request $request, int $venueId): void { $user = $request->user(); if ($user->isSuperAdmin()) { return; } $allowed = $user->venues()->where('venues.id', $venueId)->exists(); abort_unless($allowed, 403, '仅可核销已绑定场馆预约'); } }