arm-ka-docs/dev/api-contract-files-directories.md
Andrey Epifancev d2a02d76bd dev: членства ГСК/ГСЗ в списке ЮЛ (arm-core #109)
`LegalEntityOut.memberships` — в журнале изменений гайда фронта, таблице справочников и
контракте files-directories; убран совет брать членства из `related-groups/{id}` или карточки.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:07:03 +04:00

115 lines
15 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: файлы и документы организации, справочники (для экранов до сводной ОСВ)
Источник — реальный код arm-core (`api/src/schemas/files.py`, `schemas/documents.py`, `schemas/directories.py`,
`schemas/common.py`, `api/endpoints/files/{upload,read,documents}.py`, `api/endpoints/directories/*.py`; Swagger — `/docs`).
Маршруты существуют со Срезов 1 и 2c; здесь они впервые описаны для фронта (arm-core #42, П-5). Общие правила (префикс, токен,
формат ошибок, видимость 404) — `dev/api-contract-slice-4.md` §1; форма ББ/ОФР, комментарии, исходный файл — `dev/api-contract-slice-3b.md`;
консолидации — `dev/api-contract-slice-4.md`. Экраны — `context/10-user-journey.md`, номера `[…]` — снимки макета (`mockup/INDEX.md`).
## 1. Общее
- Права: файлы и документы — чтение `DOCUMENT_VIEW`, загрузка/удаление/подтверждение — `DOCUMENT_EDIT`; справочники —
`DIRECTORY_VIEW` (импорт ЕЦП — `DIRECTORY_ADMIN`, только у администратора: кнопку «Импорт» на [34] скрывайте по `GET /me`).
- Страницы — `{items, total, limit, offset}`: у файлов ЮЛ и справочников `limit` 1…200 (по умолчанию 50), у строк ОСВ документа
1…2000 (по умолчанию 500); `offset ≥ 0`; вне предела — 422 (pydantic-массив).
- ЮЛ, файл или документ вне видимости пользователя неотличимы от несуществующих: 404 `NOT_FOUND`.
- Перечисления: `FileType` — `XLS`, `XLSX`, `XLSB`, `PDF`, `PNG`, `JPG`; `FileStatus` — `UPLOADED`, `PROCESSING`, `PROCESSED`, `ERROR`,
`SUPERSEDED`; `RecognitionStatus` (задание) — `queued`, `published`, `processing`, `done`, `needs_check`, `error`, `timeout` (нет ответа к сроку),
`dlq` (сервис не подтвердил приём после повторов); `DocumentType` —
`BALANCE`, `INCOME`, `OSV`, `DISCLOSURE`, `OTHER`; `DocumentStatus` — `RECOGNIZED`, `NEEDS_CHECK`, `CONFIRMED`, `ERROR`;
`DocumentCheckResult` — `MATCH`, `MISMATCH`, `UNKNOWN`; `EntityType` — `ЮЛ`, `ИП`; `RelatedGroupKind` — `gsk`, `gsz`.
## 2. Эндпоинты: файлы и документы
| Метод и путь | Право | Запрос | Ответ | Ошибки | Экран |
|---|---|---|---|---|---|
| `GET /legal-entities/{legal_entity_id}/files` | VIEW | query `limit`, `offset`, `include_superseded` (bool, по умолчанию `false` — заменённые скрыты) | `Page[FileOut]`, новые первыми (по убыванию `uploaded_at`) | 404 | переключатель «Все документы организации» [39] / страница ЮЛ |
| `GET /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` | VIEW | query `limit`, `offset`, `include_superseded` (bool, по умолчанию `false`) | `Page[FileOut]` — рабочий стол консоли для ЮЛ (файлы с этим `consolidation_id` + файлы с документами, привязанными к версиям консолидации), новые первыми; `SUPERSEDED` — только при `include_superseded=true` (скрыт по умолчанию) | 404 | [39] drawer внутри версии (v5.18, docs #90) |
| `POST /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` | EDIT | multipart, поле `files` (повторяется) — загрузка в консолидацию | 201 `UploadResult` (принят хотя бы один файл) / 422 `UploadResult` (ни один) | 404 (консолидация, ЮЛ), 409 `CONSOLIDATION_NOT_EDITABLE`, 422 `ENTITY_NOT_IN_PERIMETER`, 503 `STORAGE_UNAVAILABLE` | [40] |
| `POST /legal-entities/{legal_entity_id}/files` | EDIT | multipart, поле `files` (повторяется) — без контекста (`consolidation_id = null`); query `consolidation_version_id` удалён (v5.18) | 201 / 422 `UploadResult` | 404, 503 `STORAGE_UNAVAILABLE` | страница ЮЛ, монозаёмщики |
| `GET /files/{file_id}` | VIEW | — | `FileOut` | 404 | опрос строки файла |
| `DELETE /files/{file_id}` | EDIT | — | 204 | 404, 409 `FILE_HAS_CONFIRMED_DOCUMENTS`, 409 `FILE_SUPERSEDES_OTHERS` | [39] иконка «удалить» |
| `GET /files/{file_id}/content` | VIEW | query `disposition` (`attachment` по умолчанию / `inline`) | поток исходного файла | 404 (`FILE_CONTENT_NOT_FOUND` — объекта нет в хранилище), 503 | [41] «Исходный документ» (см. контракт 3b) |
| `GET /documents/{document_id}` | VIEW | — | `DocumentOut` | 404 | [41], карточка документа |
| `GET /documents/{document_id}/entries` | VIEW | query `limit` (≤ 2000, по умолчанию 500), `offset` | `Page[EntryOut]` по возрастанию `row_number`; дерево — по `parent_entry_id` | 404 | строки ОСВ документа (только чтение) |
| `POST /documents/{document_id}/confirm` | EDIT | — | `DocumentOut` | 404, 409 `ALREADY_CONFIRMED` / `FILE_SUPERSEDED` / `DOCUMENT_NOT_CONFIRMABLE`; для ББ/ОФР — `PERIOD_REQUIRED`, `CHECKS_NOT_PASSED` | «Подтвердить данные» |
~~Фильтра «файлы в контексте версии консолидации» **нет** (решение владельца 07.10.2026, arm-core #42): drawer показывает все
документы ЮЛ, период — в колонке «Период».~~ (пересмотрено 09.10.2026, docs #90: drawer внутри версии — рабочий стол консоли, §9.5 п. 11.)
Подходит ли документ версии, видно по ячейкам чек-листа версии (`document_ids`).
## 3. Схемы: файлы и документы
**`UploadResult`** `{accepted[], rejected[]}`: `accepted[]` — `{file_id, job_id, name}`; `rejected[]` — `{name, code, message,
file_id?}`, где `code` — `UNSUPPORTED_FILE` (тип не из списка или сигнатура не совпала), `FILE_TOO_LARGE` (больше `UPLOAD_MAX_BYTES`, по умолчанию 50 МиБ),
`DUPLICATE_FILE` (такой файл у ЮЛ уже есть — `file_id` указывает на него), `STORAGE_UNAVAILABLE`. **Два разных тела 422**: при
отказе всех файлов — `UploadResult` (смотрите `rejected[]`); при ЮЛ вне периметра всех версий консолидации «в работе» — доменная ошибка
`{"detail": {"code": "ENTITY_NOT_IN_PERIMETER", "message": …}}`. Различайте по наличию `detail`.
**`FileOut`** `{id, legal_entity_id, original_name?, file_type, status (FileStatus), version_no, size_bytes?, content_hash?,
uploaded_at?, uploaded_by?, superseded_by_file_id?, job (JobBrief | null), documents[] (DocumentBrief)}` — поля с `?` могут быть
`null`. `consolidation_id: uuid | null` — консолидация, в которую загружен файл (v5.18); `null` — загружен без контекста. **`JobBrief`** `{id, status (RecognitionStatus), error_code?, error_message?, mapping_error?, warnings[],
mapping_warnings[], finished_at?}` — причина ошибки сервиса распознавания (`error_*`) или маппинга (`mapping_error`).
**`DocumentBrief`** `{id, document_type, osv_account_code?, period_year?, period_quarter?, is_cumulative?, status (DocumentStatus),
period_check?, counterparty_check?, locator?, comments_count}` — порядок: тип, счёт, `locator`.
**`DocumentOut`** = поля `DocumentBrief` + `{file_id, legal_entity_id, confirmed_by?, confirmed_at?, group_schema? ([{type, name?,
has_inn?}]), warnings[]}`. **`EntryOut`** `{id, row_number?, level?, parent_entry_id?, row_value?, group_type?, group_name?,
data_type?, account_code?, account_root?, account_id?, counterparty_name?, counterparty_inn?, counterparty_id?, match_kind?,
opening_debit?, opening_credit?, turnover_debit?, turnover_credit?, closing_debit?, closing_credit?}` — деньги строками.
### Как собрать строку drawer [39]
Строка — **файл** (`FileOut`); документы файла (`documents[]`) — подписи и проверки:
- «Период»: `period_year` + `period_quarter` + `is_cumulative` — квартальный «2024 К1», нарастающий Q3 — «2024 · 9 мес.»
(Q1 — «3 мес.», Q2 — «6 мес.», Q4 — «12 мес.»); `period_quarter = null` — «период не определён» (`dev/api-contract-slice-4.md`,
нюанс 17).
- «Проверка»: «Контрагент» — `counterparty_check` (`MATCH` ✓, `MISMATCH` ✗, `UNKNOWN`/`null` —); «Период» — `period_check` сейчас
всегда `UNKNOWN` → показывайте «—» (OQ-120).
- Подпись консолидации: «<год> К<квартал>» из списка консолидаций ГК (`GET /company-groups/{id}/consolidations`) по `FileOut.consolidation_id`; `null` — «вне консолидации».
- «Статус» (подписи — как в чек-листе и его CSV, `context/10-user-journey.md` «Словарь UI»): файл `UPLOADED`/`PROCESSING` —
«Обработка»; файл `ERROR` — «Ошибка» (причина — `job.error_message` / `job.mapping_error`; задание `timeout`/`dlq` — тоже
ошибка, причина в `job.error_message`); иначе по документу: `NEEDS_CHECK` и `RECOGNIZED` (старые и формы после правки) — «Требуется проверка», `CONFIRMED` (в т.ч. автоподтверждённый) —
«Загружен», `ERROR` — «Ошибка».
- Комментарий — у **документа** (`comments_count`, `GET/POST /documents/{id}/comments`, контракт 3b); у файла без документов
комментария нет.
- Опрос во время распознавания: перечитывайте активный список (стол консоли `GET /consolidations/{cid}/legal-entities/{id}/files` или файлы ЮЛ `GET /legal-entities/{id}/files`; или `GET /files/{id}` для одного файла), пока
файл в `UPLOADED`/`PROCESSING`; разумный интервал — 3–5 с.
- Клик по строке ББ/ОФР-документа (PDF и Excel) — экран [41] (`GET /documents/{id}/statement`, контракт 3b); строки ОСВ — только
чтение (`GET /documents/{id}/entries`).
## 4. Эндпоинты: справочники (экраны [03], [36], [42], [60])
Все — `GET`, право `DIRECTORY_VIEW`, префикс `/api/v1/directories`.
| Путь | Query | Ответ | Экран |
|---|---|---|---|
| `/regional-offices` | — | `RegionalOfficeOut[]` `{id, name, is_head_office, is_active}` | выбор филиала в формах (имя «Основной РФ» на [03] приходит объектом в ГК, arm-core #47) |
| `/company-groups` | `q` (подстрока name / crm_id / code), `is_archived`, `limit`, `offset` | `Page[CompanyGroupOut]` по имени | [03], выбор ГК в [42] |
| `/company-groups/{group_id}` | — | `CompanyGroupOut` | заголовок [34] («N организаций» — `legal_entities_count`) |
| `/company-groups/{company_group_id}/related-groups` | — | `RelatedGroupOut[]` (ГСК и ГСЗ; ГСК — `kind = gsk`) | выбор ГСК в [42], фильтр «ГСК» в [36] |
| `/related-groups/{group_id}` | — | `RelatedGroupDetail` (+ `members[]`) | отметки [42], колонка «ГСК» в [36] |
| `/legal-entities` | `company_group_id`, `outside_group` (bool), `inn` (точно), `q` (подстрока имени/ИНН), `closed` (bool), `limit`, `offset` | `Page[LegalEntityOut]` по имени | [36] «Показать все ЮЛ в ГК», список ЮЛ в [42], монозаёмщики [60] |
| `/legal-entities/{entity_id}` | — | `LegalEntityDetail` (+ `aliases[]`; `memberships[]` — как в списке) | карточка ЮЛ |
**`CompanyGroupOut`** `{id, code, crm_id?, name, base_regional_office_id?, base_regional_office? {id, name, is_head_office}, is_archived, valid_from, legal_entities_count, created_at?,
updated_at?}`. **`RelatedGroupOut`** `{id, code, company_group_id, kind, name, created_date?, members_count}`;
**`RelatedGroupDetail`** — + `members[] {legal_entity_id, inn, name, valid_from}` (текущий состав). **`LegalEntityOut`** `{id, inn,
crm_id?, entity_type?, name, company_group_id?, regional_office_id?, regional_office? {id, name, is_head_office}, close_date?, valid_from, created_at?, updated_at?,
documents_count, memberships[] {related_group_id, code, kind, name, valid_from}, outside_group, closed}` (`outside_group` — нет ГК, монозаёмщик; `closed` — заполнен `close_date`; оба вычисляет сервер; `memberships` — текущие ГСК/ГСЗ, arm-core #109); **`LegalEntityDetail`** — + `aliases[] {id,
alias, normalized_alias, created_at?}`.
Нюансы:
- **Модалка [42]**: ЮЛ ГК — `legal-entities?company_group_id=<ГК>&closed=false` (неархивные = без `close_date`; `limit` ≤ 200, а
периметр — до 500 ЮЛ: дочитывайте страницы по `offset`, пока не наберёте `total`), отметки — члены
выбранной ГСК из `related-groups/{id}`; создание — `POST /consolidations` с `legal_entity_ids` (`dev/api-contract-slice-4.md`,
нюанс 19).
- **Колонка «ГСК» и фильтр по ГСК/ГСЗ в [36]**: `memberships` строк `legal-entities?company_group_id=<ГК>` (arm-core #109).
- **Монозаёмщики [60]**: `legal-entities?outside_group=true`; «Документы» — `documents_count`, статусы ОФР/ББ/ОСВ — из
`GET /legal-entities/{id}/files` по ЮЛ (сводного поля нет; экран не отнесён к срезу, П-6).
- Филиал (`base_regional_office` у ГК, `regional_office` у ЮЛ и в истории) приходит объектом с именем, `*_id` рядом остаются; неактивный
филиал тоже отдаётся (arm-core #47).
- Поиск ГК по имени РФ не поддерживается (`q` — только name/crm_id/code); пользователю филиала он не нужен (видит один РФ).