feat: Introduce client management and integration updates

- Added client_id field to StoreTenantRequest and UpdateTenantRequest for tenant management.
- Updated TenantResource to include client_id in the response.
- Created migration to establish clients table and link tenants to clients.
- Migrated existing tenant integrations to client_integrations table.
- Grouped specific tenants under a shared client (OnTicket) in a new migration.
- Updated seeders to reflect new client structure and relationships.
- Adjusted integration configurations to require client instead of tenant.
- Added tests for client integration functionality and ensured existing tests reflect the new client structure.
This commit is contained in:
2026-08-18 16:18:34 -03:00
parent e6785eb5af
commit d73b4d1daf
42 changed files with 931 additions and 392 deletions

View File

@@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('clients', function (Blueprint $table): void {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->timestamps();
});
Schema::table('tenants', function (Blueprint $table): void {
$table->foreignId('client_id')->nullable()->after('id')->constrained('clients')->restrictOnDelete();
});
DB::table('tenants')
->select(['id', 'codigo', 'nombre', 'created_at', 'updated_at'])
->orderBy('id')
->each(function (object $tenant): void {
$clientId = DB::table('clients')->insertGetId([
'code' => $tenant->codigo,
'name' => $tenant->nombre,
'created_at' => $tenant->created_at ?? now(),
'updated_at' => $tenant->updated_at ?? now(),
]);
DB::table('tenants')->where('id', $tenant->id)->update(['client_id' => $clientId]);
});
Schema::table('tenants', function (Blueprint $table): void {
$table->unsignedBigInteger('client_id')->nullable(false)->change();
});
}
public function down(): void
{
Schema::table('tenants', function (Blueprint $table): void {
$table->dropConstrainedForeignId('client_id');
});
Schema::dropIfExists('clients');
}
};

View File

@@ -0,0 +1,107 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('client_integrations', function (Blueprint $table): void {
$table->id();
$table->foreignId('client_id')->constrained('clients')->cascadeOnDelete();
$table->string('integration_code');
$table->longText('integration_data')->nullable();
$table->timestamps();
$table->foreign('integration_code')
->references('integration_code')
->on('integrations')
->cascadeOnDelete();
$table->unique(['client_id', 'integration_code']);
});
DB::table('tenant_integration')
->join('tenants', 'tenants.codigo', '=', 'tenant_integration.tenant_code')
->select([
'tenants.client_id',
'tenant_integration.integration_code',
'tenant_integration.integration_data',
'tenant_integration.created_at',
'tenant_integration.updated_at',
])
->orderBy('tenant_integration.id')
->each(function (object $configuration): void {
DB::table('client_integrations')->updateOrInsert(
[
'client_id' => $configuration->client_id,
'integration_code' => $configuration->integration_code,
],
[
'integration_data' => $configuration->integration_data,
'created_at' => $configuration->created_at,
'updated_at' => $configuration->updated_at,
],
);
});
Schema::table('integrations', function (Blueprint $table): void {
$table->boolean('requires_client_configuration')->default(true);
});
DB::table('integrations')->update([
'requires_client_configuration' => DB::raw('requires_tenant_configuration'),
]);
Schema::table('integrations', function (Blueprint $table): void {
$table->dropColumn('requires_tenant_configuration');
});
Schema::dropIfExists('tenant_integration');
}
public function down(): void
{
Schema::table('integrations', function (Blueprint $table): void {
$table->boolean('requires_tenant_configuration')->default(true);
});
DB::table('integrations')->update([
'requires_tenant_configuration' => DB::raw('requires_client_configuration'),
]);
Schema::table('integrations', function (Blueprint $table): void {
$table->dropColumn('requires_client_configuration');
});
Schema::create('tenant_integration', function (Blueprint $table): void {
$table->id();
$table->string('integration_code');
$table->string('tenant_code');
$table->longText('integration_data')->nullable();
$table->timestamps();
$table->foreign('integration_code')->references('integration_code')->on('integrations')->cascadeOnDelete();
$table->foreign('tenant_code')->references('codigo')->on('tenants')->cascadeOnDelete();
$table->unique(['integration_code', 'tenant_code']);
});
DB::table('client_integrations')
->join('tenants', 'tenants.client_id', '=', 'client_integrations.client_id')
->select([
'tenants.codigo as tenant_code',
'client_integrations.integration_code',
'client_integrations.integration_data',
'client_integrations.created_at',
'client_integrations.updated_at',
])
->orderBy('client_integrations.id')
->each(function (object $configuration): void {
DB::table('tenant_integration')->insert((array) $configuration);
});
Schema::dropIfExists('client_integrations');
}
};

View File

@@ -0,0 +1,101 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
private const CLIENT_CODE = 'onticket';
private const TENANT_CODES = [
'fiesta_futbol_infantil',
'desfile_pura_tendencia',
];
public function up(): void
{
$now = now();
$clientId = DB::table('clients')->where('code', self::CLIENT_CODE)->value('id');
if ($clientId === null) {
$clientId = DB::table('clients')->insertGetId([
'code' => self::CLIENT_CODE,
'name' => 'OnTicket',
'created_at' => $now,
'updated_at' => $now,
]);
} else {
DB::table('clients')->where('id', $clientId)->update([
'name' => 'OnTicket',
'updated_at' => $now,
]);
}
$tenants = DB::table('tenants')
->whereIn('codigo', self::TENANT_CODES)
->orderByRaw("CASE codigo WHEN 'fiesta_futbol_infantil' THEN 0 ELSE 1 END")
->get(['codigo', 'client_id']);
foreach ($tenants as $tenant) {
DB::table('client_integrations')
->where('client_id', $tenant->client_id)
->orderBy('id')
->get()
->each(function (object $integration) use ($clientId): void {
DB::table('client_integrations')->insertOrIgnore([
'client_id' => $clientId,
'integration_code' => $integration->integration_code,
'integration_data' => $integration->integration_data,
'created_at' => $integration->created_at,
'updated_at' => $integration->updated_at,
]);
});
}
DB::table('tenants')
->whereIn('codigo', self::TENANT_CODES)
->update(['client_id' => $clientId]);
}
public function down(): void
{
foreach (self::TENANT_CODES as $tenantCode) {
$tenant = DB::table('tenants')->where('codigo', $tenantCode)->first(['id']);
if (! $tenant) {
continue;
}
DB::table('clients')->insertOrIgnore([
'code' => $tenantCode,
'name' => $tenantCode === 'fiesta_futbol_infantil'
? 'Fiesta Fútbol Infantil'
: 'Desfile Pura Tendencia',
'created_at' => now(),
'updated_at' => now(),
]);
$individualClientId = DB::table('clients')->where('code', $tenantCode)->value('id');
$sharedClientId = DB::table('clients')->where('code', self::CLIENT_CODE)->value('id');
DB::table('client_integrations')
->where('client_id', $sharedClientId)
->orderBy('id')
->get()
->each(function (object $integration) use ($individualClientId): void {
DB::table('client_integrations')->insertOrIgnore([
'client_id' => $individualClientId,
'integration_code' => $integration->integration_code,
'integration_data' => $integration->integration_data,
'created_at' => $integration->created_at,
'updated_at' => $integration->updated_at,
]);
});
DB::table('tenants')->where('id', $tenant->id)->update([
'client_id' => $individualClientId,
]);
}
}
};