# Контракт 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 ` (в 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`, `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[]}` | 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 /legal-entities/{legal_entity_id}/files?consolidation_version_id=` | `DOCUMENT_EDIT` | multipart `files`; параметр версии необязателен | как в Срезе 2c: 201/422 `{accepted[], rejected[]}` | 404 (ЮЛ или версия), 409 `VERSION_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"), 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[]}`: `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)}`. ## 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_<ГК>_<год>-_v.xlsx` (имя percent-кодировано целиком). Забирайте файл как blob с `Authorization` (прямая ссылка `` не подойдёт). Фильтры — те же, что у `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 /legal-entities/{id}/files` (все файлы ЮЛ, опрос во время распознавания — им же или `GET /files/{id}`; схема — `dev/api-contract-files-directories.md`), `POST /legal-entities/{id}/files?consolidation_version_id=` | | [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` (`unmapped_total`) |