Merge branch 'refactor/inventory_stock' of https://gitea.quo.ar/tbianchini/shopit-back into refactor/inventory_stock
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use App\Domains\Core\Client\Models\Client;
|
||||
use App\Domains\Ticketing\Event\Models\Event;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AddClientIdToEventsTest extends TestCase
|
||||
{
|
||||
public function test_event_client_overrides_the_tenant_client_when_configured(): void
|
||||
{
|
||||
Schema::create('clients', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('code');
|
||||
$table->string('name');
|
||||
});
|
||||
Schema::create('tenants', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('client_id');
|
||||
$table->string('codigo');
|
||||
});
|
||||
Schema::create('events', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('tenant_code');
|
||||
$table->string('title');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
DB::table('clients')->insert([
|
||||
['id' => 10, 'code' => 'tenant-client', 'name' => 'Tenant Client'],
|
||||
['id' => 11, 'code' => 'event-client', 'name' => 'Event Client'],
|
||||
]);
|
||||
DB::table('tenants')->insert(['id' => 20, 'client_id' => 10, 'codigo' => 'tenant']);
|
||||
DB::table('events')->insert([
|
||||
'id' => 30,
|
||||
'tenant_code' => 'tenant',
|
||||
'title' => 'Event',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$migration = require database_path('migrations/2026_09_28_000000_add_client_id_to_events.php');
|
||||
$migration->up();
|
||||
|
||||
$event = Event::query()->findOrFail(30);
|
||||
$this->assertSame(10, $event->effectiveClient()->id);
|
||||
|
||||
$event->update(['client_id' => 11]);
|
||||
|
||||
$this->assertSame(11, $event->fresh()->effectiveClient()->id);
|
||||
$this->assertTrue(Client::query()->findOrFail(11)->events()->whereKey(30)->exists());
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AddEventIdToPurchasesTest extends TestCase
|
||||
{
|
||||
public function test_it_backfills_the_event_from_purchase_items(): void
|
||||
{
|
||||
Schema::create('events', fn (Blueprint $table) => $table->id());
|
||||
Schema::create('compras', fn (Blueprint $table) => $table->id());
|
||||
Schema::create('catalog_items', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('event_id')->nullable();
|
||||
});
|
||||
Schema::create('compra_items', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('compra_id');
|
||||
$table->foreignId('source_catalog_item_id');
|
||||
});
|
||||
|
||||
DB::table('events')->insert([['id' => 10], ['id' => 11]]);
|
||||
DB::table('compras')->insert([['id' => 20], ['id' => 21]]);
|
||||
DB::table('catalog_items')->insert([
|
||||
['id' => 30, 'event_id' => 10],
|
||||
['id' => 31, 'event_id' => 11],
|
||||
]);
|
||||
DB::table('compra_items')->insert([
|
||||
['compra_id' => 20, 'source_catalog_item_id' => 30],
|
||||
['compra_id' => 21, 'source_catalog_item_id' => 30],
|
||||
['compra_id' => 21, 'source_catalog_item_id' => 31],
|
||||
]);
|
||||
|
||||
$migration = require database_path('migrations/2026_09_28_000100_add_event_id_to_compras.php');
|
||||
$migration->up();
|
||||
|
||||
$this->assertDatabaseHas('compras', ['id' => 20, 'event_id' => 10]);
|
||||
$this->assertDatabaseHas('compras', ['id' => 21, 'event_id' => null]);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AssignArrufoEventToPymeRuralClientTest extends TestCase
|
||||
{
|
||||
public function test_it_assigns_only_the_arrufo_event_to_pyme_rural(): void
|
||||
{
|
||||
Schema::create('clients', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('code');
|
||||
});
|
||||
Schema::create('events', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('client_id')->nullable();
|
||||
$table->string('tenant_code');
|
||||
$table->string('title');
|
||||
});
|
||||
|
||||
DB::table('clients')->insert([
|
||||
['id' => 10, 'code' => 'pyme_rural'],
|
||||
['id' => 11, 'code' => 'onticket'],
|
||||
]);
|
||||
DB::table('events')->insert([
|
||||
[
|
||||
'id' => 20,
|
||||
'client_id' => null,
|
||||
'tenant_code' => 'onticket',
|
||||
'title' => '26.º Fiesta de la Tradición y 4.º Encuentro de Agrupaciones Gauchas',
|
||||
],
|
||||
[
|
||||
'id' => 21,
|
||||
'client_id' => 11,
|
||||
'tenant_code' => 'onticket',
|
||||
'title' => 'Otro evento',
|
||||
],
|
||||
]);
|
||||
|
||||
$migration = require database_path('migrations/2026_09_28_000200_assign_arrufo_event_to_pyme_rural_client.php');
|
||||
$migration->up();
|
||||
|
||||
$this->assertDatabaseHas('events', ['id' => 20, 'client_id' => 10]);
|
||||
$this->assertDatabaseHas('events', ['id' => 21, 'client_id' => 11]);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Migrations;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Enums\InventoryMovementOperation;
|
||||
use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||
use App\Domains\Commerce\Catalog\Models\InventoryMovement;
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
class InventoryStockMovementMigrationTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Schema::create('users', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('inventories', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('sold_units')->default(0);
|
||||
$table->unsignedInteger('reserved_stock')->default(0);
|
||||
$table->unsignedInteger('real_stock')->default(0);
|
||||
$table->unsignedBigInteger('refunded_units')->default(0);
|
||||
$table->unsignedInteger('entry_reserved_stock')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Schema::disableForeignKeyConstraints();
|
||||
Schema::dropIfExists('inventory_movements');
|
||||
Schema::dropIfExists('inventories');
|
||||
Schema::dropIfExists('users');
|
||||
Schema::enableForeignKeyConstraints();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_migration_and_rollback_preserve_the_available_balance(): void
|
||||
{
|
||||
DB::table('inventories')->insert([
|
||||
'id' => 17,
|
||||
'real_stock' => 10,
|
||||
'sold_units' => 3,
|
||||
'refunded_units' => 1,
|
||||
'reserved_stock' => 2,
|
||||
'entry_reserved_stock' => 1,
|
||||
]);
|
||||
|
||||
$stockMigration = $this->stockMigration();
|
||||
$movementMigration = $this->movementMigration();
|
||||
$stockMigration->up();
|
||||
$movementMigration->up();
|
||||
|
||||
$inventory = Inventory::findOrFail(17);
|
||||
$this->assertSame(12, $inventory->real_stock);
|
||||
$this->assertSame(7, $inventory->availableStock());
|
||||
$this->assertTrue(Schema::hasTable('inventory_movements'));
|
||||
|
||||
$inventory->adjustAvailableStock(2);
|
||||
$this->assertSame(9, $inventory->availableStock());
|
||||
|
||||
$movementMigration->down();
|
||||
$stockMigration->down();
|
||||
|
||||
$this->assertFalse(Schema::hasColumn('inventories', 'available_stock'));
|
||||
$this->assertFalse(Schema::hasTable('inventory_movements'));
|
||||
$this->assertSame(12, Inventory::findOrFail(17)->real_stock);
|
||||
$this->assertSame(9, Inventory::findOrFail(17)->availableStock());
|
||||
}
|
||||
|
||||
public function test_movements_capture_counters_actor_and_idempotency(): void
|
||||
{
|
||||
$this->stockMigration()->up();
|
||||
$this->movementMigration()->up();
|
||||
DB::table('users')->insert(['id' => 5]);
|
||||
$user = User::query()->findOrFail(5);
|
||||
|
||||
$inventory = Inventory::query()->create(['real_stock' => 10]);
|
||||
$inventory->recordInitialization($user);
|
||||
$inventory->adjustAvailableStock(3, $user, '955d2afb-1e36-46df-a9db-d9ee31ed5eb4');
|
||||
$inventory->adjustAvailableStock(3, $user, '955d2afb-1e36-46df-a9db-d9ee31ed5eb4');
|
||||
$inventory->reserve(2, true);
|
||||
$inventory->buy(2, true);
|
||||
$inventory->refundStock(1, $user);
|
||||
|
||||
$this->assertSame(12, $inventory->availableStock());
|
||||
$this->assertCount(5, $inventory->movements()->get());
|
||||
|
||||
$adjustment = InventoryMovement::query()
|
||||
->where('operation', InventoryMovementOperation::StockIncreased->value)
|
||||
->firstOrFail();
|
||||
$this->assertSame(3, $adjustment->available_stock_delta);
|
||||
$this->assertSame(10, $adjustment->available_stock_before);
|
||||
$this->assertSame(13, $adjustment->available_stock_after);
|
||||
$this->assertSame(['real_stock' => 3], $adjustment->counter_deltas);
|
||||
$this->assertSame(5, $adjustment->responsible_user_id);
|
||||
|
||||
$purchase = InventoryMovement::query()
|
||||
->where('operation', InventoryMovementOperation::PurchaseCommitted->value)
|
||||
->firstOrFail();
|
||||
$this->assertSame(0, $purchase->available_stock_delta);
|
||||
$this->assertSame(['reserved_stock' => -2, 'sold_units' => 2], $purchase->counter_deltas);
|
||||
}
|
||||
|
||||
public function test_migration_rejects_an_inventory_with_negative_available_stock(): void
|
||||
{
|
||||
DB::table('inventories')->insert([
|
||||
'id' => 23,
|
||||
'real_stock' => 1,
|
||||
'reserved_stock' => 2,
|
||||
]);
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'stock disponible actual es negativo. Inventarios detectados (máximo 20): '
|
||||
.'id=23 [real=1, vendidas=0, reintegradas=0, reservadas=2, reservas_entradas=0, resultado=-1]'
|
||||
);
|
||||
|
||||
$this->stockMigration()->up();
|
||||
}
|
||||
|
||||
private function stockMigration(): Migration
|
||||
{
|
||||
return require database_path('migrations/2026_09_25_000000_generate_available_inventory_stock.php');
|
||||
}
|
||||
|
||||
private function movementMigration(): Migration
|
||||
{
|
||||
return require database_path('migrations/2026_09_25_010000_create_inventory_movements_table.php');
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
<?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']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
<?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']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
<?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);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user