- Updated tests for Accommodation, Entry, Food, Merchandise, and Sale controllers to use soft deletes for variants and ensure proper inventory counts. - Enhanced ticket generation logic to resolve validity from soft-deleted catalog sources. - Introduced a new TicketValidityResolver service to manage ticket validity based on event dates and variant definitions. - Removed unnecessary database assertions and improved the clarity of validity checks in tests. - Added comprehensive tests for the new TicketValidityResolver service, ensuring correct handling of event dates and multi-select options. - Cleaned up unused code and assertions in existing tests for better maintainability.
78 lines
2.6 KiB
PHP
78 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Ticket\Services;
|
|
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* Resultado completo de resolver la vigencia de un ticket.
|
|
*
|
|
* Cada ResolvedValidityGroup contiene condiciones AND. Entre los grupos se
|
|
* aplica OR, por lo que alcanza con que uno de ellos esté activo.
|
|
*/
|
|
final readonly class ResolvedTicketValidity
|
|
{
|
|
/** @param Collection<int, ResolvedValidityGroup> $groups */
|
|
public function __construct(
|
|
public Collection $groups,
|
|
public bool $isResolvable = true,
|
|
public bool $isUnrestricted = false,
|
|
) {}
|
|
|
|
/** No existe ninguna restricción temporal configurada. */
|
|
public static function unrestricted(): self
|
|
{
|
|
return new self(collect(), isUnrestricted: true);
|
|
}
|
|
|
|
/** La configuración fuente está incompleta o es inconsistente. */
|
|
public static function unresolvable(): self
|
|
{
|
|
return new self(collect(), isResolvable: false);
|
|
}
|
|
|
|
/** Es válido cuando no tiene restricciones o algún grupo OR está activo. */
|
|
public function isValid(?CarbonInterface $at = null): bool
|
|
{
|
|
if (! $this->isResolvable) {
|
|
return false;
|
|
}
|
|
|
|
return $this->isUnrestricted || $this->groups->contains(
|
|
fn (ResolvedValidityGroup $group): bool => $group->isValid($at)
|
|
);
|
|
}
|
|
|
|
/** Sólo está vencido cuando todos los grupos OR ya vencieron. */
|
|
public function isExpired(?CarbonInterface $at = null): bool
|
|
{
|
|
return $this->isResolvable
|
|
&& ! $this->isUnrestricted
|
|
&& $this->groups->isNotEmpty()
|
|
&& $this->groups->every(
|
|
fn (ResolvedValidityGroup $group): bool => $group->isExpired($at)
|
|
);
|
|
}
|
|
|
|
/** Inicio más temprano de todas las alternativas, usado como resumen. */
|
|
public function effectiveStartsAt(?CarbonInterface $at = null): ?CarbonInterface
|
|
{
|
|
return $this->groups
|
|
->map(fn (ResolvedValidityGroup $group): ?CarbonInterface => $group->effectiveStartsAt($at))
|
|
->filter()
|
|
->sortBy(fn (CarbonInterface $startsAt): int => $startsAt->getTimestamp())
|
|
->first();
|
|
}
|
|
|
|
/** Vencimiento más tardío de todas las alternativas, usado como resumen. */
|
|
public function effectiveExpiresAt(?CarbonInterface $at = null): ?CarbonInterface
|
|
{
|
|
return $this->groups
|
|
->map(fn (ResolvedValidityGroup $group): ?CarbonInterface => $group->effectiveExpiresAt($at))
|
|
->filter()
|
|
->sortByDesc(fn (CarbonInterface $expiresAt): int => $expiresAt->getTimestamp())
|
|
->first();
|
|
}
|
|
}
|