arm-ka-docs/dev/api-contract-slice-4.md

190 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Контракт API Среза 4 (консолидация) для разработчика фронта
Источник — реальная схема `GET /openapi.json` arm-core (ветка `feat/18-slice-4-consolidation`, Swagger — `/docs`); коды ошибок и
нюансы сверены с кодом и спекой `arm-core/openspec/changes/archive/2026-10-06-slice-4-consolidation/` (design D-1…D-16, решения №1–№81+).
Правила предметной области — `context/05-domain-rules.md` §2, §3, §9.1, §10; экраны — `context/10-user-journey.md`
(пути 2 и 3), номера в `[…]` — снимки макета (`mockup/INDEX.md`). Здесь — только то, что нужно, чтобы писать клиент.
## 1. Общее
- Префикс `/api/v1`, заголовок `Authorization: Bearer <JWT>` (в dev — `POST /dev/token`). Права: `CONSOLIDATION_VIEW` (чтение),
`CONSOLIDATION_EDIT` (правки); загрузка файлов — `DOCUMENT_EDIT`. Нет права — 403 строкой
`{"detail": "недостаточно прав: требуется <КОД>"}`; нет/плохой токен — 401. Скрывать кнопки по праву можно по `GET /me`.
- Доменные ошибки: `{"detail": {"code": "...", "message": "на русском", ...доп. поля}}`; по `code` работает клиент. 422 валидации
тела/параметров — массив pydantic (`detail: [{loc, msg, type}]`). Каталог кодов — `arm-core/README.md`, «Ошибки API».
- Видимость: ГК и версии вне области видимости пользователя (`ka_rf`/`rka`/`client_manager` — только свой филиал) неотличимы от
несуществующих: **404 с кодом `NOT_FOUND`**. Различие — только по тексту `message` (см. §4); клиент не должен ветвиться по тексту.
- **Деньги — строки JSON с двумя знаками** (`"100.00"`, `"0.00"`), могут быть `null` (нарастающий документ отдаётся «как в файле»).
Не парсить во `float`: суммы `numeric(18,2)`.
- Идентификаторы — uuid-строки; даты — ISO; `quarter` — `Q1`…`Q4`.
- Страницы — `{items, total, limit, offset}`, `offset ≥ 0`. Лимиты у списков разные: сводная ОСВ и строки ОСВ документа — `limit ≤ 2000`
(по умолчанию 500); файлы ЮЛ и справочники — `limit ≤ 200` (по умолчанию 50). Больше предела — 422. Файлы, документы и справочники —
`dev/api-contract-files-directories.md`.
- Перечисления: `ConsolidationStatus` — `INITIATED`, `DOCUMENTS_PROCESSING`, `DATA_CORRECTION`, `CONSOLIDATION_RF`, `ADJUSTMENT`,
`VERIFICATION_RF`, `CONSOLIDATION_GO`, `VERIFICATION_GO`, `VERIFIED`, `CANCELLED`, `REJECTED`, `ARCHIVED` (в Срезе 4 достижимы
первые три и `CANCELLED`); `CounterpartyType` — `GSK`, `GK`, `EXTERNAL`; `CounterpartyMatchKind` — `INN`, `NAME`, `ALIAS`,
`MANUAL`, `PROPAGATED`; `CellStatus` — `CONFIRMED`, `NEEDS_CHECK`, `RECOGNIZED`, `PROCESSING`, `ERROR`, `MISSING`;
`ConsolidationScenario` — `CREATE`, `CONFIRM`; `ConsolidationApproach` — `BASIC`, `EXTENDED`.
- «В работе» — `INITIATED`, `DOCUMENTS_PROCESSING`, `DATA_CORRECTION` (`VersionCard.is_in_work`).
## 2. Эндпоинты
Порядок проверок на каждом маршруте: право (403) → видимость (404) → для правок статус «в работе» и своя блокировка (409) → операция.
В таблице «Ошибки» — доменные коды и HTTP; 401/403 везде и ниже не повторяются.
| Метод и путь | Право | Запрос | Ответ | Ошибки | Экран |
|---|---|---|---|---|---|
| `POST /consolidations` | EDIT | `ConsolidationCreate {company_group_id, related_group_id, year (2000–2100), quarter, legal_entity_ids? (≤ 500)}` | 201 `VersionCard` (версия №1, `INITIATED`, `lock = null`) | 404 `NOT_FOUND` (ГК/ГСК не видна), 409 `CONSOLIDATION_EXISTS`, 422 `RELATED_GROUP_MISMATCH`, 422 `ENTITY_NOT_IN_GROUP` (`legal_entity_ids`), 422 `PERIMETER_TOO_SMALL`, 422 (pydantic-массив) при более чем 500 `legal_entity_ids` | [42] |
| `GET /company-groups/{group_id}/consolidations` | VIEW | query `in_work_only` (bool, по умолчанию `false`) | `ConsolidationPeriodOut[]` (период + `versions[]` — `VersionBrief`) | 404 `NOT_FOUND` | [34] |
| `GET /consolidations/versions/{version_id}` | VIEW | — | `VersionCard` | 404 `NOT_FOUND` | [34] панель «Организации», [36], [37] |
| `POST /consolidations/versions/{version_id}/lock` | EDIT | тело необязательно: `LockIn {takeover_reason?}` (≤ 1000) | 201 `LockOut` (новая блокировка или перехват) / 200 `LockOut` (уже ваша — идемпотентно) | 404 `NOT_FOUND`, 409 `VERSION_NOT_EDITABLE` (версия `CANCELLED`), 409 `LOCKED_BY_OTHER`, 422 (pydantic-массив) при `takeover_reason` длиннее 1000 символов | [35] |
| `DELETE /consolidations/versions/{version_id}/lock` | EDIT | — | 204 (в т. ч. если активной блокировки нет) | 404 `NOT_FOUND`, 409 `NOT_LOCK_OWNER` | [34] |
| `POST /consolidations/versions/{version_id}/status` | EDIT | `StatusIn {to_status, comment? (≤ 1000)}` | 200 `VersionCard` | 404, 409 `VERSION_NOT_EDITABLE` / `LOCK_REQUIRED` / `LOCKED_BY_OTHER` / `TRANSITION_NOT_ALLOWED`, 422 `COMMENT_REQUIRED`, 422 (pydantic-массив) при `comment` длиннее 1000 символов | [33-01], меню «…» [34] |
| `PUT /consolidations/versions/{version_id}/members` | EDIT | `MembersIn {legal_entity_ids[] (≤ 500)}` | 200 `VersionCard` | 404, 409 `VERSION_NOT_EDITABLE` / `LOCK_REQUIRED` / `LOCKED_BY_OTHER`, 422 `ENTITY_NOT_IN_GROUP` (`legal_entity_ids`), 422 `PERIMETER_TOO_SMALL`, 422 (pydantic-массив) при более чем 500 `legal_entity_ids` | [36] |
| `GET /consolidations/versions/{version_id}/checklist` | VIEW | query `format` (`json` по умолчанию / `csv`) | `ChecklistOut {rules[], rows[]}`; с `format=csv` — файл CSV (Срез 3b) | 404 | [37] |
| `GET /consolidations/versions/{version_id}/osv` | VIEW | query: `limit`, `offset`, `legal_entity_id`, `account_root`, `counterparty_type`, `counterparty_id` (строки с этим контрагентом после дорезолва; AURORA-1746), `unmapped` (bool), `match_kind` (повторяемый), `q` (≤ 500) | 200 `Page[ConsolidatedRowOut]` | 404, 422 (pydantic-массив) при `q` длиннее 500 символов | [30] |
| `GET /consolidations/versions/{version_id}/osv/summary` | VIEW | — | `OsvSummaryOut {rows_total, unmapped_total, auto_matched_total, documents_total, gaps[], counter_pairs[]}` | 404 | [30], баннер «Осталось: N», режим «Встречные записи», модалка «Проверьте встречные записи участников» (вместо [31-00]) |
| `GET /consolidations/versions/{version_id}/osv/export` | VIEW | те же фильтры, что у `…/osv`, без `limit`/`offset` | файл xlsx (`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`) | 404 | «Экспорт» [30] |
| `GET /consolidations/versions/{version_id}/counterparty-candidates` | VIEW | query: `q` (≤ 500), `scope` (`group` по умолчанию / `all`), `entry_id` | `CandidateOut[]` (до 100) | 404, 422 `QUERY_TOO_SHORT` (`scope=all` и `q` короче 3 символов после обрезки) | [32-osv-map-counterparty] |
| `POST /consolidations/versions/{version_id}/osv/entries/{entry_id}/counterparty` | EDIT | `MapCounterpartyIn {legal_entity_id, move_alias?=false}` | 200 `MapCounterpartyOut` | 404, 409 `VERSION_NOT_EDITABLE` / `LOCK_REQUIRED` / `LOCKED_BY_OTHER`, 409 `ALIAS_TAKEN` (`owners[] {legal_entity_id, name}`) | [32-osv-map-counterparty] «Связать» |
| `POST /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` | `DOCUMENT_EDIT` | multipart `files` (v5.18, docs #90; прежний query `consolidation_version_id` у `POST /legal-entities/{id}/files` удалён) | как в Срезе 2c: 201/422 `{accepted[], rejected[]}` | 404 (консолидация или ЮЛ), 409 `CONSOLIDATION_NOT_EDITABLE`, 422 `ENTITY_NOT_IN_PERIMETER` | [39] / [40] |
Остальные маршруты, влияющие на версию: `POST /documents/{id}/confirm` (Срез 2c) привязывает подтверждённый документ ко всем
версиям «в работе» своей ГК и может перевести версию в `DATA_CORRECTION`; `DELETE /files/{id}` и автозамена — снимают привязки
(см. `context/05-domain-rules.md` §9.5).
## 3. Схемы
`Ref {id, name}`. Поля без `?` присутствуют всегда.
**`ConsolidationPeriodOut`** `{consolidation_id, year, quarter, reporting_date, versions[]}`.
**`VersionBrief`** `{id, version_no, status, scenario, approach, lock (LockOut | null), created_at (datetime | null), members_count}`.
**`LockOut`** `{locked_by, locked_by_name, locked_at, taken_over_from (uuid | null), takeover_reason (string | null)}`.
**`VersionCard`** = `VersionBrief` + `{consolidation_id, year, quarter, reporting_date, company_group (Ref), initiator (Ref),
initiator_office (Ref), goodwill_enabled, is_in_work, previous_version_id, members[], history[]}`:
- `members[]` — `{id, name, inn, entity_type ("ЮЛ" | "ИП" | null — поле `legal_entity` nullable в DBML), is_head_entity, checklist[], osv_accounts[]}` (arm-core #42):
- `checklist[]` — ячейки `ChecklistCellOut` по правилам комплекта (тот же порядок, что `ChecklistOut.rules`); ячейка
**самоописывающая** — ищите нужную по `document_type`/`account` («Бух. баланс» — `BALANCE`, «ОФР» — `INCOME`, «ОСВ 60» —
`OSV` + `"60"`), а не по индексу;
- `osv_accounts[]` — коды счетов ОСВ ЮЛ, **загруженных** к периоду версии: документы ОСВ за год версии с кварталом не позже
квартала версии, в любом статусе, кроме ошибки, из незаменённых файлов; натуральный порядок (`["01", "51", "58.03", "60"]`).
Это колонка «ОСВ» панели «Организации» [34] — «что загружено», а не «что готово»: готовность счетов комплекта — в ячейках
`checklist`. Пока файл распознаётся, его счетов в списке ещё нет (ячейки показывают `PROCESSING`);
- `history[]` — `{from_status (null у первой записи), to_status, changed_by, changed_at, comment}`.
**`ChecklistOut`** `{rules[], rows[]}`; `rules[]` — `{document_type (BALANCE/INCOME/OSV…), account (string | null — счёт ОСВ),
is_mandatory}` в порядке: ББ, ОФР, ОСВ по коду счёта (натурально: `58 < 58.03 < 60`); `rows[]` — `{legal_entity_id,
legal_entity_name, legal_entity_inn, cells[]}`, порядок `cells` = порядок `rules`. **`ChecklistCellOut`** `{document_type, account
(string | null), status (CellStatus), missing_quarters[], document_ids[]}` — `document_type`/`account` — правило ячейки (arm-core #42):
`CONFIRMED` — пара полностью покрыта привязанными подтверждёнными документами; `NEEDS_CHECK` / `RECOGNIZED` — есть
неподтверждённый подходящий документ; `PROCESSING` — у ЮЛ есть файл в обработке (по **всем** недостающим столбцам ЮЛ, пока файл
не смаплен — это известная неточность, OQ-122); `ERROR` — последний файл ЮЛ в `ERROR`; `MISSING` — нет ничего.
`missing_quarters` и `document_ids` — при частичном покрытии квартальными ОСВ (`document_ids` — только привязанные документы).
ОСВ по субсчёту (58.03) закрывает только своё правило, не «ОСВ 58».
**`ConsolidatedRowOut`** — строка свода:
`{entry_ids[], legal_entity_id, legal_entity_name, legal_entity_inn, account_code?, account_root?, period ("<год> Q<n>"),
is_cumulative, row_number?, counterparty_name?, counterparty_inn?, counterparty_id?, counterparty_entity_name?, counterparty_entity_inn?, counterparty_type,
match_kind?, match_stored, opening_debit?, opening_credit?, turnover_debit?, turnover_credit?, closing_debit?, closing_credit?,
warnings[]}` — поля с `?` могут быть `null` (ключ присутствует всегда).
`counterparty_name/inn` — как в файле ОСВ; `counterparty_id`/`counterparty_entity_name`/`counterparty_entity_inn` — сопоставленное ЮЛ справочника (id, имя, ИНН) или `null`;
`match_stored = true` — сопоставление хранится в БД, `false` — вычислено на лету (в БД не пишется); `match_kind` — способ
(`null`, если ЮЛ не найдено). Для склеенных строк `entry_ids` — записи по кварталам (порядок — от раннего к позднему).
**`OsvSummaryOut`** `{rows_total, unmapped_total, auto_matched_total, documents_total, gaps[], counter_pairs[]}`: `rows_total` — число строк свода (без фильтров); `unmapped_total` — число строк свода с пустым `counterparty_id` **после** дорезолва на лету **и** без `counterparty_inn` (то же, что `GET …/osv?unmapped=true` даёт в `total`; `unmapped=false` — все остальные строки); `auto_matched_total` — число строк с `match_kind` `NAME` или `ALIAS` независимо от `match_stored` (`INN`, `MANUAL`, `PROPAGATED` не входят); `documents_total` — число ОСВ-документов, привязанных к версии; **`gaps[]`** —
`{legal_entity_id, legal_entity_name, account_code, missing_quarters[]}` (пара ЮЛ × счёт, у которой нет ОСВ за какие-то кварталы;
строк такой пары в своде нет). Баннер «Требуется указать соответствия… Осталось: N», кнопка «Показать ЮЛ без ИНН» [32-osv-no-inn]
и предупреждение [31-00] опираются на `unmapped_total` / `…/osv?unmapped=true`: строки свода без сопоставленного ЮЛ **и** без ИНН
из файла; строка с ИНН, которого нет в справочнике, сопоставления не требует (решение владельца 08.10.2026, arm-core #74 — отмена
П-1 от 07.10.2026). **`CandidateOut`** `{id, inn, name, company_group (Ref | null), gsk (Ref | null), gsz (Ref | null),
counterparty_type, recommended}`. **`MapCounterpartyOut`** `{row (ConsolidatedRowOut | null), propagated, alias_created,
alias_moved_from (uuid | null)}`.
**`counter_pairs[]`** (AURORA-1746, `05-domain-rules.md` §3 п. 9) — пары участников без встречных записей, вычисляются на чтение, не хранятся:
`{holder_id, holder_name, missing_id, missing_name, holder_rows, missing_unmapped_rows, missing_rows_total}`; порядок — по `missing_name`, затем `holder_name`.
Строки сторон пары берутся из `GET …/osv`: у А — `?legal_entity_id=А&counterparty_id=Б` (`total` = `holder_rows`), у Б — `?legal_entity_id=Б&unmapped=true` (`total` = `missing_unmapped_rows`).
Фильтр `counterparty_id` принимает и `GET …/osv/export`.
## 4. Нюансы, которые легко пропустить
1. **Блокировка — не «мягкая».** Любая правка версии (`PUT …/members`, `POST …/status`, ручное сопоставление) требует статуса «в
работе» **и** вашей активной блокировки; нет блокировки — 409 `LOCK_REQUIRED`, чужая — 409 `LOCKED_BY_OTHER`. Загрузка файлов и
подтверждение документов блокировкой не ограничиваются. Блокировка **не истекает по времени**; `CANCELLED` её снимает.
2. **`LOCKED_BY_OTHER` приходит в двух формах.** Из `POST …/lock` — с `locked_by`, `locked_by_name`, `locked_at`. Из правок
(`PUT …/members`, `POST …/status`, сопоставление, т. е. из `ensure_version_editable`) — только `locked_by`, `locked_at`, **без
`locked_by_name`** (доменная функция имени не знает; имя — из `lock.locked_by_name` карточки версии). `NOT_LOCK_OWNER` отдаёт
`locked_by` и `locked_at` владельца.
3. **Перехват.** `POST …/lock` с непустым `takeover_reason` при чужой блокировке снимает её и создаёт вашу (201,
`taken_over_from`, `takeover_reason`). Пустая причина или из одних пробелов считается отсутствующей (→ 409 `LOCKED_BY_OTHER`);
при свободной версии причина игнорируется. Повторный `POST` по своей блокировке — 200, а не 201.
4. **404 — всегда код `NOT_FOUND`**; различие по тексту `message`: «версия консолидации не найдена», «ГК не найдена»,
«ГСК/ГСЗ не найдена», «строка ОСВ не найдена в версии», «ЮЛ не найдено». Отдельных кодов `CONSOLIDATION_NOT_FOUND` /
`VERSION_NOT_FOUND` / `ENTRY_NOT_FOUND` **нет** (design №17, №62) — это обозначения в спеке. ГСК из чужой ГК или с видом `gsz` —
422 `RELATED_GROUP_MISMATCH`, а невидимая/несуществующая ГСК — 404.
5. **`MapCounterpartyOut.row` может быть `null`:** строка привязанного документа, которая в свод не попадает (пара с пробелом,
квартальный документ при нарастающем), сопоставляется, но строки свода для ответа нет (design №69). Если `row == null`, перечитайте
`GET …/osv` и `…/summary`; обязательно проверяйте на `null`.
6. **`match_kind` склеенной строки — от последнего квартала** (D-3, design №8). Строка свода из нескольких кварталов несёт
`entry_ids[]`; ручное сопоставление пишется на записи, чью `entry_id` вы отправили. Чтобы в ответе увидеть `MANUAL`, отправляйте
**последнюю** `entry_id` строки (`entry_ids[entry_ids.length - 1]`); для записи более раннего квартала в строке будет `PROPAGATED`
(в БД при этом одна запись `MANUAL`). Если в записях разных кварталов разные `counterparty_id`, расхождение — в `warnings`.
7. **Ручное сопоставление — только строки свода** (R-11, design №62/№63): `group_type = COUNTERPARTY`, `data_type` БУ/СУММ привязанного
ОСВ-документа версии. Строки `NU`/`ACCOUNT`, чужие и несуществующие — 404 `NOT_FOUND`. Автоприменение (`PROPAGATED`) затрагивает
строки с тем же `counterparty_name.strip()`, пустым `counterparty_id` и ИНН контрагента пустым или равным ИНН выбранного ЮЛ — только среди строк
**свода** (`COUNTERPARTY`, БУ/СУММ) ОСВ-документов, привязанных именно к ЭТОЙ версии; ранее сопоставленные строки не перевязываются;
`propagated` — число записей `osv_entry`, а не строк
свода: `unmapped_total` падает на число **склеенных** строк (R-15).
8. **`ALIAS_TAKEN`** (`owners[]`): алиас закреплён за другим ЮЛ; повторите с `move_alias: true` (право `DIRECTORY_DELETE` не нужно),
в ответе `alias_moved_from` — прежний владелец.
9. **Экспорт.** Заголовок `Content-Disposition: attachment; filename="svod-osv.xlsx"; filename*=UTF-8''svod-osv_<ГК>_<год>-<Qn>_v<n>.xlsx`
(имя percent-кодировано целиком). Забирайте файл как blob с `Authorization` (прямая ссылка `<a href>` не подойдёт). Фильтры — те
же, что у `GET …/osv`, пагинации нет, порядок строк тот же. **Лист «Пробелы» строится по своду целиком и от фильтров не зависит**
(при `unmapped=true` без совпадений лист «Сводная ОСВ» — только заголовок, «Пробелы» заполнен). Суммы — числовые ячейки
`#,##0.00`, текст не вычисляется как формула.
10. **`warnings` — не контракт.** Тексты диагностики строки (расхождение `counterparty_id` между кварталами и т. п.) могут меняться;
показывать как подсказку, не разбирать.
11. **Q1.** ОСВ за `Q1` (парсер помечает её нарастающей) подходит версии любого квартала года, в версиях Q2+ служит квартальным
источником Q1; ББ/ОФР за Q1 версиям Q2–Q4 не подходят (R-14/R-14a, OQ-124). В `ConsolidatedRowOut.is_cumulative` —
признак строки свода: `true` только для строк нарастающего документа за квартал версии.
12. **Чтение не требует блокировки** и доступно для версии любого статуса, включая `CANCELLED`.
13. **Все деньги — строки с двумя знаками**; склеенные квартальные строки не содержат `null` (пустое = `"0.00"`), строки нарастающего
документа — как в файле (могут быть `null`).
14. **`period_check` документа остаётся `UNKNOWN`**: подходящесть документа версии — свойство пары (документ, версия), показывайте её
через чек-лист версии, а не через поле документа (OQ-120).
15. **Создание не ставит блокировку.** После `POST /consolidations` версия свободна; «Взять в работу» — отдельное действие [35].
ГСК в запросе не сохраняется (`related_group_id` в версии пока пуст); члены берутся из текущего состава ГСК на момент создания.
16. **Границы входов (arm-core #26).** `MembersIn.legal_entity_ids` ≤ 500 элементов, `StatusIn.comment` ≤ 1000 символов,
`q` в `GET …/osv` и `…/osv/export` ≤ 500 символов (у `counterparty-candidates` так было и раньше). Превышение — 422 **в формате
pydantic** (`detail[] {loc, msg, type}`), без доменного `code`: показывайте как ошибку валидации формы, не ищите `code`.
Ограничивайте ввод на клиенте теми же числами.
17. **ОСВ со скользящим периодом** («Октябрь 2024 г. – Сентябрь 2025 г.», arm-tabular-parser #13, OQ-131): у документа
`period_year` = год конца диапазона, а `period_quarter` и `is_cumulative` — `null`. Такой документ не подходит ни одной версии
(привязка идёт по кварталу), в чек-листе и своде не участвует, подтверждать его можно. В списке документов показывайте год и
пометку «период не определён», не подставляйте квартал сами. Границы периода появятся в контракте v1.2 (Срез 2b).
18. **Удаление алиаса** (`DELETE /directories/legal-entities/{id}/aliases/{alias_id}`) во время переноса этого же алиаса ручным сопоставлением
(`move_alias: true`) теперь ждёт блокировку и отвечает 404 `NOT_FOUND`, если алиас уже переехал (arm-core #26, T11.3) —
это не ошибка клиента, перечитайте список алиасов.
19. **Периметр при создании** (модалка [42], arm-core #42). После выбора ГК и ГСК модалка показывает все неархивные ЮЛ ГК
(`GET /directories/legal-entities?company_group_id=<ГК>&closed=false`), отмечены члены выбранной ГСК
(`GET /directories/related-groups/{id}` → `members[]`); отметки меняются, «Создать» активна при двух и более. Отправляйте
отмеченные в `legal_entity_ids`. Без поля (или `null`) ядро берёт текущий состав ГСК; пустой список `[]` — это явный пустой
периметр → 422 `PERIMETER_TOO_SMALL`. ЮЛ не из ГК (или несуществующее) → 422 `ENTITY_NOT_IN_GROUP` с перечнем. ГСК обязательна
и проверяется (`RELATED_GROUP_MISMATCH`), но в версии **не хранится** (П-2): периметр живёт в `consolidation_member`, в
справочник переезжает при верификации (Срез 7). Колонку «ГСК» таблицы [34] до верификации заполнить нечем — оставляйте пустой.
20. **«Ответственный»** на панели «Организации» [34]: `lock.locked_by_name`, если у версии есть активная блокировка, иначе
`initiator.name`. «Утверждён в РФ» и «Верифицирован в ГО» — маршрут согласования, Срез 7 (в API пока нет; показывайте «—»).
21. **«К отчетности»** [34] доступна во всех статусах, начиная с `INITIATED` (OQ-43 пересмотрен владельцем 07.10.2026, П-3): свод
читается в любом статусе; правки свода (сопоставление контрагента) — только «в работе» со своей блокировкой.
## 5. Сквозные сценарии экранов
| Экран | Вызовы |
|---|---|
| [42] Новая консолидация | справочники: ГК → её ГСК (`…/company-groups/{id}/related-groups`, `kind = gsk`) → ЮЛ ГК и члены ГСК (нюанс 19) → `POST /consolidations` с `legal_entity_ids` |
| [34] Консолидации ГК | `GET /company-groups/{id}/consolidations` (+ `in_work_only`), клик по строке — `GET …/versions/{id}` (панель «Организации»: `members[]` — тип, ячейки ББ/ОФР, `osv_accounts`; «Ответственный» — нюанс 20); «Взять в работу» — `POST …/lock`; меню «…» → «Отмена КФО» — `POST …/status {CANCELLED, comment}` |
| [36] Состав | `GET …/versions/{id}` (члены), все ЮЛ ГК — справочники, «Сохранить» — `PUT …/members` |
| [39]/[40] Документы организации | `GET /consolidations/{cid}/legal-entities/{id}/files` (рабочий стол консоли; «Все документы организации» — `GET /legal-entities/{id}/files`; опрос во время распознавания — им же или `GET /files/{id}`; схема — `dev/api-contract-files-directories.md`), `POST /consolidations/{cid}/legal-entities/{id}/files` |
| [37] Чек-лист | `GET …/checklist`; «Скачать CSV» — `GET …/checklist?format=csv` (Срез 3b) |
| [30] ОСВ | `GET …/osv` с фильтрами, `GET …/osv/summary`, «Экспорт» — `GET …/osv/export` |
| [32-osv-no-inn], [32-osv-map-counterparty] | `GET …/osv?unmapped=true`, `GET …/counterparty-candidates`, «Связать» — `POST …/osv/entries/{entry_id}/counterparty` |
| [31-00] → модалка «Проверьте встречные записи участников» при уходе с вкладки «ОСВ» | `GET …/osv/summary` (`counter_pairs[]`; `unmapped_total` блокировок не создаёт) |