From 6b0efbc6ee4c62138fca458a53f171c048bdf8f3 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 24 Sep 2026 11:42:45 -0300 Subject: [PATCH] feat(seeder): add Mutual SMEP category seeder and test cases --- ..._24_010000_seed_mutual_smep_categories.php | 141 ++++++++++++++++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/MutualSmepCategorySeeder.php | 123 +++++++++++++++ .../Seeders/MutualSmepCategorySeederTest.php | 119 +++++++++++++++ 4 files changed, 384 insertions(+) create mode 100644 database/migrations/2026_09_24_010000_seed_mutual_smep_categories.php create mode 100644 database/seeders/MutualSmepCategorySeeder.php create mode 100644 tests/Feature/Seeders/MutualSmepCategorySeederTest.php diff --git a/database/migrations/2026_09_24_010000_seed_mutual_smep_categories.php b/database/migrations/2026_09_24_010000_seed_mutual_smep_categories.php new file mode 100644 index 0000000..f89abb0 --- /dev/null +++ b/database/migrations/2026_09_24_010000_seed_mutual_smep_categories.php @@ -0,0 +1,141 @@ +> */ + private const CATEGORIES = [ + 'Dormitorio y Blanco' => [ + 'Colchones', + 'Bases y Sommiers', + 'Acolchados y Edredones', + 'Frazadas y Mantas', + 'Infantil y Cuna', + ], + 'Electrodomésticos' => [ + 'Heladeras', + 'Lavarropas y Secarropas', + 'Cocinas', + 'Microondas', + 'Hornos Eléctricos', + 'Calefones y Termotanques', + 'Purificadores y Extractores de Cocina', + 'Preparación de Alimentos', + 'Café y Desayuno', + 'Freidoras y Cocción', + 'Cuidado Personal', + 'Limpieza y Planchado', + ], + 'Climatización' => [ + 'Aires Acondicionados', + 'Calefactores a Gas con Salida', + 'Calefactores a Gas sin Salida', + 'Calefacción Eléctrica', + ], + 'Tecnología' => [ + 'Celulares', + 'Notebooks', + 'Smart TV', + 'Audio', + 'Accesorios de Computación', + 'Soportes para TV', + ], + 'Bicicletas y Aire Libre' => [ + 'Bicicletas Infantiles', + 'Bicicletas para Adultos', + 'Piletas', + ], + 'Bazar' => [ + 'Termos', + ], + ]; + + public function up(): void + { + if (! DB::table('tenants')->where('codigo', self::TENANT_CODE)->exists()) { + return; + } + + DB::transaction(function (): void { + foreach (self::CATEGORIES as $parentName => $subcategoryNames) { + $parentId = $this->upsertCategory($parentName, null); + + foreach ($subcategoryNames as $subcategoryName) { + $this->upsertCategory($subcategoryName, $parentId); + } + } + }); + } + + public function down(): void + { + DB::transaction(function (): void { + foreach (self::CATEGORIES as $parentName => $subcategoryNames) { + $parentId = DB::table('categorias') + ->where('tenant_code', self::TENANT_CODE) + ->where('nombre', $parentName) + ->whereNull('categoria_id') + ->value('id'); + + if ($parentId === null) { + continue; + } + + $subcategoryIds = DB::table('categorias') + ->where('tenant_code', self::TENANT_CODE) + ->where('categoria_id', $parentId) + ->whereIn('nombre', $subcategoryNames) + ->pluck('id'); + + foreach ($subcategoryIds as $subcategoryId) { + $hasChildren = DB::table('categorias') + ->where('categoria_id', $subcategoryId) + ->exists(); + + if (! $hasChildren) { + DB::table('categorias')->where('id', $subcategoryId)->delete(); + } + } + + $hasChildren = DB::table('categorias') + ->where('categoria_id', $parentId) + ->exists(); + + if (! $hasChildren) { + DB::table('categorias')->where('id', $parentId)->delete(); + } + } + }); + } + + private function upsertCategory(string $name, ?int $parentId): int + { + $categoryId = DB::table('categorias') + ->where('tenant_code', self::TENANT_CODE) + ->where('nombre', $name) + ->value('id'); + + if ($categoryId !== null) { + DB::table('categorias')->where('id', $categoryId)->update([ + 'categoria_id' => $parentId, + 'is_enabled' => true, + 'updated_at' => now(), + ]); + + return (int) $categoryId; + } + + return (int) DB::table('categorias')->insertGetId([ + 'tenant_code' => self::TENANT_CODE, + 'categoria_id' => $parentId, + 'nombre' => $name, + 'is_enabled' => true, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index b5be6dc..d599f7f 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -30,6 +30,7 @@ class DatabaseSeeder extends Seeder DesfilePuraTendenciaSeeder::class, AttributeSeeder::class, CategorySeeder::class, + MutualSmepCategorySeeder::class, BrandSeeder::class, ProductCatalogFromImagesSeeder::class, FiestaFutbolInfantilProductSeeder::class, diff --git a/database/seeders/MutualSmepCategorySeeder.php b/database/seeders/MutualSmepCategorySeeder.php new file mode 100644 index 0000000..8b82060 --- /dev/null +++ b/database/seeders/MutualSmepCategorySeeder.php @@ -0,0 +1,123 @@ +> */ + private const CATEGORIES = [ + 'Dormitorio y Blanco' => [ + 'Colchones', + 'Bases y Sommiers', + 'Acolchados y Edredones', + 'Frazadas y Mantas', + 'Infantil y Cuna', + ], + 'Electrodomésticos' => [ + 'Heladeras', + 'Lavarropas y Secarropas', + 'Cocinas', + 'Microondas', + 'Hornos Eléctricos', + 'Calefones y Termotanques', + 'Purificadores y Extractores de Cocina', + 'Preparación de Alimentos', + 'Café y Desayuno', + 'Freidoras y Cocción', + 'Cuidado Personal', + 'Limpieza y Planchado', + ], + 'Climatización' => [ + 'Aires Acondicionados', + 'Calefactores a Gas con Salida', + 'Calefactores a Gas sin Salida', + 'Calefacción Eléctrica', + ], + 'Tecnología' => [ + 'Celulares', + 'Notebooks', + 'Smart TV', + 'Audio', + 'Accesorios de Computación', + 'Soportes para TV', + ], + 'Bicicletas y Aire Libre' => [ + 'Bicicletas Infantiles', + 'Bicicletas para Adultos', + 'Piletas', + ], + 'Bazar' => [ + 'Termos', + ], + ]; + + public function run(): void + { + if (! Tenant::query()->where('codigo', self::TENANT_CODE)->exists()) { + return; + } + + DB::transaction(function (): void { + foreach (self::CATEGORIES as $parentName => $subcategoryNames) { + $parent = Category::query()->updateOrCreate( + [ + 'tenant_code' => self::TENANT_CODE, + 'nombre' => $parentName, + ], + [ + 'categoria_id' => null, + 'is_enabled' => true, + ], + ); + + foreach ($subcategoryNames as $subcategoryName) { + Category::query()->updateOrCreate( + [ + 'tenant_code' => self::TENANT_CODE, + 'nombre' => $subcategoryName, + ], + [ + 'categoria_id' => $parent->id, + 'is_enabled' => true, + ], + ); + } + } + }); + } + + public function down(): void + { + DB::transaction(function (): void { + foreach (self::CATEGORIES as $parentName => $subcategoryNames) { + $parent = Category::query() + ->where('tenant_code', self::TENANT_CODE) + ->where('nombre', $parentName) + ->whereNull('categoria_id') + ->first(); + + if (! $parent instanceof Category) { + continue; + } + + Category::query() + ->where('tenant_code', self::TENANT_CODE) + ->where('categoria_id', $parent->id) + ->whereIn('nombre', $subcategoryNames) + ->whereDoesntHave('subCategories') + ->delete(); + + if (! $parent->subCategories()->exists()) { + $parent->delete(); + } + } + }); + } +} diff --git a/tests/Feature/Seeders/MutualSmepCategorySeederTest.php b/tests/Feature/Seeders/MutualSmepCategorySeederTest.php new file mode 100644 index 0000000..9e516dc --- /dev/null +++ b/tests/Feature/Seeders/MutualSmepCategorySeederTest.php @@ -0,0 +1,119 @@ +string('codigo')->primary(); + }); + + Schema::create('categorias', function (Blueprint $table): void { + $table->id(); + $table->string('tenant_code')->nullable(); + $table->unsignedBigInteger('categoria_id')->nullable(); + $table->string('nombre'); + $table->boolean('is_enabled')->default(true); + $table->timestamps(); + }); + } + + protected function tearDown(): void + { + Schema::dropIfExists('categorias'); + Schema::dropIfExists('tenants'); + + parent::tearDown(); + } + + public function test_it_creates_the_mutual_smep_category_hierarchy_idempotently(): void + { + $this->createTenant('mutual_smep'); + + $this->seed(MutualSmepCategorySeeder::class); + $this->seed(MutualSmepCategorySeeder::class); + + $parents = Category::query() + ->where('tenant_code', 'mutual_smep') + ->whereNull('categoria_id') + ->with('subCategories') + ->get() + ->keyBy('nombre'); + + $this->assertCount(6, $parents); + $this->assertCount(37, Category::query()->where('tenant_code', 'mutual_smep')->get()); + $this->assertEqualsCanonicalizing([ + 'Colchones', + 'Bases y Sommiers', + 'Acolchados y Edredones', + 'Frazadas y Mantas', + 'Infantil y Cuna', + ], $parents->get('Dormitorio y Blanco')->subCategories->pluck('nombre')->all()); + $this->assertEqualsCanonicalizing([ + 'Celulares', + 'Notebooks', + 'Smart TV', + 'Audio', + 'Accesorios de Computación', + 'Soportes para TV', + ], $parents->get('Tecnología')->subCategories->pluck('nombre')->all()); + } + + public function test_down_removes_only_the_seeded_mutual_smep_hierarchy(): void + { + $this->createTenant('mutual_smep'); + $this->createTenant('other'); + Category::query()->create([ + 'tenant_code' => 'other', + 'nombre' => 'Tecnología', + ]); + + $seeder = app(MutualSmepCategorySeeder::class); + $seeder->run(); + $seeder->down(); + + $this->assertDatabaseMissing('categorias', [ + 'tenant_code' => 'mutual_smep', + ]); + $this->assertDatabaseHas('categorias', [ + 'tenant_code' => 'other', + 'nombre' => 'Tecnología', + ]); + } + + public function test_the_data_migration_runs_and_rolls_back_the_seeder(): void + { + $this->createTenant('mutual_smep'); + $migration = require database_path( + 'migrations/2026_09_24_010000_seed_mutual_smep_categories.php' + ); + + $migration->up(); + + $this->assertCount(37, Category::query()->where('tenant_code', 'mutual_smep')->get()); + + $migration->down(); + + $this->assertDatabaseMissing('categorias', [ + 'tenant_code' => 'mutual_smep', + ]); + } + + private function createTenant(string $code): void + { + DB::table('tenants')->insert([ + 'codigo' => $code, + ]); + } +}