174 lines
18 KiB
Markdown
174 lines
18 KiB
Markdown
## 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 Среза 4, §3 п. 4). Файл отдаётся той же карточкой `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, меняется в том же выпуске. Цена ошибки: низкая.
|
||
9. 09.10.2026 — **R-9:** решение владельца 07.10.2026 «фильтра файлов ЮЛ в контексте версии нет и не будет»
|
||
(`dev/frontend-api-guide.md`) считается пересмотренным решениями 09.10.2026; строка в гайде зачёркнута со ссылкой на
|
||
docs #90, не удалена. Цена ошибки: низкая — документальная.
|
||
10. 09.10.2026 — **R-10:** note `recognition_job.reply_deadline` (OQ-159, «20 мин») исправлен в v5.18 попутно: владелец
|
||
просил править при ближайшей версии модели. Цена ошибки: нулевая.
|
||
11. 09.10.2026 — **R-11:** колонка `stored_file.consolidation_id` ставится последней в блоке `Table stored_file` DBML
|
||
(вопреки порядку в плане) — `check_dbml_vs_orm` в arm-core сверяет порядок колонок с физическим, а `ALTER TABLE ADD
|
||
COLUMN` в миграции 0006 добавляет колонку в конец (как `kpp` в v5.17). Цена ошибки: падение `make dbml-check` в
|
||
arm-core; правится перестановкой.
|
||
12. 09.10.2026 — **R-12:** ссылки на текущую версию модели v5.17 → v5.18 в `context/05-domain-rules.md` (заголовок) и
|
||
скобки об истории версий в `context/08-source-docs-index.md`. Выполнено в коммите 64ccfe3 (docs #90); в задаче 5
|
||
правок не потребовалось, исторические упоминания v5.13…v5.17 не трогаются. Цена ошибки: лишняя правка исторической
|
||
ссылки.
|
||
13. 09.10.2026 — **R-13:** `openspec/config.yaml` (контекст схемы): ссылки на текущую модель переведены на v5.18
|
||
(`data-model-v5.18.dbml`, `entities-v5.18.md`); исторические упоминания не трогались. Цена ошибки: низкая —
|
||
текст контекста для агента, не исполняемый.
|
||
14. 09.10.2026 — **R-14 / R-15:** формулировка про `SUPERSEDED` одна по смыслу в `dev/frontend-api-guide.md`,
|
||
`dev/api-contract-files-directories.md` и `context/05-domain-rules.md` §9.5 п. 11: «`SUPERSEDED` — только при
|
||
`include_superseded=true` (скрыт по умолчанию)». Цена ошибки: разночтение, правится текстом.
|
||
15. 09.10.2026 — **R-16:** push ветки и создание PR выполняет контроллер, а не исполнитель Task 5 (шаг 6 плана). Цена ошибки:
|
||
нулевая.
|
||
16. 09.10.2026 — **R-17:** трейлер коммита Task 5 исправлен amend'ом локального непушнутого коммита на
|
||
`Co-Authored-By: Claude Fable 5.1` по явному требованию задачи. Цена ошибки: переписана одна локальная запись.
|