openspec: change console-files — файлы в контексте консолидации (#90)
Спека по решениям владельца 09.10.2026: stored_file.consolidation_id (период, не версия),
маршруты /consolidations/{cid}/legal-entities/{le}/files, рабочий стол = DESK ∪ BOUND,
заморозка через consolidation_document и ЕХ. Дочерние change'и — arm-core и arm-web.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
ff5a9c8450
commit
a329e5e9fb
3
openspec/changes/console-files/.openspec.yaml
Normal file
3
openspec/changes/console-files/.openspec.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-09
|
||||
skip_specs: true
|
||||
150
openspec/changes/console-files/design.md
Normal file
150
openspec/changes/console-files/design.md
Normal file
@ -0,0 +1,150 @@
|
||||
## 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, меняется в том же выпуске. Цена ошибки: низкая.
|
||||
74
openspec/changes/console-files/proposal.md
Normal file
74
openspec/changes/console-files/proposal.md
Normal file
@ -0,0 +1,74 @@
|
||||
## Why
|
||||
|
||||
Панель «Первичные документы» ЮЛ [39], открытая из версии консолидации, показывает все файлы ЮЛ, в том числе
|
||||
загруженные в другие консолидации той же ГК. Причина в модели: файл принадлежит ЮЛ (`stored_file.legal_entity_id`),
|
||||
контекста консолидации у него нет, `GET /legal-entities/{id}/files` ничем не фильтруется. Связь с версией есть только у
|
||||
подтверждённых документов (`consolidation_document`), поэтому «что версия использует» и «что аналитик загрузил в эту
|
||||
консоль» сегодня разные множества, и второе нигде не хранится (`consolidation_version_id` при загрузке используется лишь
|
||||
для перехода статуса версии).
|
||||
|
||||
Решения владельца 09.10.2026 (RSHB/arm-ka-docs#90), целевая картина:
|
||||
|
||||
- файл загружается **в консолидацию** (ГК + год + квартал) и становится частью её «рабочего стола», общего для всех
|
||||
рабочих версий периода; рабочие версии одного периода различаются периметром;
|
||||
- верифицированная версия заморожена: её первичка фиксируется в ЕХ (`ex_snapshot_item.stored_file_id`, Срез 7);
|
||||
новая версия после `VERIFIED` может менять документы при том же периметре;
|
||||
- что использует конкретная версия, по-прежнему определяют `consolidation_document` и чек-лист версии;
|
||||
- загрузка и список файлов в контексте консоли идут по маршрутам от консолидации, а не query-параметром у ЮЛ.
|
||||
|
||||
Срез: вне плана `09-delivery-plan.md` — доработка Среза 2d/4 (UI первичных документов в карточке версии).
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Модель, DBML v5.18:** `stored_file.consolidation_id uuid` (nullable, FK → `consolidation`) — консолидация, в
|
||||
контексте которой загружен файл; `NULL` — загрузка без контекста (страница ЮЛ, монозаёмщики, API). Индекс
|
||||
`(legal_entity_id, consolidation_id)`. Пишется один раз при загрузке; при автозамене наследуется заменённым файлом,
|
||||
если у нового файла контекста нет.
|
||||
- **API ядра:** новые маршруты `POST /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files`
|
||||
(загрузка в консоль) и `GET /consolidations/{consolidation_id}/legal-entities/{legal_entity_id}/files` (рабочий стол
|
||||
консоли для ЮЛ). У `POST /legal-entities/{id}/files` query `consolidation_version_id` убирается — маршрут остаётся
|
||||
для загрузки без контекста. `FileOut.consolidation_id` в списках и карточке файла. Переход `INITIATED →
|
||||
DOCUMENTS_PROCESSING` после загрузки — у всех версий консоли «в работе», где ЮЛ в периметре.
|
||||
- **Правила домена, §9.5:** загрузка в консоль (проверки, коды ошибок, переход версий), состав рабочего стола,
|
||||
наследование при автозамене.
|
||||
- **Фронт arm-web:** панель [39] и загрузка [40] ходят на маршруты консолидации; переключатель «Все документы
|
||||
организации» с подписью консолидации у файла; загрузка недоступна, если открытая версия не «в работе».
|
||||
- **Документы:** `model/data-model-v5.18.dbml`, `model/entities-v5.18.md`, `04-data-model-guide.md`,
|
||||
`05-domain-rules.md` §9.5, `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`.
|
||||
|
||||
Дочерние change'и `console-files`: RSHB/arm-core (миграция `0006_dbml_v5_18`, сервис, маршруты, спеки `primary-files`
|
||||
и `documents-api`), RSHB/arm-web (спека `primary-files-ui`). Issue этого репозитория — RSHB/arm-ka-docs#90.
|
||||
Контракт обмена с сервисами распознавания не меняется, `schema_version` прежняя.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Несколько рабочих версий одного периода с разным периметром и «новая версия после `VERIFIED`» ядро пока не создаёт
|
||||
(`POST /consolidations` создаёт только версию 1); эти правила — отдельный срез (OQ, design «Decisions» №6). Этот
|
||||
change лишь делает модель файлов совместимой с ними.
|
||||
- Замороженный список первички верифицированной версии в панели не строится: заморозка — `ex_snapshot` (Срез 7).
|
||||
Панель из карточки версии не «в работе» показывает рабочий стол консоли как есть, без кнопки загрузки.
|
||||
- Признак «документ подходит / привязан к версии» в списке файлов не вводится: это чек-лист версии (OQ-120).
|
||||
- Привязка файла к версии (`consolidation_file`) не вводится: отвергнута на brainstorming 09.10.2026 (design, №1).
|
||||
- `consolidation_document`, правило `fits`, подхват при создании версии и добавлении ЮЛ в периметр — без изменений.
|
||||
- Горячие папки scan-gateway файлов в ядре не создают; их контекст не рассматривается.
|
||||
- Backfill касается только файлов с документами в `consolidation_document`; прочие остаются `NULL`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
### Modified Capabilities
|
||||
- Спецификации этого репозитория (`dev-environment`, `quality-checks`, `recognition-contract`) не меняются; требования
|
||||
к API и UI — в дочерних change'ах arm-core (`primary-files`, `documents-api`) и arm-web (`primary-files-ui`).
|
||||
|
||||
## Impact
|
||||
|
||||
- `arm-core`: миграция с backfill; `services/primary_files.py` (загрузка, `page_files`, автозамена в
|
||||
`services/result_mapping.py`), `repositories/files.py`, `api/endpoints/files/upload.py`, `read.py`, новый роутер
|
||||
под `/consolidations/{id}/legal-entities/{le}/files`, `schemas/files.py`; тесты; README «Ошибки API».
|
||||
- `arm-web`: `services/api/files.ts`, стор `consolidations` (`current_consolidation_id`), `widgets/FilesDrawer/*`,
|
||||
типы `FileOut`.
|
||||
- Совместимость: query `consolidation_version_id` у `POST /legal-entities/{id}/files` исчезает — фронт меняется
|
||||
одновременно с ядром (стенд обновляется после слияния обоих PR). Файлы, загруженные до миграции без привязок,
|
||||
остаются «вне консолидации» и видны только по переключателю «Все документы организации».
|
||||
19
openspec/changes/console-files/tasks.md
Normal file
19
openspec/changes/console-files/tasks.md
Normal file
@ -0,0 +1,19 @@
|
||||
## Порядок
|
||||
|
||||
Подробный план с шагами и тестами — superpowers writing-plans (ledger SDD); здесь — крупные задачи по репозиториям
|
||||
в порядке зависимостей.
|
||||
|
||||
- [ ] 1. **docs** (этот change): `model/data-model-v5.18.dbml` + changelog, `model/entities-v5.18.md`, `04-data-model-guide.md`,
|
||||
`05-domain-rules.md` §9.5, `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`
|
||||
(целевая модель версий), ссылки на версию DBML в `CLAUDE.md` workspace и `repo-templates/*.CLAUDE.md`.
|
||||
- [ ] 2. **arm-core** (change `console-files`, спеки `primary-files`, `documents-api`): модель `StoredFile.consolidation_id`,
|
||||
миграция `0006_dbml_v5_18` с backfill и откатом, `make dbml-check`; `repositories/files.py` (рабочий стол);
|
||||
`services/primary_files.py` (загрузка в консоль, проверки, переход версий, `page_desk`); `services/result_mapping.py`
|
||||
(наследование при автозамене); маршруты `/consolidations/{cid}/legal-entities/{le}/files`; удаление query у
|
||||
`POST /legal-entities/{id}/files`; `schemas/files.py`; README «Ошибки API»; тесты; `make check-full`.
|
||||
- [ ] 3. **arm-web** (change `console-files`, спека `primary-files-ui`): стор `current_consolidation_id`, `services/api/files.ts`,
|
||||
`widgets/FilesDrawer/*` (стол, переключатель, подпись консолидации, недоступная загрузка вне «в работе»), типы;
|
||||
`make check`; ручной прогон сценария из design на стенде в Playwright со сверкой с макетом [39]/[40].
|
||||
- [ ] 4. **Закрытие:** обновить `03-architecture.md` (если затронуты схемы), `07-open-questions.md`, `09-delivery-plan.md`
|
||||
(строка про доработку 2d/4); архив change'ей в трёх репозиториях.
|
||||
Loading…
x
Reference in New Issue
Block a user