## Context Панель «Первичные документы» [39] открывается из строки ЮЛ в карточке версии консолидации и показывает `GET /legal-entities/{id}/files` — все файлы ЮЛ без фильтра. У одной ГК несколько консолидаций за разные периоды, их файлы лежат в одном списке ЮЛ. Хранится только привязка подтверждённых документов к версии (`consolidation_document`, §3 п. 1); факт «файл загружен в эту консоль» не хранится: `consolidation_version_id` при загрузке используется для проверки версии и перехода `INITIATED → DOCUMENTS_PROCESSING` и забывается. Целевая картина (владелец, 09.10.2026): - рабочие версии одного периода делят первичку; различаются периметром; - верифицированная версия заморожена, первичка зафиксирована в ЕХ (`ex_snapshot_item.stored_file_id`, Срез 7); - новая версия после `VERIFIED` может менять документы, периметр сохраняется; - загрузка в консоль — маршрутом от консолидации. Текущее состояние ядра: одна консолидация — одна версия (`POST /consolidations` создаёт версию 1; «новая версия» и параллельные версии не реализованы). Единственная точка создания `stored_file` — `services/primary_files._store` (загрузка); горячие папки scan-gateway файлов в ядре не создают. Автозамена (`services/result_mapping`) создаёт `SUPERSEDED` у старых файлов по ключу документа (тип, счёт, год, квартал), не по консоли. ## Goals / Non-Goals Goals: панель ЮЛ в версии показывает рабочий стол консоли этого ЮЛ; файл помнит консоль загрузки; маршруты загрузки и списка в контексте консоли — от консолидации; модель совместима с целевой картиной версий без копирования связей. Non-goals: см. proposal. ## Модель `stored_file.consolidation_id uuid [ref: > consolidation.id]`, nullable. Семантика: консолидация (ГК + год + квартал), в контексте которой файл загружен; `NULL` — без контекста. Пишется один раз при загрузке. Индекс `(legal_entity_id, consolidation_id)` — выборка рабочего стола. Почему период, а не версия: | Вариант | Плюс | Минус | |---|---|---| | `consolidation_file(version_id, file_id)` | буквально «загружено в версию» | при второй рабочей версии и новой версии после `VERIFIED` связи надо копировать; вторая связь рядом с `consolidation_document`; верифицированная версия и так заморожена привязками и ЕХ | | `stored_file.uploaded_in_version_id` | дешевле таблицы | те же проблемы разделения между рабочими версиями | | **`stored_file.consolidation_id`** | рабочие версии периода делят стол автоматически; новая версия после `VERIFIED` видит стол и подхватывает подтверждённое | нет истории «в какой версии загружено» — она в `FILE_UPLOAD.after_value` | Заморозка остаётся на `consolidation_document` (после `VERIFIED` привязки не снимаются) и на `ex_snapshot` (Срез 7). ## Алгоритмы (§9.5) **Загрузка в консоль.** `POST /consolidations/{cid}/legal-entities/{le}/files` (`DOCUMENT_EDIT`): ``` консолидация cid видима (policy по ГК)? нет → 404 NOT_FOUND («консолидация не найдена») ЮЛ le видимо? нет → 404 NOT_FOUND есть версия cid в работе (IN_WORK_STATUSES)? нет → 409 CONSOLIDATION_NOT_EDITABLE le в периметре хотя бы одной такой версии? нет → 422 ENTITY_NOT_IN_PERIMETER приём файлов как в POST /legal-entities/{le}/files; file.consolidation_id = cid после приёма ≥ 1 файла, отдельной транзакцией: для каждой версии cid в работе с le в периметре и статусом INITIATED: FOR UPDATE → перечитать статус → transition(INITIATED → DOCUMENTS_PROCESSING, changed_by = загрузивший) ``` Аудит `FILE_UPLOAD.after_value` дополняется `consolidation_id`; версии, переведённые в `DOCUMENTS_PROCESSING`, пишут `CONSOLIDATION_STATUS_CHANGE` как сейчас. **Загрузка без контекста.** `POST /legal-entities/{le}/files` — как сейчас, без query; `consolidation_id = NULL`. **Рабочий стол.** `GET /consolidations/{cid}/legal-entities/{le}/files` (`DOCUMENT_VIEW`; 404 на невидимую консолидацию или ЮЛ; `limit`, `offset`, `include_superseded`): ``` DESK = файлы le с consolidation_id = cid BOUND = файлы le, у которых есть документ с consolidation_document к любой версии cid результат = DESK ∪ BOUND, новые первыми (uploaded_at desc, id) SUPERSEDED — только при include_superseded = true (и для DESK, и для BOUND) ``` `BOUND` нужен для файлов, загруженных без контекста или в другой период и подхваченных версией при подтверждении (ОСВ за `Q1`, подходящая версиям `Q2`–`Q4`, R-14). Файл отдаётся той же карточкой `FileOut`, что и список ЮЛ. **Автозамена.** Новый файл после маппинга: если `consolidation_id IS NULL`, берётся `consolidation_id` первого заменённого файла (по порядку `id`), у которого он не `NULL`. При загрузке в контексте своё значение сохраняется. **Удаление, подтверждение, привязки** — без изменений. **Backfill (миграция 0006):** `stored_file.consolidation_id` = консолидация версии, к которой привязан любой документ файла; при привязках к версиям разных консолидаций — самый ранний период (год, затем квартал). Файлы без привязок — `NULL`. Откат — удаление колонки. ## API | Маршрут | Право | Вход | Ответ | Ошибки | |---|---|---|---|---| | `POST /consolidations/{cid}/legal-entities/{le}/files` | `DOCUMENT_EDIT` | multipart `files` (повторяется) | 201 `UploadResult`; 422 `UploadResult`, если не принят ни один | 404; 409 `CONSOLIDATION_NOT_EDITABLE`; 422 `ENTITY_NOT_IN_PERIMETER`; 503 `STORAGE_UNAVAILABLE` | | `GET /consolidations/{cid}/legal-entities/{le}/files` | `DOCUMENT_VIEW` | `limit`, `offset`, `include_superseded` | `Page[FileOut]` | 404 | | `POST /legal-entities/{le}/files` | `DOCUMENT_EDIT` | как сейчас, **без** `consolidation_version_id` | как сейчас | как сейчас | | `GET /legal-entities/{le}/files`, `GET /files/{id}` | `DOCUMENT_VIEW` | как сейчас | `FileOut` + `consolidation_id: uuid \| null` | как сейчас | ## Фронт (arm-web) - Стор `consolidations`: `current_consolidation_id` рядом с `current_version_id`; ставится там же (строка списка — `con_id`, карточка версии — `consolidation_id`). - `services/api/files.ts`: `uploadFiles(legalEntityId, files, consolidationId?)` и `listLegalEntityFiles(legalEntityId, consolidationId?)` — с консолидацией маршруты `/consolidations/{cid}/…`, без неё маршруты ЮЛ. - Панель [39]: список рабочего стола; переключатель «Все документы организации» внизу переключает на список ЮЛ; у файла с `consolidation_id`, отличным от текущего, подпись «2025 К1» (год и квартал из списка консолидаций ГК в сторе), у файла без консолидации — «вне консолидации». Опрос раз в 5 с — как сейчас, по активному списку. - Кнопка «Загрузить документы» недоступна, если открытая версия не «в работе» (статус из строки списка). - Экран [60] «Монозаёмщики» (когда появится) — панель без консолидации. ## Документы `model/data-model-v5.18.dbml` (+ changelog в шапке), `model/entities-v5.18.md`, `04-data-model-guide.md` (v5.18 имена не меняет), `05-domain-rules.md` §9.5 п. 1, 3 и новый пункт «Рабочий стол консоли», `10-user-journey.md` п. 2.5, `dev/frontend-api-guide.md`, `dev/api-contract-files-directories.md`, `dev/api-contract-slice-4.md`, `dev/qa-guide.md`, `07-open-questions.md` (новый вопрос о целевой модели версий), ссылки на v5.17 в `CLAUDE.md` workspace и `repo-templates/*.CLAUDE.md`. ## Risks / Trade-offs - Файл с неподходящим периодом, загруженный в консоль, остаётся в её столе без пометки: «не подходит версии» видно только по чек-листу. Приемлемо: владелец выбрал семантику «загружено сюда». - Два списка (стол и все файлы ЮЛ) с одинаковыми карточками — пользователь может не заметить переключатель. Подпись консолидации у файла и заголовок панели это смягчают. - Backfill по «самому раннему периоду» для файла, привязанного к версиям разных периодов, — эвристика; таких файлов на стенде единицы (ОСВ `Q1`, подхваченные версией `Q2`). - Переход `INITIATED → DOCUMENTS_PROCESSING` у нескольких версий сразу — сейчас версия одна; порядок блокировок (по возрастанию `id`, R-2 Среза 4) сохраняется. ## Decisions made without discussion 1. 09.10.2026 — **R-1:** `consolidation_file` (связь файл–версия) не вводится; поле `stored_file.consolidation_id` на уровне периода. Обсуждено с владельцем на brainstorming; здесь зафиксировано как итог. Цена ошибки: средняя — при переходе к «файлы по версиям» потребуется таблица связей и миграция данных. 2. 09.10.2026 — **R-2:** коды ошибок загрузки в консоль: 404 `NOT_FOUND` (консолидация/ЮЛ вне видимости, как везде), 409 `CONSOLIDATION_NOT_EDITABLE` (нет версии «в работе»), 422 `ENTITY_NOT_IN_PERIMETER` (существующий код). Цена ошибки: низкая — коды в README и гайде фронта. 3. 09.10.2026 — **R-3:** backfill — самый ранний период среди привязок; файлы без привязок остаются `NULL`. Цена ошибки: низкая — стенд разработки, данные тестовые. 4. 09.10.2026 — **R-4:** при автозамене новый файл без контекста наследует консоль заменённого; с контекстом — своё. Цена ошибки: низкая — влияет на то, в каком столе виден файл; привязки документов не затрагиваются. 5. 09.10.2026 — **R-5:** рабочий стол = `DESK ∪ BOUND` (файлы консоли плюс файлы с документами, привязанными к любой версии консоли). Без `BOUND` файл, загруженный без контекста и подхваченный версией, отсутствовал бы в панели при заполненной ячейке чек-листа. Цена ошибки: низкая — лишние строки в панели. 6. 09.10.2026 — **R-6:** целевые правила версий («вторая рабочая версия = другой периметр», «новая версия после `VERIFIED` с тем же периметром и правом менять документы») в этом change не реализуются и не специфицируются — фиксируются вопросом в `07-open-questions.md` для среза «версии консолидации». Цена ошибки: низкая — модель файлов с ними совместима. 7. 09.10.2026 — **R-7:** замороженный список первички верифицированной версии в панели не строится (ЕХ, Срез 7); панель из карточки версии не «в работе» показывает стол консоли без кнопки загрузки. Цена ошибки: средняя — если Срез 7 задержится, у верифицированной версии не будет экрана «что именно использовано»; чек-лист версии частично закрывает (`document_ids` в ячейках). 8. 09.10.2026 — **R-8:** query `consolidation_version_id` у `POST /legal-entities/{id}/files` удаляется, а не сохраняется для совместимости: единственный клиент — arm-web, меняется в том же выпуске. Цена ошибки: низкая.