You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
szkp-map-service/app/Services/TicketGrabReleaseDayService...

155 lines
5.7 KiB

4 days ago
<?php
namespace App\Services;
use App\Models\TicketGrabEvent;
use App\Models\TicketGrabEventVenue;
use App\Models\TicketGrabVenueReleaseDay;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class TicketGrabReleaseDayService
{
/**
* 将 total 均分至 n 天,不能整除时余量依次分给前 n 个日期各 +1等价于前 remainder 天多 1 张(此处按「多给前面日期」与需求「多的分配到第一日」一致为:余数 r 个日期各 +1从第 0 天开始;即 [ceil, floor, floor, ...])。
*
* @return int[]
*/
public static function equalSplit(int $total, int $n): array
{
if ($n <= 0) {
return [];
}
if ($total < 0) {
$total = 0;
}
$base = intdiv($total, $n);
$r = $total % $n;
$out = array_fill(0, $n, $base);
for ($i = 0; $i < $r; $i++) {
$out[$i]++;
}
return $out;
}
public static function syncCarryInChain(int $eventId, ?int $venueId = null): void
{
$q = TicketGrabEventVenue::query()->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);
}
}