[vCenter::class, null, [self::TYPE_DATACENTER]], self::TYPE_DATACENTER => [ModuleDatacenter::class, 'vcenter_id', [self::TYPE_HOST]], self::TYPE_HOST => [ModuleHost::class, 'module_datacenter_id', [self::TYPE_DATASTORE, self::TYPE_NETWORK]], self::TYPE_DATASTORE => [ModuleDatastore::class, 'module_host_id', []], self::TYPE_NETWORK => [ModuleNetwork::class, 'module_host_id', []], ]; public function __construct(private readonly VmProviderSettingsService $providerSettings) { } // ───────────────────────────────────────────────────────────────────────── // Resolve helpers // ───────────────────────────────────────────────────────────────────────── public function activeVcenter(): ?vCenter { return vCenter::where('status', 1)->first(); } public function activeDatacenter(int $vcenterId): ?ModuleDatacenter { return ModuleDatacenter::where('vcenter_id', $vcenterId) ->where('status', 1) ->first(); } public function activeHost(int $datacenterId): ?ModuleHost { return ModuleHost::where('module_datacenter_id', $datacenterId) ->where('status', 1) ->first(); } public function activeDatastore(int $hostId): ?ModuleDatastore { return ModuleDatastore::where('module_host_id', $hostId) ->where('status', 1) ->first(); } public function activeNetwork(int $hostId): ?ModuleNetwork { return ModuleNetwork::where('module_host_id', $hostId) ->where('status', 1) ->first(); } /** * Return the full active chain. Throws if any layer has no active row. * * @return array{vcenter:vCenter,datacenter:ModuleDatacenter,host:ModuleHost,datastore:ModuleDatastore,network:ModuleNetwork} */ public function resolveChain(): array { $vcenter = $this->activeVcenter(); if (!$vcenter) { throw new RuntimeException('Không có vCenter nào đang active. Vui lòng kích hoạt 1 vCenter trong admin.'); } $datacenter = $this->activeDatacenter($vcenter->id); if (!$datacenter) { throw new RuntimeException("vCenter active id={$vcenter->id} chưa có Datacenter nào active."); } $host = $this->activeHost($datacenter->id); if (!$host) { throw new RuntimeException("Datacenter active id={$datacenter->id} chưa có Host nào active."); } $datastore = $this->activeDatastore($host->id); if (!$datastore) { throw new RuntimeException("Host active id={$host->id} chưa có Datastore nào active."); } $network = $this->activeNetwork($host->id); if (!$network) { throw new RuntimeException("Host active id={$host->id} chưa có Network nào active."); } return compact('vcenter', 'datacenter', 'host', 'datastore', 'network'); } // ───────────────────────────────────────────────────────────────────────── // Mutators // ───────────────────────────────────────────────────────────────────────── /** * Set this row active, deactivating all siblings in the same parent scope. */ public function activate(string $type, int $id): Model { [$modelClass, $parentColumn] = $this->mapOrFail($type); return DB::transaction(function () use ($modelClass, $parentColumn, $type, $id) { /** @var Model $row */ $row = $modelClass::query()->lockForUpdate()->findOrFail($id); // Deactivate siblings under same parent (or all rows if global scope) $siblings = $modelClass::query()->where('id', '!=', $row->getKey()); if ($parentColumn) { $siblings->where($parentColumn, $row->{$parentColumn}); } $siblings->update(['status' => 0]); $row->status = 1; $row->save(); if ($type === self::TYPE_VCENTER) { $this->providerSettings->setActiveVcenterId((int) $row->getKey()); } return $row->refresh(); }); } /** * Set this row inactive AND cascade-deactivate every descendant beneath it. */ public function deactivate(string $type, int $id): Model { [$modelClass] = $this->mapOrFail($type); return DB::transaction(function () use ($modelClass, $type, $id) { /** @var Model $row */ $row = $modelClass::query()->lockForUpdate()->findOrFail($id); $row->status = 0; $row->save(); $this->cascadeDeactivate($type, (int) $row->getKey()); if ($type === self::TYPE_VCENTER) { $this->providerSettings->setActiveVcenterId(null); } return $row->refresh(); }); } /** * Recursively set status=0 on every descendant of $parentId under $type. */ private function cascadeDeactivate(string $type, int $parentId): void { $childTypes = self::MAP[$type][2] ?? []; foreach ($childTypes as $childType) { [$childModel, $childParentColumn] = $this->mapOrFail($childType); if (!$childParentColumn) { continue; } $childIds = $childModel::query() ->where($childParentColumn, $parentId) ->pluck('id') ->all(); if (!$childIds) { continue; } $childModel::query()->whereIn('id', $childIds)->update(['status' => 0]); foreach ($childIds as $childId) { $this->cascadeDeactivate($childType, (int) $childId); } } } /** * @return array{0:class-string,1:?string,2:string[]} */ private function mapOrFail(string $type): array { if (!isset(self::MAP[$type])) { throw new RuntimeException("Unknown infra type: {$type}"); } return self::MAP[$type]; } }