Refactor ticket validity handling and improve tests

- 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.
This commit is contained in:
2026-08-14 09:00:51 -03:00
parent bfdaf1c38b
commit 573d4fe5e6
59 changed files with 1185 additions and 679 deletions

View File

@@ -8,9 +8,10 @@ use App\Domains\Catalog\Models\Variant;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Models\Ticket;
use App\Domains\Ticket\Models\TicketValidityGroup;
use App\Domains\Ticket\Models\ValidityTime;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use App\Domains\Ticket\Services\ResolvedTicketValidity;
use App\Domains\Ticket\Services\ResolvedValidityGroup;
use App\Domains\Ticket\Services\TicketValidityResolver;
use Illuminate\Support\Carbon;
use Tests\TestCase;
@@ -46,8 +47,6 @@ class TicketTest extends TestCase
$this->assertInstanceOf(User::class, $ticket->scannerUser()->getRelated());
$this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated());
$this->assertInstanceOf(Variant::class, $ticket->sourceVariant()->getRelated());
$this->assertInstanceOf(TicketValidityGroup::class, $ticket->validityGroups()->getRelated());
$this->assertInstanceOf(ValidityTime::class, (new TicketValidityGroup)->validityTimes()->getRelated());
}
public function test_unused_ticket_without_validity_time_is_valid(): void
@@ -190,13 +189,23 @@ class TicketTest extends TestCase
private function ticketWithValidityGroups(array $validityGroups): Ticket
{
$ticket = new Ticket;
$groups = collect($validityGroups)->map(function (array $validityTimes): TicketValidityGroup {
$group = new TicketValidityGroup;
$group->setRelation('validityTimes', new EloquentCollection($validityTimes));
$resolved = new ResolvedTicketValidity(
collect($validityGroups)->map(
fn (array $validityTimes): ResolvedValidityGroup => new ResolvedValidityGroup(collect($validityTimes))
)
);
$this->app->instance(
TicketValidityResolver::class,
new class($resolved) extends TicketValidityResolver
{
public function __construct(private readonly ResolvedTicketValidity $resolved) {}
return $group;
});
$ticket->setRelation('validityGroups', new EloquentCollection($groups->all()));
public function resolveTicket(Ticket $ticket): ResolvedTicketValidity
{
return $this->resolved;
}
}
);
return $ticket;
}

View File

