feat(ticket): implement validity time management for tickets and catalog items

- Added ValidityTime model and migration to manage ticket validity periods.
- Updated TicketGeneratorService to resolve and assign validity times to tickets.
- Refactored ticket generation logic to remove legacy date fields and use validity time.
- Introduced timezone support for tenants to handle service dates correctly.
- Updated migrations to remove deprecated columns and add foreign keys for validity times.
- Modified seeders and tests to accommodate new validity time structure.
- Enhanced tests to validate ticket generation and validity time behavior.
This commit is contained in:
2026-08-06 15:52:19 -03:00
parent 7f01adab73
commit 448ffb4102
30 changed files with 556 additions and 381 deletions

View File

@@ -32,14 +32,11 @@ class CatalogItemControllerTest extends TestCase
'slug' => 'shirt',
'nombre' => 'Shirt',
'precio' => 100,
'minimum_use_date' => '2026-08-01 09:00:00',
'maximum_use_date' => '2026-08-31 18:00:00',
'attribute_codes' => [$attribute->codigo],
'images' => [$image, $image],
'variants' => [
[
'real_stock' => 5,
'maximum_use_date' => '2026-08-15 18:00:00',
'values' => ['size' => 'M'],
'images' => [$image],
],
@@ -51,20 +48,7 @@ class CatalogItemControllerTest extends TestCase
->assertJsonPath('data.nombre', 'Shirt')
->assertJsonCount(2, 'data.images')
->assertJsonCount(1, 'data.variants')
->assertJsonCount(1, 'data.variants.0.images')
->assertJsonPath('data.variants.0.minimum_use_date', null)
->assertJsonPath(
'data.variants.0.maximum_use_date',
fn (string $value): bool => str_starts_with($value, '2026-08-15T18:00:00'),
)
->assertJsonPath(
'data.variants.0.effective_minimum_use_date',
fn (string $value): bool => str_starts_with($value, '2026-08-01T09:00:00'),
)
->assertJsonPath(
'data.variants.0.effective_maximum_use_date',
fn (string $value): bool => str_starts_with($value, '2026-08-15T18:00:00'),
);
->assertJsonCount(1, 'data.variants.0.images');
$item = CatalogItem::query()->where('slug', 'shirt')->firstOrFail();
$variant = $item->variants()->firstOrFail();

View File

@@ -44,8 +44,7 @@ class CatalogSchemaTest extends TestCase
'precio',
'inventory_policy',
'has_tickets',
'maximum_use_date',
'minimum_use_date',
'validity_time_id',
], Schema::getColumnListing('catalog_items'));
}
@@ -183,8 +182,6 @@ class CatalogSchemaTest extends TestCase
{
$this->assertTrue(Schema::hasColumns('variantes', [
'event_date_id',
'minimum_use_date',
'maximum_use_date',
]));
}

View File

