diff --git a/.env.example b/.env.example index 5a82a88..cbab789 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost +# 与活动预约开放窗口等业务日期时间一致(数据库 datetime 按本地时刻存储时使用) +APP_TIMEZONE=Asia/Shanghai SANCTUM_STATEFUL_DOMAINS=localhost:5173,127.0.0.1:5173,localhost:5174,127.0.0.1:5174,localhost,127.0.0.1 CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173,http://localhost:5174,http://127.0.0.1:5174 CORS_SUPPORTS_CREDENTIALS=true diff --git a/app/Http/Controllers/Api/ActivityRegistrationController.php b/app/Http/Controllers/Api/ActivityRegistrationController.php index 8cee7e7..d3c445e 100644 --- a/app/Http/Controllers/Api/ActivityRegistrationController.php +++ b/app/Http/Controllers/Api/ActivityRegistrationController.php @@ -104,7 +104,7 @@ class ActivityRegistrationController extends Controller return response()->streamDownload(function () use ($rows) { $out = fopen('php://output', 'w'); fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF)); - fputcsv($out, ['ID', '活动', '场馆', '报名人', '手机号', '身份证', '预约票数', '预约入馆日期', '状态', '预约时间', '核销时间', '二维码Token']); + fputcsv($out, ['ID', '活动', '场馆', '报名人', '手机号', '身份证', '预约类型', '预约票数', '预约入馆日期', '状态', '预约时间', '核销时间', '二维码Token']); foreach ($rows as $row) { $entryDate = $row->activityDay?->activity_date; $entryDateStr = $entryDate ? Carbon::parse($entryDate)->timezone('Asia/Shanghai')->format('Y-m-d') : ''; @@ -115,6 +115,7 @@ class ActivityRegistrationController extends Controller $row->visitor_name, $row->visitor_phone ?? '', $row->id_card ?? '', + self::bookingTypeLabel($row->booking_type, $row->ticket_count), (string) ($row->ticket_count ?? 1), $entryDateStr, self::statusLabel($row->status), @@ -217,4 +218,17 @@ class ActivityRegistrationController extends Controller default => $status, }; } + + private static function bookingTypeLabel(?string $bookingType, mixed $ticketCount): string + { + if ($bookingType === 'group') { + return '团体'; + } + if ($bookingType === 'individual') { + return '个人'; + } + $n = max(1, (int) $ticketCount); + + return $n > 1 ? '团体' : '个人'; + } } diff --git a/app/Http/Controllers/Api/H5ReservationController.php b/app/Http/Controllers/Api/H5ReservationController.php index b033191..b1955e1 100644 --- a/app/Http/Controllers/Api/H5ReservationController.php +++ b/app/Http/Controllers/Api/H5ReservationController.php @@ -115,26 +115,34 @@ class H5ReservationController extends Controller [$bookingType, $peopleCount] = $this->resolveBookingTypeAndPeopleCount($mode, $data, $minPeople, $maxPeople); - $reservation = DB::transaction(function () use ($request, $activity, $day, $data, $peopleCount, $bookingType) { + $wechatUser = $this->authWechatUser($request); + + $reservation = DB::transaction(function () use ($activity, $day, $data, $peopleCount, $bookingType, $wechatUser) { $day->refresh(); $available = (int) $day->day_quota - (int) $day->booked_count; if ($available < $peopleCount) { throw ValidationException::withMessages(['activity_day_id' => ['该日期余票不足']]); } - // 去重规则:同活动同活动日同手机号只能预约一单(未取消) - $dupByPhone = Reservation::query() + // 去重:同一活动、同一活动日,同一用户只能有一单未取消预约(取消后可再约) + // — 按手机号;已登录时同时按微信用户,避免换手机号重复占坑 + $dup = Reservation::query() ->where('activity_id', $activity->id) ->where('activity_day_id', $day->id) - ->where('visitor_phone', $data['visitor_phone']) ->where('status', '!=', 'cancelled') + ->where(function ($q) use ($data, $wechatUser) { + $q->where('visitor_phone', $data['visitor_phone']); + if ($wechatUser) { + $q->orWhere('wechat_user_id', $wechatUser->id); + } + }) ->exists(); - if ($dupByPhone) { - throw ValidationException::withMessages(['visitor_phone' => ['该手机号在该活动日期已预约过']]); + if ($dup) { + throw ValidationException::withMessages([ + 'activity_day_id' => ['您在该活动该日期已有未取消的预约,请先取消原预约后再预约'], + ]); } - $wechatUser = $this->authWechatUser($request); - $row = Reservation::create([ 'venue_id' => $activity->venue_id, 'activity_id' => $activity->id, diff --git a/config/app.php b/config/app.php index 9207160..d3826fb 100644 --- a/config/app.php +++ b/config/app.php @@ -70,7 +70,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => env('APP_TIMEZONE', 'Asia/Shanghai'), /* |--------------------------------------------------------------------------