*/ public function ticket(): BelongsTo { return $this->belongsTo(Ticket::class); } /** @return BelongsToMany */ public function validityTimes(): BelongsToMany { return $this->belongsToMany( ValidityTime::class, 'ticket_validity_group_times', 'ticket_validity_group_id', 'validity_time_id', ); } public function isValid(?CarbonInterface $at = null): bool { $at ??= now(); $validityTimes = $this->resolvedValidityTimes(); return $validityTimes->isNotEmpty() && $validityTimes->every( fn (ValidityTime $validityTime): bool => $validityTime->isValid($at) ); } public function isExpired(?CarbonInterface $at = null): bool { $at ??= now(); $expiresAt = $this->effectiveExpiresAt($at); return $expiresAt !== null && $expiresAt->lessThanOrEqualTo($at); } public function effectiveStartsAt(?CarbonInterface $at = null): ?CarbonInterface { $at ??= now(); $anchor = $this->dateAnchor() ?? $at; return $this->resolvedValidityTimes() ->map(fn (ValidityTime $validityTime): ?CarbonInterface => $validityTime->startsAt($anchor)) ->filter() ->sortByDesc(fn (CarbonInterface $startsAt): int => $startsAt->getTimestamp()) ->first(); } public function effectiveExpiresAt(?CarbonInterface $at = null): ?CarbonInterface { $at ??= now(); $anchor = $this->dateAnchor() ?? $at; return $this->resolvedValidityTimes() ->map(function (ValidityTime $validityTime) use ($anchor): ?CarbonInterface { $startsAt = $validityTime->startsAt($anchor); $expiresAt = $validityTime->expiresAt($anchor); if ( $startsAt !== null && $expiresAt !== null && $expiresAt->lessThanOrEqualTo($startsAt) ) { return $expiresAt->addDay(); } return $expiresAt; }) ->filter() ->sortBy(fn (CarbonInterface $expiresAt): int => $expiresAt->getTimestamp()) ->first(); } /** @return EloquentCollection */ public function resolvedValidityTimes(): EloquentCollection { if ($this->relationLoaded('validityTimes')) { return $this->getRelation('validityTimes'); } if (! $this->exists) { return new EloquentCollection; } $validityTimes = $this->validityTimes()->get(); $this->setRelation('validityTimes', $validityTimes); return $validityTimes; } private function dateAnchor(): ?CarbonInterface { return $this->resolvedValidityTimes() ->filter(fn (ValidityTime $validityTime): bool => $validityTime->type === ValidityTimeType::FixedWindow) ->map(fn (ValidityTime $validityTime): ?CarbonInterface => $validityTime->fixed_starts_at) ->filter() ->sortByDesc(fn (CarbonInterface $startsAt): int => $startsAt->getTimestamp()) ->first(); } }