# План: смена VRF у префикса и целостность «VRF ⊂ организация» (изменение 002) ## Контекст VRF — часть адресного плана организации. Сейчас: - уникальность VRF уже в пределах организации: `UNIQUE(organization_id, name)` — одно имя допустимо в разных организациях; - несколько префиксов организации могут быть в одном VRF (`UNIQUE(vrf_id, prefix)` — дубль CIDR запрещён только внутри VRF); - **смена VRF у существующего префикса невозможна**: нет ни поля в UI (`web/app.js`, `prefixDialog`), ни `vrf_id` в `PrefixUpdate` (`app/schemas.py`); - принадлежность VRF организации префикса проверяется только в коде (`create_prefix`), в БД гарантии нет. Цель: разрешить смену VRF **только среди VRF той же организации**, сохранив целостность дерева, и закрепить правило на уровне БД. ## Правила 1. Организация префикса неизменна; целевой VRF обязан принадлежать той же организации (иначе 422). 2. Смена VRF переносит **префикс вместе со всем поддеревом** (вложенные по `parent_id`), чтобы дерево не «разрывалось» между VRF. 3. Дубль `(vrf, prefix)` у любого переносимого префикса в целевом VRF → 409 с перечнем конфликтующих CIDR; перенос отменяется целиком. 4. После переноса родитель корня поддерева пересчитывается в целевом VRF (самый узкий объемлющий); префиксы целевого VRF, лежащие внутри перенесённого, переподчиняются ему — та же логика, что при создании (выносим в общую функцию `attach_to_tree`). 5. Адреса остаются у своих префиксов (`prefix_id` не меняется); использование/ёмкость пересчитываются автоматически. 6. CIDR и родитель в окне редактирования по-прежнему не меняются (родитель определяется автоматически). ## Изменения **Backend** - `app/schemas.py`: `PrefixUpdate.vrf_id: int | None`. - `app/api/v1/prefixes.py`: в `update_prefix` — валидация правил 1–3, перенос поддерева, `attach_to_tree` (рефакторинг из `create_prefix`), запись в audit `{vrf: "old → new", moved: N}`. - Alembic `0002`: - `UNIQUE(id, organization_id)` на `vrfs` + составной FK `prefixes(vrf_id, organization_id) → vrfs(id, organization_id)` — БД не даст связать префикс с VRF чужой организации; - уникальность имени VRF без учёта регистра в пределах организации: индекс `(organization_id, lower(name))` вместо `UNIQUE(organization_id, name)`; перед применением — проверка на существующие дубли. **UI (`web/app.js`)** - В окне редактирования префикса показать поле «VRF» (только VRF текущей организации, уже загружены в `S.vrfs`). - При выборе другого VRF — подсказка «Вместе с префиксом будет перенесено N вложенных» (N считается из `S.prefixes`); ошибки 409/422 — в стандартном баннере окна. - Вёрстка — из существующего окна создания (макета редактирования нет, отступлений от дизайна не появляется). **Тесты (2 новых, всего 6)** 1. Одноимённый VRF в двух организациях — ок; повтор в одной (в т.ч. другим регистром) — 409. 2. Смена VRF: поддерево переехало, родитель пересчитан, дубль в целевом VRF — 409 без частичных изменений, VRF другой организации — 422. ## Порядок работ 1. Рефакторинг `attach_to_tree` (поведение создания не меняется, тесты зелёные). 2. Миграция `0002` (сначала проверка дублей, затем применение через `docker compose up`). 3. `PrefixUpdate` + логика переноса. 4. UI: поле VRF и подсказка. 5. Тесты в контейнерах, прогон e2e-сценария UI, сверка окна с макетом «Новый префикс». 6. Обновить `README.md` (правила VRF), написать `SUMMARY.md` в этой папке. ## Риски - Составной FK требует, чтобы `prefixes.organization_id` всегда совпадал с организацией VRF — уже так; миграция проверит данные и откажется применяться при расхождении. - Перенос крупного поддерева — одна транзакция; при 409 откат целиком. ## Допущения (скажите, если не так) - Переносится всё поддерево, а не только выбранный префикс (альтернатива — запретить смену VRF у префиксов с вложенными). - Регистронезависимая уникальность имён VRF в организации допустима (`Lab` и `lab` считаются одним VRF).