master
lion 4 days ago
parent bf7be0de37
commit ba9adad10d

@ -9,7 +9,7 @@ class ExpirePastReservationsCommand extends Command
{
protected $signature = 'reservations:expire-past';
protected $description = '将活动日已过仍未核销的待核销预约标记为已过期expired';
protected $description = '将场次结束/活动日/入馆日已过仍未核销的待核销预约标记为已过期expired';
public function handle(ReservationExpiryService $expiry): int
{

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Blacklist;
use App\Models\Reservation;
use App\Services\ReservationExpiryService;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@ -12,8 +13,10 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class ActivityRegistrationController extends Controller
{
public function index(Request $request): JsonResponse
public function index(Request $request, ReservationExpiryService $reservationExpiryService): JsonResponse
{
$reservationExpiryService->expireStalePendingReservations();
$kind = (string) $request->input('reservation_kind', 'activity');
if ($kind === 'ticket_grab') {
$query = Reservation::with([
@ -97,8 +100,10 @@ class ActivityRegistrationController extends Controller
return response()->json($page);
}
public function export(Request $request): StreamedResponse
public function export(Request $request, ReservationExpiryService $reservationExpiryService): StreamedResponse
{
$reservationExpiryService->expireStalePendingReservations();
$query = Reservation::with([
'venue:id,name',
'activity:id,title',

@ -117,15 +117,34 @@ class NoShowBlacklistService
}
/**
* 未履约场次expired或定时任务尚未执行时的 pending + 活动日已过。
* 未履约场次expired或定时任务尚未执行时的 pending + 场次已结束/活动日已过(与 {@see ReservationExpiryService} 一致)
*/
private function noShowBaseQuery(string $today)
{
$now = Carbon::now((string) config('app.timezone'));
return Reservation::query()
->join('activity_days', 'activity_days.id', '=', 'reservations.activity_day_id')
->whereNull('reservations.verified_at')
->whereNotNull('reservations.activity_day_id')
->whereDate('activity_days.activity_date', '<', $today)
->where(function ($q) use ($today, $now) {
$q->where(function ($q2) use ($now) {
$q2->whereNotNull('activity_days.session_start_at')
->whereNotNull('activity_days.session_end_at')
->whereNotNull('activity_days.booking_deadline_at')
->where('activity_days.session_end_at', '<', $now);
})->orWhere(function ($q2) use ($today) {
$q2->where(function ($q3) {
$q3->whereNull('activity_days.session_start_at')
->orWhereNull('activity_days.session_end_at')
->orWhereNull('activity_days.booking_deadline_at');
})->whereDate('activity_days.activity_date', '<', $today);
});
})
->where(function ($q) {
$q->whereNull('reservations.reservation_kind')
->orWhere('reservations.reservation_kind', Reservation::KIND_ACTIVITY);
})
->where(function ($q) {
$q->where('reservations.status', 'expired')
->orWhere('reservations.status', 'pending');

@ -4,25 +4,23 @@ namespace App\Services;
use App\Models\Reservation;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class ReservationExpiryService
{
/**
* 将「活动日已过、仍为待核销、未核销」的预约标记为 expired与未履约统计一致
* 将「场次/活动日已过、仍为待核销、未核销」的预约标记为 expired。
* - 场次模式:以 activity_days.session_end_at 为准(晚于「活动日」日历的场次结束时刻)。
* - 非场次(仅日历日):仍以 activity_date 早于今天为准。
* - 抢票:入馆日 entry_date 早于今天。
*/
public function expireStalePendingReservations(): int
{
$tz = (string) config('app.timezone');
$today = Carbon::now($tz)->toDateString();
$now = Carbon::now($tz);
$ids = Reservation::query()
->join('activity_days', 'activity_days.id', '=', 'reservations.activity_day_id')
->where('reservations.status', 'pending')
->whereNull('reservations.verified_at')
->whereNotNull('reservations.activity_day_id')
->whereDate('activity_days.activity_date', '<', $today)
->pluck('reservations.id');
$ids = $this->stalePendingReservationIds($today, $now);
if ($ids->isEmpty()) {
return 0;
}
@ -32,4 +30,45 @@ class ReservationExpiryService
'updated_at' => now(),
]);
}
/**
* @return Collection<int, int>
*/
private function stalePendingReservationIds(string $today, Carbon $now): Collection
{
$idsActivity = Reservation::query()
->join('activity_days', 'activity_days.id', '=', 'reservations.activity_day_id')
->where('reservations.status', 'pending')
->whereNull('reservations.verified_at')
->whereNotNull('reservations.activity_day_id')
->where(function ($q) use ($today, $now) {
$q->where(function ($q2) use ($now) {
$q2->whereNotNull('activity_days.session_start_at')
->whereNotNull('activity_days.session_end_at')
->whereNotNull('activity_days.booking_deadline_at')
->where('activity_days.session_end_at', '<', $now);
})->orWhere(function ($q2) use ($today) {
$q2->where(function ($q3) {
$q3->whereNull('activity_days.session_start_at')
->orWhereNull('activity_days.session_end_at')
->orWhereNull('activity_days.booking_deadline_at');
})->whereDate('activity_days.activity_date', '<', $today);
});
})
->where(function ($q) {
$q->whereNull('reservations.reservation_kind')
->orWhere('reservations.reservation_kind', Reservation::KIND_ACTIVITY);
})
->pluck('reservations.id');
$idsTicketGrab = Reservation::query()
->where('reservation_kind', Reservation::KIND_TICKET_GRAB)
->where('status', 'pending')
->whereNull('verified_at')
->whereNotNull('entry_date')
->whereDate('entry_date', '<', $today)
->pluck('id');
return $idsActivity->merge($idsTicketGrab)->unique()->values();
}
}

Loading…
Cancel
Save