Andrey Epifancev 2bb0e999c0 openspec: архив сквозного change console-files (#90)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-10 10:06:16 +04:00

18 KiB
Raw Blame History

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