diff --git a/database/migrations/2026_07_17_000200_expand_bundles_tenant_codigo.php b/database/migrations/2026_07_17_000200_expand_bundles_tenant_codigo.php new file mode 100644 index 0000000..3980e56 --- /dev/null +++ b/database/migrations/2026_07_17_000200_expand_bundles_tenant_codigo.php @@ -0,0 +1,32 @@ +dropForeign(['tenant_codigo']); + }); + + Schema::table('bundles', function (Blueprint $table) { + $table->string('tenant_codigo')->change(); + $table->foreign('tenant_codigo')->references('codigo')->on('tenants')->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::table('bundles', function (Blueprint $table) { + $table->dropForeign(['tenant_codigo']); + }); + + Schema::table('bundles', function (Blueprint $table) { + $table->string('tenant_codigo', 10)->change(); + $table->foreign('tenant_codigo')->references('codigo')->on('tenants')->onDelete('cascade'); + }); + } +}; diff --git a/database/seeders/AttributeSeeder.php b/database/seeders/AttributeSeeder.php index 7c16392..ee3ba19 100644 --- a/database/seeders/AttributeSeeder.php +++ b/database/seeders/AttributeSeeder.php @@ -18,7 +18,7 @@ class AttributeSeeder extends Seeder $tenants = Tenant::all(); foreach ($tenants as $tenant) { - // Seed Color attribute + // Fiesta Futbol Infantil only uses the Fecha attribute. $existingColor = Attribute::query() ->where('tenant_codigo', $tenant->codigo) ->where('codigo', 'color') @@ -28,41 +28,43 @@ class AttributeSeeder extends Seeder Product::deleteAttribute($existingColor); } - Product::createAttribute($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'], + if ($tenant->codigo !== 'fiesta_futbol_infantil') { + Product::createAttribute($tenant, [ + 'codigo' => 'color', + 'nombre' => 'Color', + 'type' => FieldType::Select->value, + 'is_required' => true, + 'metadata_schema' => [ + 'hex' => ['type' => 'string'], ], - [ - 'value' => 'Gris', - 'label' => 'Gris', - 'sort_order' => 2, - 'metadata' => ['hex' => '#808080'], + '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' => '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 $existingTalle = Attribute::query() @@ -74,18 +76,20 @@ class AttributeSeeder extends Seeder Product::deleteAttribute($existingTalle); } - Product::createAttribute($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], - ], - ]); + if ($tenant->codigo !== 'fiesta_futbol_infantil') { + Product::createAttribute($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 $existingTalleNumerico = Attribute::query() @@ -97,18 +101,20 @@ class AttributeSeeder extends Seeder Product::deleteAttribute($existingTalleNumerico); } - Product::createAttribute($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], - ], - ]); + if ($tenant->codigo !== 'fiesta_futbol_infantil') { + Product::createAttribute($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 $existingFecha = Attribute::query() diff --git a/database/seeders/FiestaFutbolInfantilProductSeeder.php b/database/seeders/FiestaFutbolInfantilProductSeeder.php index cb380e6..9b1e1da 100644 --- a/database/seeders/FiestaFutbolInfantilProductSeeder.php +++ b/database/seeders/FiestaFutbolInfantilProductSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Domains\Bundle\Models\Bundle; use App\Domains\Catalog\Enums\InventoryPolicy; use App\Domains\Catalog\Models\Attribute; use App\Domains\Catalog\Models\Category; @@ -24,6 +25,9 @@ class FiestaFutbolInfantilProductSeeder extends Seeder throw new RuntimeException("Tenant 'fiesta_futbol_infantil' no encontrado."); } + // Bundles reference product variants, so remove them before reseeding products. + Bundle::query()->where('tenant_codigo', $tenant->codigo)->delete(); + // Delete existing products for this tenant $existingProducts = Product::query() ->where('tenant_codigo', $tenant->codigo) @@ -37,6 +41,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder $category = Category::firstOrCreate(['nombre' => 'Entradas', 'tenant_code' => null]); $dates = ['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12']; + $entradaVariants = []; // 1. Entrada General $entrada = $this->productService->create($tenant, [ @@ -51,7 +56,7 @@ class FiestaFutbolInfantilProductSeeder extends Seeder ]); foreach ($dates as $date) { - $this->productService->createVariant($entrada, [ + $entradaVariants[] = $this->productService->createVariant($entrada, [ 'stock' => 0, 'inventory_policy' => InventoryPolicy::Unlimited->value, 'has_tickets' => true, @@ -80,8 +85,10 @@ class FiestaFutbolInfantilProductSeeder extends Seeder ['slug' => 'estacionamiento-moto', 'nombre' => 'Estacionamiento Moto', 'precio' => 2000, 'cat' => $category->id], ]; + $createdProducts = []; + foreach ($simpleProducts as $p) { - $this->productService->create($tenant, [ + $createdProducts[$p['slug']] = $this->productService->create($tenant, [ 'categoria_id' => $p['cat'], 'slug' => $p['slug'], 'nombre' => $p['nombre'], @@ -91,5 +98,37 @@ class FiestaFutbolInfantilProductSeeder extends Seeder 'inventory_policy' => InventoryPolicy::Unlimited->value, ]); } + + $allDaysBundle = Bundle::query()->create([ + 'tenant_codigo' => $tenant->codigo, + 'nombre' => 'Entrada General - Todos los días', + 'descripcion' => 'Incluye una entrada para cada día de la Fiesta Nacional del Fútbol Infantil.', + 'precio' => 40000, + ]); + + foreach ($entradaVariants as $variant) { + $allDaysBundle->items()->create([ + 'producto_variante_id' => $variant->id, + 'cantidad' => 1, + ]); + } + + $foodBundle = Bundle::query()->create([ + 'tenant_codigo' => $tenant->codigo, + 'nombre' => 'Combo 2 Panchos + 2 Hamburguesas', + 'descripcion' => 'Incluye 2 panchos y 2 hamburguesas con papa frita.', + 'precio' => 24000, + ]); + + $foodBundle->items()->createMany([ + [ + 'producto_variante_id' => $createdProducts['pancho']->variants()->sole()->id, + 'cantidad' => 2, + ], + [ + 'producto_variante_id' => $createdProducts['hamburguesa-papa-frita']->variants()->sole()->id, + 'cantidad' => 2, + ], + ]); } } diff --git a/database/seeders/TenantSeeder.php b/database/seeders/TenantSeeder.php index 55a77b8..68ae64b 100644 --- a/database/seeders/TenantSeeder.php +++ b/database/seeders/TenantSeeder.php @@ -162,7 +162,7 @@ class TenantSeeder extends Seeder 'secondary_color' => '#A0A0A0', 'danger_color' => '#FF8888', 'success_color' => '#198754', - 'header_bg_color' => '#015327', + 'header_bg_color' => '#ffffff', 'footer_bg_color' => '#015327', 'header_logo' => $fiestaHeaderLogo, 'footer_logo' => $fiestaFooterLogo, diff --git a/tests/Feature/Seeders/FiestaFutbolInfantilProductSeederTest.php b/tests/Feature/Seeders/FiestaFutbolInfantilProductSeederTest.php new file mode 100644 index 0000000..5388b7f --- /dev/null +++ b/tests/Feature/Seeders/FiestaFutbolInfantilProductSeederTest.php @@ -0,0 +1,95 @@ +create([ + 'path' => 'tests/header.png', + 'filename' => 'header.png', + 'type' => AttachmentType::Image, + 'mime_type' => 'image/png', + ]); + $footerLogo = Attachment::query()->create([ + 'path' => 'tests/footer.png', + 'filename' => 'footer.png', + 'type' => AttachmentType::Image, + 'mime_type' => 'image/png', + ]); + + $tenant = Tenant::query()->create([ + 'codigo' => 'fiesta_futbol_infantil', + 'nombre' => 'Fiesta Fútbol Infantil', + 'dominio' => 'fiesta-futbol-infantil.localhost', + 'primary_color' => '#00973F', + 'secondary_color' => '#A0A0A0', + 'danger_color' => '#FF8888', + 'success_color' => '#198754', + 'header_bg_color' => '#ffffff', + 'footer_bg_color' => '#015327', + 'header_logo_id' => $headerLogo->id, + 'footer_logo_id' => $footerLogo->id, + ]); + + $this->seed([ + AttributeSeeder::class, + FiestaFutbolInfantilProductSeeder::class, + ]); + + $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() + ); + + $allDaysBundle = Bundle::query() + ->where('tenant_codigo', $tenant->codigo) + ->where('nombre', 'Entrada General - Todos los días') + ->with('items.variant.product', 'items.variant.definitions') + ->sole(); + + $this->assertSame('40000.00', $allDaysBundle->precio); + $this->assertCount(4, $allDaysBundle->items); + $this->assertEqualsCanonicalizing( + ['2026-10-09', '2026-10-10', '2026-10-11', '2026-10-12'], + $allDaysBundle->items->map(fn ($item) => $item->variant->definitions->sole()->value)->all() + ); + $this->assertTrue($allDaysBundle->items->every( + fn ($item) => $item->cantidad === 1 && $item->variant->product->slug === 'entrada-general' + )); + + $foodBundle = Bundle::query() + ->where('tenant_codigo', $tenant->codigo) + ->where('nombre', 'Combo 2 Panchos + 2 Hamburguesas') + ->with('items.variant.product') + ->sole(); + + $this->assertSame('24000.00', $foodBundle->precio); + $this->assertEqualsCanonicalizing( + [ + 'pancho' => 2, + 'hamburguesa-papa-frita' => 2, + ], + $foodBundle->items->mapWithKeys( + fn ($item) => [$item->variant->product->slug => $item->cantidad] + )->all() + ); + } +}