@@ -13,7 +13,6 @@ use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Shared\Enums\FieldType;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
@@ -94,73 +93,6 @@ class CatalogServiceTest extends TestCase
}
}
public function test_variant_use_dates_override_or_inherit_catalog_item_dates(): void
{
$attribute = $this->createAttribute('day');
$itemMinimum = Carbon::parse('2026-08-01 09:00:00');
$itemMaximum = Carbon::parse('2026-08-31 18:00:00');
$variantMaximum = Carbon::parse('2026-08-15 18:00:00');
$item = $this->service->create([
'tenant_code' => $this->tenant->codigo,
'slug' => 'dated-variants',
'nombre' => 'Dated variants',
'precio' => 100,
'minimum_use_date' => $itemMinimum,
'maximum_use_date' => $itemMaximum,
'attribute_codes' => [$attribute->codigo],
'variants' => [
[
'real_stock' => 5,
'maximum_use_date' => $variantMaximum,
'values' => [$attribute->codigo => 'Saturday'],
],
],
]);
$variant = $item->variants->firstOrFail();
$this->assertNull($variant->minimum_use_date);
$this->assertTrue($variant->maximum_use_date->equalTo($variantMaximum));
$this->assertTrue($variant->getMinimumUseDate()->equalTo($itemMinimum));
$this->assertTrue($variant->getMaximumUseDate()->equalTo($variantMaximum));
}
public function test_it_rejects_an_invalid_effective_variant_use_date_range(): void
{
$attribute = $this->createAttribute('day');
try {
$this->service->create([
'tenant_code' => $this->tenant->codigo,
'slug' => 'invalid-dated-variant',
'nombre' => 'Invalid dated variant',
'precio' => 100,
'minimum_use_date' => '2026-08-10 09:00:00',
'maximum_use_date' => '2026-08-31 18:00:00',
'attribute_codes' => [$attribute->codigo],
'variants' => [
[
'real_stock' => 5,
'maximum_use_date' => '2026-08-09 18:00:00',
'values' => [$attribute->codigo => 'Saturday'],
],
],
]);
$this->fail('A validation exception was not thrown.');
} catch (ValidationException $exception) {
$this->assertArrayHasKey(
'variants.0.maximum_use_date',
$exception->errors(),
);
}
$this->assertDatabaseMissing('catalog_items', [
'slug' => 'invalid-dated-variant',
]);
}
public function test_it_rejects_direct_inventory_together_with_variants(): void
{
$attribute = $this->createAttribute('size');

View File

@@ -259,7 +259,7 @@ class TelepagosWebhookTest extends TestCase
]);
}
public function test_webhook_confirms_a_purchase_when_its_ticket_use_date_has_ended(): void
public function test_webhook_confirms_a_purchase_with_tickets_enabled(): void
{
$tenant = $this->createTenant('expired-ticket', 'Expired Ticket', 'expired-ticket.com.ar');
$this->configureTelepagosIntegration($tenant);
@@ -267,7 +267,6 @@ class TelepagosWebhookTest extends TestCase
$variant = $this->createVariantForTenant('expired-ticket', 1, '50.00');
$variant->catalogItem->update([
'has_tickets' => true,
'maximum_use_date' => now()->subMinute(),
]);
$purchase = $this->createPendingTransferPurchase(
$tenant,

View File

@@ -95,22 +95,8 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
->map(fn ($variant) => $variant->eventDate->date->format('Y-m-d'))
->all()
);
$this->assertSame(
[
['2026-10-09 00:00:00', '2026-10-09 23:59:59'],
['2026-10-10 00:00:00', '2026-10-10 23:59:59'],
['2026-10-11 00:00:00', '2026-10-11 23:59:59'],
['2026-10-12 00:00:00', '2026-10-12 23:59:59'],
],
$generalAdmission->variants
->sortBy(fn ($variant) => $variant->eventDate->date)
->map(fn ($variant): array => [
$variant->getMinimumUseDate()->format('Y-m-d H:i:s'),
$variant->getMaximumUseDate()->format('Y-m-d H:i:s'),
])
->values()
->all()
);
$this->assertSame('2026-10-09 00:00:00', $generalAdmission->validityTime->fixed_starts_at->format('Y-m-d H:i:s'));
$this->assertSame('2026-10-12 23:59:59', $generalAdmission->validityTime->fixed_expires_at->format('Y-m-d H:i:s'));
$standardItems = CatalogItem::query()
->where('tenant_code', $tenant->codigo)
@@ -122,11 +108,11 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
$this->assertSame($event->id, $standardItem->event_id);
$this->assertSame(
'2026-10-09 00:00:00',
$standardItem->minimum_use_date->format('Y-m-d H:i:s'),
$standardItem->validityTime->fixed_starts_at->format('Y-m-d H:i:s'),
);
$this->assertSame(
'2026-10-12 23:59:59',
$standardItem->maximum_use_date->format('Y-m-d H:i:s'),
$standardItem->validityTime->fixed_expires_at->format('Y-m-d H:i:s'),
);
}

View File

