feat: introduce multi-tenancy by adding a structures table, structure_id to key models, and updating seeders for structure management.

This commit is contained in:
jeremy bayse
2026-02-21 20:38:40 +01:00
parent 6a3de5847d
commit abca346b3e
7 changed files with 122 additions and 40 deletions

View File

@@ -8,4 +8,6 @@ use App\Traits\BelongsToStructure;
class Role extends SpatieRole class Role extends SpatieRole
{ {
use BelongsToStructure; use BelongsToStructure;
protected $fillable = ['name', 'guard_name', 'structure_id'];
} }

View File

@@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('agents', function (Blueprint $table) {
$table->foreignId('structure_id')->nullable()->constrained()->onDelete('cascade');
});
// Rattacher les agents existants au CABM
\Illuminate\Support\Facades\DB::table('agents')->whereNull('structure_id')->update(['structure_id' => 1]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('agents', function (Blueprint $table) {
$table->dropForeign(['structure_id']);
$table->dropColumn('structure_id');
});
}
};

View File

@@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$tables = [
'users',
'integration_templates',
'services',
'integration_requests',
'comments',
'service_tasks'
];
foreach ($tables as $tableName) {
if (Schema::hasTable($tableName)) {
Schema::table($tableName, function (Blueprint $table) {
// Si la colonne n'existe pas déjà (sécurité)
if (!Schema::hasColumn($table->getTable(), 'structure_id')) {
$table->foreignId('structure_id')->nullable()->constrained()->onDelete('cascade');
}
});
}
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tables = [
'users',
'integration_templates',
'services',
'integration_requests',
'comments',
'service_tasks'
];
foreach ($tables as $tableName) {
if (Schema::hasTable($tableName)) {
Schema::table($tableName, function (Blueprint $table) {
if (Schema::hasColumn($table->getTable(), 'structure_id')) {
$table->dropForeign([$table->getTable() . '_structure_id_foreign']);
$table->dropColumn('structure_id');
}
});
}
}
}
};

View File

@@ -13,18 +13,49 @@ class DatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// 1. Initialiser la structure par défaut (CABM)
$cabm = \App\Models\Structure::firstOrCreate([
'slug' => 'cabm'
], [
'name' => 'CABM',
'is_active' => true,
]);
// 2. Définir le contexte global pour les seeders suivants
config(['tenant.structure_id' => $cabm->id]);
app()[\Spatie\Permission\PermissionRegistrar::class]->setPermissionsTeamId($cabm->id);
// 3. Appeler les seeders de base (ils utiliseront le contexte CABM)
$this->call([ $this->call([
ServiceSeeder::class, ServiceSeeder::class,
RolesAndPermissionsSeeder::class, RolesAndPermissionsSeeder::class,
IntegrationTemplateSeeder::class, IntegrationTemplateSeeder::class,
]); ]);
$admin = User::factory()->create([ // 4. Créer le compte Admin lié à cette structure
$admin = User::withoutGlobalScope('structure')->updateOrCreate(
['email' => 'admin@admin.com'],
[
'name' => 'Admin User', 'name' => 'Admin User',
'email' => 'admin@admin.com',
'password' => bcrypt('password'), 'password' => bcrypt('password'),
]); 'structure_id' => $cabm->id,
]
);
// 5. Lui assigner le rôle Admin (dans le contexte de CABM)
if (!$admin->hasRole('Admin')) {
$admin->assignRole('Admin'); $admin->assignRole('Admin');
} }
// 6. Créer le rôle SuperAdmin lié à CABM pour le seeder
$superAdminRole = \App\Models\Role::updateOrCreate(
['name' => 'SuperAdmin'],
['guard_name' => 'web', 'structure_id' => $cabm->id]
);
// L'admin de base sera aussi SuperAdmin
if (!$admin->hasRole('SuperAdmin')) {
$admin->assignRole('SuperAdmin');
}
}
} }

View File

@@ -3,7 +3,7 @@
namespace Database\Seeders; namespace Database\Seeders;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role; use App\Models\Role;
use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Permission;
class RolesAndPermissionsSeeder extends Seeder class RolesAndPermissionsSeeder extends Seeder
@@ -13,6 +13,11 @@ class RolesAndPermissionsSeeder extends Seeder
// Reset cached roles and permissions // Reset cached roles and permissions
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// Rétablir le context multi-tenant s'il est défini en config
if (config('tenant.structure_id')) {
app()[\Spatie\Permission\PermissionRegistrar::class]->setPermissionsTeamId(config('tenant.structure_id'));
}
// Create permissions // Create permissions
$permissions = [ $permissions = [
'create integration', 'create integration',

View File

@@ -8,6 +8,7 @@ use App\Models\Role;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class SaaSTenantSeeder extends Seeder class SaaSTenantSeeder extends Seeder
{ {
@@ -28,7 +29,7 @@ class SaaSTenantSeeder extends Seeder
DB::table('agents')->whereNull('structure_id')->update(['structure_id' => $cabm->id]); DB::table('agents')->whereNull('structure_id')->update(['structure_id' => $cabm->id]);
// 3. Mettre à jour tous les services, templates, users, etc. vers cette structure // 3. Mettre à jour tous les services, templates, users, etc. vers cette structure
$tables = ['services', 'integration_templates', 'integration_requests', 'users', 'service_tasks']; $tables = ['services', 'integration_templates', 'integration_requests', 'users', 'service_tasks', 'comments'];
foreach ($tables as $table) { foreach ($tables as $table) {
if (Schema::hasColumn($table, 'structure_id')) { if (Schema::hasColumn($table, 'structure_id')) {
DB::table($table)->whereNull('structure_id')->update(['structure_id' => $cabm->id]); DB::table($table)->whereNull('structure_id')->update(['structure_id' => $cabm->id]);
@@ -41,6 +42,21 @@ class SaaSTenantSeeder extends Seeder
['guard_name' => 'web', 'structure_id' => null] ['guard_name' => 'web', 'structure_id' => null]
); );
// 5. Créer l'utilisateur Admin par défaut s'il n'existe pas
$admin = User::withoutGlobalScope('structure')->firstOrCreate(
['email' => 'admin@admin.com'],
[
'name' => 'Super Admin',
'password' => Hash::make('password'),
'structure_id' => $cabm->id
]
);
// Lui assigner le rôle SuperAdmin
if (!$admin->hasRole('SuperAdmin')) {
$admin->assignRole($superAdminRole);
}
$this->command->info('Migration vers le mode SaaS terminée. Structure par défaut : CABM.'); $this->command->info('Migration vers le mode SaaS terminée. Structure par défaut : CABM.');
} }
} }