feat: Add event management to tenant and catalog
- Introduced Event and EventDate models with relationships to Tenant and CatalogItem. - Added active_event_id to Tenant model to track the currently active event. - Updated TenantResource to include active event details in the response. - Enhanced Ticket model to link to CatalogItem and Variant, allowing for event-specific ticketing. - Implemented migrations to create events and link them to catalog items and variants. - Updated seeders to populate events and their associated dates for the Fiesta Futbol Infantil tenant. - Modified Ticket generation logic to respect event dates over standard ticket dates. - Added tests for event and ticket functionalities, ensuring proper relationships and date handling.
This commit is contained in:
@@ -32,6 +32,8 @@ class CatalogSchemaTest extends TestCase
|
||||
$this->assertEqualsCanonicalizing([
|
||||
'id',
|
||||
'tenant_code',
|
||||
'event_id',
|
||||
'event_product_type',
|
||||
'category_id',
|
||||
'brand_id',
|
||||
'inventory_id',
|
||||
@@ -178,8 +180,27 @@ class CatalogSchemaTest extends TestCase
|
||||
public function test_variants_can_override_catalog_item_use_dates(): void
|
||||
{
|
||||
$this->assertTrue(Schema::hasColumns('variantes', [
|
||||
'event_date_id',
|
||||
'minimum_use_date',
|
||||
'maximum_use_date',
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_events_and_event_dates_are_linked_to_the_catalog(): void
|
||||
{
|
||||
$this->assertEqualsCanonicalizing([
|
||||
'id',
|
||||
'tenant_code',
|
||||
'name',
|
||||
'address',
|
||||
], Schema::getColumnListing('events'));
|
||||
$this->assertEqualsCanonicalizing([
|
||||
'id',
|
||||
'event_id',
|
||||
'date',
|
||||
'time_start',
|
||||
'time_end',
|
||||
], Schema::getColumnListing('event_dates'));
|
||||
$this->assertTrue(Schema::hasColumn('tenants', 'active_event_id'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Tests\Feature\Seeders;
|
||||
use App\Domains\Attachable\Enums\AttachmentType;
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Catalog\Enums\CatalogItemType;
|
||||
use App\Domains\Catalog\Enums\EventProductType;
|
||||
use App\Domains\Catalog\Enums\GroupLayout;
|
||||
use App\Domains\Catalog\Enums\ProductLayout;
|
||||
use App\Domains\Catalog\Models\Attribute;
|
||||
@@ -12,6 +13,8 @@ use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Catalog\Models\FeaturedGroup;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Event\Models\Event;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Database\Seeders\AttributeSeeder;
|
||||
use Database\Seeders\FiestaFutbolInfantilProductSeeder;
|
||||
@@ -62,24 +65,34 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
|
||||
|
||||
$this->assertFalse(Attribute::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->whereIn('codigo', ['color', 'talle', 'talle_numerico'])
|
||||
->exists());
|
||||
$this->assertSame(
|
||||
['fecha'],
|
||||
Attribute::query()->where('tenant_codigo', $tenant->codigo)->pluck('codigo')->all()
|
||||
);
|
||||
|
||||
$event = Event::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->with('dates')
|
||||
->sole();
|
||||
|
||||
$this->assertSame($event->id, $tenant->fresh()->active_event_id);
|
||||
$this->assertCount(4, $event->dates);
|
||||
$this->assertSame(1, Event::query()->where('tenant_code', $tenant->codigo)->count());
|
||||
$this->assertSame(4, EventDate::query()->where('event_id', $event->id)->count());
|
||||
|
||||
$generalAdmission = CatalogItem::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->where('slug', 'entrada-general')
|
||||
->with('variants.definitions')
|
||||
->with('variants.eventDate', 'itemAttributes')
|
||||
->sole();
|
||||
|
||||
$this->assertNull($generalAdmission->inventory_id);
|
||||
$this->assertTrue($generalAdmission->event->is($event));
|
||||
$this->assertSame(EventProductType::Entry, $generalAdmission->event_product_type);
|
||||
$this->assertCount(0, $generalAdmission->itemAttributes);
|
||||
$this->assertCount(4, $generalAdmission->variants);
|
||||
$this->assertEqualsCanonicalizing(
|
||||
['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12'],
|
||||
$generalAdmission->variants->map(fn ($variant) => $variant->definitions->sole()->value)->all()
|
||||
$generalAdmission->variants
|
||||
->map(fn ($variant) => $variant->eventDate->date->format('Y-m-d'))
|
||||
->all()
|
||||
);
|
||||
$this->assertSame(
|
||||
[
|
||||
@@ -89,10 +102,10 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
|
||||
['2026-10-12 00:00:00', '2026-10-12 23:59:59'],
|
||||
],
|
||||
$generalAdmission->variants
|
||||
->sortBy(fn ($variant) => $variant->definitions->sole()->value)
|
||||
->sortBy(fn ($variant) => $variant->eventDate->date)
|
||||
->map(fn ($variant): array => [
|
||||
$variant->minimum_use_date->format('Y-m-d H:i:s'),
|
||||
$variant->maximum_use_date->format('Y-m-d H:i:s'),
|
||||
$variant->getMinimumUseDate()->format('Y-m-d H:i:s'),
|
||||
$variant->getMaximumUseDate()->format('Y-m-d H:i:s'),
|
||||
])
|
||||
->values()
|
||||
->all()
|
||||
@@ -105,6 +118,7 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
|
||||
|
||||
$this->assertCount(7, $standardItems);
|
||||
foreach ($standardItems as $standardItem) {
|
||||
$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'),
|
||||
@@ -118,18 +132,20 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
|
||||
$allDaysItem = CatalogItem::query()
|
||||
->where('tenant_code', $tenant->codigo)
|
||||
->where('nombre', 'Entrada General - Todos los días')
|
||||
->with('bundleComponents.variant.definitions')
|
||||
->with('bundleComponents.variant.eventDate')
|
||||
->sole();
|
||||
|
||||
$this->assertSame(CatalogItemType::Bundle, $allDaysItem->type);
|
||||
$this->assertSame('40000.00', $allDaysItem->precio);
|
||||
$this->assertNull($allDaysItem->inventory_id);
|
||||
$this->assertFalse($allDaysItem->has_tickets);
|
||||
$this->assertSame($event->id, $allDaysItem->event_id);
|
||||
$this->assertSame(EventProductType::Entry, $allDaysItem->event_product_type);
|
||||
$this->assertCount(4, $allDaysItem->bundleComponents);
|
||||
$this->assertEqualsCanonicalizing(
|
||||
['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12'],
|
||||
$allDaysItem->bundleComponents
|
||||
->map(fn ($component) => $component->variant->definitions->sole()->value)
|
||||
->map(fn ($component) => $component->variant->eventDate->date->format('Y-m-d'))
|
||||
->all()
|
||||
);
|
||||
|
||||
@@ -142,6 +158,7 @@ class FiestaFutbolInfantilProductSeederTest extends TestCase
|
||||
$this->assertSame(CatalogItemType::Bundle, $foodCombo->type);
|
||||
$this->assertSame('24000.00', $foodCombo->precio);
|
||||
$this->assertNull($foodCombo->inventory_id);
|
||||
$this->assertSame(EventProductType::Product, $foodCombo->event_product_type);
|
||||
$this->assertSame(
|
||||
[
|
||||
'hamburguesa-papa-frita' => 2,
|
||||
|
||||
@@ -128,7 +128,8 @@ class TicketGeneratorServiceTest extends TestCase
|
||||
$tickets = $this->service->generate($bundle, $this->user, 2);
|
||||
|
||||
$this->assertCount(6, $tickets);
|
||||
$this->assertCount(6, $tickets->where('source_catalog_item_id', $bundle->id));
|
||||
$this->assertCount(4, $tickets->where('source_catalog_item_id', $first->id));
|
||||
$this->assertCount(2, $tickets->where('source_catalog_item_id', $second->id));
|
||||
$this->assertCount(4, $tickets->where('name', $first->nombre));
|
||||
$this->assertCount(2, $tickets->where('name', $second->nombre));
|
||||
}
|
||||
@@ -157,6 +158,8 @@ class TicketGeneratorServiceTest extends TestCase
|
||||
->generate($bundle, $this->user)
|
||||
->firstOrFail();
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Tests\Unit\Catalog;
|
||||
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Catalog\Enums\CatalogItemType;
|
||||
use App\Domains\Catalog\Enums\EventProductType;
|
||||
use App\Domains\Catalog\Enums\GroupLayout;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Catalog\Enums\ProductLayout;
|
||||
@@ -19,8 +20,11 @@ use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\ItemAttribute;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Models\VariantDefinition;
|
||||
use App\Domains\Event\Models\Event;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
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;
|
||||
@@ -61,6 +65,8 @@ class CatalogModelsTest extends TestCase
|
||||
'category_id' => '10',
|
||||
'brand_id' => '20',
|
||||
'inventory_id' => '30',
|
||||
'event_id' => '40',
|
||||
'event_product_type' => EventProductType::Entry->value,
|
||||
'type' => CatalogItemType::Standard->value,
|
||||
'precio' => '12.50',
|
||||
'inventory_policy' => InventoryPolicy::Tracked->value,
|
||||
@@ -72,17 +78,21 @@ class CatalogModelsTest extends TestCase
|
||||
$this->assertSame(10, $item->category_id);
|
||||
$this->assertSame(20, $item->brand_id);
|
||||
$this->assertSame(30, $item->inventory_id);
|
||||
$this->assertSame(40, $item->event_id);
|
||||
$this->assertSame(EventProductType::Entry, $item->event_product_type);
|
||||
$this->assertSame(CatalogItemType::Standard, $item->type);
|
||||
$this->assertSame('12.50', $item->precio);
|
||||
$this->assertSame(InventoryPolicy::Tracked, $item->inventory_policy);
|
||||
$this->assertTrue($item->has_tickets);
|
||||
$this->assertInstanceOf(Tenant::class, $item->tenant()->getRelated());
|
||||
$this->assertInstanceOf(Event::class, $item->event()->getRelated());
|
||||
$this->assertInstanceOf(Category::class, $item->category()->getRelated());
|
||||
$this->assertInstanceOf(Brand::class, $item->brand()->getRelated());
|
||||
$this->assertInstanceOf(Inventory::class, $item->inventory()->getRelated());
|
||||
$this->assertInstanceOf(BundleComponent::class, $item->bundleComponents()->getRelated());
|
||||
$this->assertInstanceOf(BundleComponent::class, $item->bundleComponentUsages()->getRelated());
|
||||
$this->assertInstanceOf(Variant::class, $item->variants()->getRelated());
|
||||
$this->assertInstanceOf(Ticket::class, $item->sourceTickets()->getRelated());
|
||||
$this->assertInstanceOf(Attribute::class, $item->attributes()->getRelated());
|
||||
$this->assertInstanceOf(ItemAttribute::class, $item->itemAttributes()->getRelated());
|
||||
$this->assertInstanceOf(FeaturedItem::class, $item->featuredItems()->getRelated());
|
||||
@@ -125,13 +135,20 @@ class CatalogModelsTest extends TestCase
|
||||
public function test_variant_has_direct_catalog_and_inventory_relations(): void
|
||||
{
|
||||
$variant = new Variant;
|
||||
$variant->setRawAttributes(['catalog_item_id' => '10', 'inventory_id' => '20']);
|
||||
$variant->setRawAttributes([
|
||||
'catalog_item_id' => '10',
|
||||
'event_date_id' => '15',
|
||||
'inventory_id' => '20',
|
||||
]);
|
||||
|
||||
$this->assertSame('variantes', $variant->getTable());
|
||||
$this->assertSame(10, $variant->catalog_item_id);
|
||||
$this->assertSame(20, $variant->inventory_id);
|
||||
$this->assertSame(15, $variant->event_date_id);
|
||||
$this->assertInstanceOf(CatalogItem::class, $variant->catalogItem()->getRelated());
|
||||
$this->assertInstanceOf(Inventory::class, $variant->inventory()->getRelated());
|
||||
$this->assertInstanceOf(EventDate::class, $variant->eventDate()->getRelated());
|
||||
$this->assertInstanceOf(Ticket::class, $variant->sourceTickets()->getRelated());
|
||||
$this->assertInstanceOf(VariantDefinition::class, $variant->definitions()->getRelated());
|
||||
$this->assertInstanceOf(Attachment::class, $variant->attachments()->getRelated());
|
||||
$this->assertSame('catalog_items_attachments', $variant->attachments()->getTable());
|
||||
@@ -155,6 +172,28 @@ class CatalogModelsTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function test_event_date_identifies_a_variant_without_catalog_attributes(): void
|
||||
{
|
||||
$item = new CatalogItem;
|
||||
$item->nombre = 'Entrada General';
|
||||
|
||||
$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 20:00:00');
|
||||
$variant->setRelation('catalogItem', $item);
|
||||
$variant->setRelation('eventDate', $eventDate);
|
||||
$variant->setRelation('definitions', new EloquentCollection);
|
||||
|
||||
$this->assertSame('Entrada General (Fecha: 2026-10-09)', $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'));
|
||||
}
|
||||
|
||||
public function test_inventory_maps_stock_without_a_polymorphic_owner(): void
|
||||
{
|
||||
$inventory = $this->trackedInventory(realStock: 10, reservedStock: 3);
|
||||
|
||||
49
tests/Unit/Event/EventModelsTest.php
Normal file
49
tests/Unit/Event/EventModelsTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Event;
|
||||
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Event\Models\Event;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EventModelsTest extends TestCase
|
||||
{
|
||||
public function test_event_maps_its_tenant_dates_and_catalog_items(): void
|
||||
{
|
||||
$event = new Event;
|
||||
|
||||
$this->assertFalse($event->usesTimestamps());
|
||||
$this->assertInstanceOf(Tenant::class, $event->tenant()->getRelated());
|
||||
$this->assertInstanceOf(EventDate::class, $event->dates()->getRelated());
|
||||
$this->assertInstanceOf(CatalogItem::class, $event->catalogItems()->getRelated());
|
||||
}
|
||||
|
||||
public function test_event_date_maps_schedule_and_variants(): void
|
||||
{
|
||||
$eventDate = new EventDate;
|
||||
$eventDate->setRawAttributes([
|
||||
'event_id' => '10',
|
||||
'date' => '2026-10-09',
|
||||
'time_start' => '09:00:00',
|
||||
'time_end' => '18:30:00',
|
||||
]);
|
||||
|
||||
$this->assertFalse($eventDate->usesTimestamps());
|
||||
$this->assertSame(10, $eventDate->event_id);
|
||||
$this->assertSame('2026-10-09 09:00:00', $eventDate->startsAt()->format('Y-m-d H:i:s'));
|
||||
$this->assertSame('2026-10-09 18:30:00', $eventDate->endsAt()->format('Y-m-d H:i:s'));
|
||||
$this->assertInstanceOf(Event::class, $eventDate->event()->getRelated());
|
||||
$this->assertInstanceOf(Variant::class, $eventDate->variants()->getRelated());
|
||||
}
|
||||
|
||||
public function test_tenant_has_many_events_and_one_active_event(): void
|
||||
{
|
||||
$tenant = new Tenant;
|
||||
|
||||
$this->assertInstanceOf(Event::class, $tenant->events()->getRelated());
|
||||
$this->assertInstanceOf(Event::class, $tenant->activeEvent()->getRelated());
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
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\Models\Ticket;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -39,6 +42,8 @@ class TicketTest extends TestCase
|
||||
$this->assertSame(10, $ticket->user_id);
|
||||
$this->assertInstanceOf(Tenant::class, $ticket->tenant()->getRelated());
|
||||
$this->assertInstanceOf(User::class, $ticket->user()->getRelated());
|
||||
$this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated());
|
||||
$this->assertInstanceOf(Variant::class, $ticket->sourceVariant()->getRelated());
|
||||
}
|
||||
|
||||
public function test_unused_ticket_without_date_restrictions_is_valid(): void
|
||||
@@ -114,4 +119,30 @@ class TicketTest extends TestCase
|
||||
$this->assertFalse($ticket->is_expired);
|
||||
$this->assertTrue($ticket->is_used);
|
||||
}
|
||||
|
||||
public function test_event_date_has_priority_over_ticket_and_variant_dates(): void
|
||||
{
|
||||
Carbon::setTestNow('2026-10-09 19:00:00');
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user