@@ -51,11 +51,7 @@ class TicketGeneratorServiceTest extends TestCase
public function test_it_generates_tickets_from_a_standard_catalog_item(): void
{
$item = $this->createTicketableItem(
'single-day',
now()->subHour(),
now()->addDay(),
);
$item = $this->createTicketableItem('single-day');
$tickets = $this->service->generate($item, $this->user, 2);
@@ -68,8 +64,8 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertSame($item->descripcion, $ticket->description);
$this->assertSame($item->id, $ticket->source_catalog_item_id);
$this->assertNull($ticket->source_variant_id);
$this->assertTrue($ticket->starts_at->equalTo($item->minimum_use_date));
$this->assertTrue($ticket->expires_at->equalTo($item->maximum_use_date));
$this->assertNull($ticket->validity_time_id);
$this->assertNull($ticket->service_date);
}
}
@@ -84,41 +80,10 @@ class TicketGeneratorServiceTest extends TestCase
$this->service->generate($item->fresh(), $this->user);
}
public function test_it_generates_a_ticket_when_its_maximum_use_date_was_reached(): void
{
$item = $this->createTicketableItem('expired', maximumUseDate: now());
$tickets = $this->service->generate($item, $this->user);
$this->assertCount(1, $tickets);
$this->assertTrue($tickets->first()->expires_at->equalTo($item->maximum_use_date));
}
public function test_variant_dates_override_and_inherit_catalog_item_dates(): void
{
$item = $this->createTicketableItem(
'variant-dates',
now()->subDay(),
now()->addMonth(),
);
$inventory = Inventory::query()->create();
$variant = $item->variants()->create([
'inventory_id' => $inventory->id,
'maximum_use_date' => now()->addWeek(),
]);
$ticket = $this->service
->generate($item, $this->user, sourceVariantId: $variant->id)
->firstOrFail();
$this->assertTrue($ticket->starts_at->equalTo($item->minimum_use_date));
$this->assertTrue($ticket->expires_at->equalTo($variant->maximum_use_date));
}
public function test_it_generates_tickets_for_every_bundle_component_and_quantity(): void
{
$first = $this->createTicketableItem('first', maximumUseDate: now()->addDay());
$second = $this->createTicketableItem('second', maximumUseDate: now()->addDays(2));
$first = $this->createTicketableItem('first');
$second = $this->createTicketableItem('second');
$bundle = $this->createBundle('bundle');
$bundle->bundleComponents()->createMany([
['component_catalog_item_id' => $first->id, 'quantity' => 2],
@@ -134,18 +99,12 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertCount(2, $tickets->where('name', $second->nombre));
}
public function test_bundle_component_uses_its_variant_dates(): void
public function test_bundle_component_preserves_its_source_variant(): void
{
$component = $this->createTicketableItem(
'variant-component',
now()->subDay(),
now()->addMonth(),
);
$component = $this->createTicketableItem('variant-component');
$inventory = Inventory::query()->create();
$variant = $component->variants()->create([
'inventory_id' => $inventory->id,
'minimum_use_date' => now()->addDay(),
'maximum_use_date' => now()->addWeek(),
]);
$bundle = $this->createBundle('variant-bundle');
$bundle->bundleComponents()->create([
@@ -160,8 +119,6 @@ class TicketGeneratorServiceTest extends TestCase
$this->assertSame($component->id, $ticket->source_catalog_item_id);
$this->assertSame($variant->id, $ticket->source_variant_id);
$this->assertTrue($ticket->starts_at->equalTo($variant->minimum_use_date));
$this->assertTrue($ticket->expires_at->equalTo($variant->maximum_use_date));
}
public function test_bundle_generation_is_rolled_back_when_a_component_is_invalid(): void
@@ -244,9 +201,9 @@ class TicketGeneratorServiceTest extends TestCase
->assertJsonPath('data.has_generated_tickets', false);
}
public function test_paid_status_is_confirmed_when_ticket_maximum_use_date_was_reached(): void
public function test_paid_status_is_confirmed_when_ticket_is_generated(): void
{
$item = $this->createTicketableItem('expired-paid-ticket', maximumUseDate: now());
$item = $this->createTicketableItem('paid-ticket');
$purchase = $this->createPurchase($item, 1);
$purchase->markAsPaid();
@@ -258,11 +215,8 @@ class TicketGeneratorServiceTest extends TestCase
]);
}
private function createTicketableItem(
string $slug,
mixed $minimumUseDate = null,
mixed $maximumUseDate = null,
): CatalogItem {
private function createTicketableItem(string $slug): CatalogItem
{
return CatalogItem::query()->create([
'tenant_code' => $this->tenant->codigo,
'slug' => $slug,
@@ -270,8 +224,6 @@ class TicketGeneratorServiceTest extends TestCase
'descripcion' => "Descripción de {$slug}",
'precio' => 10,
'has_tickets' => true,
'minimum_use_date' => $minimumUseDate,
'maximum_use_date' => $maximumUseDate,
]);
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Tests\Feature\Ticket;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class TicketValiditySchemaTest extends TestCase
{
use RefreshDatabase;
public function test_ticket_validity_time_schema_is_available(): void
{
$this->assertEqualsCanonicalizing([
'id',
'type',
'start_time',
'end_time',
'fixed_starts_at',
'fixed_expires_at',
'active',
'created_at',
'updated_at',
], Schema::getColumnListing('validity_times'));
$this->assertTrue(Schema::hasColumns('tickets', [
'validity_time_id',
'service_date',
]));
$this->assertFalse(Schema::hasColumn('tickets', 'starts_at'));
$this->assertFalse(Schema::hasColumn('tickets', 'expires_at'));
$this->assertTrue(Schema::hasColumn('catalog_items', 'validity_time_id'));
$this->assertFalse(Schema::hasColumn('catalog_items', 'minimum_use_date'));
$this->assertFalse(Schema::hasColumn('catalog_items', 'maximum_use_date'));
$this->assertTrue(Schema::hasColumn('attribute_options', 'validity_time_id'));
$this->assertFalse(Schema::hasColumn('variantes', 'minimum_use_date'));
$this->assertFalse(Schema::hasColumn('variantes', 'maximum_use_date'));
$this->assertTrue(Schema::hasColumn('tenants', 'timezone'));
}
}

View File

@@ -27,7 +27,6 @@ use App\Domains\Shared\Enums\FieldType;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Models\Ticket;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Carbon;
use Tests\TestCase;
class CatalogModelsTest extends TestCase
@@ -160,24 +159,6 @@ class CatalogModelsTest extends TestCase
$this->assertSame('catalog_items_attachments', $variant->attachments()->getTable());
}
public function test_variant_use_dates_override_or_inherit_catalog_item_dates(): void
{
$item = new CatalogItem;
$item->minimum_use_date = Carbon::parse('2026-08-01 09:00:00');
$item->maximum_use_date = Carbon::parse('2026-08-31 18:00:00');
$variant = new Variant;
$variant->maximum_use_date = Carbon::parse('2026-08-15 18:00:00');
$variant->setRelation('catalogItem', $item);
$this->assertTrue(
$variant->getMinimumUseDate()->equalTo($item->minimum_use_date),
);
$this->assertTrue(
$variant->getMaximumUseDate()->equalTo($variant->maximum_use_date),
);
}
public function test_event_date_identifies_a_variant_without_catalog_attributes(): void
{
$item = new CatalogItem;
@@ -189,15 +170,12 @@ class CatalogModelsTest extends TestCase
$eventDate->time_end = '18:00:00';
$variant = new Variant;
$variant->minimum_use_date = Carbon::parse('2026-10-09 08:00:00');
$variant->maximum_use_date = Carbon::parse('2026-10-09 20:00:00');
$variant->setRelation('catalogItem', $item);
$variant->setRelation('eventDate', $eventDate);
$variant->setRelation('definitions', new EloquentCollection);
$this->assertSame('Entrada General', $variant->getName());
$this->assertSame('2026-10-09 09:00:00', $variant->getMinimumUseDate()->format('Y-m-d H:i:s'));
$this->assertSame('2026-10-09 18:00:00', $variant->getMaximumUseDate()->format('Y-m-d H:i:s'));
$this->assertSame('2026-10-09', $variant->eventDate->date->format('Y-m-d'));
}
public function test_inventory_maps_stock_without_a_polymorphic_owner(): void

View File

@@ -5,9 +5,10 @@ namespace Tests\Unit\Ticket;
use App\Domains\Auth\Models\User;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Variant;
use App\Domains\Event\Models\EventDate;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticket\Enums\ValidityTimeType;
use App\Domains\Ticket\Models\Ticket;
use App\Domains\Ticket\Models\ValidityTime;
use Illuminate\Support\Carbon;
use Tests\TestCase;
@@ -20,14 +21,14 @@ class TicketTest extends TestCase
parent::tearDown();
}
public function test_it_maps_its_dates_and_relations(): void
public function test_it_maps_its_fields_and_relations(): void
{
$ticket = new Ticket;
$ticket->setRawAttributes([
'source_catalog_item_id' => '20',
'source_variant_id' => '30',
'starts_at' => '2026-07-21 10:00:00',
'expires_at' => '2026-07-22 10:00:00',
'validity_time_id' => '40',
'service_date' => '2026-07-21',
'used_at' => null,
'scanner_user_id' => '15',
'user_id' => '10',
@@ -37,8 +38,8 @@ class TicketTest extends TestCase
$this->assertFalse($ticket->usesTimestamps());
$this->assertSame(20, $ticket->source_catalog_item_id);
$this->assertSame(30, $ticket->source_variant_id);
$this->assertInstanceOf(Carbon::class, $ticket->starts_at);
$this->assertInstanceOf(Carbon::class, $ticket->expires_at);
$this->assertSame(40, $ticket->validity_time_id);
$this->assertInstanceOf(Carbon::class, $ticket->service_date);
$this->assertNull($ticket->used_at);
$this->assertSame(15, $ticket->scanner_user_id);
$this->assertSame(10, $ticket->user_id);
@@ -47,105 +48,74 @@ 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(ValidityTime::class, $ticket->validityTime()->getRelated());
}
public function test_unused_ticket_without_date_restrictions_is_valid(): void
public function test_unused_ticket_without_validity_time_is_valid(): void
{
$this->assertTrue((new Ticket)->isValid());
}
public function test_ticket_is_invalid_before_its_start_date(): void
public function test_fixed_window_controls_ticket_validity(): void
{
Carbon::setTestNow('2026-07-21 10:00:00');
$ticket = new Ticket(['starts_at' => now()->addSecond()]);
$ticket = $this->ticketWithValidityTime(new ValidityTime([
'type' => ValidityTimeType::FixedWindow,
'fixed_starts_at' => now()->subHour(),
'fixed_expires_at' => now()->addHour(),
]));
$this->assertFalse($ticket->isValid());
$this->assertTrue($ticket->isValid());
$this->assertFalse($ticket->is_expired);
}
public function test_ticket_is_valid_when_its_start_date_is_reached(): void
public function test_service_date_window_is_resolved_in_tenant_timezone(): void
{
Carbon::setTestNow('2026-07-21 10:00:00');
$ticket = new Ticket(['starts_at' => now()]);
Carbon::setTestNow('2026-07-21 14:00:00');
$ticket = new Ticket(['service_date' => '2026-07-21']);
$ticket->setRelation('validityTime', new ValidityTime([
'type' => ValidityTimeType::ServiceDateWindow,
'start_time' => '10:00:00',
'end_time' => '12:00:00',
]));
$ticket->setRelation('tenant', new Tenant(['timezone' => 'America/Argentina/Buenos_Aires']));
$this->assertSame('2026-07-21 13:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s'));
$this->assertSame('2026-07-21 15:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s'));
$this->assertTrue($ticket->isValid());
}
public function test_ticket_is_invalid_when_it_expires(): void
public function test_ticket_is_invalid_when_validity_time_expires(): void
{
Carbon::setTestNow('2026-07-21 10:00:00');
$ticket = new Ticket(['expires_at' => now()]);
$ticket = $this->ticketWithValidityTime(new ValidityTime([
'type' => ValidityTimeType::FixedWindow,
'fixed_expires_at' => now(),
]));
$this->assertFalse($ticket->isValid());
}
public function test_used_ticket_is_invalid(): void
{
$ticket = new Ticket(['used_at' => now()->subSecond()]);
$this->assertFalse($ticket->isValid());
}
public function test_it_appends_computed_status_fields(): void
{
Carbon::setTestNow('2026-07-21 10:00:00');
$ticket = new Ticket([
'starts_at' => now()->subHour(),
'expires_at' => now()->addHour(),
]);
$attributes = $ticket->toArray();
$this->assertTrue($attributes['is_valid']);
$this->assertFalse($attributes['is_expired']);
$this->assertFalse($attributes['is_used']);
}
public function test_unused_ticket_is_expired_when_its_expiration_date_is_reached(): void
{
Carbon::setTestNow('2026-07-21 10:00:00');
$ticket = new Ticket(['expires_at' => now()]);
$this->assertFalse($ticket->is_valid);
$this->assertTrue($ticket->is_expired);
$this->assertFalse($ticket->is_used);
}
public function test_used_ticket_is_not_reported_as_expired(): void
public function test_used_ticket_is_invalid_and_not_reported_as_expired(): void
{
Carbon::setTestNow('2026-07-21 10:00:00');
$ticket = new Ticket([
'expires_at' => now()->subHour(),
'used_at' => now()->subDay(),
]);
$ticket = $this->ticketWithValidityTime(new ValidityTime([
'type' => ValidityTimeType::FixedWindow,
'fixed_expires_at' => now()->subHour(),
]));
$ticket->used_at = now()->subMinute();
$this->assertFalse($ticket->is_valid);
$this->assertFalse($ticket->is_expired);
$this->assertTrue($ticket->is_used);
}
public function test_event_date_has_priority_over_ticket_and_variant_dates(): void
private function ticketWithValidityTime(ValidityTime $validityTime): Ticket
{
Carbon::setTestNow('2026-10-09 19:00:00');
$ticket = new Ticket;
$ticket->setRelation('validityTime', $validityTime);
$ticket->setRelation('tenant', new Tenant(['timezone' => 'UTC']));
$eventDate = new EventDate;
$eventDate->date = '2026-10-09';
$eventDate->time_start = '09:00:00';
$eventDate->time_end = '18:00:00';
$variant = new Variant;
$variant->minimum_use_date = Carbon::parse('2026-10-09 08:00:00');
$variant->maximum_use_date = Carbon::parse('2026-10-09 22:00:00');
$variant->setRelation('eventDate', $eventDate);
$ticket = new Ticket([
'starts_at' => Carbon::parse('2026-10-09 07:00:00'),
'expires_at' => Carbon::parse('2026-10-10 23:59:59'),
]);
$ticket->setRelation('sourceVariant', $variant);
$this->assertSame('2026-10-09 09:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s'));
$this->assertSame('2026-10-09 18:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s'));
$this->assertFalse($ticket->isValid());
$this->assertTrue($ticket->is_expired);
return $ticket;
}
}

