$this->orderId, 'customer_id' => $this->customerId, ]); $orderItems = OrderItem::where('order_id', $this->orderId)->get(); if ($orderItems->isEmpty()) { $this->failStep('order_items', 'Không tìm thấy order_items cho order #' . $this->orderId, $telegram); return; } foreach ($orderItems as $item) { $this->processItem($item, $vCenterService, $telegram); } } private function processItem(OrderItem $item, vCenter $vCenterService, Telegram $telegram): void { $serverName = $item->server_name; if (empty($serverName)) { $this->failStep('order_item', "OrderItem #{$item->id} không có server_name", $telegram); return; } // ── Already linked and has IP? Skip ───────────────────────────── $existingService = Service::where('order_item_id', $item->id)->whereNull('deleted_at')->first(); if ($existingService && $existingService->vm_id) { $vmware = ModuleVmware::find($existingService->vm_id); if ($vmware && $vmware->ip) { Log::info('RetryProvisioningOrchestrator: already linked with IP, skipping', [ 'order_item_id' => $item->id, 'service_id' => $existingService->id, 'vm_id' => $existingService->vm_id, 'ip' => $vmware->ip, ]); return; } } // ── Step 1: Resolve vcenter from DB chain ──────────────────────── $vcenterModel = $this->resolveVcenter($item, $telegram); if (!$vcenterModel) { $vcenterModel = VcenterModel::first(); if (!$vcenterModel) { $this->failStep('vcenter', 'Không tìm thấy vCenter nào trong hệ thống cho order_item #' . $item->id, $telegram); return; } Log::warning('RetryProvisioningOrchestrator: fallback to first vcenter', ['vcenter_id' => $vcenterModel->id]); } // ── Step 2: Login vCenter and search VM ───────────────────────── $loginResult = $vCenterService->loginServer($vcenterModel); if (!$loginResult) { $this->failStep('vcenter_login', 'Không thể đăng nhập vào vCenter ' . $vcenterModel->host . ' cho VM ' . $serverName, $telegram); return; } // Search by REST API first $vm = $vCenterService->findVirtualMachineByName($serverName); // If REST search fails, try listing all VMs and filter manually if (!$vm) { Log::info('RetryProvisioningOrchestrator: REST search returned null, trying list-all fallback', [ 'server_name' => $serverName, ]); $allVms = $vCenterService->getAllVirtualMachine(); if (is_array($allVms)) { foreach ($allVms as $candidate) { if (isset($candidate['name']) && $candidate['name'] === $serverName) { $vm = $candidate; Log::info('RetryProvisioningOrchestrator: found VM via list-all fallback', [ 'server_name' => $serverName, 'vm_id' => $candidate['vm'] ?? null, ]); break; } } } } if ($vm) { // ── VM EXISTS → link to DB + assign IP + send email ──────────── $vmId = $vm['vm'] ?? null; if (!$vmId) { $this->failStep('vm_search', 'Tìm thấy VM nhưng không lấy được vm_id cho ' . $serverName, $telegram); return; } Log::info('RetryProvisioningOrchestrator: VM found in vSphere', [ 'server_name' => $serverName, 'vm_id' => $vmId, 'vcenter' => $vcenterModel->host, ]); try { $moduleVmware = $this->linkVmToDb($item, $vmId, $vcenterModel, $serverName); // Determine OS family for AssignIpToGuestJob $osModel = $item->os_id ? Os::find($item->os_id) : null; $osFamily = $this->resolveOsFamily($osModel); // If VM has no IP yet → dispatch AssignIpToGuestJob if (!$moduleVmware->ip) { AssignIpToGuestJob::dispatch( moduleVmwareId: $moduleVmware->id, vmId: $vmId, osFamily: $osFamily, guestPassword: $moduleVmware->plain_password ?? \App\Services\VmProvisionService::TEMPLATE_PASSWORD, ); Log::info('RetryProvisioningOrchestrator: dispatched AssignIpToGuestJob', [ 'module_vmware_id' => $moduleVmware->id, 'vm_id' => $vmId, ]); } else { // Already has IP → just send credential email SendVmCredentialsEmailJob::dispatch($moduleVmware->id); Log::info('RetryProvisioningOrchestrator: dispatched SendVmCredentialsEmailJob', [ 'module_vmware_id' => $moduleVmware->id, ]); } $telegram->SendMessage( "✅ Phục hồi VM thành công\n" . "━━━━━━━━━━━━━━━━━━━━\n" . "🖥️ Tên VM: {$serverName}\n" . "🔑 VM ID: {$vmId}\n" . "📦 Đơn hàng: #{$this->orderId}\n" . "🌐 Có IP: " . ($moduleVmware->ip ? 'Có (' . $moduleVmware->ip . ')' : 'Chưa có — đang gán IP') . "\n" . "⏱️ Thời gian: " . now()->format('d/m/Y H:i:s') ); } catch (\Throwable $e) { $this->failStep('link_vm', 'Lỗi liên kết VM ' . $serverName . ': ' . $e->getMessage(), $telegram); } } else { // ── VM NOT in vSphere → dispatch CreateVirtualMachineJob ────── Log::info('RetryProvisioningOrchestrator: VM not found in vSphere, dispatching CreateVirtualMachineJob', [ 'server_name' => $serverName, 'order_item_id' => $item->id, ]); CreateVirtualMachineJob::dispatch($item, true); $telegram->SendMessage( "🆕 Tạo VM mới\n" . "━━━━━━━━━━━━━━━━━━━━\n" . "🖥️ Tên VM: {$serverName}\n" . "📦 Đơn hàng: #{$this->orderId}\n" . "📝 VM không có trong vSphere, đã gửi job tạo mới\n" . "⏱️ Thời gian: " . now()->format('d/m/Y H:i:s') ); } } /** * Resolve vcenter from DB chain: * Path 1: order_items.os_id → os → osGroup → host → datacenter → vcenter * Path 2: invoices → invoice_items → product_id → os_product → os → osGroup → host → datacenter → vcenter * Path 3: module_vmware with same order_id (partial provisioning) */ private function resolveVcenter(OrderItem $item, Telegram $telegram): ?VcenterModel { if ($item->os_id) { $os = Os::with('osGroup.host.datacenter.vCenter')->find($item->os_id); $vcenter = $os?->osGroup?->host?->datacenter?->vCenter; if ($vcenter) { Log::info('resolveVcenter: found via os → osGroup chain', ['vcenter_id' => $vcenter->id]); return $vcenter; } } $invoice = Invoice::where('order_id', $item->order_id)->first(); if ($invoice) { $invoiceItem = $invoice->invoiceItems()->whereNotNull('product_id')->first(); if ($invoiceItem) { $osRecord = DB::table('os_product') ->where('product_id', $invoiceItem->product_id) ->first(); if ($osRecord) { $os = Os::with('osGroup.host.datacenter.vCenter')->find($osRecord->os_id); $vcenter = $os?->osGroup?->host?->datacenter?->vCenter; if ($vcenter) { Log::info('resolveVcenter: found via invoice_items → os_product chain', ['vcenter_id' => $vcenter->id]); return $vcenter; } } } } $existingVmware = ModuleVmware::where('order_id', $item->order_id)->first(); if ($existingVmware?->vcenter_id) { $vcenter = VcenterModel::find($existingVmware->vcenter_id); if ($vcenter) { Log::info('resolveVcenter: found via existing module_vmware', ['vcenter_id' => $vcenter->id]); return $vcenter; } } $this->failStep('resolve_vcenter', 'Không thể tìm vcenter cho order_item #' . $item->id, $telegram); return null; } /** * Link an existing VM from vSphere to module_vmware + services tables. * Returns the ModuleVmware record. */ private function linkVmToDb(OrderItem $item, string $vmId, VcenterModel $vcenterModel, string $serverName): ModuleVmware { $order = $item->order; $customer = $order->customer; $months = (int) ($item->months ?? 0); $firstPayment = (int) $item->price * ($months ?: 1); $nextDueDate = $months > 0 ? now()->addMonths($months)->timestamp : null; $moduleVmware = ModuleVmware::where('vmware_id', $vmId) ->where('vcenter_id', $vcenterModel->id) ->first(); if (!$moduleVmware) { $moduleVmware = ModuleVmware::create([ 'name' => $serverName, 'module_ip_address' => null, 'ip' => null, 'port' => 3389, 'order_id' => $order->id, 'first_payment' => $firstPayment, 'next_payment' => $firstPayment, 'next_due_date' => $nextDueDate, 'next_invoice' => $nextDueDate, 'user' => 'Administrator', 'password' => \App\Services\VmProvisionService::TEMPLATE_PASSWORD, 'plain_password' => \App\Services\VmProvisionService::TEMPLATE_PASSWORD, 'hostname' => $serverName, 'vmware_id' => $vmId, 'vcenter_id' => $vcenterModel->id, 'os_id' => $item->os_id ?? 1, 'customer_id' => $customer->id, 'status' => 1, ]); } $service = Service::withTrashed()->firstOrNew([ 'order_item_id' => $item->id, ]); if ($service->trashed()) { $service->restore(); } $service->fill([ 'customer_id' => $customer->id, 'product_id' => $item->product_id, 'invoice_id' => $order->invoice?->id, 'vm_id' => $moduleVmware->id, 'server_name' => $serverName, 'status' => 'active', 'started_at' => now(), 'expires_at' => $months > 0 ? now()->addMonths($months) : null, ]); if (!$service->exists && !$service->uuid) { $service->uuid = (string) \Illuminate\Support\Str::uuid(); } $service->save(); if (!$moduleVmware->service_id) { $moduleVmware->update(['service_id' => $service->id]); } Log::info('RetryProvisioningOrchestrator: VM linked to DB', [ 'module_vmware_id' => $moduleVmware->id, 'service_id' => $service->id, 'vmware_id' => $vmId, ]); return $moduleVmware; } private function resolveOsFamily(?Os $os): string { if (!$os) { return 'LINUX'; } $groupName = strtolower($os->osGroup?->name ?? ''); if (str_contains($groupName, 'windows')) { return 'WINDOWS'; } return 'LINUX'; } private function failStep(string $step, string $message, Telegram $telegram): void { Log::error("RetryProvisioningOrchestrator [{$step}]: {$message}", [ 'order_id' => $this->orderId, ]); $telegram->SendErrorMessage( "Bước: {$step}\n" . "Đơn hàng: #{$this->orderId}\n" . "Lỗi: {$message}" ); } }