$this->moduleVmwareId, 'new_vm_id' => $this->newVmId, ]); $ctx = $rebuildService ->buildContext($this->moduleVmwareId, $this->newOsId, $this->newVmId); // [1] Health check before touching DB. $swapService->healthCheck($ctx); $oldVmId = $ctx->oldVmId; // [2] Atomic DB swap. $swapService->swapDatabaseReferences($ctx); // [3] Notify customer with new credentials. $this->sendCredentialsEmail($this->moduleVmwareId); ReinstallLog::where('id', $this->reinstallLogId)->update([ 'status' => ReinstallLog::STATUS_COMPLETED, 'new_vmware_id' => $ctx->newVmId, 'note' => 'Rebuild OK', 'completed_at' => now(), 'updated_at' => now(), ]); // [4] Schedule old-VM deletion — last step, after everything is committed. RebuildDeleteOldVmJob::dispatch( $this->moduleVmwareId, $oldVmId, $this->reinstallLogId, )->onQueue('default'); Log::info('RebuildFinalizeJob: completed', [ 'module_vmware_id' => $this->moduleVmwareId, 'new_vm_id' => $this->newVmId, 'old_vm_id' => $oldVmId, ]); (new Telegram)->SendRebuildStepNotification($ctx->vmName, $ctx->ip, 'Hoàn tất & chuyển đổi VM', true, 'Rebuild thành công, VM mới đã online.'); } public function failed(Throwable $e): void { Log::error('RebuildFinalizeJob: failed — DB swap aborted, old VM remains attached', [ 'module_vmware_id' => $this->moduleVmwareId, 'new_vm_id' => $this->newVmId, 'error' => $e->getMessage(), ]); ReinstallLog::where('id', $this->reinstallLogId)->update([ 'status' => ReinstallLog::STATUS_FAILED, 'error_message' => $e->getMessage(), 'updated_at' => now(), ]); $vm = ModuleVmware::with('customer')->find($this->moduleVmwareId); (new Telegram)->SendRebuildStepNotification( $vm->name ?? "VM #{$this->moduleVmwareId}", $vm->ip ?? '', 'Hoàn tất & chuyển đổi VM', false, $e->getMessage(), ); VmRebuildFailedJob::dispatch([ 'customer_email' => $vm?->customer?->email, 'customer_name' => $vm?->customer?->fullname, 'vm_name' => $vm->name ?? "VM #{$this->moduleVmwareId}", 'ip' => $vm->ip ?? '', 'module_vmware_id' => $this->moduleVmwareId, ]); } private function sendCredentialsEmail(int $moduleVmwareId): void { $vm = ModuleVmware::with(['customer', 'os.osGroup', 'order.orderItems.service']) ->find($moduleVmwareId); if (!$vm || !$vm->customer?->email) { Log::warning('RebuildFinalizeJob: skip credentials email — VM/customer missing', [ 'module_vmware_id' => $moduleVmwareId, ]); return; } $setting = Setting::where('key', 'setting')->first(); $settingValue = $setting ? json_decode((string) $setting->value) : null; $orderItem = $vm->order?->orderItems?->first(); $months = (int) ($orderItem?->months ?? 0); $osGroup = $vm->os?->osGroup?->name ?? ''; $isWindows = str_contains(strtolower($osGroup), 'windows'); $osUsername = $isWindows ? 'Administrator' : 'root'; $emailData = [ 'company_name' => $settingValue->company ?? 'Thế Giới Số', 'company_logo' => $settingValue->logo ?? '', 'client_name' => $vm->customer->fullname ?? '', 'product_name' => $orderItem?->getProductAttribute()?->name ?? 'Cloud VPS', 'product_billing' => ProductBillingCycle::vietnameseLabelForMonths($months), 'product_price' => $orderItem ? number_format((float) $orderItem->price, 0, ',', '.') : 'N/A', 'service_host' => $vm->ip ?? 'N/A', 'service_username' => $osUsername, 'service_port' => (string) ($vm->port ?? '22'), 'service_password' => $vm->plain_password ?? '(xem tai khoan quan ly)', 'os_name' => $vm->os ? ($osGroup . ' ' . $vm->os->version) : 'Unknown OS', 'service_due_exp' => $vm->next_due_date ? date('d/m/Y', (int) $vm->next_due_date) : 'N/A', 'is_rebuild' => true, ]; $invoice = Invoice::where('order_id', $vm->order_id)->first(); SendInformationVirtualMachine::dispatch($emailData, $vm->customer, $invoice?->id); AdminOrderNotificationJob::dispatch([ 'customer_name' => $vm->customer->fullname ?? '', 'customer_email' => $vm->customer->email, 'client_ip' => null, 'order_num' => $vm->order?->order_num ?? $vm->order?->id ?? 'N/A', 'product_name' => ($orderItem?->getProductAttribute()?->name ?? 'Cloud VPS') . ' (Rebuild)', 'product_type' => 'Cloud VPS Rebuild', 'billing_cycle' => ProductBillingCycle::vietnameseLabelForMonths($months), 'total' => $orderItem?->price ?? 0, 'created_at' => now()->format('d/m/Y H:i:s'), 'ip' => $vm->ip ?? '', 'username' => $osUsername, 'password' => $vm->plain_password ?? '', ]); } }