resources($customer, $data); if ($mikrotik->connection_type !== 'api') { throw ValidationException::withMessages(['nas_mikrotik_id' => 'Tahap ini hanya mendukung Mikrotik dengan koneksi API.']); } $target = $this->deployments->assertSynced($profile, $mikrotik); $username = trim((string) ($data['external_username'] ?? '')); $password = (string) ($data['external_password'] ?? ''); if ($username === '' || $password === '') { throw ValidationException::withMessages(['external_username' => 'Username dan password RouterOS wajib diisi.']); } $remote = $this->routerOs->createAccount($mikrotik, $profile->service_type, [ 'username' => $username, 'password' => $password, 'profile' => $target->external_profile_name, 'comment' => $this->comment($customer), 'disabled' => 'no', ]); try { return DB::transaction(fn () => CustomerNasAccount::create([ 'tenant_id' => $customer->tenant_id, 'customer_id' => $customer->id, 'nas_mikrotik_id' => $mikrotik->id, 'nas_package_profile_id' => $profile->id, 'nas_package_profile_target_id' => $target->id, 'service_type' => $profile->service_type, 'usage_mode' => $profile->usage_mode, 'username' => $username, 'password' => $password, 'mac_address' => $data['mac_address'] ?? null, 'serial_number' => $data['serial_number'] ?? null, 'assigned_ip' => $data['assigned_ip'] ?? null, 'remote_id' => $remote['.id'] ?? null, 'remote_profile' => $target->external_profile_name, 'status' => 'active', 'validity_started_at' => $profile->validity_start === 'activation' ? now() : null, 'expires_at' => $profile->validity_start === 'activation' ? $this->expiresAt($profile) : null, 'last_synced_at' => now(), ])); } catch (Throwable $exception) { try { $this->routerOs->deleteAccount($mikrotik, $profile->service_type, $username); } catch (Throwable) { // Compensating delete is best effort; original database error remains authoritative. } throw $exception; } } public function setEnabled(Customer $customer, bool $enabled): CustomerNasAccount { $account = $this->account($customer); $remote = $this->routerOs->setEnabled( $account->mikrotik, $account->service_type, $account->username, $enabled, ); $account->update([ 'remote_id' => $remote['.id'] ?? $account->remote_id, 'status' => $enabled ? 'active' : 'disabled', 'last_synced_at' => now(), 'last_sync_error' => null, ]); return $account->fresh(); } public function update(Customer $customer, array $attributes): CustomerNasAccount { $account = $this->account($customer); $remote = $this->routerOs->updateAccount( $account->mikrotik, $account->service_type, $account->username, $attributes, ); $account->update([ 'username' => $attributes['username'] ?? $account->username, 'password' => $attributes['password'] ?? $account->password, 'remote_profile' => $attributes['profile'] ?? $account->remote_profile, 'remote_id' => $remote['.id'] ?? $account->remote_id, 'last_synced_at' => now(), 'last_sync_error' => null, ]); return $account->fresh(); } public function delete(Customer $customer): void { $account = $customer->nasAccount()->with('mikrotik')->first(); if (! $account) { return; } $this->routerOs->deleteAccount($account->mikrotik, $account->service_type, $account->username); $account->delete(); } private function resources(Customer $customer, array $data): array { $mikrotik = NasMikrotik::whereKey($data['nas_mikrotik_id']) ->where('tenant_id', $customer->tenant_id)->firstOrFail(); $profile = NasPackageProfile::whereKey($data['package_profile_id']) ->where('tenant_id', $customer->tenant_id)->firstOrFail(); return [$mikrotik, $profile]; } private function account(Customer $customer): CustomerNasAccount { $account = $customer->nasAccount()->with('mikrotik')->first(); if (! $account) { throw ValidationException::withMessages(['nas_account' => 'Akun RouterOS customer belum tercatat.']); } return $account; } private function comment(Customer $customer): string { return "ManjaPro | {$customer->customer_code} | {$customer->name}"; } private function expiresAt(NasPackageProfile $profile): ?Carbon { if (! $profile->validity_value || ! $profile->validity_unit) return null; return match ($profile->validity_unit) { 'minutes' => now()->addMinutes($profile->validity_value), 'hours' => now()->addHours($profile->validity_value), 'days' => now()->addDays($profile->validity_value), 'months' => now()->addMonths($profile->validity_value), default => null, }; } }