2026-10-09 23:44:04 +04:00

174 lines
18 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.

## 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` по явному требованию задачи. Цена ошибки: переписана одна локальная запись.