`LegalEntityOut.memberships` — в журнале изменений гайда фронта, таблице справочников и
контракте files-directories; убран совет брать членства из `related-groups/{id}` или карточки.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
15 KiB
Markdown
115 lines
15 KiB
Markdown
# Контракт 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); пользователю филиала он не нужен (видит один РФ).
|