From 14c81fec1beb70c0ee15d089efccd9976966844d Mon Sep 17 00:00:00 2001 From: ncoronel Date: Mon, 21 Sep 2026 09:35:40 -0300 Subject: [PATCH] feat(migrations): add migration for grouping Pyme Rural tenants and updating client associations --- ..._09_21_020000_group_pyme_rural_tenants.php | 93 +++++++++++++ .../seeders/DesfilePuraTendenciaSeeder.php | 10 +- database/seeders/TenantSeeder.php | 15 +- storage/framework/lsp-52251fd8d9839a2d.php | 130 ++++++++++++++++++ storage/framework/lsp-cd371f347782ba98.php | 130 ++++++++++++++++++ .../Migrations/GroupPymeRuralTenantsTest.php | 109 +++++++++++++++ .../DesfilePuraTendenciaSeederTest.php | 8 ++ tests/Feature/Seeders/TenantSeederTest.php | 6 +- 8 files changed, 490 insertions(+), 11 deletions(-) create mode 100644 database/migrations/2026_09_21_020000_group_pyme_rural_tenants.php create mode 100644 storage/framework/lsp-52251fd8d9839a2d.php create mode 100644 storage/framework/lsp-cd371f347782ba98.php create mode 100644 tests/Feature/Migrations/GroupPymeRuralTenantsTest.php diff --git a/database/migrations/2026_09_21_020000_group_pyme_rural_tenants.php b/database/migrations/2026_09_21_020000_group_pyme_rural_tenants.php new file mode 100644 index 0000000..d0138f7 --- /dev/null +++ b/database/migrations/2026_09_21_020000_group_pyme_rural_tenants.php @@ -0,0 +1,93 @@ +clientId(self::CLIENT_CODE, self::CLIENT_NAME); + $sourceClientIds = DB::table('tenants') + ->whereIn('codigo', self::TENANT_CODES) + ->pluck('client_id') + ->unique(); + + $this->copyIntegrations($sourceClientIds, $clientId); + + DB::table('tenants') + ->whereIn('codigo', self::TENANT_CODES) + ->update(['client_id' => $clientId]); + }); + } + + public function down(): void + { + DB::transaction(function (): void { + $previousClientId = $this->clientId(self::PREVIOUS_CLIENT_CODE, 'OnTicket'); + $pymeRuralClientId = DB::table('clients') + ->where('code', self::CLIENT_CODE) + ->value('id'); + + if ($pymeRuralClientId !== null) { + $this->copyIntegrations(collect([$pymeRuralClientId]), $previousClientId); + } + + DB::table('tenants') + ->whereIn('codigo', self::TENANT_CODES) + ->update(['client_id' => $previousClientId]); + }); + } + + private function clientId(string $code, string $name): int + { + $clientId = DB::table('clients')->where('code', $code)->value('id'); + + if ($clientId === null) { + return (int) DB::table('clients')->insertGetId([ + 'code' => $code, + 'name' => $name, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + DB::table('clients')->where('id', $clientId)->update([ + 'name' => $name, + 'updated_at' => now(), + ]); + + return (int) $clientId; + } + + private function copyIntegrations(iterable $sourceClientIds, int $targetClientId): void + { + foreach ($sourceClientIds as $sourceClientId) { + DB::table('client_integrations') + ->where('client_id', $sourceClientId) + ->orderBy('id') + ->get() + ->each(function (object $integration) use ($targetClientId): void { + DB::table('client_integrations')->insertOrIgnore([ + 'client_id' => $targetClientId, + 'integration_code' => $integration->integration_code, + 'integration_instance_id' => $integration->integration_instance_id, + 'created_at' => $integration->created_at, + 'updated_at' => $integration->updated_at, + ]); + }); + } + } +}; diff --git a/database/seeders/DesfilePuraTendenciaSeeder.php b/database/seeders/DesfilePuraTendenciaSeeder.php index a9f9902..a16c397 100644 --- a/database/seeders/DesfilePuraTendenciaSeeder.php +++ b/database/seeders/DesfilePuraTendenciaSeeder.php @@ -12,9 +12,9 @@ use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Domains\Commerce\Catalog\Models\FeaturedGroup; use App\Domains\Commerce\Catalog\Services\CatalogService; use App\Domains\Core\Client\Models\Client; -use App\Domains\Ticketing\Desfile\Services\InvitationPurchaseProvisioner; use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Core\Tenant\Services\TenantService; +use App\Domains\Ticketing\Desfile\Services\InvitationPurchaseProvisioner; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\DB; @@ -22,7 +22,7 @@ use RuntimeException; class DesfilePuraTendenciaSeeder extends Seeder { - private const ONTICKET_CLIENT_CODE = 'onticket'; + private const PYME_RURAL_CLIENT_CODE = 'pyme_rural'; private const TENANT_CODE = 'desfile_pura_tendencia'; @@ -34,9 +34,9 @@ class DesfilePuraTendenciaSeeder extends Seeder public function run(): void { - $client = Client::query()->firstOrCreate( - ['code' => self::ONTICKET_CLIENT_CODE], - ['name' => 'OnTicket'], + $client = Client::query()->updateOrCreate( + ['code' => self::PYME_RURAL_CLIENT_CODE], + ['name' => 'Pyme Rural'], ); $tenant = Tenant::query()->where('codigo', self::TENANT_CODE)->first(); diff --git a/database/seeders/TenantSeeder.php b/database/seeders/TenantSeeder.php index dacb6b6..059e7f3 100644 --- a/database/seeders/TenantSeeder.php +++ b/database/seeders/TenantSeeder.php @@ -2,13 +2,13 @@ namespace Database\Seeders; -use App\Shared\Attachable\Models\Attachment; -use App\Shared\Attachable\Services\AttachmentService; use App\Domains\Core\Client\Models\Client; -use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Core\Tenant\Models\AdminWebsiteType; +use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Core\Tenant\Services\TenantService; use App\Domains\Core\Tenant\Services\WebsiteExtraService; +use App\Shared\Attachable\Models\Attachment; +use App\Shared\Attachable\Services\AttachmentService; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; use RuntimeException; @@ -18,6 +18,8 @@ class TenantSeeder extends Seeder { private const ONTICKET_CLIENT_CODE = 'onticket'; + private const PYME_RURAL_CLIENT_CODE = 'pyme_rural'; + private const SONDER_CLIENT_CODE = 'sonder'; private const SOCIAL_MEDIA = [ @@ -60,6 +62,11 @@ class TenantSeeder extends Seeder ['name' => 'OnTicket'], ); + $pymeRuralClient = Client::query()->updateOrCreate( + ['code' => self::PYME_RURAL_CLIENT_CODE], + ['name' => 'Pyme Rural'], + ); + $onTicketType = AdminWebsiteType::query()->where('codigo', 'onticket')->firstOrFail(); $onTicketTenant = Tenant::query()->where('codigo', 'onticket')->first(); @@ -199,7 +206,7 @@ class TenantSeeder extends Seeder ->delete(); $this->tenantService->create([ - 'client_id' => $onTicketClient->id, + 'client_id' => $pymeRuralClient->id, 'codigo' => 'fiesta_futbol_infantil', 'nombre' => 'Fiesta FĂștbol Infantil', 'dominio' => $fiestaDomain, diff --git a/storage/framework/lsp-52251fd8d9839a2d.php b/storage/framework/lsp-52251fd8d9839a2d.php new file mode 100644 index 0000000..f14d849 --- /dev/null +++ b/storage/framework/lsp-52251fd8d9839a2d.php @@ -0,0 +1,130 @@ +hasDefaultValue()) { +return ['default' => $property->getDefaultValue()]; +} + +if ($parameter?->isDefaultValueAvailable()) { +return ['default' => $parameter->getDefaultValue()]; +} + +return []; +} + +public static function formatDefaultValue(mixed $value): mixed +{ +return match (true) { +is_array($value) => 'array(...)', +$value instanceof UnitEnum => get_class($value) . '::' . $value->name, +$value instanceof Closure => 'Closure', +is_object($value) => get_class($value), +is_string($value) => var_export($value, true), +is_null($value) => 'null', +is_bool($value) => $value ? 'true' : 'false', +default => $value, +}; +} +} + +use Pest\Expectation; +use Pest\TestSuite; + +$pest = new class +{ +public function __construct() +{ +if ($this->isInstalled()) { +$this->boot(); +} +} + +public function isInstalled(): bool +{ +return class_exists(TestSuite::class); +} + +protected function boot(): void +{ +require_once base_path('vendor/pestphp/pest/overrides/Runner/TestSuiteLoader.php'); + +TestSuite::getInstance(base_path(), 'tests'); + +if (file_exists($pestFile = base_path('tests/Pest.php'))) { +require_once $pestFile; +} +} + +public function config(): ?array +{ +if (!$this->isInstalled()) { +return null; +} + +return [ +'uses' => $this->uses(), +'expectations' => $this->expectations(), +]; +} + +protected function uses(): array +{ +if (is_null($instance = TestSuite::getInstance())) { +return []; +} + +$reflection = new ReflectionProperty($instance->tests, 'uses'); +$uses = $reflection->getValue($instance->tests); + +return collect($uses)->map(function (array $use, string $path) { +[$classOrTraits] = $use; + +return [ +'path' => LspHelper::relativePath($path), +'classes' => array_values(array_filter($classOrTraits, fn ($c) => class_exists($c))), +'traits' => array_values(array_filter($classOrTraits, fn ($c) => trait_exists($c))), +]; +})->values()->all(); +} + +protected function expectations(): array +{ +$reflection = new ReflectionProperty(Expectation::class, 'extends'); +$extends = $reflection->getValue(); + +return collect($extends)->map(function (Closure $closure, string $name) { +$parameters = collect((new ReflectionFunction($closure))->getParameters()) +->map(function (ReflectionParameter $param) { +$type = $param->hasType() ? $param->getType() . ' ' : ''; + +$default = $param->isOptional() && $param->isDefaultValueAvailable() +? ' = ' . var_export($param->getDefaultValue(), true) +: ''; + +return $type . '$' . $param->getName() . $default; +}) +->join(', '); + +return compact('name', 'parameters'); +})->values()->all(); +} +}; + +echo json_encode($pest->config()); diff --git a/storage/framework/lsp-cd371f347782ba98.php b/storage/framework/lsp-cd371f347782ba98.php new file mode 100644 index 0000000..f14d849 --- /dev/null +++ b/storage/framework/lsp-cd371f347782ba98.php @@ -0,0 +1,130 @@ +hasDefaultValue()) { +return ['default' => $property->getDefaultValue()]; +} + +if ($parameter?->isDefaultValueAvailable()) { +return ['default' => $parameter->getDefaultValue()]; +} + +return []; +} + +public static function formatDefaultValue(mixed $value): mixed +{ +return match (true) { +is_array($value) => 'array(...)', +$value instanceof UnitEnum => get_class($value) . '::' . $value->name, +$value instanceof Closure => 'Closure', +is_object($value) => get_class($value), +is_string($value) => var_export($value, true), +is_null($value) => 'null', +is_bool($value) => $value ? 'true' : 'false', +default => $value, +}; +} +} + +use Pest\Expectation; +use Pest\TestSuite; + +$pest = new class +{ +public function __construct() +{ +if ($this->isInstalled()) { +$this->boot(); +} +} + +public function isInstalled(): bool +{ +return class_exists(TestSuite::class); +} + +protected function boot(): void +{ +require_once base_path('vendor/pestphp/pest/overrides/Runner/TestSuiteLoader.php'); + +TestSuite::getInstance(base_path(), 'tests'); + +if (file_exists($pestFile = base_path('tests/Pest.php'))) { +require_once $pestFile; +} +} + +public function config(): ?array +{ +if (!$this->isInstalled()) { +return null; +} + +return [ +'uses' => $this->uses(), +'expectations' => $this->expectations(), +]; +} + +protected function uses(): array +{ +if (is_null($instance = TestSuite::getInstance())) { +return []; +} + +$reflection = new ReflectionProperty($instance->tests, 'uses'); +$uses = $reflection->getValue($instance->tests); + +return collect($uses)->map(function (array $use, string $path) { +[$classOrTraits] = $use; + +return [ +'path' => LspHelper::relativePath($path), +'classes' => array_values(array_filter($classOrTraits, fn ($c) => class_exists($c))), +'traits' => array_values(array_filter($classOrTraits, fn ($c) => trait_exists($c))), +]; +})->values()->all(); +} + +protected function expectations(): array +{ +$reflection = new ReflectionProperty(Expectation::class, 'extends'); +$extends = $reflection->getValue(); + +return collect($extends)->map(function (Closure $closure, string $name) { +$parameters = collect((new ReflectionFunction($closure))->getParameters()) +->map(function (ReflectionParameter $param) { +$type = $param->hasType() ? $param->getType() . ' ' : ''; + +$default = $param->isOptional() && $param->isDefaultValueAvailable() +? ' = ' . var_export($param->getDefaultValue(), true) +: ''; + +return $type . '$' . $param->getName() . $default; +}) +->join(', '); + +return compact('name', 'parameters'); +})->values()->all(); +} +}; + +echo json_encode($pest->config()); diff --git a/tests/Feature/Migrations/GroupPymeRuralTenantsTest.php b/tests/Feature/Migrations/GroupPymeRuralTenantsTest.php new file mode 100644 index 0000000..23c73c1 --- /dev/null +++ b/tests/Feature/Migrations/GroupPymeRuralTenantsTest.php @@ -0,0 +1,109 @@ +createSchema(); + + $now = now(); + $onTicketClientId = DB::table('clients')->insertGetId([ + 'code' => 'onticket', + 'name' => 'OnTicket', + 'created_at' => $now, + 'updated_at' => $now, + ]); + DB::table('tenants')->insert([ + ['client_id' => $onTicketClientId, 'codigo' => 'onticket'], + ['client_id' => $onTicketClientId, 'codigo' => 'fiesta_futbol_infantil'], + ['client_id' => $onTicketClientId, 'codigo' => 'desfile_pura_tendencia'], + ]); + DB::table('integrations')->insert([ + 'integration_code' => 'email', + 'name' => 'Email', + ]); + $instanceId = DB::table('integration_instances')->insertGetId([ + 'integration_code' => 'email', + 'name' => 'OnTicket email', + 'integration_data' => null, + 'created_at' => $now, + 'updated_at' => $now, + ]); + DB::table('client_integrations')->insert([ + 'client_id' => $onTicketClientId, + 'integration_code' => 'email', + 'integration_instance_id' => $instanceId, + 'created_at' => $now, + 'updated_at' => $now, + ]); + + $migration = require database_path( + 'migrations/2026_09_21_020000_group_pyme_rural_tenants.php' + ); + $migration->up(); + + $pymeRuralClient = DB::table('clients')->where('code', 'pyme_rural')->sole(); + $this->assertSame('Pyme Rural', $pymeRuralClient->name); + $this->assertSame(2, DB::table('tenants') + ->whereIn('codigo', ['fiesta_futbol_infantil', 'desfile_pura_tendencia']) + ->where('client_id', $pymeRuralClient->id) + ->count()); + $this->assertSame($onTicketClientId, DB::table('tenants') + ->where('codigo', 'onticket') + ->value('client_id')); + + $this->assertDatabaseHas('client_integrations', [ + 'client_id' => $pymeRuralClient->id, + 'integration_code' => 'email', + 'integration_instance_id' => $instanceId, + ]); + + $migration->down(); + + $this->assertSame(2, DB::table('tenants') + ->whereIn('codigo', ['fiesta_futbol_infantil', 'desfile_pura_tendencia']) + ->where('client_id', $onTicketClientId) + ->count()); + } + + private function createSchema(): void + { + Schema::create('clients', function (Blueprint $table): void { + $table->id(); + $table->string('code')->unique(); + $table->string('name'); + $table->timestamps(); + }); + Schema::create('tenants', function (Blueprint $table): void { + $table->id(); + $table->foreignId('client_id'); + $table->string('codigo')->unique(); + }); + Schema::create('integrations', function (Blueprint $table): void { + $table->string('integration_code')->unique(); + $table->string('name'); + }); + Schema::create('integration_instances', function (Blueprint $table): void { + $table->id(); + $table->string('integration_code'); + $table->string('name'); + $table->longText('integration_data')->nullable(); + $table->timestamps(); + }); + Schema::create('client_integrations', function (Blueprint $table): void { + $table->id(); + $table->foreignId('client_id'); + $table->string('integration_code'); + $table->foreignId('integration_instance_id'); + $table->timestamps(); + $table->unique(['client_id', 'integration_code']); + }); + } +} diff --git a/tests/Feature/Seeders/DesfilePuraTendenciaSeederTest.php b/tests/Feature/Seeders/DesfilePuraTendenciaSeederTest.php index 14dae15..6dc80cb 100644 --- a/tests/Feature/Seeders/DesfilePuraTendenciaSeederTest.php +++ b/tests/Feature/Seeders/DesfilePuraTendenciaSeederTest.php @@ -55,6 +55,14 @@ class DesfilePuraTendenciaSeederTest extends TestCase 'site_title' => 'Fiesta FĂștbol Infantil', ]); + $pymeRuralClientId = DB::table('clients')->where('code', 'pyme_rural')->value('id'); + $this->assertNotNull($pymeRuralClientId); + $this->assertSame('Pyme Rural', DB::table('clients')->where('id', $pymeRuralClientId)->value('name')); + $this->assertSame(2, DB::table('tenants') + ->whereIn('codigo', ['fiesta_futbol_infantil', 'desfile_pura_tendencia']) + ->where('client_id', $pymeRuralClientId) + ->count()); + foreach ([ 'fiesta_futbol_infantil' => 'futbol_infantil_favicon.png', 'desfile_pura_tendencia' => 'pura_tendencia_favicon.png', diff --git a/tests/Feature/Seeders/TenantSeederTest.php b/tests/Feature/Seeders/TenantSeederTest.php index 7106a60..df9157c 100644 --- a/tests/Feature/Seeders/TenantSeederTest.php +++ b/tests/Feature/Seeders/TenantSeederTest.php @@ -2,9 +2,9 @@ namespace Tests\Feature\Seeders; -use App\Shared\Attachable\Models\Attachment; -use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Core\Tenant\Models\AdminWebsiteType; +use App\Domains\Core\Tenant\Models\Tenant; +use App\Shared\Attachable\Models\Attachment; use Database\Seeders\SocialMediaSeeder; use Database\Seeders\TenantSeeder; use Database\Seeders\WebsiteTypeSeeder; @@ -150,6 +150,8 @@ class TenantSeederTest extends TestCase ->with('websiteExtras.websiteTypeExtra') ->sole(); + $this->assertSame('pyme_rural', $fiesta->client->code); + $this->assertSame('Pyme Rural', $fiesta->client->name); $this->assertSame('onticket', $fiesta->storefront_website_type_code); $this->assertSame('onticket', $fiesta->admin_website_type_code); $this->assertSame('full', $fiesta->cart_editing_policy->value);