master
lion 6 days ago
parent 1a19e2ca03
commit a9ac9cc44c

@ -153,6 +153,9 @@ class ActivityBookingController extends Controller
}
});
$activity->load('activityDays');
$activity->forceFill(['schedule_status' => Activity::computeScheduleStatusForDisplay($activity)])->saveQuietly();
return response()->json(array_merge(
['message' => '保存成功'],
$this->formatBookingPayload($activity->fresh()->load('venue:id,appointment_type'))

@ -17,7 +17,7 @@ class ActivityController extends Controller
use AuthorizesActivitySubmitter;
public function index(Request $request): JsonResponse
{
$query = Activity::with('venue:id,name')->orderByHotThenScheduleStatusPriority();
$query = Activity::with(['venue:id,name', 'activityDays'])->orderByHotThenScheduleStatusPriority();
$this->restrictByVenue($request, $query);
if ($request->filled('keyword')) {
@ -107,10 +107,6 @@ class ActivityController extends Controller
}
$this->normalizeActivityDates($data);
$data['schedule_status'] = Activity::computeScheduleStatusFromBounds(
$data['start_at'] ?? null,
$data['end_at'] ?? null
);
$auditStatus = $request->user()->isSuperAdmin() ? Activity::AUDIT_APPROVED : Activity::AUDIT_PENDING;
@ -129,6 +125,9 @@ class ActivityController extends Controller
}
VerifyPortalCode::ensureForActivity($activity);
$activity->load('activityDays');
$activity->forceFill(['schedule_status' => Activity::computeScheduleStatusForDisplay($activity)])->saveQuietly();
$act = $activity->load('venue:id,name');
if (! $request->user()?->isSuperAdmin()) {
$act->makeHidden(['is_hot']);
@ -197,17 +196,16 @@ class ActivityController extends Controller
$this->normalizeActivityDates($data);
$start = array_key_exists('start_at', $data) ? $data['start_at'] : $activity->start_at;
$end = array_key_exists('end_at', $data) ? $data['end_at'] : $activity->end_at;
$data['schedule_status'] = Activity::computeScheduleStatusFromBounds($start, $end);
$activity->fill($data)->save();
if (array_key_exists('tags', $data)) {
$activity->tags = array_values($data['tags'] ?? []);
$activity->save();
}
$fresh = $activity->fresh()->load('venue:id,name');
$activity->load('activityDays');
$activity->forceFill(['schedule_status' => Activity::computeScheduleStatusForDisplay($activity)])->saveQuietly();
$fresh = $activity->fresh()->load(['venue:id,name', 'activityDays']);
if (! $request->user()?->isSuperAdmin()) {
$fresh->makeHidden(['is_hot']);
}
@ -237,7 +235,7 @@ class ActivityController extends Controller
{
$this->ensureVenuePermission($request, $activity->venue_id);
$this->authorizeActivityEditForNonSuperAdmin($request, $activity);
$status = Activity::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at);
$status = Activity::computeScheduleStatusForDisplay($activity->load('activityDays'));
abort_unless($status === 'ended', 422, '仅已结束活动可上传花絮');
$data = $request->validate([

@ -63,7 +63,7 @@ class H5ContentController extends Controller
'venue_lng' => $a->venue?->lng,
'start_at' => optional($a->start_at)?->toIso8601String(),
'end_at' => optional($a->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($a),
'registered_count' => (int) ($a->registered_count ?? 0),
'tags' => array_values($a->tags ?? []),
'is_bookable' => $isBookable,
@ -142,7 +142,7 @@ class H5ContentController extends Controller
'lng' => $a->lng,
'start_at' => optional($a->start_at)?->toIso8601String(),
'end_at' => optional($a->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($a),
'registered_count' => (int) ($a->registered_count ?? 0),
'tags' => array_values($a->tags ?? []),
'reservation_notice' => $a->reservation_notice,
@ -414,9 +414,10 @@ class H5ContentController extends Controller
->where('venue_id', $v->id)
->visibleOnH5()
->orderByHotThenScheduleStatusPriority()
->with('activityDays')
->with('venue:id,cover_image')
->limit(50)
->get(['id', 'title', 'summary', 'cover_image', 'start_at', 'end_at', 'registered_count', 'address', 'location', 'venue_id', 'behind_scenes_media', 'is_hot'])
->get(['id', 'title', 'summary', 'cover_image', 'start_at', 'end_at', 'registered_count', 'address', 'location', 'venue_id', 'behind_scenes_media', 'is_hot', 'reservation_type'])
->map(function (Activity $a) {
return [
'id' => $a->id,
@ -425,7 +426,7 @@ class H5ContentController extends Controller
'cover_image' => ActivityH5View::listCover($a),
'start_at' => optional($a->start_at)?->toIso8601String(),
'end_at' => optional($a->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($a),
'registered_count' => (int) ($a->registered_count ?? 0),
'address' => $a->location ?: $a->address,
'has_behind_scenes' => $this->activityHasBehindScenes($a),
@ -727,7 +728,7 @@ class H5ContentController extends Controller
'venue_lng' => $a->venue?->lng,
'start_at' => optional($a->start_at)?->toIso8601String(),
'end_at' => optional($a->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($a),
'registered_count' => (int) ($a->registered_count ?? 0),
'tags' => array_values($a->tags ?? []),
'is_bookable' => $isBookable,

@ -127,7 +127,7 @@ class H5HomeController extends Controller
'address' => $a->address,
'start_at' => optional($a->start_at)?->toIso8601String(),
'end_at' => optional($a->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($a->start_at, $a->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($a),
'registered_count' => (int) ($a->registered_count ?? 0),
'is_bookable' => $isBookable,
'all_slots_full' => $a->areAllActivityDaySlotsExhausted(),

@ -51,7 +51,7 @@ class H5ReservationController extends Controller
'reservation_notice' => $activity->reservation_notice,
'start_at' => optional($activity->start_at)?->toIso8601String(),
'end_at' => optional($activity->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($activity),
'venue' => $activity->venue,
'reservation_type' => $activity->reservation_type,
],
@ -109,7 +109,7 @@ class H5ReservationController extends Controller
'reservation_notice' => $activity->reservation_notice,
'start_at' => optional($activity->start_at)?->toIso8601String(),
'end_at' => optional($activity->end_at)?->toIso8601String(),
'schedule_status' => Activity::computeScheduleStatusFromBounds($activity->start_at, $activity->end_at),
'schedule_status' => Activity::computeScheduleStatusForDisplay($activity),
'venue' => $activity->venue,
],
'days' => $days,

@ -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,

Loading…
Cancel
Save