Merge branch 'tenant/smep' into homo
This commit is contained in:
@@ -72,6 +72,29 @@ class CategoryDetailTest extends TestCase
|
||||
->assertJsonCount(0, 'data');
|
||||
}
|
||||
|
||||
public function test_it_includes_products_from_all_descendant_categories(): void
|
||||
{
|
||||
$tenant = $this->createTenant('category-descendants');
|
||||
$parent = $this->createCategory($tenant, 'Indumentaria');
|
||||
$child = $this->createCategory($tenant, 'Remeras', $parent);
|
||||
$grandchild = $this->createCategory($tenant, 'Manga corta', $child);
|
||||
$sibling = $this->createCategory($tenant, 'Pantalones');
|
||||
|
||||
$parentItem = $this->createCatalogItem($tenant, $parent, 'Producto padre');
|
||||
$childItem = $this->createCatalogItem($tenant, $child, 'Producto hijo');
|
||||
$grandchildItem = $this->createCatalogItem($tenant, $grandchild, 'Producto nieto');
|
||||
$this->createCatalogItem($tenant, $sibling, 'Producto ajeno');
|
||||
|
||||
$this->getJson("/api/tenants/{$tenant->codigo}/categories/{$parent->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('meta.total', 3)
|
||||
->assertJsonCount(3, 'data')
|
||||
->assertJsonPath('data.0.id', $childItem->id)
|
||||
->assertJsonPath('data.1.id', $grandchildItem->id)
|
||||
->assertJsonPath('data.2.id', $parentItem->id)
|
||||
->assertJsonMissing(['nombre' => 'Producto ajeno']);
|
||||
}
|
||||
|
||||
public function test_it_rejects_categories_from_another_tenant(): void
|
||||
{
|
||||
$tenant = $this->createTenant('category-owner');
|
||||
@@ -107,10 +130,14 @@ class CategoryDetailTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
private function createCategory(Tenant $tenant, string $name): Category
|
||||
{
|
||||
private function createCategory(
|
||||
Tenant $tenant,
|
||||
string $name,
|
||||
?Category $parent = null,
|
||||
): Category {
|
||||
return Category::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'categoria_id' => $parent?->id,
|
||||
'nombre' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
197
tests/Feature/Migrations/ProvisionMutualSmepTenantTest.php
Normal file
197
tests/Feature/Migrations/ProvisionMutualSmepTenantTest.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProvisionMutualSmepTenantTest extends TestCase
|
||||
{
|
||||
public function test_it_provisions_the_mutual_smep_tenant_once_with_its_branding(): void
|
||||
{
|
||||
Storage::fake('s3');
|
||||
$this->createSchema();
|
||||
|
||||
DB::table('admin_website_types')->insert(['codigo' => 'shopit']);
|
||||
DB::table('storefront_website_types')->insert(['codigo' => 'shopit']);
|
||||
DB::table('menues')->insert([
|
||||
['code' => 'product.detail'],
|
||||
['code' => 'help.faq'],
|
||||
['code' => 'help.contact'],
|
||||
['code' => 'account.tickets'],
|
||||
['code' => 'event.index'],
|
||||
]);
|
||||
|
||||
$migration = require database_path('migrations/2026_09_24_000000_provision_mutual_smep_tenant.php');
|
||||
$migration->up();
|
||||
$migration->up();
|
||||
|
||||
$this->assertDatabaseCount('tenants', 1);
|
||||
$this->assertDatabaseHas('clients', [
|
||||
'code' => 'mutual_smep',
|
||||
'name' => 'Mutual SMEP',
|
||||
]);
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'codigo' => 'mutual_smep',
|
||||
'nombre' => 'Mutual SMEP',
|
||||
'dominio' => 'mutual-smep.localhost',
|
||||
'site_title' => 'Tienda Mutual SMEP',
|
||||
'primary_color' => '#4A7FF5',
|
||||
'secondary_color' => '#0051A4',
|
||||
'header_bg_color' => '#FFFFFF',
|
||||
'footer_bg_color' => '#0051A4',
|
||||
'admin_website_type_code' => 'shopit',
|
||||
'storefront_website_type_code' => 'shopit',
|
||||
'display_categories' => true,
|
||||
'display_seach_bar' => true,
|
||||
'display_cart' => true,
|
||||
'cart_editing_policy' => 'quantity_and_remove',
|
||||
'checkout_editing_policy' => 'disabled',
|
||||
]);
|
||||
|
||||
$this->assertSame([
|
||||
'mutual_smep_favicon.png',
|
||||
'mutual_smep_footer.png',
|
||||
'mutual_smep_header.png',
|
||||
], DB::table('attachments')->orderBy('filename')->pluck('filename')->all());
|
||||
|
||||
DB::table('attachments')->orderBy('id')->each(
|
||||
fn (object $attachment) => Storage::disk('s3')->assertExists($attachment->path)
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
['instagram', 'facebook', 'whatsapp'],
|
||||
DB::table('tenant_social_media')
|
||||
->where('tenant_code', 'mutual_smep')
|
||||
->orderBy('orden')
|
||||
->pluck('social_media_code')
|
||||
->all(),
|
||||
);
|
||||
$this->assertDatabaseHas('tenants_menues', [
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'menu_code' => 'product.detail',
|
||||
]);
|
||||
$this->assertDatabaseMissing('tenants_menues', [
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'menu_code' => 'account.tickets',
|
||||
]);
|
||||
$this->assertDatabaseMissing('tenants_menues', [
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'menu_code' => 'event.index',
|
||||
]);
|
||||
|
||||
$contact = json_decode(
|
||||
DB::table('tenants_menues')
|
||||
->where('tenant_code', 'mutual_smep')
|
||||
->where('menu_code', 'help.contact')
|
||||
->value('static_content'),
|
||||
true,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
$this->assertSame('https://wa.me/5493412474530', $contact['whatsapp']['whatsapp_url']);
|
||||
$this->assertSame('San Lorenzo 1543, Rosario, Santa Fe', $contact['locations']['rosario']['addresses'][0]['address']);
|
||||
|
||||
$storedPaths = DB::table('attachments')->pluck('path')->all();
|
||||
$migration->down();
|
||||
|
||||
$this->assertDatabaseMissing('tenants', ['codigo' => 'mutual_smep']);
|
||||
$this->assertDatabaseMissing('clients', ['code' => 'mutual_smep']);
|
||||
$this->assertDatabaseCount('attachments', 0);
|
||||
$this->assertDatabaseCount('tenant_social_media', 0);
|
||||
$this->assertDatabaseCount('tenants_menues', 0);
|
||||
|
||||
foreach ($storedPaths as $storedPath) {
|
||||
Storage::disk('s3')->assertMissing($storedPath);
|
||||
}
|
||||
}
|
||||
|
||||
private function createSchema(): void
|
||||
{
|
||||
Schema::create('clients', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('code')->unique();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('attachments', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->uuid('key');
|
||||
$table->string('path');
|
||||
$table->string('filename');
|
||||
$table->string('type');
|
||||
$table->string('mime_type');
|
||||
$table->string('extension');
|
||||
$table->unsignedBigInteger('size');
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('admin_website_types', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('codigo')->unique();
|
||||
});
|
||||
Schema::create('storefront_website_types', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('codigo')->unique();
|
||||
});
|
||||
Schema::create('tenants', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('client_id');
|
||||
$table->string('codigo')->unique();
|
||||
$table->string('nombre');
|
||||
$table->string('timezone');
|
||||
$table->string('dominio');
|
||||
$table->string('base_path');
|
||||
$table->string('site_title');
|
||||
$table->string('address');
|
||||
$table->string('phone');
|
||||
$table->string('primary_color');
|
||||
$table->string('secondary_color');
|
||||
$table->string('danger_color');
|
||||
$table->string('success_color');
|
||||
$table->string('header_bg_color');
|
||||
$table->string('footer_bg_color');
|
||||
$table->foreignId('header_logo_id');
|
||||
$table->foreignId('footer_logo_id');
|
||||
$table->foreignId('favicon_id');
|
||||
$table->string('admin_website_type_code');
|
||||
$table->string('storefront_website_type_code');
|
||||
$table->boolean('display_categories');
|
||||
$table->boolean('display_seach_bar');
|
||||
$table->boolean('display_cart');
|
||||
$table->string('cart_editing_policy');
|
||||
$table->string('checkout_editing_policy');
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('social_media', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('code')->unique();
|
||||
$table->string('icon');
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('tenant_social_media', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->string('social_media_code');
|
||||
$table->string('url');
|
||||
$table->unsignedInteger('orden');
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_code', 'social_media_code']);
|
||||
});
|
||||
Schema::create('menues', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('code')->unique();
|
||||
});
|
||||
Schema::create('tenants_menues', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->string('menu_code');
|
||||
$table->json('static_content')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_code', 'menu_code']);
|
||||
});
|
||||
}
|
||||
}
|
||||
119
tests/Feature/Migrations/SeedMutualSmepCarouselTest.php
Normal file
119
tests/Feature/Migrations/SeedMutualSmepCarouselTest.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SeedMutualSmepCarouselTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Storage::fake('s3');
|
||||
$this->createSchema();
|
||||
|
||||
DB::table('tenants')->insert([
|
||||
'codigo' => 'mutual_smep',
|
||||
'storefront_website_type_code' => 'shopit',
|
||||
]);
|
||||
DB::table('storefront_website_type_extras')->insert([
|
||||
'storefront_website_type_code' => 'shopit',
|
||||
'codigo' => 'carousel',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
foreach (['websites_extras', 'attachments', 'storefront_website_type_extras', 'tenants'] as $table) {
|
||||
Schema::dropIfExists($table);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_loads_and_rolls_back_the_mutual_smep_carousel(): void
|
||||
{
|
||||
$migration = require database_path(
|
||||
'migrations/2026_09_24_040000_seed_mutual_smep_carousel.php'
|
||||
);
|
||||
|
||||
$migration->up();
|
||||
$migration->up();
|
||||
|
||||
$this->assertDatabaseCount('attachments', 4);
|
||||
$this->assertDatabaseCount('websites_extras', 1);
|
||||
|
||||
$attachments = DB::table('attachments')->orderBy('filename')->get();
|
||||
$this->assertSame([
|
||||
'01-tecnologia.webp',
|
||||
'02-hogar.webp',
|
||||
'03-vida-activa.webp',
|
||||
'04-descanso.webp',
|
||||
], $attachments->pluck('filename')->all());
|
||||
$this->assertSame(
|
||||
$attachments->pluck('id')->all(),
|
||||
json_decode(
|
||||
DB::table('websites_extras')->value('config'),
|
||||
true,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$this->assertSame('image/webp', $attachment->mime_type);
|
||||
$this->assertStringStartsWith(
|
||||
'tenants/mutual_smep/extras/carousel-v1/',
|
||||
$attachment->path,
|
||||
);
|
||||
Storage::disk('s3')->assertExists($attachment->path);
|
||||
}
|
||||
|
||||
$storedPaths = $attachments->pluck('path')->all();
|
||||
$migration->down();
|
||||
|
||||
$this->assertDatabaseCount('websites_extras', 0);
|
||||
$this->assertDatabaseCount('attachments', 0);
|
||||
|
||||
foreach ($storedPaths as $storedPath) {
|
||||
Storage::disk('s3')->assertMissing($storedPath);
|
||||
}
|
||||
}
|
||||
|
||||
private function createSchema(): void
|
||||
{
|
||||
Schema::create('tenants', function (Blueprint $table): void {
|
||||
$table->string('codigo')->primary();
|
||||
$table->string('storefront_website_type_code');
|
||||
});
|
||||
Schema::create('storefront_website_type_extras', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('storefront_website_type_code');
|
||||
$table->string('codigo');
|
||||
});
|
||||
Schema::create('attachments', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->uuid('key')->unique();
|
||||
$table->string('path');
|
||||
$table->string('filename');
|
||||
$table->string('type');
|
||||
$table->string('mime_type');
|
||||
$table->string('extension')->nullable();
|
||||
$table->unsignedBigInteger('size')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('websites_extras', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('website_code');
|
||||
$table->unsignedBigInteger('website_type_extra_id');
|
||||
$table->json('config');
|
||||
$table->boolean('is_enabled')->default(true);
|
||||
$table->timestamps();
|
||||
$table->unique(['website_code', 'website_type_extra_id']);
|
||||
});
|
||||
}
|
||||
}
|
||||
375
tests/Feature/Migrations/SeedMutualSmepCatalogTest.php
Normal file
375
tests/Feature/Migrations/SeedMutualSmepCatalogTest.php
Normal file
@@ -0,0 +1,375 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SeedMutualSmepCatalogTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Storage::fake('s3');
|
||||
$this->createSchema();
|
||||
DB::table('tenants')->insert(['codigo' => 'mutual_smep']);
|
||||
$this->seedCategoriesAndAttributes();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
foreach ([
|
||||
'featured_items',
|
||||
'featured_groups',
|
||||
'catalog_items_attachments',
|
||||
'attachments',
|
||||
'variant_values',
|
||||
'item_attributes',
|
||||
'variantes',
|
||||
'catalog_items',
|
||||
'inventories',
|
||||
'brands',
|
||||
'attribute',
|
||||
'categorias',
|
||||
'tenants',
|
||||
] as $table) {
|
||||
Schema::dropIfExists($table);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_loads_and_rolls_back_the_complete_catalog(): void
|
||||
{
|
||||
$migration = require database_path(
|
||||
'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php'
|
||||
);
|
||||
|
||||
$migration->up();
|
||||
|
||||
$this->assertSame(142, DB::table('catalog_items')->count());
|
||||
$this->assertSame(150, DB::table('inventories')->count());
|
||||
$this->assertSame(15, DB::table('variantes')->count());
|
||||
$this->assertSame(8, DB::table('item_attributes')->count());
|
||||
$this->assertSame(17, DB::table('variant_values')->count());
|
||||
$this->assertSame(511, DB::table('attachments')->count());
|
||||
$this->assertSame(511, DB::table('catalog_items_attachments')->count());
|
||||
$this->assertSame(
|
||||
511,
|
||||
count(Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2')),
|
||||
);
|
||||
$this->assertDatabaseCount('featured_groups', 7);
|
||||
$this->assertSame([
|
||||
'Productos destacados',
|
||||
'Dormitorio y Blanco',
|
||||
'Electrodomésticos',
|
||||
'Climatización',
|
||||
'Tecnología',
|
||||
'Bicicletas y Aire Libre',
|
||||
'Bazar',
|
||||
], DB::table('featured_groups')->orderBy('group_order')->pluck('group_name')->all());
|
||||
|
||||
DB::table('featured_groups')->orderBy('group_order')->each(function (object $group): void {
|
||||
$this->assertSame('manual', $group->source_type);
|
||||
$this->assertSame('carousel', $group->group_layout);
|
||||
$this->assertSame('column_with_image', $group->product_layout);
|
||||
if ($group->code === 'productos-destacados-smep') {
|
||||
$this->assertNull($group->category_id);
|
||||
} else {
|
||||
$this->assertNotNull($group->category_id);
|
||||
$this->assertDatabaseHas('categorias', [
|
||||
'id' => $group->category_id,
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'categoria_id' => null,
|
||||
'nombre' => $group->group_name,
|
||||
]);
|
||||
}
|
||||
$this->assertGreaterThan(
|
||||
0,
|
||||
DB::table('featured_items')->where('featured_group_id', $group->id)->count(),
|
||||
);
|
||||
});
|
||||
|
||||
$highlightedGroupId = DB::table('featured_groups')
|
||||
->where('code', 'productos-destacados-smep')
|
||||
->value('id');
|
||||
$highlightedSlugs = DB::table('featured_items')
|
||||
->join('catalog_items', 'catalog_items.id', '=', 'featured_items.catalog_item_id')
|
||||
->where('featured_items.featured_group_id', $highlightedGroupId)
|
||||
->orderBy('featured_items.order')
|
||||
->pluck('catalog_items.slug')
|
||||
->all();
|
||||
$variantProductSlugs = DB::table('catalog_items')
|
||||
->whereExists(fn ($query) => $query
|
||||
->selectRaw('1')
|
||||
->from('variantes')
|
||||
->whereColumn('variantes.catalog_item_id', 'catalog_items.id'))
|
||||
->pluck('slug')
|
||||
->all();
|
||||
|
||||
$this->assertCount(13, $highlightedSlugs);
|
||||
$this->assertSame([], array_values(array_diff($variantProductSlugs, $highlightedSlugs)));
|
||||
$this->assertSame(46, DB::table('featured_items')->count());
|
||||
DB::table('featured_groups')
|
||||
->where('code', '!=', 'productos-destacados-smep')
|
||||
->orderBy('id')
|
||||
->each(function (object $group): void {
|
||||
$this->assertLessThanOrEqual(
|
||||
6,
|
||||
DB::table('featured_items')->where('featured_group_id', $group->id)->count(),
|
||||
);
|
||||
});
|
||||
$this->assertDatabaseHas('catalog_items', [
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'slug' => 'motorola-g15',
|
||||
'precio' => 330000,
|
||||
]);
|
||||
$this->assertSame(10, DB::table('inventories')->min('real_stock'));
|
||||
$this->assertSame(10, DB::table('inventories')->max('real_stock'));
|
||||
|
||||
$migration->down();
|
||||
|
||||
foreach ([
|
||||
'featured_items',
|
||||
'featured_groups',
|
||||
'catalog_items_attachments',
|
||||
'attachments',
|
||||
'variant_values',
|
||||
'item_attributes',
|
||||
'variantes',
|
||||
'catalog_items',
|
||||
'inventories',
|
||||
'brands',
|
||||
] as $table) {
|
||||
$this->assertSame(0, DB::table($table)->count(), $table);
|
||||
}
|
||||
$this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2'));
|
||||
}
|
||||
|
||||
public function test_it_limits_existing_category_groups_and_restores_them_on_rollback(): void
|
||||
{
|
||||
$catalogMigration = require database_path(
|
||||
'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php'
|
||||
);
|
||||
$limitMigration = require database_path(
|
||||
'migrations/2026_09_24_050000_limit_mutual_smep_category_featured_groups.php'
|
||||
);
|
||||
|
||||
$catalogMigration->up();
|
||||
|
||||
$limitMigration->down();
|
||||
$this->assertSame(155, DB::table('featured_items')->count());
|
||||
|
||||
$limitMigration->up();
|
||||
$this->assertSame(46, DB::table('featured_items')->count());
|
||||
|
||||
$categoryGroupCounts = DB::table('featured_groups')
|
||||
->leftJoin('featured_items', 'featured_items.featured_group_id', '=', 'featured_groups.id')
|
||||
->where('featured_groups.code', '!=', 'productos-destacados-smep')
|
||||
->groupBy('featured_groups.id', 'featured_groups.group_name', 'featured_groups.group_order')
|
||||
->orderBy('featured_groups.group_order')
|
||||
->selectRaw('featured_groups.group_name, COUNT(featured_items.id) AS item_count')
|
||||
->pluck('item_count', 'featured_groups.group_name')
|
||||
->map(fn ($count): int => (int) $count)
|
||||
->all();
|
||||
|
||||
$this->assertSame([
|
||||
'Dormitorio y Blanco' => 6,
|
||||
'Electrodomésticos' => 6,
|
||||
'Climatización' => 6,
|
||||
'Tecnología' => 6,
|
||||
'Bicicletas y Aire Libre' => 6,
|
||||
'Bazar' => 3,
|
||||
], $categoryGroupCounts);
|
||||
|
||||
$limitMigration->down();
|
||||
$this->assertSame(155, DB::table('featured_items')->count());
|
||||
|
||||
$catalogMigration->down();
|
||||
}
|
||||
|
||||
public function test_it_upgrades_legacy_catalog_images_and_restores_them_on_rollback(): void
|
||||
{
|
||||
$catalogMigration = require database_path(
|
||||
'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php'
|
||||
);
|
||||
$imageMigration = require database_path(
|
||||
'migrations/2026_09_24_060000_optimize_mutual_smep_catalog_images.php'
|
||||
);
|
||||
|
||||
$catalogMigration->up();
|
||||
|
||||
DB::table('attachments')->orderBy('id')->each(function (object $attachment): void {
|
||||
$optimizedPath = (string) $attachment->path;
|
||||
$legacyPath = str_replace('catalog-v2/', 'catalog-v1/', $optimizedPath);
|
||||
$contents = Storage::disk('s3')->get($optimizedPath);
|
||||
|
||||
Storage::disk('s3')->put($legacyPath, $contents.'legacy');
|
||||
Storage::disk('s3')->delete($optimizedPath);
|
||||
DB::table('attachments')->where('id', $attachment->id)->update([
|
||||
'path' => $legacyPath,
|
||||
'size' => strlen($contents.'legacy'),
|
||||
]);
|
||||
});
|
||||
|
||||
$imageMigration->up();
|
||||
|
||||
$this->assertSame(511, count(Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2')));
|
||||
$this->assertSame(0, DB::table('attachments')->where('path', 'like', '%catalog-v1/%')->count());
|
||||
$this->assertSame(511, DB::table('attachments')->where('path', 'like', '%catalog-v2/%')->count());
|
||||
|
||||
$imageMigration->down();
|
||||
|
||||
$this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2'));
|
||||
$this->assertSame(511, DB::table('attachments')->where('path', 'like', '%catalog-v1/%')->count());
|
||||
|
||||
$catalogMigration->down();
|
||||
$this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v1'));
|
||||
}
|
||||
|
||||
private function seedCategoriesAndAttributes(): void
|
||||
{
|
||||
$manifest = json_decode(
|
||||
(string) file_get_contents(database_path('data/mutual_smep_catalog_v1.json')),
|
||||
true,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
);
|
||||
$parents = [];
|
||||
|
||||
foreach ($manifest['items'] as $item) {
|
||||
$parentName = $item['parent_category'];
|
||||
$parentId = $parents[$parentName] ??= DB::table('categorias')->insertGetId([
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'categoria_id' => null,
|
||||
'nombre' => $parentName,
|
||||
]);
|
||||
|
||||
DB::table('categorias')->insertOrIgnore([
|
||||
'tenant_code' => 'mutual_smep',
|
||||
'categoria_id' => $parentId,
|
||||
'nombre' => $item['category'],
|
||||
]);
|
||||
}
|
||||
|
||||
foreach (['color', 'medida_cama', 'almacenamiento', 'memoria_ram', 'rodado'] as $code) {
|
||||
DB::table('attribute')->insert([
|
||||
'tenant_codigo' => 'mutual_smep',
|
||||
'codigo' => $code,
|
||||
'nombre' => $code,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function createSchema(): void
|
||||
{
|
||||
Schema::create('tenants', fn (Blueprint $table) => $table->string('codigo')->primary());
|
||||
Schema::create('categorias', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->unsignedBigInteger('categoria_id')->nullable();
|
||||
$table->string('nombre');
|
||||
$table->unique(['tenant_code', 'categoria_id', 'nombre']);
|
||||
});
|
||||
Schema::create('attribute', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_codigo');
|
||||
$table->string('codigo');
|
||||
$table->string('nombre');
|
||||
});
|
||||
Schema::create('brands', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_codigo');
|
||||
$table->string('nombre');
|
||||
$table->text('descripcion')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('inventories', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('sold_units')->default(0);
|
||||
$table->unsignedBigInteger('refunded_units')->default(0);
|
||||
$table->unsignedInteger('reserved_stock')->default(0);
|
||||
$table->unsignedInteger('real_stock')->default(0);
|
||||
});
|
||||
Schema::create('catalog_items', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->unsignedBigInteger('category_id')->nullable();
|
||||
$table->unsignedBigInteger('brand_id')->nullable();
|
||||
$table->unsignedBigInteger('inventory_id')->nullable();
|
||||
$table->string('type');
|
||||
$table->string('slug');
|
||||
$table->string('nombre');
|
||||
$table->unsignedInteger('group_order')->default(0);
|
||||
$table->text('descripcion')->nullable();
|
||||
$table->decimal('precio', 10, 2);
|
||||
$table->string('inventory_policy');
|
||||
$table->string('inventory_subject');
|
||||
$table->boolean('has_tickets')->default(false);
|
||||
$table->unique(['tenant_code', 'slug']);
|
||||
});
|
||||
Schema::create('variantes', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
|
||||
$table->unsignedBigInteger('inventory_id');
|
||||
$table->text('descripcion')->nullable();
|
||||
$table->decimal('precio', 10, 2)->nullable();
|
||||
});
|
||||
Schema::create('item_attributes', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
|
||||
$table->unsignedBigInteger('attribute_id');
|
||||
$table->boolean('allow_multi_select')->default(false);
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->boolean('show_in_selector')->default(true);
|
||||
$table->string('ticket_label')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('variant_values', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('variant_id')->constrained('variantes')->cascadeOnDelete();
|
||||
$table->foreignId('item_attribute_id')->constrained('item_attributes')->cascadeOnDelete();
|
||||
$table->text('value')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('attachments', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->uuid('key')->unique();
|
||||
$table->string('path');
|
||||
$table->string('filename');
|
||||
$table->string('type');
|
||||
$table->string('mime_type');
|
||||
$table->string('extension')->nullable();
|
||||
$table->unsignedBigInteger('size')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('catalog_items_attachments', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('variant_id')->nullable()->constrained('variantes')->cascadeOnDelete();
|
||||
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
|
||||
$table->foreignId('attachment_id')->constrained('attachments')->cascadeOnDelete();
|
||||
$table->unsignedInteger('orden')->default(0);
|
||||
$table->boolean('is_enabled')->default(true);
|
||||
});
|
||||
Schema::create('featured_groups', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->string('code');
|
||||
$table->string('source_type');
|
||||
$table->unsignedBigInteger('category_id')->nullable();
|
||||
$table->string('product_layout');
|
||||
$table->string('group_layout');
|
||||
$table->string('group_name');
|
||||
$table->unsignedInteger('group_order')->default(0);
|
||||
});
|
||||
Schema::create('featured_items', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('featured_group_id')->constrained('featured_groups')->cascadeOnDelete();
|
||||
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
|
||||
$table->unsignedInteger('order')->default(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
119
tests/Feature/Seeders/MutualSmepAttributeSeederTest.php
Normal file
119
tests/Feature/Seeders/MutualSmepAttributeSeederTest.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Seeders;
|
||||
|
||||
use Database\Seeders\MutualSmepAttributeSeeder;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class MutualSmepAttributeSeederTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Schema::create('tenants', function (Blueprint $table): void {
|
||||
$table->string('codigo')->primary();
|
||||
});
|
||||
Schema::create('attribute', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_codigo');
|
||||
$table->string('codigo');
|
||||
$table->string('nombre');
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->json('metadata_schema')->nullable();
|
||||
$table->string('type');
|
||||
$table->timestamps();
|
||||
$table->unique(['tenant_codigo', 'codigo']);
|
||||
});
|
||||
Schema::create('attribute_options', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('attribute_id')->constrained('attribute')->cascadeOnDelete();
|
||||
$table->string('value');
|
||||
$table->string('label');
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Schema::dropIfExists('attribute_options');
|
||||
Schema::dropIfExists('attribute');
|
||||
Schema::dropIfExists('tenants');
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_it_creates_the_attributes_and_options_idempotently(): void
|
||||
{
|
||||
$this->createTenant('mutual_smep');
|
||||
|
||||
$this->seed(MutualSmepAttributeSeeder::class);
|
||||
$this->seed(MutualSmepAttributeSeeder::class);
|
||||
|
||||
$this->assertSame(5, DB::table('attribute')->where('tenant_codigo', 'mutual_smep')->count());
|
||||
$this->assertSame(25, DB::table('attribute_options')->count());
|
||||
$this->assertDatabaseHas('attribute', [
|
||||
'tenant_codigo' => 'mutual_smep',
|
||||
'codigo' => 'medida_cama',
|
||||
'nombre' => 'Medida',
|
||||
'type' => 'select',
|
||||
'is_required' => false,
|
||||
]);
|
||||
$this->assertDatabaseHas('attribute_options', [
|
||||
'value' => '512 GB',
|
||||
'label' => '512 GB',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_down_only_removes_mutual_smep_attributes(): void
|
||||
{
|
||||
$this->createTenant('mutual_smep');
|
||||
$this->createTenant('other');
|
||||
DB::table('attribute')->insert([
|
||||
'tenant_codigo' => 'other',
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
'type' => 'select',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$seeder = app(MutualSmepAttributeSeeder::class);
|
||||
$seeder->run();
|
||||
$seeder->down();
|
||||
|
||||
$this->assertDatabaseMissing('attribute', ['tenant_codigo' => 'mutual_smep']);
|
||||
$this->assertDatabaseHas('attribute', [
|
||||
'tenant_codigo' => 'other',
|
||||
'codigo' => 'color',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_the_data_migration_runs_and_rolls_back_the_attributes(): void
|
||||
{
|
||||
$this->createTenant('mutual_smep');
|
||||
$migration = require database_path(
|
||||
'migrations/2026_09_24_020000_seed_mutual_smep_attributes.php'
|
||||
);
|
||||
|
||||
$migration->up();
|
||||
|
||||
$this->assertSame(5, DB::table('attribute')->where('tenant_codigo', 'mutual_smep')->count());
|
||||
$this->assertSame(25, DB::table('attribute_options')->count());
|
||||
|
||||
$migration->down();
|
||||
|
||||
$this->assertDatabaseMissing('attribute', ['tenant_codigo' => 'mutual_smep']);
|
||||
$this->assertSame(0, DB::table('attribute_options')->count());
|
||||
}
|
||||
|
||||
private function createTenant(string $code): void
|
||||
{
|
||||
DB::table('tenants')->insert(['codigo' => $code]);
|
||||
}
|
||||
}
|
||||
119
tests/Feature/Seeders/MutualSmepCategorySeederTest.php
Normal file
119
tests/Feature/Seeders/MutualSmepCategorySeederTest.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Seeders;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Models\Category;
|
||||
use Database\Seeders\MutualSmepCategorySeeder;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class MutualSmepCategorySeederTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Schema::create('tenants', function (Blueprint $table): void {
|
||||
$table->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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user