View File

@@ -0,0 +1,41 @@
<?php
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\Ticket;
use App\Domains\Ticket\Models\ValidityTime;
use Illuminate\Support\Carbon;
use Tests\TestCase;
class ValidityTimeTest extends TestCase
{
public function test_it_casts_its_type_dates_and_flags(): void
{
$validityTime = new ValidityTime;
$validityTime->setRawAttributes([
'type' => ValidityTimeType::FixedWindow->value,
'fixed_starts_at' => '2026-08-20 10:00:00',
'fixed_expires_at' => '2026-08-21 02:00:00',
'active' => '1',
]);
$this->assertSame(ValidityTimeType::FixedWindow, $validityTime->type);
$this->assertInstanceOf(Carbon::class, $validityTime->fixed_starts_at);
$this->assertInstanceOf(Carbon::class, $validityTime->fixed_expires_at);
$this->assertTrue($validityTime->active);
$this->assertInstanceOf(CatalogItem::class, $validityTime->catalogItems()->getRelated());
$this->assertInstanceOf(AttributeOption::class, $validityTime->attributeOptions()->getRelated());
$this->assertInstanceOf(Ticket::class, $validityTime->tickets()->getRelated());
}
public function test_it_exposes_supported_type_values(): void
{
$this->assertSame([
'service_date_window',
'fixed_window',
], ValidityTimeType::values());
}
}