208 lines
8.5 KiB
PHP
208 lines
8.5 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Queue\Queueable;
|
|
|
|
use App\Models\Inquiry;
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantEntitlement;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Str;
|
|
|
|
class ProvisionTenantJob implements ShouldQueue
|
|
{
|
|
use Queueable;
|
|
|
|
public Inquiry $inquiry;
|
|
|
|
public function __construct(Inquiry $inquiry)
|
|
{
|
|
$this->inquiry = $inquiry;
|
|
}
|
|
|
|
public function handle(): void
|
|
{
|
|
try {
|
|
$this->inquiry->update(['status' => 'provisioning']);
|
|
|
|
$tenantId = Str::slug($this->inquiry->desired_subdomain);
|
|
|
|
// 1. Create or retrieve Tenant Record (Idempotent)
|
|
$tenant = Tenant::find($tenantId);
|
|
if (!$tenant) {
|
|
$tenant = Tenant::create([
|
|
'id' => $tenantId,
|
|
'tenancy_db_name' => 'tenant_' . $tenantId,
|
|
'company_name' => $this->inquiry->company_name,
|
|
'email' => $this->inquiry->email,
|
|
]);
|
|
}
|
|
|
|
// 2. Create Subdomain Mapping
|
|
$domainHost = config('app.url_domain', parse_url(config('app.url'), PHP_URL_HOST) ?? 'localhost');
|
|
$domainName = $tenantId . '.' . $domainHost;
|
|
\Stancl\Tenancy\Database\Models\Domain::firstOrCreate(
|
|
['domain' => $domainName],
|
|
['tenant_id' => $tenant->id]
|
|
);
|
|
|
|
// 3. Store Tenant Entitlements in Central DB
|
|
$requestedFeatures = $this->inquiry->requested_features ?? [];
|
|
if (empty($requestedFeatures)) {
|
|
$requestedFeatures = \App\Constants\ModuleContract::getAllInquiryKeys();
|
|
}
|
|
|
|
TenantEntitlement::updateOrCreate(
|
|
['tenant_id' => $tenantId],
|
|
[
|
|
'max_employees' => (int) ($this->inquiry->employee_count ?? 25),
|
|
'enabled_modules' => $requestedFeatures,
|
|
'source_inquiry_id' => $this->inquiry->id,
|
|
]
|
|
);
|
|
|
|
// 4. Store tenant module configurations in Central DB
|
|
foreach (\App\Constants\ModuleContract::MODULE_MAP as $inquiryKey => $config) {
|
|
$systemModuleKey = $config['module_key'];
|
|
$isEnabled = in_array($inquiryKey, $requestedFeatures);
|
|
|
|
\App\Models\CentralTenantModule::on('sqlite')->updateOrCreate(
|
|
[
|
|
'tenant_id' => $tenantId,
|
|
'module_key' => $systemModuleKey,
|
|
],
|
|
[
|
|
'enabled' => $isEnabled,
|
|
'enabled_at' => $isEnabled ? now() : null,
|
|
]
|
|
);
|
|
}
|
|
|
|
// 5. Migrate Tenant DB & Seed Initial Setup Permission Matrix
|
|
$dbFile = database_path('tenant_' . $tenantId . '.sqlite');
|
|
|
|
if (app()->environment('testing')) {
|
|
if (file_exists($dbFile)) { @unlink($dbFile); }
|
|
}
|
|
|
|
if (!file_exists($dbFile)) { touch($dbFile); }
|
|
|
|
$tenant->run(function () use ($dbFile, $requestedFeatures) {
|
|
config([
|
|
'database.connections.tenant.database' => $dbFile,
|
|
'database.default' => 'tenant',
|
|
]);
|
|
\Illuminate\Support\Facades\DB::purge('tenant');
|
|
\Illuminate\Support\Facades\DB::reconnect('tenant');
|
|
\Illuminate\Support\Facades\DB::setDefaultConnection('tenant');
|
|
|
|
\Illuminate\Support\Facades\Artisan::call('migrate', [
|
|
'--database' => 'tenant',
|
|
'--path' => 'database/migrations/tenant',
|
|
'--force' => true,
|
|
]);
|
|
|
|
// Seed/update initial admin user with default password 12345678
|
|
$user = \App\Models\User::updateOrCreate(
|
|
['email' => $this->inquiry->email],
|
|
[
|
|
'name' => $this->inquiry->contact_name,
|
|
'password' => Hash::make('12345678'),
|
|
'type' => 'company',
|
|
'status' => 'active',
|
|
'is_enable_login' => 1,
|
|
'email_verified_at' => now(),
|
|
]
|
|
);
|
|
|
|
// Ensure company role exists and is assigned
|
|
$companyRole = \Spatie\Permission\Models\Role::findOrCreate('company', 'web');
|
|
if (method_exists($user, 'assignRole')) {
|
|
$user->assignRole($companyRole);
|
|
}
|
|
|
|
// Seed tenant_modules table inside tenant DB
|
|
foreach (\App\Constants\ModuleContract::MODULE_MAP as $inquiryKey => $config) {
|
|
$systemModuleKey = $config['module_key'];
|
|
$isEnabled = in_array($inquiryKey, $requestedFeatures);
|
|
|
|
\Illuminate\Support\Facades\DB::connection('tenant')->table('tenant_modules')->updateOrInsert(
|
|
['module_key' => $systemModuleKey],
|
|
[
|
|
'enabled' => $isEnabled ? 1 : 0,
|
|
'enabled_at' => $isEnabled ? now() : null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
|
|
// Store max_employees setting inside tenant DB
|
|
\Illuminate\Support\Facades\DB::connection('tenant')->table('settings')->updateOrInsert(
|
|
['key' => 'max_employees'],
|
|
[
|
|
'user_id' => $user->id,
|
|
'value' => (string) ($this->inquiry->employee_count ?? 25),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
|
|
// Grant permissions for enabled modules
|
|
$allowedPermissions = \App\Constants\ModuleContract::getPermissionsForInquiryKeys($requestedFeatures);
|
|
if (method_exists($user, 'syncPermissions')) {
|
|
foreach ($allowedPermissions as $permissionName) {
|
|
\Spatie\Permission\Models\Permission::findOrCreate($permissionName, 'web');
|
|
}
|
|
$user->syncPermissions($allowedPermissions);
|
|
}
|
|
});
|
|
|
|
$centralConn = config('tenancy.database.central_connection', 'sqlite');
|
|
\Illuminate\Support\Facades\DB::setDefaultConnection($centralConn);
|
|
config(['database.default' => $centralConn]);
|
|
\Illuminate\Support\Facades\DB::purge('tenant');
|
|
|
|
// 6. Generate One-Time Hashed Setup Token (Valid 24 Hours)
|
|
$rawToken = Str::random(40);
|
|
$tokenHash = hash('sha256', $rawToken);
|
|
|
|
\App\Models\TenantSetupToken::create([
|
|
'tenant_id' => $tenantId,
|
|
'email' => $this->inquiry->email,
|
|
'token_hash' => $tokenHash,
|
|
'expires_at' => now()->addHours(24),
|
|
]);
|
|
|
|
// Construct setup link: https://{subdomain}.domain.com/setup-admin?token={rawToken}
|
|
$scheme = parse_url(config('app.url'), PHP_URL_SCHEME) ?? 'http';
|
|
$setupUrl = "{$scheme}://{$domainName}/setup-admin?token={$rawToken}";
|
|
|
|
// 7. Dispatch Email Invitation
|
|
try {
|
|
Mail::to($this->inquiry->email)->send(new \App\Mail\TenantSetupInvitationMail($this->inquiry->company_name, $setupUrl));
|
|
} catch (\Exception $mailEx) {
|
|
Log::warning("Could not send setup invitation email to {$this->inquiry->email}: " . $mailEx->getMessage());
|
|
}
|
|
|
|
// 8. Update Inquiry Status to active
|
|
$this->inquiry->update([
|
|
'status' => 'active',
|
|
]);
|
|
|
|
Log::info("Successfully provisioned tenant [{$tenantId}] & dispatched setup token invitation for inquiry ID: {$this->inquiry->id}");
|
|
} catch (\Exception $e) {
|
|
Log::error("Failed to provision tenant for inquiry ID: {$this->inquiry->id}. Error: " . $e->getMessage());
|
|
$this->inquiry->update([
|
|
'status' => 'pending_payment_approval',
|
|
'rejection_reason' => 'Provisioning Error: ' . $e->getMessage(),
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|