DBML v5.17: КПП юридического лица (legal_entity.kpp), пересмотр OQ-31 (#53) #54

Merged
aep merged 3 commits from feat/53-kpp-dbml-v5-17 into main 2026-10-07 18:01:23 +03:00
9 changed files with 1567 additions and 22 deletions
Showing only changes of commit 6197cf404b - Show all commits

View File

@ -1,4 +1,4 @@
# 03. Архитектура (целевая, по решениям DECISION-1…5 и ARCH-* в DBML v5.16)
# 03. Архитектура (целевая, по решениям DECISION-1…5 и ARCH-* в DBML v5.17)
## Компоненты (= репозитории, см. `00-workspace-layout.md`)
@ -230,7 +230,7 @@ api/src/
# машины статусов, policy — pydantic-модели на входе и выходе
services/ # use-cases: оркестрация domain + repositories + outbox + audit_event
repositories/ # SQLAlchemy-запросы, в т. ч. запросы-вычисления (сводная ОСВ, шахматка)
models/ # ORM 1:1 с DBML v5.16; имена таблиц/колонок/enum — как в DBML
models/ # ORM 1:1 с DBML v5.17; имена таблиц/колонок/enum — как в DBML
schemas/ # pydantic DTO API /api/v1
core/ # config, clients (S3; NATS outbox-publisher и result-consumer), auth (JWT РАИСА / dev-эмитент),
# logger, exception_handlers
@ -315,7 +315,7 @@ FastAPI» платформы РАИСА/aurora): дерево импортиро
`version_table_schema` и `include_name` Alembic). Следуем шаблону, не `public`.
- Alembic берёт URL из настроек, не из `alembic.ini`; ревизии — `api/alembic/versions/`, имя файла стандартное
`<rev>_<slug>.py`. Автогенерация — только черновик: итоговую ревизию сверяет с DBML `api/scripts/check_dbml_vs_orm.py`.
- PK везде `uuid` через `UUIDPrimaryMixin`; имена таблиц, колонок и enum'ов — ровно как в DBML v5.16.
- PK везде `uuid` через `UUIDPrimaryMixin`; имена таблиц, колонок и enum'ов — ровно как в DBML v5.17.
### Инфраструктурные клиенты

View File

