Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
13 KiB
Design: API до сводной ОСВ — пробелы по макету и комментариям фронтенда
Context
Срезы 4 и 3b закрыли API пути пользователя до сводной ОСВ по 10-user-journey.md. Аудит «экраны макета ↔ API»
(07.10.2026, находки П-1…П-14) и комментарии фронтенд-разработчика показали три места, где экран собирается только
цепочкой вызовов или не собирается вовсе:
- модалка [42] «Новая консолидация» — в коде макета (
docs/mockup/dkb_24.09.26/dkb_24.09.26.html, блок «ЮЛ консолидации») после выбора ГК и ГСК показывает все неархивные ЮЛ ГК, отмечены члены ГСК, отметки меняются, минимум два; создаётся консолидация с отмеченным списком. Снимок42-new-consolidation.pngснят до выбора ГК, поэтому10-user-journey.mdшаг 2.1 иmockup/INDEX.mdэтот блок не описывают (П-17); - панель «Организации» [34] — ячейки чек-листа участника позиционные (П-15); нет «Тип» и перечня счетов ОСВ (П-9);
- комментарий фронта «нет ручки для файлов ЮЛ» — эндпоинт есть (
GET /legal-entities/{id}/files, Срез 2c), но не описан в контракте для фронта (П-5).
Модель данных не меняется: всё вычисляется из существующих таблиц DBML v5.16.
Goals / Non-Goals
Goals: экран [42] создаёт консолидацию одним запросом с выбранным периметром; экран [34] строится из одной карточки
версии; обратная совместимость ответов (поля только добавляются, вход без legal_entity_ids ведёт себя как прежде).
Non-Goals: см. proposal.md «Non-goals» — хранение ГСК версии, поля Среза 7, period_check (OQ-120), фильтр
файлов по версии, П-4/П-6/П-11/П-12/П-13, DBML и контракт распознавания.
Decisions
D-1. Периметр при создании — необязательное legal_entity_ids (решение владельца, П-17)
ConsolidationCreate.legal_entity_ids: list[uuid] | None = Field(default=None, max_length=MAX_MEMBERS). Без поля —
прежнее поведение (члены ГСК, PERIMETER_TOO_SMALL по ГСК). С полем — ровно выбранные ЮЛ ГК. ГСК остаётся
обязательной: модалка требует её всегда, по ней предзаполняются отметки, проверка RELATED_GROUP_MISMATCH сохраняется.
ГСК в версии не хранится (П-2): consolidation.related_group_id — NULL до верификации.
Проверка списка — та же, что у PUT …/members: дубли схлопываются в порядке первого вхождения,
repo.entities_in_group → 422 ENTITY_NOT_IN_GROUP с перечнем, затем PERIMETER_TOO_SMALL. Выношу её в функцию
сервиса _validate_perimeter(company_group_id, ids) -> list[uuid], которую вызывают create и set_members: правила не
разойдутся. В create проверка стоит на месте проверки числа членов ГСК — после ГСК, до проверки периода (порядок
ошибок не меняется для запросов без списка).
_insert получает список ЮЛ (сейчас — сущности ГСК); подхват документов и аудит — как сейчас, по фактическому периметру.
Архивность ЮЛ отдельно не проверяется: принадлежность ГК определяется legal_entity.company_group_id, как в
PUT …/members (выведенное из ГК ЮЛ ГК уже не принадлежит).
Альтернатива «фронт делает create → lock → PUT members» отвергнута: не атомарна, два события аудита, не позволяет ГСК из одного ЮЛ.
D-2. Самоописывающие ячейки чек-листа (подход B)
ChecklistCellOut получает document_type: DocumentType и account: str | None правила. _cell_out(rule, cell)
заполняет их в обоих местах — карточка версии и GET …/checklist; порядок ячеек прежний. Доменный checklist.build
не меняется; checklist_csv.render_checklist_csv читает ChecklistOut (подписи столбцов — из rules, статусы — из
ячеек), новые поля ячейки не использует — вывод CSV не меняется, меняются только конструкторы ячеек в его тестах.
Альтернатива A (rules[] на уровне карточки) отвергнута: сохраняет позиционную связь, на которой споткнулся фронт.
Повтор двух коротких полей в каждой ячейке дешевле ошибки сопоставления по индексу.
D-3. Участник периметра: entity_type и osv_accounts (вариант B)
MemberOut.entity_type: EntityType — из legal_entity. MemberOut.osv_accounts: list[str] — новый запрос репозитория
osv_accounts(session, entity_ids, year, quarters) -> dict[uuid, list[str]] (quarters — quarters_through(quarter версии)) одним SELECT DISTINCT legal_entity_id, osv_account_code по document ⋈ stored_file: document_type = OSV, period_year = year, period_quarter IN quarters, document.status != ERROR, stored_file.status NOT IN (SUPERSEDED, ERROR),
osv_account_code IS NOT NULL. Сортировка — в Python account_sort_key (та же натуральная, что у столбцов чек-листа);
ЮЛ без документов — пустой список.
Смысл колонки — «какие счета загружены к периоду версии», а не «что подойдёт своду»: подходящесть по §3 (нарастающая vs квартальная, Q1-эквивалентность) здесь не применяется, готовность счетов комплекта видна по ячейкам чек-листа. Квартал «не позже квартала версии», потому что свод склеивает квартальные ОСВ Q1…Qn.
D-4. Документы — bounded-PR в docs после реализации
Контракт для фронта (файлы и документы, справочники, новые поля, правило «Ответственный» = lock.locked_by_name при
активной блокировке, иначе initiator.name), OQ-43 (П-3), формулировка баннера (П-1), шаг 2.1 пути и [42] в
mockup/INDEX.md, устаревшее «CSV в Срезе 4 нет». Сквозного change в docs нет: модель и контракт обмена не меняются.
Risks / Trade-offs
- Шире ответ карточки —
osv_accountsдобавляет один запрос на карточку (индексыdocument(legal_entity_id)есть); на реальных группах — десятки ЮЛ, нагрузка несущественна. - Фронт может спутать
osv_accountsс «готово для свода» — в контракте прямо сказать, что готовность — в ячейках чек-листа. - Выбранный периметр без членов ГСК допустим (отметки можно снять все, кроме двух любых ЮЛ ГК) — так в макете; ГСК версии в Срезе 7 будет определяться периметром, а не выбранной ГСК (П-2).
Migration Plan
Миграций нет. Ответы только расширяются; запросы без legal_entity_ids работают как прежде. Откат — revert PR.
Open Questions
Новых нет. Решения П-1/П-2/П-3 владельца — в docs-PR (OQ-43 переписывается).
Decisions made without discussion
| № | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| 1 | 2026-10-07 | Явный периметр не обязан включать членов выбранной ГСК | так в макете (отметки ГСК снимаются); ГСК версии не хранится (П-2) | версия без ЮЛ ГСК — исправляется PUT …/members |
| 2 | 2026-10-07 | Архивность ЮЛ при создании не проверяется отдельно — только company_group_id, как в PUT …/members |
одно правило периметра для создания и правки | фронт может отправить ЮЛ, которое в макете скрыто как архивное; ядро примет, если ЮЛ ещё в ГК |
| 3 | 2026-10-07 | legal_entity_ids: null равнозначно отсутствию поля |
pydantic None по умолчанию; фронту проще |
— |
| 4 | 2026-10-07 | osv_accounts не учитывает документы без квартала и без кода счёта |
их не к чему отнести в колонке; пустой код — дефект распознавания, виден в drawer | ОСВ без периода не видна в колонке «ОСВ» до правки периода |
| 5 | 2026-10-07 | Порядок проверок создания: ГК → ГСК → периметр → период | периметр зависит от ГК и ГСК, конфликт периода — последним, как сейчас | при одновременной ошибке периметра и занятом периоде клиент увидит 422, а не 409 |
| 6 | 2026-10-07 | Фильтр файлов ЮЛ по версии не делается (решение владельца, вопрос 1, вариант A) | drawer [39] показывает все документы ЮЛ; OQ-120 | — |
| 7 | 2026-10-07 | osv_accounts — документы за год версии с кварталом ≤ версии в любом статусе, кроме ERROR (решение владельца, вопрос 2, вариант B) |
«что загружено к периоду»; свод склеивает кварталы | — |
| 8 | 2026-10-07 | Ячейки чек-листа самоописывающие, без rules[] в карточке (решение владельца, подход B) |
фронт не сопоставляет по индексу | — |
| 9 | 2026-10-07 | «Неархивные ЮЛ ГК» модалки [42] в контракте — legal-entities?closed=false (без close_date) |
в API архив ЮЛ выражен только close_date; вывод из ГК и так снимает company_group_id |
фронт покажет/скроет не те ЮЛ — правка строки контракта |
| 10 | 2026-10-07 | Интервал опроса файлов 3–5 с — рекомендация в контракте | спека молчит, фронту нужен ориентир | лишняя нагрузка или медленный отклик — правится в контракте |
| 11 | 2026-10-07 | MemberOut.entity_type nullable (I-1 финального ревью) |
legal_entity.entity_type nullable в DBML; не-nullable схема давала бы 500 на карточке и создании |
фронт обязан показать «—» для null |
| 12 | 2026-10-07 | Подписи статусов drawer [39] в контракте — как в чек-листе: RECOGNIZED/NEEDS_CHECK — «Требуется проверка», CONFIRMED — «Загружен» (I-4) |
одна подпись на один статус документа во всех экранах | если владелец хочет «Загружен» для RECOGNIZED — правка контракта |