where('ticket_grab_event_id', $eventId); if ($venueId !== null) { $q->where('venue_id', $venueId); } $pivots = $q->get(); foreach ($pivots as $p) { $days = TicketGrabVenueReleaseDay::query() ->where('ticket_grab_event_id', $eventId) ->where('venue_id', $p->venue_id) ->orderBy('release_date') ->get(); $tz = (string) config('app.timezone'); $today = Carbon::now($tz)->toDateString(); $run = 0; foreach ($days as $d) { $d->carry_in = $run; $raw = (int) $d->carry_in + (int) $d->day_quota - (int) $d->booked_count; $rem = max(0, $raw); $rd = $d->release_date instanceof Carbon ? $d->release_date->format('Y-m-d') : Carbon::parse($d->release_date)->format('Y-m-d'); // 仅当该放票日已早于「今天」才把当日余量滚入下一天;当日与未来的日期不提前把余量算进次日 if ($rd < $today) { $run = $rem; } else { $run = 0; } $d->save(); } } } /** * 根据活动与预约起止、场馆及配额,重建每日放票行(会删除该活动下未删除场馆对应的旧行并插入新行,booked 清零 —— 见调用方在更新时的保护). */ public static function rebuildAllReleaseDaysForEvent(int $eventId, bool $preserveBooked = false): void { $event = TicketGrabEvent::query()->with('eventVenuePivots')->find($eventId); if (! $event || ! $event->booking_start_at || ! $event->booking_end_at) { return; } $start = $event->booking_start_at->copy(); $end = $event->booking_end_at->copy(); if ($end->lt($start)) { return; } $dates = []; foreach (CarbonPeriod::create($start, $end) as $d) { $dates[] = $d->toDateString(); } $n = count($dates); if ($n === 0) { return; } DB::transaction(function () use ($event, $eventId, $dates, $n, $preserveBooked) { if (! $preserveBooked) { TicketGrabVenueReleaseDay::query() ->where('ticket_grab_event_id', $eventId) ->delete(); } $pivots = TicketGrabEventVenue::query()->where('ticket_grab_event_id', $eventId)->get(); foreach ($pivots as $p) { $alloc = self::equalSplit((int) $p->venue_total_quota, $n); if (! $preserveBooked) { foreach ($dates as $i => $dateStr) { TicketGrabVenueReleaseDay::query()->create([ 'ticket_grab_event_id' => $eventId, 'venue_id' => $p->venue_id, 'release_date' => $dateStr, 'day_quota' => (int) ($alloc[$i] ?? 0), 'booked_count' => 0, 'carry_in' => 0, ]); } } } self::syncCarryInChain($eventId); }); if (! $preserveBooked) { $sum = (int) TicketGrabEventVenue::query() ->where('ticket_grab_event_id', $eventId) ->sum('venue_total_quota'); TicketGrabEvent::query()->where('id', $eventId)->update(['total_quota' => $sum]); } } public static function updateDayQuotasFromAdmin(int $eventId, int $venueId, array $dateToQuota): void { $pivot = TicketGrabEventVenue::query() ->where('ticket_grab_event_id', $eventId) ->where('venue_id', $venueId) ->firstOrFail(); $sum = 0; foreach ($dateToQuota as $date => $q) { $sum += (int) $q; TicketGrabVenueReleaseDay::query() ->where('ticket_grab_event_id', $eventId) ->where('venue_id', $venueId) ->whereDate('release_date', $date) ->update(['day_quota' => (int) $q]); } if ($sum !== (int) $pivot->venue_total_quota) { throw ValidationException::withMessages([ 'day_quota' => ["各日放票数之和必须等于该馆放票总数({$pivot->venue_total_quota})。"], ]); } self::syncCarryInChain($eventId, $venueId); } }