# Design: Срез 1 (сквозное) — DBML v5.14, импорт ЕЦП, видимость ## Context См. `proposal.md`. Факты, на которых стоит дизайн: - Выгрузка ЕЦП (`docs/source/biq/ecp-export-lagal-entity.xlsx`, лист «Выписки_01.04.26»): 8400 строк, колонки `ИНН`, `iD ГК`, `Базовый РФ ГК в CRM`, `Группа компаний`, `Организация`, `ГСК`, `ГСЗ`, `Тип`; 345 ГК; 53 значения РФ (51 «… РФ», плюс «ЦКБ» 1370 строк и «Головная организация»); типы ЮЛ 7395, ИП 469, `нерез.` 354, `ФЛ` 180, `нет инн` 2; ИНН: 10 цифр у ЮЛ, 12 у ИП, 279 нечисловых; 215 ИНН встречаются в нескольких ГК; флаги ГСК/ГСЗ — строки «входит в ГСК» / «входит в ГСЗ» (плюс мусор: `0`, ИНН в колонке флага); идентификатора ГСК/ГСЗ нет; у 213 ГК есть члены ГСК (до 83). - Решения владельца 04.10.2026: без UI справочника; КПП не нужен; ЮЛ в одной ГК, одновременно в одной ГСК и одной ГСЗ; экран администратора минимальный. - Инварианты: права — данные, код роли в логике не проверяется; первичка не мутируется; append-only аудит; поля вне DBML не вводить. ## Goals / Non-Goals **Goals:** одна версия DBML, которую реализует миграция 0002; правила импорта, по которым можно написать тесты на реальной выгрузке; правило видимости без кода роли. **Non-Goals:** см. proposal. ## Decisions ### D-1. v5.14: денормализованный `kind` в `related_group_member` Уникальность «одна группа каждого вида на ЮЛ» нельзя выразить индексом через join; варианты — триггер или денормализация. Выбрана денормализация (`kind` = `related_group.kind`, равенство проверяет ядро при вставке, тест на рассинхрон), индекс `(legal_entity_id, kind) WHERE is_current`. *Альтернатива* — триггер: сложнее переносить, невидим в ORM. *Цена ошибки:* убрать колонку и индекс миграцией. ### D-2. Версия DBML — новый файл, старый остаётся `data-model-v5.14.dbml` рядом с v5.13 (история версий — файлы, changelog в шапке); `entities-v5.14.md` — копия с таблицей изменений наверху; ссылки в CLAUDE.md репозиториев и `Makefile`/`DBML_VERSION` arm-core переводятся на v5.14 в дочернем change. ### D-3. Импорт — правила §13 как данные для тестов, строки не блокируют файл Построчные коды (`UNSUPPORTED_TYPE`, `INVALID_INN`, `DUPLICATE_IN_FILE`, `RF_CREATED`, `GK_*`, `LE_*`, `GSK_CREATED`, `MEMBER_ADDED`, `UNKNOWN_FLAG`) фиксируются в §13.2 и становятся контрактом отчёта и тестов `arm-core`; файл отвергается только при отсутствии обязательных колонок или нечитаемости. Одна транзакция, `dry_run`, стратегия `on_existing ∈ {skip, update}`. ### D-4. Неоднозначности выгрузки — осторожные допущения + OQ, не угадывание `нерез.`/`ФЛ` — пропуск (OQ-87); ИНН только по длине (OQ-88); «ЦКБ»/«Головная организация» — обычные филиалы (OQ-89); ИНН в нескольких ГК — первая выигрывает, остальные в отчёт (OQ-90); одна неявная ГСК/ГСЗ на ГК с именем ГК (OQ-91). Каждое допущение помечается `ASSUMPTION(OQ-nn)` в коде импортёра, все обратимы одной командой или повторным импортом с `update`. ### D-5. Видимость — по `regional_office.is_head_office`, без ролей ГО видит всё, филиал — ГК своего РФ и своих монозаёмщиков; ЗО не фильтрует (OQ-86). Запись вне видимости — 404. ### D-6. Выгрузка хранится в `docs/source/biq/`, срез — в fixtures Полная выгрузка — первоисточник формата и прогон «на всём объёме» (помечена как внутренняя: реальные ИНН); для unit-тестов — 21 строка ГК «Русский Бисквит» в `fixtures/directories/`. `fixtures/README` и `06-test-data.md` §6 обновляются. ## Risks / Trade-offs - [Импорт всей выгрузки создаст 53 филиала и ~345 ГК в dev-БД] → `dry_run` первым, отчёт показывает `RF_CREATED`. - [Денормализованный `kind` разойдётся с группой] → проверка в сервисе + тест; индекс всё равно защищает уникальность. - [Допущения D-4 окажутся неверны] → данные исправимы повторным импортом с `update` или одной миграцией данных; OQ отслеживаются. ## Migration Plan Миграция 0002 (arm-core): `ALTER TABLE related_group_member ADD kind` (заполнение из `related_group.kind` для существующих строк — в dev их нет), замена индекса. Откат — обратная замена индекса и `DROP COLUMN`. ## Open Questions OQ-86…OQ-91 (см. `07-open-questions.md`) — ни один не блокирует: приняты допущения D-4/D-5. Вопросы владельцу: «ЦКБ» и «Головная организация» = ГО? нужны ли нерезиденты/ФЛ? ## Decisions made without discussion | № | Дата | Решение | Обоснование | Цена ошибки | |---|---|---|---|---| | 1 | 2026-10-04 | D-1: `kind` в `related_group_member` вместо триггера | индекс виден в ORM и сверке с DBML | миграция: убрать колонку | | 2 | 2026-10-04 | D-2: новый файл v5.14, v5.13 остаётся | правило «новая версия DBML с changelog» | нет | | 9 | 2026-10-04 | v5.14: два новых значения `audit_action` — `LEGAL_ENTITY_CHANGE`, `USER_CHANGE` | без них события ЮЛ/пользователей пришлось бы писать под чужим действием (`GROUP_CHANGE`) | удалить значения enum (миграция) | | 3 | 2026-10-04 | D-3: коды отчёта импорта фиксированы в §13.2; строки не блокируют файл; `on_existing` skip/update; `dry_run` | макет [38] «Пропустить существующие»; реальная выгрузка грязная | добавить/переименовать коды | | 4 | 2026-10-04 | D-4: пять допущений по неоднозначностям выгрузки (OQ-87…OQ-91) | нельзя угадывать; все обратимы | повторный импорт / миграция данных | | 5 | 2026-10-04 | D-5: видимость по `is_head_office`, ЗО не фильтрует | права — данные; у ГК нет сегмента | добавить поле сегмента (DBML v5.15) и фильтр | | 6 | 2026-10-04 | D-6: полная выгрузка в `docs/source/biq`, срез в fixtures | первоисточник формата vs тестовые данные | перенос файла | | 7 | 2026-10-04 | Срез 1 без UI справочника, экран администратора минимальный | решение владельца 04.10.2026 | UI — отдельный срез | | 8 | 2026-10-04 | Записи вне видимости отвечают 404, не 403 | не раскрывать существование чужих ГК | одна константа |