@ -1,9 +1,9 @@
# 04. Как читать модель данных v5.16
# 04. Как читать модель данных v5.17
Файлы: `docs/model/data-model-v5.16.dbml` (источник истины), `docs/model/entities-v5.16.md` (смысл таблиц; предыдущие
версии `data-model-v5.13…v5.15.dbml` и `entities-v5.13…v5.15.md` остаются как история),
Файлы: `docs/model/data-model-v5.17.dbml` (источник истины), `docs/model/entities-v5.17.md` (смысл таблиц; предыдущие
версии `data-model-v5.13…v5.16.dbml` и `entities-v5.13…v5.16.md` остаются как история),
`docs/model/diagrams/part01…09.svg` (диаграммы v5.12 — на одну версию старше: нет `permission`/`role_permission`,
нет `document`, есть `osv_group_schema`, нет `related_group_member.kind`; читать с поправкой на changelog v5.13…v5.16 в шапке DBML).
нет `document`, есть `osv_group_schema`, нет `related_group_member.kind`; читать с поправкой на changelog v5.13…v5.17 в шапке DBML).
## Девять частей модели
@ -57,9 +57,9 @@
## v4 → v5.13: таблица переименований (обязательна при чтении ТЗ)
v5.14 имена таблиц и полей не менял (добавлены `related_group_member.kind` и два значения `audit_action`), v5.15 — тоже
(добавлено значение `osv_data_type.NU`), v5.16 — тоже (колонка `osv_entry.counterparty_match_kind`), поэтому таблица справедлива и для v5.14, v5.15 и v5.16.
(добавлено значение `osv_data_type.NU`), v5.16 — тоже (колонка `osv_entry.counterparty_match_kind`), v5.17 — тоже (колонка `legal_entity.kpp`), поэтому таблица справедлива и для v5.14…v5.17.
| В страницах ТЗ (модель v4 и старше) | В v5.13 / v5.14 / v5.15 / v5.16 |
| В страницах ТЗ (модель v4 и старше) | В v5.13 / v5.14 / v5.15 / v5.16 / v5.17 |
|---|---|
| `stored_file.period_year / period_quarter / document_type / osv_account` | `document.period_year / period_quarter / document_type / osv_account_code / is_cumulative` |
| `stored_file.status` «Загружен / Обработка / Требуется проверка / Ошибка» | техника: `stored_file.status` UPLOADED/PROCESSING/PROCESSED/ERROR/SUPERSEDED; проверка: `document.status` RECOGNIZED/NEEDS_CHECK/CONFIRMED/ERROR |
@ -82,7 +82,7 @@ v5.14 имена таблиц и полей не менял (добавлены
## Открытые места модели (не придумывать, спросить)
- **Закрыто в v5.14:** OQ-32 (членство в ГСК и ГСЗ одновременно) — решение владельца 04.10.2026: ЮЛ в одной ГК и одновременно
в одной ГСК и одной ГСЗ (ANL-030 отменён); OQ-31 (КПП) — не вводится.
в одной ГСК и одной ГСЗ (ANL-030 отменён); OQ-31 (КПП) — v5.14 не вводил, **введён в v5.17** (`legal_entity.kpp`, решение владельца 07.10.2026).
- **№2 `osv_entry.parent_entry_id`**: хранить ли все строки ОСВ (п. 11 «Парсинг ОСВ» — да) или только контрагентские;
нужно ли дерево. Рекомендация для среза 2: хранить **все** строки с `level` и `parent_entry_id` — это дёшево и

View File

@ -724,9 +724,9 @@ DOCUMENTS_PROCESSING, DATA_CORRECTION; иначе 409 `VERSION_NOT_EDITABLE`) **
- xlsx (первый лист, первая непустая строка — заголовки) или csv (UTF-8, разделитель `;` или `,` — определяется по строке
заголовков). Заголовки сопоставляются по нормализованному тексту (нижний регистр, без пробелов, `ё` = `е`):
`ИНН`, `iD ГК`, `Базовый РФ ГК в CRM`, `Группа компаний`, `Организация`, `ГСК`, `ГСЗ`, `Тип`. Порядок колонок не важен,
`ИНН`, `КПП`, `iD ГК`, `Базовый РФ ГК в CRM`, `Группа компаний`, `Организация`, `ГСК`, `ГСЗ`, `Тип`. Порядок колонок не важен,
лишние колонки игнорируются. Нет любой из `ИНН`, `iD ГК`, `Группа компаний`, `Организация`, `Тип` → файл отвергается целиком
(`MISSING_COLUMNS`). `Базовый РФ ГК в CRM`, `ГСК`, `ГСЗ` — необязательные.
(`MISSING_COLUMNS`). `Базовый РФ ГК в CRM`, `ГСК`, `ГСЗ`, `КПП` — необязательные (v5.17: отсутствие колонки `КПП` не ошибка; значение — trim, все пробельные символы убираются, регистр букв приводится к верхнему; число в xlsx читается как целое без дробной части, ведущие нули не восстанавливаются — как у ИНН).
- Значения: trim; `ИНН` — убрать все пробельные символы; `Тип` — без регистра. Пустые строки пропускаются. Число в ячейке
xlsx читается как целое без дробной части (`3528052718.0` → `3528052718`); **ведущий ноль, потерянный ещё в Excel
(`0123456789` → `123456789`), не восстанавливается** — строка получит `INVALID_INN` (ИНН нужно задавать текстом или
@ -737,9 +737,9 @@ DOCUMENTS_PROCESSING, DATA_CORRECTION; иначе 409 `VERSION_NOT_EDITABLE`) **
### 13.2 Строка → решение
Строки обрабатываются в порядке файла; **порядок проверок внутри строки**: тип → ИНН → дубль ИНН в файле → ГК (пустой
Строки обрабатываются в порядке файла; **порядок проверок внутри строки**: тип → ИНН → КПП → дубль ИНН в файле → ГК (пустой
`iD ГК`, конфликт имени) → РФ → ГК (создание/обновление) → ЮЛ → флаги ГСК/ГСЗ. У каждой строки ровно один **итоговый** код
(пять кодов пропуска — `UNSUPPORTED_TYPE`, `INVALID_INN`, `DUPLICATE_IN_FILE`, `LE_IN_OTHER_GROUP`, `GK_NAME_CONFLICT`, —
(шесть кодов пропуска — `UNSUPPORTED_TYPE`, `INVALID_INN`, `INVALID_KPP`, `DUPLICATE_IN_FILE`, `LE_IN_OTHER_GROUP`, `GK_NAME_CONFLICT`, —
и `LE_CREATED` / `LE_UPDATED` / `LE_EXISTS`) и ноль или более **сопутствующих** событий той же строки (`RF_*`, `GK_*`,
`GSK_*`/`GSZ_*`, `MEMBER_ADDED`, `UNKNOWN_FLAG`). Счётчик `errors` = число строк с кодом пропуска.
@ -747,17 +747,18 @@ DOCUMENTS_PROCESSING, DATA_CORRECTION; иначе 409 `VERSION_NOT_EDITABLE`) **
|---|---|---|
| Тип | `ЮЛ` / `ИП` → `entity_type`; иное (`нерез.`, `ФЛ`, `нет инн`, пусто) → строка пропускается **[открыто, OQ-87]** | `UNSUPPORTED_TYPE` |
| ИНН | ЮЛ — ровно 10 цифр, ИП — 12; иначе пропуск (контрольные разряды в Срезе 1 не проверяются **[открыто, OQ-88]**) | `INVALID_INN` |
| КПП | v5.17, OQ-31: пустой КПП — допустим (у ЮЛ необязателен). Непустой: у ИП — запрещён, у ЮЛ — по формату §13.5 (9 символов: 4 цифры, 2 цифры/латинские A–Z, 3 цифры); нарушение — строка пропускается с сообщением | `INVALID_KPP` |
| Дубль в файле | второй и далее ИНН в файле пропускается. ИНН считается «встречавшимся» только после успешных проверок типа и формата, поэтому «ФЛ», идущая после ИП с тем же ИНН, получает `UNSUPPORTED_TYPE`, а не `DUPLICATE_IN_FILE` | `DUPLICATE_IN_FILE` |
| РФ | по `regional_office.name` (точное после trim); нет → **создаётся** `regional_office(name, is_head_office=false)` с предупреждением (один раз на имя в файле); пусто → `base_regional_office_id = NULL`. РФ разрешается один раз на ГК — по первой строке ГК и только после проверок пустого `iD ГК` и конфликта имени (отвергнутая строка филиал не создаёт); у **существующей** ГК при `skip` филиал из файла всё равно создаётся, но ГК не меняется. Значения «ЦКБ» и «Головная организация» в выгрузке — **[открыто, OQ-89]**: до решения создаются как обычные филиалы | `RF_CREATED` |
| ГК | **пустой `iD ГК`** → строка пропускается (`GK_NAME_CONFLICT`, сообщение «пустой iD ГК»). По `company_group.crm_id = iD ГК`: нет → создать (`code` генерируется `GK-NNNNNN`, `name`, `base_regional_office_id`, `valid_from = now`); есть → при стратегии `update` обновить `name`/`base_regional_office_id` c снимком в `company_group_history` и `audit_event(GROUP_CHANGE / ASSIGNMENT_CHANGE)`, при `skip` — оставить; **`GK_EXISTS` ставится и для неизменной ГК при `update`** (нет различий в имени и РФ — нечего обновлять; это обеспечивает идемпотентность). Разные имена одной ГК в файле (имя первой строки против последующих) → ошибка ГК, строка пропускается. Архивные ГК (`is_archived`) входят в сопоставление и при `update` обновляются **без разархивации** **[открыто, OQ-94]**. `update` пишет снимок `company_group_history` и событие `audit_event` теми же функциями, что ручная правка (PATCH); смена только имени — `GROUP_CHANGE`, смена РФ (в т.ч. вместе с именем) — одно `ASSIGNMENT_CHANGE` | `GK_CREATED`, `GK_UPDATED`, `GK_EXISTS`, `GK_NAME_CONFLICT` |
| ЮЛ | по `legal_entity.inn`: нет → создать (`entity_type`, `name`, `company_group_id`, `regional_office_id = base RF ГК`, `valid_from = now`); есть в этой же ГК → `update`: обновить `name`/`entity_type` со снимком `legal_entity_history(UPDATE)`, `skip`: оставить; есть **в другой ГК или вне ГК (монозаёмщик)** → строка пропускается, перевод между ГК только явной операцией (§13.4) **[открыто, OQ-90]**; сообщение называет имя ЮЛ, независимо от филиала инициатора — импорт доступен только `DIRECTORY_ADMIN` **[открыто, OQ-95]**. `regional_office_id` нового ЮЛ = действующий РФ ГК. `LE_UPDATED` пишет снимок `legal_entity_history(UPDATE)` и `audit_event(LEGAL_ENTITY_CHANGE)` как ручная правка; `LE_EXISTS` — и при `skip`, и при `update` без различий | `LE_CREATED`, `LE_UPDATED`, `LE_EXISTS`, `LE_IN_OTHER_GROUP` |
| ГСК / ГСЗ | флаг = нормализованное `входит в гск` / `входит в гсз`; любое другое непустое значение → предупреждение и «не входит». Если у ГК есть хотя бы один член с флагом — создаётся (если нет) **одна** группа вида `gsk`/`gsz` с `name` = имя ГК и `code` `GSK-NNNNNN`/`GSZ-NNNNNN` **[открыто, OQ-91]**; член добавляется записью `related_group_member(kind, valid_from = now, is_current)`, если текущего членства **этого вида** (в этой или другой группе) нет — существующее членство импорт не меняет и не закрывает. Флаги обрабатываются **и при `skip`** (недостающее членство добавляется у существующих ЮЛ). Группа создаётся по самому факту флага, даже если у ЮЛ уже есть членство того же вида в другой группе (возможна пустая группа). Значение, не равное ожидаемому для колонки (в т.ч. «входит в ГСЗ» в колонке `ГСК`), — `UNKNOWN_FLAG` (предупреждение, не ошибка: «не входит»). Отсутствие флага членство **не закрывает** (импорт не удаляет) | `GSK_CREATED`, `GSZ_CREATED`, `MEMBER_ADDED`, `UNKNOWN_FLAG` |
**Перечень кодов строки (16; совпадает с `ImportCode` в `arm-core/api/src/domain/directory_import.py`):**
**Перечень кодов строки (17; совпадает с `ImportCode` в `arm-core/api/src/domain/directory_import.py`):**
| Код | Роль в строке | Счётчик отчёта |
|---|---|---|
| `UNSUPPORTED_TYPE`, `INVALID_INN`, `DUPLICATE_IN_FILE`, `LE_IN_OTHER_GROUP`, `GK_NAME_CONFLICT` | итоговый, строка пропущена | `errors` |
| `UNSUPPORTED_TYPE`, `INVALID_INN`, `INVALID_KPP`, `DUPLICATE_IN_FILE`, `LE_IN_OTHER_GROUP`, `GK_NAME_CONFLICT` | итоговый, строка пропущена | `errors` |
| `LE_CREATED`, `LE_UPDATED`, `LE_EXISTS` | итоговый | `entities_created`, `entities_updated`, `entities_skipped` |
| `RF_CREATED` | сопутствующий | `offices_created` |
| `GK_CREATED`, `GK_UPDATED`, `GK_EXISTS` | сопутствующий | `groups_created`, `groups_updated`, `groups_skipped` |
@ -801,6 +802,15 @@ OQ-92 (пустые «Организация»/«Группа компаний»
- **ГК:** перезакрепление за другим филиалом — `audit_event(ASSIGNMENT_CHANGE)` + снимок `company_group_history`; архив —
`is_archived` (ГК с активными консолидациями архивировать нельзя — проверка появится в Срезе 4).
### 13.5 КПП (v5.17, OQ-31, решение владельца 07.10.2026)
- Поле `legal_entity.kpp` (и снимок `legal_entity_history.kpp`), nullable. Формат — ровно 9 символов: `^[0-9]{4}[0-9A-Z]{2}[0-9]{3}$` (4 цифры, 2 символа — цифра или латинская A–Z, 3 цифры); иначе `INVALID_KPP` (422 в API). Контрольных разрядов у КПП нет.
- **ИП:** КПП запрещён — непустой `kpp` при `entity_type = ИП` → `INVALID_KPP` (создание и PATCH; смена `entity_type` на `ИП` у ЮЛ с КПП требует одновременной очистки КПП, иначе `INVALID_KPP`). Для ЮЛ КПП необязателен; очистить его (`null`) можно.
- **Уникальность:** только `inn` (КПП — атрибут). Сопоставление контрагентов ОСВ по ИНН (§3) КПП не использует.
- **Изменение КПП** — обычный PATCH ЮЛ: снимок `legal_entity_history(event_type = UPDATE)` с прежними значениями и `audit_event(LEGAL_ENTITY_CHANGE)` с `before/after`, содержащими `kpp`.
- **Импорт:** колонка `КПП` необязательна; для существующего ЮЛ при стратегии `update` непустой КПП из файла обновляет `kpp` (со снимком, как `name`/`entity_type`), пустая ячейка существующий КПП не стирает.
- **Поиск ЮЛ:** подстрочный поиск `q` по справочнику ЮЛ (имя, ИНН) ищет и по КПП; точный фильтр `inn` не меняется.
Все мутации пишут `audit_event` (`GROUP_CHANGE`, `ASSIGNMENT_CHANGE`, `MEMBERSHIP_CHANGE`, `LEGAL_ENTITY_CHANGE` — v5.14, `DIRECTORY_IMPORT`) с `before/after`.
---

View File

@ -36,7 +36,7 @@
| OQ-28 | ~~Нет экрана проверки распознавания~~ — есть, открывается кликом по документу в панели «Первичные документы» (снимок `41`) | макет [41] | срез 3 | **закрыт**: реализовать по макету; для ОСВ (xlsx) левая панель — уточнить |
| OQ-29 | Макет: нет экранов пользователей / ролей / матрицы прав (ТЗ 8.1, `role_permission`); в журнале макета роли «Аналитик ДКБ», «Кредитный аналитик», «Утверждающий РФ», «Верификатор ГО», «Система распознавания / Сервис» (ANL-034, снимок `43-02`) | макет vs ТЗ 8.1 | срез 1 | допущение: минимальный экран администратора по §10 правил; роли — коды ТЗ 8.1, названия макета не используются |
| OQ-30 | Макет: шахматка и КФО показаны в тыс. руб. с 2 знаками, ОСВ — в рублях; где и как округлять (связано с OQ-26) | макет [30] [31-01] | срез 5–6 | допущение: хранить и считать в рублях, переключатель единиц — только отображение |
| OQ-31 | КПП ЮЛ: в макете колонка и поле форм (`50`, `61`, `51-02`, `79`), в DBML v5.13 у `legal_entity` поля нет. Нужен ли КПП (и в импорте, и для ИП, у которых его нет)? | макет [50] [51-02] | срез 1, DBML v5.14 | **закрыт решением владельца 04.10.2026: КПП не нужен** (DBML v5.14 без поля; импорт его не читает; формы — без КПП) |
| OQ-31 | КПП ЮЛ: в макете колонка и поле форм (`50`, `61`, `51-02`, `79`), в DBML v5.13 у `legal_entity` поля нет. Нужен ли КПП (и в импорте, и для ИП, у которых его нет)? | макет [50] [51-02] | срез 1, DBML v5.14; пересмотр — DBML v5.17 (docs#53, arm-core#45) | **закрыт решением владельца 07.10.2026: КПП нужен** (пересмотр; повод — макет и комментарий фронтенда «в ручке получения ЮЛ не приходит КПП»): DBML v5.17 — `legal_entity.kpp`, `legal_entity_history.kpp` (nullable). Формат — 9 символов: 4 цифры, 2 символа (цифра или латинская A–Z), 3 цифры, иначе `INVALID_KPP`; у ИП КПП запрещён (422 `INVALID_KPP`, при импорте строка пропускается с предупреждением); для ЮЛ необязателен (старые записи и импорт без колонки валидны); уникален только `inn` (КПП — атрибут, сопоставление контрагентов ОСВ по ИНН не меняется); изменение КПП — обычный PATCH со снимком в `legal_entity_history`. *История: 04.10.2026 — «КПП не нужен» (DBML v5.14 без поля; импорт его не читал; формы — без КПП); отменено 07.10.2026* |
| OQ-32 | ЮЛ в макете состоит одновременно в ГСК и ГСЗ («Входит в ГСК» и «Входит в ГСЗ» заполнены, [79]); ТЗ 6.1 / ANL-030 — только в одной. Что верно? | макет [79] [51-02] | срез 1 (partial unique `related_group_member.legal_entity_id`) | **закрыт решением владельца 04.10.2026: ЮЛ только в одной ГК, одновременно — в одной ГСК и одной ГСЗ.** DBML v5.14: `related_group_member.kind` + partial unique `(legal_entity_id, kind) WHERE is_current` (отменяет ANL-030); выгрузка ЕЦП это подтверждает (флаги «входит в ГСК» и «входит в ГСЗ» одновременно) |
| OQ-33 | «ID ГСК/ГСЗ в CRM» в формах (`51-03`, `51-04`, `77-02`; пример «3-9CGSK01» в формате ЕЦП CRM): у `related_group` в DBML только внутренний `code` (GSK-001). Есть ли у ГСК/ГСЗ идентификатор в CRM? | макет [77-02] | срез 1, DBML | **закрыт реализацией Среза 1:** `related_group.code` генерируется системой (`GSK-NNNNNN`/`GSZ-NNNNNN`), ID из CRM не вводится и не импортируется (в выгрузке ЕЦП его нет — OQ-91) |
| OQ-34 | «Удалить» ГСК в меню строки (`77-01`): физическое удаление или закрытие с историей? В DBML у `related_group` нет флага/истории удаления | макет [77-01] | срез 1 | **закрыт реализацией Среза 1 (ASSUMPTION подтверждено отсутствием возражений):** удаляется только пустая ГСК/ГСЗ (409 `GROUP_NOT_EMPTY`), состав сохраняется в `before_value` аудита; группа с FK из консолидаций — follow-up `GROUP_IN_USE` (arm-core#8, T8.1) |

View File

@ -61,8 +61,8 @@
| Шаг | Экран | Действие | БД |
|---|---|---|---|
| 1.1 | Справочники → ГК и организации [50] | **Импорт из файла** [38]: файл .xlsx/.csv, РФ, колонки ИНН, Наименование, Тип, Базовый РФ ГК, ID ГК; дубли — «Пропустить существующие». Та же модалка открывается кнопкой **Импорт** на экране консолидаций ГК [34] | `legal_entity`, `company_group`, `audit_event(DIRECTORY_IMPORT)` |
| 1.2 | [51] | **Создать ГК/ЮЛ** → модалка «Выберите объект для добавления»: ГК / ЮЛ / ГСК / ГСЗ → форма. **ГК** [51-01]: Название ГК, ID ГК в CRM («генерируется автоматически» — это внутренний `code`, OQ-20; `crm_id` приходит из ЕЦП CRM), Базовый РФ ГК. **ЮЛ** [51-02] (добавляется в открытую карточку ГК): ИНН, КПП, ID ЮЛ в CRM, Тип (ЮЛ/ИП), Наименование организации, Альтернативные наименования (теги), Входит в ГСК, Входит в ГСЗ. **ГСК** [51-03] / **ГСЗ** [51-04]: ID в CRM, Наименование, Дата создания | `company_group(+_history)`, `legal_entity`, `legal_entity_alias`, `related_group`, `related_group_member` |
| 1.1 | Справочники → ГК и организации [50] | **Импорт из файла** [38]: файл .xlsx/.csv, РФ, колонки ИНН, КПП (необязательная, v5.17), Наименование, Тип, Базовый РФ ГК, ID ГК; дубли — «Пропустить существующие». Та же модалка открывается кнопкой **Импорт** на экране консолидаций ГК [34] | `legal_entity`, `company_group`, `audit_event(DIRECTORY_IMPORT)` |
| 1.2 | [51] | **Создать ГК/ЮЛ** → модалка «Выберите объект для добавления»: ГК / ЮЛ / ГСК / ГСЗ → форма. **ГК** [51-01]: Название ГК, ID ГК в CRM («генерируется автоматически» — это внутренний `code`, OQ-20; `crm_id` приходит из ЕЦП CRM), Базовый РФ ГК. **ЮЛ** [51-02] (добавляется в открытую карточку ГК): ИНН, КПП (`legal_entity.kpp`, v5.17: 9 символов, только у ЮЛ, необязателен), ID ЮЛ в CRM, Тип (ЮЛ/ИП), Наименование организации, Альтернативные наименования (теги), Входит в ГСК, Входит в ГСЗ. **ГСК** [51-03] / **ГСЗ** [51-04]: ID в CRM, Наименование, Дата создания | `company_group(+_history)`, `legal_entity`, `legal_entity_alias`, `related_group`, `related_group_member` |
| 1.3 | Карточка ГК → вкладка ГСК/ГСЗ [77] | список: ID ГСК/ГСЗ (GSK-001, GSZ-001), Наименование, Число участников, Дата создания; состав — через форму ЮЛ («Входит в ГСК/ГСЗ»). Меню «…» строки [77-01]: **Изменить ГСК** [77-02] (ID ГСК в CRM, Наименование, Дата создания), **Удалить** | `related_group`, `related_group_member` |
| 1.4 | Карточка ГК → ЮЛ → меню «…» [78] | **Изменить ЮЛ** [79] (те же поля, что при создании, + ГК; алиасы с удалением; членство в ГСК/ГСЗ); **История изменений** [79-02] → Журнал действий с фильтром по ГК и категории «Справочники»; **Перевести в другую ГК** [79-01]: ЮЛ, Текущая ГК, Новая ГК, Дата перевода (подсказка: «запись появится в архиве текущей ГК с событием „Переход в другую ГК“»); **Вывести из состава ГК** [80]: ЮЛ, Событие (Закрытие / Переход в другую ГК), Дата закрытия, Комментарий (необязательно) | `legal_entity`, `legal_entity_alias`, `related_group_member`, `legal_entity_history(event_type, effective_date)` |
| 1.5 | ЮЛ вне ГК [61] (заголовок экрана — «Юридические лица») | **Создать ЮЛ** (монозаёмщик), поиск по ЮЛ, ИНН или ГК, фильтр «Архив». По снимку таблица показывает **все** ЮЛ с заполненной колонкой ГК, а не только вне ГК — расхождение с названием пункта (OQ-41) | `legal_entity(company_group_id NULL)` |

View File

@ -61,7 +61,7 @@ GSK-002), консолидации 2024 К1 (v1 Верифицирован, v2
| `77-02` | Модалка «Изменить группу связанных компаний» | ID ГСК в CRM (в примере «3-9CGSK01» — формат ЕЦП CRM), Наименование, Дата создания; Отмена / Сохранить. Состав ГСК здесь не редактируется (через ЮЛ) | `related_group(code?, name, created_date)` — поля `crm_id` у `related_group` в DBML нет — OQ-33 | 1 |
| `51` | «Создать ГК/ЮЛ» | модалка «Выберите объект для добавления»: ГК (Группа компаний) / ЮЛ (Юридическое лицо) / ГСК (Группа связанных компаний) / ГСЗ (Группа связанных заемщиков) → форма | | 1 |
| `51-01` | «Создать группу компаний» | Название ГК («Например: Агроком»), ID ГК в CRM («Генерируется автоматически»), Базовый РФ ГК (выбор); Отмена / Создать | `company_group(name, code — генерируется, crm_id — из ЕЦП CRM, base_regional_office_id)`; «ID в CRM генерируется автоматически» в макете = внутренний `code` (OQ-20) | 1 |
| `51-02` | «Добавить юридическое лицо» (в открытой карточке ГК) | ИНН (0000000000), **КПП** (000000000), ID ЮЛ в CRM («Генерируется автоматически»), Тип (ЮЛ / ИП), Наименование организации, **Альтернативные наименования** (теги: «Введите синоним и нажмите Enter»), Входит в ГСК, Входит в ГСЗ (выпадающие списки, «— не указан —»); Отмена / Добавить. Полей «ГК» (берётся из карточки) и «Основной РФ» в форме нет | `legal_entity(inn, crm_id, entity_type, name, company_group_id)`, `legal_entity_alias`, `related_group_member`; `kpp` в DBML нет — OQ-31 | 1 |
| `51-02` | «Добавить юридическое лицо» (в открытой карточке ГК) | ИНН (0000000000), **КПП** (000000000), ID ЮЛ в CRM («Генерируется автоматически»), Тип (ЮЛ / ИП), Наименование организации, **Альтернативные наименования** (теги: «Введите синоним и нажмите Enter»), Входит в ГСК, Входит в ГСЗ (выпадающие списки, «— не указан —»); Отмена / Добавить. Полей «ГК» (берётся из карточки) и «Основной РФ» в форме нет | `legal_entity(inn, kpp, crm_id, entity_type, name, company_group_id)`, `legal_entity_alias`, `related_group_member`; `kpp` — DBML v5.17, OQ-31 | 1 |
| `51-03` | «Создать группу связанных компаний» | ID ГСК в CRM («Генерируется автоматически»), Наименование («PM Агро — название группы»), Дата создания (ДД.ММ.ГГГГ); Отмена / Создать | `related_group(kind=gsk, code, name, created_date)` | 1 |
| `51-04` | «Создать группу связанных заёмщиков» | ID ГСЗ в CRM, Наименование, Дата создания; Отмена / Создать | `related_group(kind=gsz, …)` | 1 |
| `61` | **ЮЛ вне ГК** (заголовок экрана — «Юридические лица») | поиск «по ЮЛ, ИНН или ГК», Настройка таблицы, чекбокс Архив, **Создать ЮЛ**; таблица: Статус (Активно), ИНН, КПП, Организация, ГК, Тип, Основной РФ, меню «…». По снимку это **сводный список всех ЮЛ** (колонка ГК заполнена у каждой строки), а не только монозаёмщиков — OQ-41 | `legal_entity` (все), фильтр «вне ГК» = `company_group_id IS NULL` — в снимке не виден | 1 |
@ -87,7 +87,7 @@ GSK-002), консолидации 2024 К1 (v1 Верифицирован, v2
- Версии консолидации показаны деревом под периодом; «Взять в работу» — на уровне списка, не карточки (ANL-036).
- Шахматка в макете — в тыс. руб. с 2 знаками, хотя ОСВ в рублях — подтверждает OQ-26 (единицы/округление).
- Гудвилл: колонка «Фондовые вложения» (= финансовые вложения 58.01), «Доля участия ГО в УК ДО», «Уставный капитал» — без 1350 (OQ-10).
- По досъёмке 03.10.2026 (OQ-31…OQ-44 в `07-open-questions.md`): КПП у ЮЛ (`50`, `61`, `51-02`, `79`) — поля нет в DBML;
- По досъёмке 03.10.2026 (OQ-31…OQ-44 в `07-open-questions.md`): КПП у ЮЛ (`50`, `61`, `51-02`, `79`) — поля не было в DBML v5.14…v5.16, введено в v5.17 (OQ-31, решение владельца 07.10.2026);
ЮЛ одновременно в ГСК и ГСЗ (`79`) против ТЗ 6.1 / ANL-030; «ID ГСК/ГСЗ в CRM» (`77-02`, `51-03`) — у `related_group`
нет `crm_id`; «Удалить» ГСК (`77-01`); процент отклонения считается к стороне ЮЛ-1 (`31-02`) и к версии банка
(`31-06`), а не к меньшей сумме (Методика сноска 14); строки без кода в результате распознавания (`41`); ЕХ показывает

1362
model/data-model-v5.17.dbml Normal file

File diff suppressed because it is too large Load Diff

173
model/entities-v5.17.md Normal file
View File

@ -0,0 +1,173 @@
# АРМ КА — описание сущностей модели данных v5.17
Источник истины по структуре — `data-model-v5.17.dbml` (полный). Диаграммы по частям — `diagrams/` (part01…09 svg/png; отрисованы по v5.13: v5.14 меняет только `related_group_member` — колонку `kind` и индекс, v5.15 — одно значение enum и три индекса, v5.16 — один enum и одна колонка, v5.17 — две колонки `kpp`, — диаграммы не перерисовывались). Здесь — что каждая таблица значит для бизнеса, как связана с соседями и откуда взялась.
## Что изменилось в v5.17 (решение владельца 07.10.2026, пересмотр OQ-31, RSHB/arm-ka-docs#53)
| № | Изменение | Решение |
|---|---|---|
| 1 | `legal_entity.kpp` и `legal_entity_history.kpp` (`varchar`, nullable) | КПП юридического лица нужен (макет: колонка «КПП» на экранах `50`, `61`, поле форм `51-02`, `79`; фронтенд: «в ручке получения ЮЛ не приходит КПП»). Формат — 9 символов: 4 цифры, 2 символа (цифра или латинская A–Z), 3 цифры, иначе `INVALID_KPP`. У ИП (`entity_type = ИП`) КПП запрещён. Для ЮЛ необязателен: существующие записи и импорт без колонки остаются валидными. Уникален по-прежнему только `inn` (КПП — атрибут; сопоставление контрагентов ОСВ по ИНН не меняется). Изменение КПП — обычная правка со снимком в `legal_entity_history`. Отменяет п. 2 v5.14 («КПП не вводится») |
Остальные таблицы и enum — как в v5.16. Реализация — миграция `0005` в `arm-core`.
## Что изменилось в v5.16 (Срез 4, решения владельца 06.10.2026)
| № | Изменение | Решение |
|---|---|---|
| 1 | Enum `counterparty_match_kind` (`INN`, `NAME`, `ALIAS`, `MANUAL`, `PROPAGATED`) и колонка `osv_entry.counterparty_match_kind` (nullable) | Способ сопоставления контрагента строки ОСВ хранится. `NULL` ⇔ `counterparty_id` `NULL`; `INN` / `NAME` / `ALIAS` — шаг автоматического резолва при маппинге (ИНН; точное имя; нормализованное имя ЮЛ или алиас); `MANUAL` — ручное сопоставление аналитиком; `PROPAGATED` — автоприменение по имени в версии (DOC-221278 п. 12–13). Вычислять на чтение нельзя: после ручного связывания появляется алиас, и ручное сопоставление неотличимо от `ALIAS`. Миграция `0004` заполняет колонку по уже смапленным строкам (ИНН без пробелов = `legal_entity.inn` → `INN`; `trim(name) = legal_entity.name` → `NAME`; остальные с `counterparty_id` → `ALIAS` — приближение: ручных сопоставлений до Среза 4 не было) |
| 2 | Семантика `consolidation_document` | Без изменения структуры: строка создаётся при подтверждении документа (по документам ЮЛ — членов периметра версий «в работе») и при добавлении ЮЛ в периметр (подтверждённые подходящие документы подхватываются); удаляется при замене/удалении файла и исключении ЮЛ из периметра. Для одной (ЮЛ, `document_type`, `osv_account_code`, период, признак нарастающего) привязан один документ — самый поздний `confirmed_at` (D-5 Среза 4) |
| 3 | Семантика `consolidation_lock` | Без изменения структуры: блокировка без истечения по времени — активна, пока её не снял владелец, администратор или не перехватил другой пользователь с `CONSOLIDATION_EDIT` по причине (OQ-119) |
Пробелы квартальной склейки и сама сводная ОСВ по-прежнему не хранятся (DECISION-5). Остальные таблицы и enum — как в v5.15 (43 таблицы; enum — 28). Реализация — миграция `0004` в `arm-core`.
## Что изменилось в v5.15 (Срез 2c, решения владельца 05.10.2026)
| № | Изменение | Решение |
|---|---|---|
| 1 | `osv_data_type` + `NU` (после `BU`) | Строки налогового учёта из ОСВ «БУ, НУ» хранятся в `osv_entry` как есть (OQ-45); контракт результата допускал `NU` с v1.0, ядро раньше такой строки принять не могло |
| 2 | `app_user.email` — в нижнем регистре | Без изменения структуры: нормализация при записи (seed, администрирование), поиск по `lower(email)` сохраняется, уникальность — существующий UNIQUE по `email` (OQ-59). Expression-индекс `lower(email)` не вводится: сверка ORM↔DBML не умеет expression-индексы. Миграция `0003` приводит существующие email к нижнему регистру (при дублях по регистру — падает, слияние решает человек) |
| 3 | Семантика `stored_file.status`, `recognition_job.group_schema` | Без изменения структуры: `PROCESSING` ставит inbox по ack сервиса, `PROCESSED`/`ERROR` — цикл маппинга результата (OQ-67); маппинг выполняется отдельно от inbox (OQ-69); `group_schema` заполняет маппер; «Итого» в `osv_entry` не хранится (OQ-01) |
| 4 | Индексы `osv_entry (parent_entry_id)`, `osv_entry (recognition_job_id)`, `stored_file (current_recognition_job_id)` | Индексы для удаления файла и обхода дерева строк: без них проверки FK (в том числе self-FK `parent_entry_id`) при `DELETE /files/{id}` и очистке файла перед повторным маппингом шли seq scan'ом. Замер: DELETE документа 1000 строк 8,6 с → 16 мс на 200 тыс. строк. Обычные (не unique) индексы; закрывает OQ-117 (I-1 финального ревью Среза 2c) |
Остальные таблицы и enum — как в v5.14 (43 таблицы, 27 enum). Реализация — миграция `0003` в `arm-core`.
## Что изменилось в v5.14 (Срез 1, решения владельца 04.10.2026)
| № | Изменение | Решение |
|---|---|---|
| 1 | `related_group_member.kind` + индекс `(legal_entity_id, kind) WHERE is_current` | ЮЛ состоит **одновременно в одной ГСК и одной ГСЗ** (но только в одной группе каждого вида). Отменяет ANL-030 / v5.8 («только в одной ГСК или ГСЗ»). `kind` — денормализация `related_group.kind` (равенство проверяет ядро при записи, тест на рассинхрон); индекс не выразить через join, триггер не выбран. Закрывает OQ-32 |
| 2 | КПП | **Не вводится** *(отменено в v5.17: КПП введён)*. Поле есть только в макете; у `legal_entity` его нет, импорт его не читает (закрывал OQ-31 до v5.17) |
| 3 | Семантика `company_group.crm_id`, `base_regional_office_id` | Без изменения структуры: `crm_id` = «iD ГК» выгрузки ЕЦП CRM, `base_regional_office_id` = «Базовый РФ ГК в CRM»; флаги «входит в ГСК/ГСЗ» выгрузки → членство в одной неявной ГСК/ГСЗ на ГК (правила — `05-domain-rules.md` §13) |
| 4 | `audit_action` + `LEGAL_ENTITY_CHANGE`, `USER_CHANGE` | Журнал изменений ЮЛ (создание/изменение/закрытие/перевод, алиасы) и пользователей (создание/деактивация администратором) — ТЗ 8.2; в v5.13 для них не было действия (были `GROUP_CHANGE`, `MEMBERSHIP_CHANGE`, `USER_ROLE_CHANGE`) |
Остальные таблицы и enum — как в v5.13 (43 таблицы). Реализация — миграция `0002` в `arm-core`.
## Что изменилось в v5.13 (ревью 30.09–01.10)
| № | Замечание | Решение |
|---|---|---|
| 1 | Вместо RBAC «в коде» — роли **и** права | `permission` + `role_permission` возвращены; права только через роли, прямых назначений пользователю нет. Исключения — заведением роли |
| 2 | `osv_entry.parent_entry_id` не нужен — храним только строки с контрагентами | **Отложено.** Открыт вопрос: хранить ли строки уровня ACCOUNT для сверки «Σ контрагентов = итог счёта»; аналитика «Парсинг ОСВ» п.11 пока говорит «все строки» |
| 3 | `osv_group_schema` не особо нужна | Убрана. Схема шапки — `recognition_job.group_schema jsonb`; тип строки — `osv_entry.group_type` |
| 4 | `financial_statement_value.needs_review` непонятен | Убран: производное от `confidence` / `is_recognized`, порог может меняться |
| 5 | В `recognition_job` не нужны и `result`, и `raw_result` | Оставлен только `result_s3_object_id`. Сырой ответ ИС Сканирование — S3 `raw/{job_id}.json` по конвенции, в БД не регистрируется |
| 6 | `stored_file.fields_modified` — что это | Счётчик «исправлено полей» с карточки документа (v5.7). Убран — `COUNT(is_modified)` по данным |
| 6-бис | `document_type` не предусматривает несколько ОСВ или ББ+ОФР в одном файле | **Разделение файл / документ**: `stored_file` — физический файл, `document` — логическая единица (1:N). Чек-лист, проверки, подтверждение, FK разобранных данных, `consolidation_document`, `ex_snapshot_item` — на `document`. Замена (`superseded_by`) — файл целиком |
| 7 | `file_type`: убрать Р7 | Убран. `xls/xlsx/xlsb → tabular-parser`, `pdf/png/jpg → scan-gateway` |
**Принципы модели (коротко):**
- Первичка не редактируется: файлы и распознанные значения хранятся как пришли; правки аналитика — рядом (`is_modified`, `original_*`) и в аудите.
- Производные консолидации в работе (сводная ОСВ, пары ВГО, шахматка) **не хранятся** — считаются запросом. Хранятся только ручные решения (корректировки, гудвилл, подтверждение КФО клиента).
- При верификации всё замораживается в Единое хранилище (ЕХ) как самодостаточный срез, который не зависит от таблиц консолидации.
- Единственный писатель в БД — ядро `arm-core`; сервисы распознавания видят только NATS и S3.
- Права — данные (`role_permission`), ограничения по ЗО/филиалу/статусу — policy-слой поверх прав.
---
## Часть 01 — Группы компаний, организации, филиалы
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `regional_office` | Филиал банка: головной офис (ГО, `is_head_office`) и региональные филиалы (РФ). Определяет зону видимости пользователя и закрепление ГК. | ← `company_group.base_regional_office_id`, `legal_entity.regional_office_id`, `app_user.regional_office_id` | ТЗ 8.1 |
| `company_group` | Группа компаний (ГК) — верхний уровень справочника клиентов. Текущее состояние (SCD4). `crm_id` = «iD ГК» из ЕЦП CRM (NULL, если создана вручную Куратором; v5.14 — семантика по выгрузке), внутренний `code`, закрепление за филиалом (`base_regional_office_id` = «Базовый РФ ГК в CRM»), флаг архива. | → `regional_office`; ← `legal_entity`, `related_group`, `consolidation` | ТЗ 6.1, Прил. 2; макет |
| `company_group_history` | Прошлые снимки ГК: каждое изменение — новая строка с `valid_from/valid_to`. | → `company_group` | ТЗ 6.10, 8.2 |
| `legal_entity` | Юридическое лицо / ИП. Натуральный ключ — ИНН; **v5.17:** `kpp` — КПП (9 символов, nullable, только у ЮЛ, не ключ). `company_group_id` = текущая ГК; **NULL = монозаёмщик**. `close_date` — закрыто/архивировано. | → `company_group`, `regional_office`; ← `related_group_member`, `stored_file`, `document`, `osv_entry.counterparty_id`, `consolidation_member` | ТЗ 6.1, Прил. 2; ЕХ 6.1 |
| `legal_entity_history` | Прошлые снимки ЮЛ + вид события (создание, изменение, переход в другую ГК, закрытие, вывод из ГК) и дата вступления в силу. | → `legal_entity` | ТЗ 8.2; макет |
| `legal_entity_alias` | Словарь написаний контрагента: как имя встречается в ОСВ и нормализованный ключ для матчинга. Пополняется при ручном сопоставлении. | → `legal_entity` | ТЗ 6.5 |
| `related_group` | ГСК или ГСЗ внутри ГК; `kind` различает. Для ГСК формируется КФО. Иерархия не поддерживается. | → `company_group`; ← `related_group_member`, `consolidation.related_group_id` | ТЗ 6.1, Прил. 2 |
| `related_group_member` | Состав ГСК/ГСЗ — история членства (SCD2). **v5.14:** ЮЛ может одновременно входить в **одну ГСК и одну ГСЗ** (не более одной группы каждого вида): колонка `kind` = `related_group.kind` (денормализация) и частичный уникальный индекс `(legal_entity_id, kind) WHERE is_current`. Решение владельца 04.10.2026, отменяет ANL-030. | → `related_group`, `legal_entity` | ТЗ 6.1; макет [79] |
## Часть 02 — Пользователи, роли, права
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `app_user` | Пользователь АРМ. Аутентификация внешняя (РАИСА/SSO, `external_id`). Филиал (ГО/РФ), зона ответственности (ДКБ/ДМСБ/ДМ), табельный номер. Видимость данных = права роли + филиал + ЗО. | → `regional_office`; ← `user_role`, все `*_by` поля | ТЗ 8.1, Прил. 2 |
| `role` | Роли: seed из ТЗ 8.1 (КА РФ, КА ГО, РКА, КМ, Куратор ГО, Эксперт, Администратор; `is_system`) + роли, заведённые администратором для исключений. **v5.13:** код роли в бизнес-логике не проверяется — только права. | ← `user_role`, `role_permission` | ТЗ 8.1 |
| `permission` | **v5.13.** Атомарное право «объект × действие»: `DIRECTORY/DOCUMENT/CONSOLIDATION/KFO/EX/EXPORT/USERS/AUDIT` × `VIEW/EDIT/APPROVE/VERIFY/DELETE/EXPORT/ADMIN`. Seed, привязан к коду — администратор новые коды не создаёт. Право говорит «может»; policy-слой (ЗО, филиал, статус КФО) — «эту ли и сейчас ли». | ← `role_permission` | ТЗ Прил. 2, сноска 29 |
| `role_permission` | **v5.13.** Матрица доступов как данные: `effective_permissions(user) = ∪ permissions(roles(user))`. Редактируется администратором на экране «Изменение ролей и прав доступа»; история — `audit_event(ROLE_PERMISSION_CHANGE)`. Прямых прав у пользователя нет. | → `role`, `permission` | ТЗ Прил. 2; макет |
| `user_role` | Назначение ролей пользователю (M:N). История — в аудите. | → `app_user`, `role` | ТЗ 8.1 |
## Часть 03 — Бухгалтерские справочники и правила
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `chart_of_account` | План счетов с иерархией, тип A/P/AP, признак забалансового. Seed — полный план 1С; нестандартные субсчета клиентов добавляет администратор. | ← `osv_entry.account_id`, `elimination_rule_account`, `required_document_rule` | ТЗ 6.2/6.5, Прил. 3/4 |
| `elimination_rule` | Правило элиминации из Прил. 3 (ББ) / Прил. 4 (ОФР) как данные: пара статей, сторона, мера, режим, НДС, акцизы. Версионируется. | ← `elimination_rule_account`, `correction.rule_id`; версия фиксируется в `consolidation_version` | ТЗ 6.4, Прил. 3/4 |
| `elimination_rule_account` | Счета/субсчета каждой стороны правила, включая исключения («76 кроме 76.7.1»). | → `elimination_rule`, `chart_of_account` | ТЗ Прил. 3/4 |
| `required_document_rule` | Какие документы обязательны по подходу. **v5.13:** сверяется с `document` (тип + счёт + период), а не с файлом: выполнено = есть `document CONFIRMED` у каждого ЮЛ периметра. | → `chart_of_account` | ТЗ 5.1, 6.2, сноска 12 |
## Часть 04 — Файлы, документы, S3, задания распознавания
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `s3_object` | Реестр всех объектов в S3: бакет-роль, ключ, размер, sha256, срок жизни, ссылка на архив в data lake. Единая точка ретеншна (5 лет по ТЗ 8.4) и дедупа. | ← `stored_file`, `recognition_job` | ТЗ 6.2, 8.4; ЕХ 8.3 |
| `stored_file` | **v5.13: только физический файл** одной загрузки: чей (ЮЛ), формат, hash, кто/когда загрузил, статус обработки (`UPLOADED → PROCESSING → PROCESSED / ERROR / SUPERSEDED`), версия при замене. Что внутри файла — `document`. Замена — файл целиком со всеми документами. | → `legal_entity`, `s3_object`, `recognition_job`; ← `document`, `recognition_job` | ТЗ 6.2, 8.2; UC-01…05 |
| `document` | **v5.13, новая.** Логический документ внутри файла: ББ, ОФР, ОСВ по счёту (`osv_account_code`), расшифровка, прочее. Период, отчётная дата, где в файле (`locator`: лист / страницы), статус проверки (`RECOGNIZED / NEEDS_CHECK / CONFIRMED / ERROR`), результаты проверок периода и контрагента, кто подтвердил. Один файл → N документов (PDF с ББ+ОФР; xlsx с несколькими ОСВ). Создаёт ядро при маппинге результата `recognition_job`. На документ ссылаются разобранные данные, чек-лист, консолидация и ЕХ. | → `stored_file`, `legal_entity`, `app_user`; ← `osv_entry`, `financial_statement_meta`, `consolidation_document`, `ex_snapshot_item` | ТЗ 6.2, 5.1, 8.2; макет (чек-лист, «Подтвердить данные») |
| `recognition_job` | Одна попытка распознать/распарсить файл. **Единственная точка обмена с NATS** (outbox/inbox, дедуп по `result_msg_id`, DLQ). Хранит сервис и версию, **одну** ссылку на нормализованный JSON-результат в S3 (v5.13), метрики, и **схему группировок шапки ОСВ по листам** (`group_schema jsonb`, v5.13 — вместо `osv_group_schema`). | → `stored_file`, `s3_object`; ← `osv_entry`, `financial_statement_meta` | ТЗ 6.2, 6.10; DECISION-4 |
## Часть 05 — Разобранные ОСВ
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `osv_entry` | Строка ОСВ: документ (счёт + период), владелец, тип строки по схеме шапки (`group_type`, v5.13), уровень и родитель (`parent_entry_id` — **открыт вопрос, замечание 2**), код счёта и резолв в план счетов, сырое имя/ИНН контрагента, резолв в ЮЛ и способ сопоставления (`counterparty_match_kind`, v5.16), шесть сумм, тип данных (БУ/валюта/кол-во), «подакцизный» для 90-го. Ручные правки: `is_modified`, `original_values`, `modified_by`. Признак ГСК/ГК/Внешний не хранится — выводится из периметра версии; способ сопоставления контрагента хранится (`NULL` ⇔ контрагент не сопоставлен). | → `document`, `recognition_job`, `legal_entity` ×2, `chart_of_account` | ТЗ 6.2/6.5, Прил. 2; «Парсинг ОСВ» |
## Часть 06 — Распознанные формы ББ/ОФР
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `financial_statement_meta` | Шапка распознанной формы (1:1 с `document` BALANCE/INCOME): ИНН и наименование как в документе, отчётная дата, единица измерения, реквизиты. Ревью формы (`reviewed_by/at`) и акцепт КА непройденных логических проверок с комментарием. Проверки (ТЗ 6.3) не хранятся — считаются на лету. Подтверждение документа — `document.confirmed_by`. | → `document`, `recognition_job`, `app_user`; ← `column`, `line` | ТЗ 6.2/6.3; FNR01; макет |
| `financial_statement_column` | Колонки-периоды формы с датой — нужны для межформенной проверки (1370 на начало года) и гудвилла. | → `meta`; ← `value` | ТЗ 6.3 п.7, 6.7 |
| `financial_statement_line` | Строка формы с кодом ОКУД и наименованием как распознано. Только строки с кодом; порядок и разделы — шаблон формы на фронте. | → `meta`; ← `value` | ТЗ 6.2, Прил. 2 |
| `financial_statement_value` | Число в ячейке (строка × колонка): сумма, уверенность, `is_recognized`, ручная правка с исходным значением. **v5.13:** подсветка «требует проверки» = `confidence < порог OR NOT is_recognized` — считается, флаг `needs_review` убран. | → `line`, `column`, `app_user` | ТЗ 6.2, Прил. 2 |
## Часть 07 — Консолидация
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `consolidation` | Логическая консолидация: **ГК** + отчётный период. ГСК фиксируется/создаётся при верификации по фактическому периметру. Уникальна по (ГК, год, квартал). | → `company_group`, `related_group`; ← `consolidation_version` | ТЗ 6.10, 7.2 |
| `consolidation_version` | Версия консолидации: номер, статус, сценарий, подход, гудвилл, инициатор и его офис (маршрут), кто утвердил / верифицировал, решение УОБ, отклонения от Методики, версия правил элиминации, ссылка на предыдущую. После VERIFIED — read-only. | → `consolidation`, `app_user`, `regional_office`, self; ← всё в частях 07–08 | ТЗ 5.1, 6.4, 6.10; макет |
| `consolidation_status_history` | Каждый переход статуса: из → в, кто, когда, комментарий (обязателен при возврате). | → `consolidation_version`, `app_user` | ТЗ 6.10, 8.2 |
| `consolidation_lock` | «Взять в работу»: кто редактирует версию, перехват с причиной (v5.16: без истечения по времени, OQ-119). Из макета — ANL-036. | → `consolidation_version`, `app_user` | макет |
| `consolidation_member` | Периметр версии — зафиксированный список ЮЛ; `is_head_entity` для гудвилла. | → `consolidation_version`, `legal_entity` | ТЗ 6.1, 6.10 |
| `consolidation_document` | **v5.13:** какие **документы** (не файлы) участвуют в версии. Чек-лист = `required_document_rule` × периметр − `consolidation_document`. Переиспользование одного документа в нескольких версиях/периодах, монозаёмщики из ЕХ. | → `consolidation_version`, `document` | ТЗ Прил. 2; ЕХ 7 |
*Не хранятся (DECISION-5), считаются запросом по версии:* сводная ОСВ, признак контрагента ГСК/ГК/Внешний, пары ВГО, шахматка, результирующая таблица ВГО.
## Часть 08 — Корректировки, гудвилл, результат КФО
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `correction` | Ручная корректировка ВГО: пара (правило + компания 1 + компания 2 — естественный ключ), ББ/ОФР, строки и суммы по Дт и Кт, дочерние строки для распределения, причина, обязательный комментарий, статус, номер версии (история — в аудите). КФО = свод − Σ корректировок. | → `consolidation_version`, `elimination_rule`, `legal_entity` ×2, `chart_of_account` ×2, self | ТЗ 6.4, 6.8, Прил. 9 |
| `goodwill` | Строка расчёта гудвилла: головная × дочерняя, доля владения, ФВ по 58.01, УК (1310) и добавочный капитал (1350 — вопрос методологам), гудвилл, «включить в КФО», комментарий. | → `consolidation_version`, `legal_entity` ×2, `app_user` | ТЗ 6.7, Прил. 3 п.2, Прил. 8 |
| `kfo_result` | Шапка результата КФО: когда посчитан, заморожен ли, ЧА и ВБ, выручка, прибыль, расхождения, пороги, обоснование, итог гудвилла. | → `consolidation_version` (1:1); ← `kfo_result_line` | ТЗ 6.4, 6.8, Прил. 10 |
| `kfo_result_line` | Строка консолидированного ББ/ОФР по коду ФСБУ 4/2023: сумма → авто-элиминация → корректировки → гудвилл → итог; «в т.ч. ВГО», источник, расхождение, комментарий. | → `kfo_result`; ← `kfo_result_line_company` | ТЗ 6.4, Прил. 10; макет |
| `kfo_result_line_company` | Разрез строки КФО по компаниям периметра: сумма и доля. | → `kfo_result_line`, `legal_entity` | ЕХ 6.6; макет |
| `client_kfo_line` | Сценарий «подтверждение КФО клиента»: по строке — значение и ВГО от клиента (ручной ввод), значение и ВГО банка, расхождение, причина. | → `consolidation_version`, `app_user` | ТЗ 5.1, 6.10; макет |
## Часть 09 — Комментарии, аудит, Единое хранилище
| Сущность | Что это | Ключевые связи | Откуда |
|---|---|---|---|
| `comment` | Полиморфный комментарий к объекту (документу, ячейке шахматки по ключу пары, строке КФО). | → `app_user`; `object_type + object_id` без FK | ТЗ 6.3/6.4; макет |
| `audit_event` | Журнал действий (append-only): кто, когда, действие, раздел, объект, версия консолидации, before/after, результат, IP/браузер. Покрывает историю правок распознанных данных, корректировок, выгрузок, импорта справочников, входов, **изменений матрицы прав** (v5.13), подтверждения документов, **изменений ЮЛ и пользователей** (v5.14: `LEGAL_ENTITY_CHANGE`, `USER_CHANGE`). | → `app_user`, `consolidation_version` | ТЗ 8.2, Прил. 2; макет |
| `ex_snapshot` | Срез ЕХ: одна ГСК на одну отчётную дату, версия, статус (актуальный/архив), когда и кем верифицирован и записан. Самодостаточен — на консолидацию ссылается только данными. | → `related_group`, `company_group`, `app_user`, self; ← `member`, `item` | ЕХ ТЗ 6.2, 6.4, 5.3 |
| `ex_snapshot_member` | Периметр среза на дату: ЮЛ с ИНН и наименованием на момент верификации. | → `ex_snapshot`, `legal_entity` | ЕХ ТЗ 6.3 |
| `ex_snapshot_item` | Содержимое среза. Два вида: исходный документ → ссылка на **`document`** (v5.13; скачать — через его файл) и всё производное → `payload` jsonb. | → `ex_snapshot`, `document`, `legal_entity` | ЕХ ТЗ 5.2, 6.7 |
---
## Как читать модель по сценарию «сформировать КФО»
1. **Справочники** (01–03): ГК → ЮЛ → ГСК/ГСЗ; план счетов; правила элиминации; что обязательно загрузить.
2. **Создание консолидации** (07): `consolidation` от ГК + период → `consolidation_version` → `consolidation_member` (периметр).
3. **Загрузка** (04): `stored_file` + `s3_object` → `recognition_job` (queued → NATS → сервис → результат в S3 → done) → ядро создаёт **`document`** на каждый ББ / ОФР / ОСВ по счёту, найденный в файле.
4. **Разбор** (05–06): ОСВ → `osv_entry`; ББ/ОФР → `financial_statement_meta/column/line/value`. Аналитик проверяет, правит, подтверждает **документ**; логические проверки — на лету; сопоставляет контрагентов (→ `osv_entry.counterparty_id`, `legal_entity_alias`). Чек-лист закрывается по `document CONFIRMED`.
5. **Консолидация** (вычисляется): сводная ОСВ, пары ВГО, шахматка — из `osv_entry` × периметр × правила. Аналитик вносит `correction`, заполняет `goodwill`, при сценарии подтверждения — `client_kfo_line`. Результат — `kfo_result(_line)`.
6. **Согласование** (07): `consolidation_status_history` по маршруту; блокировка `consolidation_lock`. Что можно — решает `role_permission` + policy-слой по статусу/ЗО.
7. **Верификация → ЕХ** (09): `ex_snapshot` + `member` + `item` (документы по ссылке, остальное — payload). Версия → read-only. Всё это время — `audit_event`.
## Открытые вопросы после v5.14
- **Замечание 2** (`osv_entry.parent_entry_id`, какие строки ОСВ хранить) — отложено; затрагивает и аналитику «Парсинг ОСВ» п.11.
- Замена по-документно (одна ОСВ из файла с пятью) — сейчас не предусмотрена, замена файлом целиком. Если понадобится — `superseded_by` переезжает в `document`.
- Статус `document.RECOGNIZED` (ручная проверка не требуется по порогу) — подтвердить с аналитиками, нужен ли, или все документы проходят через `NEEDS_CHECK → CONFIRMED`.

View File

@ -16,7 +16,7 @@
- NATS subjects и схема ключей S3 — `../docs/context/03-architecture.md`.
## Правила репозитория
- ORM 1:1 с `../docs/model/data-model-v5.16.dbml`; `scripts/check_dbml_vs_orm.py` — обязательная проверка CI.
- ORM 1:1 с `../docs/model/data-model-v5.17.dbml`; `scripts/check_dbml_vs_orm.py` — обязательная проверка CI.
- Алгоритмы — только из `../docs/context/05-domain-rules.md`; номер § — в docstring.
- Вычисляемые объекты консолидации — `backend/app/domain/`, без ORM-таблиц.
- Фронт: пользователь слаб во фронте — для TS/React изменений делай ревью сам и объясняй решения в PR.