@@ -0,0 +1,209 @@
<?php
namespace Tests\Unit\Ticket;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\AttributeOption;
use App\Domains\Catalog\Models\ItemAttribute;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Catalog\Models\VariantDefinition;
use App\Domains\Event\Models\EventDate;
use App\Domains\Shared\Enums\FieldType;
use App\Domains\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Models\ValidityTime;
use App\Domains\Ticket\Services\TicketValidityResolver;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Carbon;
use Tests\TestCase;
class TicketValidityResolverTest extends TestCase
{
private TicketValidityResolver $resolver;
protected function setUp(): void
{
parent::setUp();
$this->resolver = new TicketValidityResolver;
}
public function test_event_dates_and_multi_select_options_expand_to_or_groups(): void
{
$dates = new EloquentCollection([
$this->eventDate('2026-08-20'),
$this->eventDate('2026-08-21'),
]);
[$itemAttribute, $definitions] = $this->attributeDimension(
allowMultiSelect: true,
options: [
'Desayuno' => $this->timeWindow('07:00:00', '12:00:00'),
'Cena' => $this->timeWindow('20:00:00', '24:00:00'),
],
);
$variant = $this->variant($dates, $definitions);
$validity = $this->resolver->resolveVariant($variant);
$this->assertTrue($validity->isResolvable);
$this->assertFalse($validity->isUnrestricted);
$this->assertCount(4, $validity->groups);
$this->assertTrue($validity->groups->every(
fn ($group): bool => $group->validityTimes->count() === 2
));
$this->assertSame($itemAttribute, $definitions->first()->itemAttribute);
}
public function test_different_temporal_attributes_are_anded_in_the_same_group(): void
{
[, $scheduleDefinitions] = $this->attributeDimension(
allowMultiSelect: false,
options: ['Almuerzo' => $this->timeWindow('12:00:00', '15:00:00')],
);
[, $admissionDefinitions] = $this->attributeDimension(
allowMultiSelect: false,
options: ['Ingreso' => $this->timeWindow('11:00:00', '14:00:00')],
);
$variant = $this->variant(
new EloquentCollection([$this->eventDate('2026-08-20')]),
new EloquentCollection([
...$scheduleDefinitions,
...$admissionDefinitions,
]),
);
$validity = $this->resolver->resolveVariant($variant);
$this->assertCount(1, $validity->groups);
$this->assertCount(3, $validity->groups->sole()->validityTimes);
$this->assertSame(
'2026-08-20 12:00:00',
$validity->effectiveStartsAt(Carbon::parse('2026-08-20 13:00:00'))->format('Y-m-d H:i:s'),
);
$this->assertSame(
'2026-08-20 14:00:00',
$validity->effectiveExpiresAt(Carbon::parse('2026-08-20 13:00:00'))->format('Y-m-d H:i:s'),
);
$this->assertTrue($validity->isValid(Carbon::parse('2026-08-20 13:00:00')));
$this->assertFalse($validity->isValid(Carbon::parse('2026-08-20 14:00:00')));
}
public function test_variant_without_temporal_dimensions_is_unrestricted(): void
{
$validity = $this->resolver->resolveVariant(
$this->variant(new EloquentCollection, new EloquentCollection)
);
$this->assertTrue($validity->isResolvable);
$this->assertTrue($validity->isUnrestricted);
$this->assertTrue($validity->isValid());
$this->assertFalse($validity->isExpired());
}
public function test_non_option_attributes_do_not_affect_validity(): void
{
$attribute = new Attribute(['type' => FieldType::String]);
$attribute->setRelation('options', new EloquentCollection);
$itemAttribute = new ItemAttribute(['allow_multi_select' => false]);
$itemAttribute->setRelation('attribute', $attribute);
$definition = new VariantDefinition(['value' => 'Comedor principal']);
$definition->setAttribute('item_attribute_id', 123);
$definition->setRelation('itemAttribute', $itemAttribute);
$validity = $this->resolver->resolveVariant(
$this->variant(new EloquentCollection, new EloquentCollection([$definition]))
);
$this->assertTrue($validity->isResolvable);
$this->assertTrue($validity->isUnrestricted);
}
public function test_multiple_values_for_a_single_select_attribute_are_unresolvable(): void
{
[, $definitions] = $this->attributeDimension(
allowMultiSelect: false,
options: [
'Desayuno' => $this->timeWindow('07:00:00', '12:00:00'),
'Cena' => $this->timeWindow('20:00:00', '24:00:00'),
],
);
$validity = $this->resolver->resolveVariant(
$this->variant(new EloquentCollection, $definitions)
);
$this->assertFalse($validity->isResolvable);
$this->assertFalse($validity->isValid());
}
private function eventDate(string $date): EventDate
{
$validityTime = new ValidityTime([
'type' => ValidityTimeType::FixedWindow,
'fixed_starts_at' => "{$date} 00:00:00",
'fixed_expires_at' => "{$date} 23:59:59",
]);
$eventDate = new EventDate([
'date' => $date,
'time_start' => '00:00:00',
'time_end' => '23:59:59',
]);
$eventDate->setRelation('validityTime', $validityTime);
return $eventDate;
}
private function timeWindow(string $start, string $end): ValidityTime
{
return new ValidityTime([
'type' => ValidityTimeType::TimeWindow,
'start_time' => $start,
'end_time' => $end,
]);
}
/**
* @param array<string, ValidityTime|null> $options
* @return array{ItemAttribute, EloquentCollection<int, VariantDefinition>}
*/
private function attributeDimension(bool $allowMultiSelect, array $options): array
{
$attribute = new Attribute([
'type' => FieldType::Select,
]);
$attributeOptions = collect($options)->map(
function (?ValidityTime $validityTime, string $value): AttributeOption {
$option = new AttributeOption(['value' => $value, 'label' => $value]);
$option->setRelation('validityTime', $validityTime);
return $option;
}
);
$attribute->setRelation('options', new EloquentCollection($attributeOptions));
$itemAttribute = new ItemAttribute(['allow_multi_select' => $allowMultiSelect]);
$itemAttribute->setRelation('attribute', $attribute);
$definitions = $attributeOptions->map(function (AttributeOption $option) use ($itemAttribute): VariantDefinition {
$definition = new VariantDefinition(['value' => $option->value]);
$definition->setAttribute('item_attribute_id', spl_object_id($itemAttribute));
$definition->setRelation('itemAttribute', $itemAttribute);
return $definition;
});
return [$itemAttribute, new EloquentCollection($definitions)];
}
/**
* @param EloquentCollection<int, EventDate> $eventDates
* @param EloquentCollection<int, VariantDefinition> $definitions
*/
private function variant(EloquentCollection $eventDates, EloquentCollection $definitions): Variant
{
$variant = new Variant;
$variant->setRelation('eventDates', $eventDates);
$variant->setRelation('eventDate', null);
$variant->setRelation('definitions', $definitions);
return $variant;
}
}

View File

@@ -3,9 +3,7 @@
namespace Tests\Unit\Ticket;
use App\Domains\Catalog\Models\AttributeOption;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Models\TicketValidityGroup;
use App\Domains\Ticket\Models\ValidityTime;
use Illuminate\Support\Carbon;
use Tests\TestCase;
@@ -24,12 +22,7 @@ class ValidityTimeTest extends TestCase
$this->assertSame(ValidityTimeType::FixedWindow, $validityTime->type);
$this->assertInstanceOf(Carbon::class, $validityTime->fixed_starts_at);
$this->assertInstanceOf(Carbon::class, $validityTime->fixed_expires_at);
$this->assertInstanceOf(CatalogItem::class, $validityTime->catalogItems()->getRelated());
$this->assertInstanceOf(AttributeOption::class, $validityTime->attributeOptions()->getRelated());
$this->assertInstanceOf(
TicketValidityGroup::class,
$validityTime->ticketValidityGroups()->getRelated(),
);
}
public function test_it_exposes_supported_type_values(): void