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:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Catalog\Enums\EventProductType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('events', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->string('name');
|
||||
$table->string('address');
|
||||
|
||||
$table->foreign('tenant_code')
|
||||
->references('codigo')
|
||||
->on('tenants')
|
||||
->cascadeOnUpdate()
|
||||
->cascadeOnDelete();
|
||||
});
|
||||
|
||||
Schema::create('event_dates', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('event_id')->constrained('events')->cascadeOnDelete();
|
||||
$table->date('date');
|
||||
$table->time('time_start');
|
||||
$table->time('time_end');
|
||||
});
|
||||
|
||||
Schema::table('tenants', function (Blueprint $table): void {
|
||||
$table->foreignId('active_event_id')
|
||||
->nullable()
|
||||
->constrained('events')
|
||||
->nullOnDelete();
|
||||
});
|
||||
|
||||
Schema::table('catalog_items', function (Blueprint $table): void {
|
||||
$table->foreignId('event_id')
|
||||
->nullable()
|
||||
->after('tenant_code')
|
||||
->constrained('events')
|
||||
->nullOnDelete();
|
||||
$table->enum('event_product_type', EventProductType::values())
|
||||
->nullable()
|
||||
->after('event_id');
|
||||
});
|
||||
|
||||
Schema::table('variantes', function (Blueprint $table): void {
|
||||
$table->foreignId('event_date_id')
|
||||
->nullable()
|
||||
->after('catalog_item_id')
|
||||
->constrained('event_dates')
|
||||
->nullOnDelete();
|
||||
$table->unique(['catalog_item_id', 'event_date_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table): void {
|
||||
$table->dropConstrainedForeignId('active_event_id');
|
||||
});
|
||||
|
||||
Schema::table('variantes', function (Blueprint $table): void {
|
||||
$table->dropUnique(['catalog_item_id', 'event_date_id']);
|
||||
$table->dropConstrainedForeignId('event_date_id');
|
||||
});
|
||||
|
||||
Schema::table('catalog_items', function (Blueprint $table): void {
|
||||
$table->dropConstrainedForeignId('event_id');
|
||||
$table->dropColumn('event_product_type');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('event_dates');
|
||||
Schema::dropIfExists('events');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('tickets')
|
||||
->whereNotNull('source_variant_id')
|
||||
->whereNotIn('source_variant_id', DB::table('variantes')->select('id'))
|
||||
->update(['source_variant_id' => null]);
|
||||
|
||||
DB::table('tickets')
|
||||
->whereNotNull('source_catalog_item_id')
|
||||
->whereNotIn('source_catalog_item_id', DB::table('catalog_items')->select('id'))
|
||||
->update(['source_catalog_item_id' => null]);
|
||||
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->foreign('source_catalog_item_id')
|
||||
->references('id')
|
||||
->on('catalog_items')
|
||||
->cascadeOnUpdate()
|
||||
->nullOnDelete();
|
||||
$table->foreign('source_variant_id')
|
||||
->references('id')
|
||||
->on('variantes')
|
||||
->cascadeOnUpdate()
|
||||
->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->dropForeign(['source_catalog_item_id']);
|
||||
$table->dropForeign(['source_variant_id']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -17,83 +17,79 @@ class AttributeSeeder extends Seeder
|
||||
$tenants = Tenant::all();
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
// Fiesta Futbol Infantil only uses the Fecha attribute.
|
||||
// Event dates replace catalog attributes for Fiesta Futbol Infantil.
|
||||
if ($tenant->codigo === 'fiesta_futbol_infantil') {
|
||||
Attribute::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->whereIn('codigo', ['color', 'talle', 'talle_numerico'])
|
||||
->whereIn('codigo', ['color', 'talle', 'talle_numerico', 'fecha'])
|
||||
->delete();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tenant->codigo !== 'fiesta_futbol_infantil') {
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => true,
|
||||
'metadata_schema' => [
|
||||
'hex' => ['type' => 'string'],
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => true,
|
||||
'metadata_schema' => [
|
||||
'hex' => ['type' => 'string'],
|
||||
],
|
||||
'options' => [
|
||||
[
|
||||
'value' => 'Negro',
|
||||
'label' => 'Negro',
|
||||
'sort_order' => 1,
|
||||
'metadata' => ['hex' => '#000000'],
|
||||
],
|
||||
'options' => [
|
||||
[
|
||||
'value' => 'Negro',
|
||||
'label' => 'Negro',
|
||||
'sort_order' => 1,
|
||||
'metadata' => ['hex' => '#000000'],
|
||||
],
|
||||
[
|
||||
'value' => 'Gris',
|
||||
'label' => 'Gris',
|
||||
'sort_order' => 2,
|
||||
'metadata' => ['hex' => '#808080'],
|
||||
],
|
||||
[
|
||||
'value' => 'Blanco',
|
||||
'label' => 'Blanco',
|
||||
'sort_order' => 3,
|
||||
'metadata' => ['hex' => '#FFFFFF'],
|
||||
],
|
||||
[
|
||||
'value' => 'Azul',
|
||||
'label' => 'Azul',
|
||||
'sort_order' => 4,
|
||||
'metadata' => ['hex' => '#0000FF'],
|
||||
],
|
||||
[
|
||||
'value' => 'Gris',
|
||||
'label' => 'Gris',
|
||||
'sort_order' => 2,
|
||||
'metadata' => ['hex' => '#808080'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
[
|
||||
'value' => 'Blanco',
|
||||
'label' => 'Blanco',
|
||||
'sort_order' => 3,
|
||||
'metadata' => ['hex' => '#FFFFFF'],
|
||||
],
|
||||
[
|
||||
'value' => 'Azul',
|
||||
'label' => 'Azul',
|
||||
'sort_order' => 4,
|
||||
'metadata' => ['hex' => '#0000FF'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Seed Talle (Size - Text options) attribute
|
||||
if ($tenant->codigo !== 'fiesta_futbol_infantil') {
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'talle',
|
||||
'nombre' => 'Talle',
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => true,
|
||||
'options' => [
|
||||
['value' => 'S', 'label' => 'S', 'sort_order' => 1],
|
||||
['value' => 'M', 'label' => 'M', 'sort_order' => 2],
|
||||
['value' => 'L', 'label' => 'L', 'sort_order' => 3],
|
||||
['value' => 'XL', 'label' => 'XL', 'sort_order' => 4],
|
||||
],
|
||||
]);
|
||||
}
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'talle',
|
||||
'nombre' => 'Talle',
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => true,
|
||||
'options' => [
|
||||
['value' => 'S', 'label' => 'S', 'sort_order' => 1],
|
||||
['value' => 'M', 'label' => 'M', 'sort_order' => 2],
|
||||
['value' => 'L', 'label' => 'L', 'sort_order' => 3],
|
||||
['value' => 'XL', 'label' => 'XL', 'sort_order' => 4],
|
||||
],
|
||||
]);
|
||||
|
||||
// Seed Talle Numérico (Numeric Size options) attribute
|
||||
if ($tenant->codigo !== 'fiesta_futbol_infantil') {
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'talle_numerico',
|
||||
'nombre' => 'Talle Numérico',
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => true,
|
||||
'options' => [
|
||||
['value' => '38', 'label' => '38', 'sort_order' => 1],
|
||||
['value' => '40', 'label' => '40', 'sort_order' => 2],
|
||||
['value' => '42', 'label' => '42', 'sort_order' => 3],
|
||||
['value' => '44', 'label' => '44', 'sort_order' => 4],
|
||||
],
|
||||
]);
|
||||
}
|
||||
$this->seedAttribute($tenant, [
|
||||
'codigo' => 'talle_numerico',
|
||||
'nombre' => 'Talle Numérico',
|
||||
'type' => FieldType::Select->value,
|
||||
'is_required' => true,
|
||||
'options' => [
|
||||
['value' => '38', 'label' => '38', 'sort_order' => 1],
|
||||
['value' => '40', 'label' => '40', 'sort_order' => 2],
|
||||
['value' => '42', 'label' => '42', 'sort_order' => 3],
|
||||
['value' => '44', 'label' => '44', 'sort_order' => 4],
|
||||
],
|
||||
]);
|
||||
|
||||
// Seed Fecha attribute
|
||||
$this->seedAttribute($tenant, [
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
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;
|
||||
@@ -10,6 +11,7 @@ use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Catalog\Models\FeaturedGroup;
|
||||
use App\Domains\Catalog\Services\CatalogService;
|
||||
use App\Domains\Event\Models\Event;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Seeder;
|
||||
use RuntimeException;
|
||||
@@ -28,6 +30,29 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
|
||||
$this->deleteExistingCatalog($tenant);
|
||||
|
||||
$event = Event::query()->updateOrCreate(
|
||||
[
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'name' => 'Fiesta Nacional del Fútbol Infantil',
|
||||
],
|
||||
['address' => 'Sunchales, Santa Fe'],
|
||||
);
|
||||
$event->dates()->delete();
|
||||
|
||||
$eventDates = collect(['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12'])
|
||||
->mapWithKeys(function (string $date) use ($event): array {
|
||||
$eventDate = $event->dates()->create([
|
||||
'date' => $date,
|
||||
'time_start' => '00:00:00',
|
||||
'time_end' => '23:59:59',
|
||||
]);
|
||||
|
||||
return [$date => $eventDate];
|
||||
});
|
||||
|
||||
$tenant->active_event_id = $event->id;
|
||||
$tenant->save();
|
||||
|
||||
$ticketCategory = Category::query()->firstOrCreate([
|
||||
'nombre' => 'Entradas',
|
||||
'tenant_code' => $tenant->codigo,
|
||||
@@ -41,12 +66,14 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
'tenant_code' => $tenant->codigo,
|
||||
]);
|
||||
|
||||
$dates = ['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12'];
|
||||
$dates = $eventDates->keys()->all();
|
||||
$minimumUseDate = $dates[0].' 00:00:00';
|
||||
$maximumUseDate = $dates[array_key_last($dates)].' 23:59:59';
|
||||
|
||||
$generalAdmission = $this->catalogService->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'event_id' => $event->id,
|
||||
'event_product_type' => EventProductType::Entry->value,
|
||||
'category_id' => $ticketCategory->id,
|
||||
'slug' => 'entrada-general',
|
||||
'nombre' => 'Entrada General',
|
||||
@@ -56,13 +83,10 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
'has_tickets' => true,
|
||||
'minimum_use_date' => $minimumUseDate,
|
||||
'maximum_use_date' => $maximumUseDate,
|
||||
'attribute_codes' => ['fecha'],
|
||||
'variants' => array_map(
|
||||
fn (string $date): array => [
|
||||
'real_stock' => 0,
|
||||
'minimum_use_date' => $date.' 00:00:00',
|
||||
'maximum_use_date' => $date.' 23:59:59',
|
||||
'values' => ['fecha' => $date],
|
||||
'event_date_id' => $eventDates->get($date)->id,
|
||||
],
|
||||
$dates,
|
||||
),
|
||||
@@ -81,6 +105,8 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
foreach ($items as $item) {
|
||||
$createdItems[$item['slug']] = $this->catalogService->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'event_id' => $event->id,
|
||||
'event_product_type' => EventProductType::Product->value,
|
||||
'descripcion' => $item['descripcion'] ?? $item['nombre'],
|
||||
'inventory_policy' => InventoryPolicy::Unlimited->value,
|
||||
'real_stock' => 0,
|
||||
@@ -92,6 +118,8 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
|
||||
$this->catalogService->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'event_id' => $event->id,
|
||||
'event_product_type' => EventProductType::Entry->value,
|
||||
'type' => CatalogItemType::Bundle->value,
|
||||
'slug' => 'entrada-general-todos-los-dias',
|
||||
'nombre' => 'Entrada General - Todos los días',
|
||||
@@ -109,6 +137,8 @@ class FiestaFutbolInfantilProductSeeder extends Seeder
|
||||
|
||||
$this->catalogService->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'event_id' => $event->id,
|
||||
'event_product_type' => EventProductType::Product->value,
|
||||
'type' => CatalogItemType::Bundle->value,
|
||||
'slug' => 'combo-2-panchos-2-hamburguesas',
|
||||
'nombre' => 'Combo 2 Panchos + 2 Hamburguesas',
|
||||
|
||||
Reference in New Issue
Block a user