@ -107,7 +107,7 @@ class Activity extends Model
protected static function booted(): void
{
static::saving(function (Activity $activity) {
if (self::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at ) === 'ended') {
if (self::computeScheduleStatusForDisplay($activity ) === 'ended') {
$activity->is_hot = false;
}
});
@ -120,11 +120,33 @@ class Activity extends Model
{
$tz = (string) config('app.timezone');
$today = Carbon::now($tz)->toDateString();
$now = Carbon::now($tz);
$sessionDaySql = 'ad.activity_id = activities.id AND ad.session_start_at IS NOT NULL AND ad.session_end_at IS NOT NULL AND ad.booking_deadline_at IS NOT NULL';
return (int) static::query()
->where('is_hot', true)
->whereNotNull('end_at')
->whereDate('end_at', '< ', $today)
->where(function (Builder $q) use ($today, $now, $sessionDaySql) {
$q->where(function (Builder $sess) use ($now, $sessionDaySql) {
$sess->where(function (Builder $t) {
$t->whereNull('reservation_type')
->orWhere('reservation_type', '')
->orWhere('reservation_type', self::RESERVATION_TYPE_ONLINE);
})->whereRaw(
'(SELECT MAX(ad.session_end_at) FROM activity_days ad WHERE '.$sessionDaySql.') < ?',
[$now]
);
})->orWhere(function (Builder $cls) use ($today, $sessionDaySql) {
$cls->where(function (Builder $x) use ($sessionDaySql) {
$x->where(function (Builder $off) {
$off->whereNotNull('reservation_type')
->where('reservation_type', '!=', '')
->where('reservation_type', '!=', self::RESERVATION_TYPE_ONLINE);
})->orWhereRaw(
'(SELECT COUNT(*) FROM activity_days ad WHERE '.$sessionDaySql.') = 0'
);
})->whereNotNull('end_at')->whereDate('end_at', '< ', $today);
});
})
->update(['is_hot' => false]);
}
@ -257,20 +279,127 @@ class Activity extends Model
return 'ongoing';
}
/** 列表筛选:按日期实时计算,不依赖 schedule_status 字段的缓存值。 */
/**
* 平台内「需要报名」( online / 空类型)且存在场次模式活动日时,用各场 session 起止时间的 min/max
* 判断未开始 / 进行中 / 已结束;否则与 {@see computeScheduleStatusFromBounds} 一致(活动级日历日)。
*/
public static function computeScheduleStatusForDisplay(Activity $activity): string
{
if (! self::isOnlineReservationType((string) ($activity->reservation_type ?? self::RESERVATION_TYPE_ONLINE))) {
return self::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at);
}
$days = $activity->relationLoaded('activityDays')
? $activity->activityDays
: $activity->activityDays()->get();
$sessionDays = $days->filter(fn (ActivityDay $d) => $d->isSessionMode());
if ($sessionDays->isEmpty()) {
return self::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at);
}
$tz = (string) config('app.timezone');
$now = Carbon::now($tz);
$minStart = $sessionDays->min('session_start_at');
$maxEnd = $sessionDays->max('session_end_at');
if (! $minStart instanceof Carbon || ! $maxEnd instanceof Carbon) {
return self::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at);
}
$minStart = $minStart->copy()->timezone($tz);
$maxEnd = $maxEnd->copy()->timezone($tz);
if ($now->lt($minStart)) {
return 'not_started';
}
if ($now->gt($maxEnd)) {
return 'ended';
}
return 'ongoing';
}
public static function isOnlineReservationType(string $reservationType): bool
{
$t = trim($reservationType);
return $t === '' || $t === self::RESERVATION_TYPE_ONLINE;
}
/** 列表筛选:实时计算,与 {@see computeScheduleStatusForDisplay} 一致(含场次结束时间)。 */
public function scopeWhereComputedScheduleStatus(Builder $query, string $status): Builder
{
$tz = (string) config('app.timezone');
$today = Carbon::now($tz)->toDateString();
$now = Carbon::now($tz);
$sessionDaySql = 'ad.activity_id = activities.id AND ad.session_start_at IS NOT NULL AND ad.session_end_at IS NOT NULL AND ad.booking_deadline_at IS NOT NULL';
$onlineReservation = function (Builder $q): void {
$q->where(function (Builder $t) {
$t->whereNull('reservation_type')
->orWhere('reservation_type', '')
->orWhere('reservation_type', self::RESERVATION_TYPE_ONLINE);
});
};
$offlineOrNoSessionDays = function (Builder $q) use ($sessionDaySql): void {
$q->where(function (Builder $x) use ($sessionDaySql) {
$x->where(function (Builder $off) {
$off->whereNotNull('reservation_type')
->where('reservation_type', '!=', '')
->where('reservation_type', '!=', self::RESERVATION_TYPE_ONLINE);
})->orWhereRaw(
'(SELECT COUNT(*) FROM activity_days ad WHERE '.$sessionDaySql.') = 0'
);
});
};
return match ($status) {
'not_started' => $query->whereNotNull('start_at')->whereDate('start_at', '>', $today),
'ended' => $query->whereNotNull('end_at')->whereDate('end_at', '< ', $today),
'ongoing' => $query->where(function (Builder $q) use ($today) {
$q->where(function (Builder $q2) use ($today) {
$q2->whereNull('start_at')->orWhereDate('start_at', '< =', $today);
})->where(function (Builder $q2) use ($today) {
$q2->whereNull('end_at')->orWhereDate('end_at', '>=', $today);
'not_started' => $query->where(function (Builder $q) use ($onlineReservation, $offlineOrNoSessionDays, $today, $now, $sessionDaySql) {
$q->where(function (Builder $sess) use ($onlineReservation, $now, $sessionDaySql) {
$onlineReservation($sess);
$sess->whereRaw(
'(SELECT MIN(ad.session_start_at) FROM activity_days ad WHERE '.$sessionDaySql.') > ?',
[$now]
);
})->orWhere(function (Builder $cls) use ($offlineOrNoSessionDays, $today) {
$offlineOrNoSessionDays($cls);
$cls->whereNotNull('start_at')->whereDate('start_at', '>', $today);
});
}),
'ended' => $query->where(function (Builder $q) use ($onlineReservation, $offlineOrNoSessionDays, $today, $now, $sessionDaySql) {
$q->where(function (Builder $sess) use ($onlineReservation, $now, $sessionDaySql) {
$onlineReservation($sess);
$sess->whereRaw(
'(SELECT MAX(ad.session_end_at) FROM activity_days ad WHERE '.$sessionDaySql.') < ?',
[$now]
);
})->orWhere(function (Builder $cls) use ($offlineOrNoSessionDays, $today) {
$offlineOrNoSessionDays($cls);
$cls->whereNotNull('end_at')->whereDate('end_at', '< ', $today);
});
}),
'ongoing' => $query->where(function (Builder $q) use ($onlineReservation, $offlineOrNoSessionDays, $today, $now, $sessionDaySql) {
$q->where(function (Builder $sess) use ($onlineReservation, $now, $sessionDaySql) {
$onlineReservation($sess);
$sess->whereRaw('(SELECT COUNT(*) FROM activity_days ad WHERE '.$sessionDaySql.') > 0')
->whereRaw(
'(SELECT MIN(ad.session_start_at) FROM activity_days ad WHERE '.$sessionDaySql.') < = ?',
[$now]
)
->whereRaw(
'(SELECT MAX(ad.session_end_at) FROM activity_days ad WHERE '.$sessionDaySql.') >= ?',
[$now]
);
})->orWhere(function (Builder $cls) use ($offlineOrNoSessionDays, $today) {
$offlineOrNoSessionDays($cls);
$cls->where(function (Builder $c) use ($today) {
$c->whereNull('start_at')->orWhereDate('start_at', '< =', $today);
})->where(function (Builder $c) use ($today) {
$c->whereNull('end_at')->orWhereDate('end_at', '>=', $today);
});
});
}),
default => $query,