|
|
<?php
|
|
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
use App\Models\Reservation;
|
|
|
use Carbon\CarbonInterface;
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
class ReservationVerifyController extends Controller
|
|
|
{
|
|
|
/**
|
|
|
* 移动端「今日」核销:仅允许活动日(activity_days.activity_date)为当天的预约核销。
|
|
|
*/
|
|
|
private function activityDayNotTodayMessage(Reservation $reservation): ?string
|
|
|
{
|
|
|
$reservation->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, '仅可核销已绑定场馆预约');
|
|
|
}
|
|
|
}
|