diff --git a/openspec/changes/front-api-gaps/.openspec.yaml b/openspec/changes/front-api-gaps/.openspec.yaml new file mode 100644 index 0000000..a26662f --- /dev/null +++ b/openspec/changes/front-api-gaps/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-07 diff --git a/openspec/changes/front-api-gaps/design.md b/openspec/changes/front-api-gaps/design.md new file mode 100644 index 0000000..fa19bc9 --- /dev/null +++ b/openspec/changes/front-api-gaps/design.md @@ -0,0 +1,105 @@ +# 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` не меняются (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, quarter) -> dict[uuid, list[str]]` одним `SELECT DISTINCT legal_entity_id, +osv_account_code` по `document ⋈ stored_file`: `document_type = OSV`, `period_year = year`, `period_quarter IN +quarters_through(quarter)`, `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) | фронт не сопоставляет по индексу | — | diff --git a/openspec/changes/front-api-gaps/proposal.md b/openspec/changes/front-api-gaps/proposal.md new file mode 100644 index 0000000..c9f181f --- /dev/null +++ b/openspec/changes/front-api-gaps/proposal.md @@ -0,0 +1,65 @@ +# Proposal: API до сводной ОСВ — пробелы по макету и комментариям фронтенда + +**Срез:** доработка Срезов 4 и 3b (`../docs/context/09-delivery-plan.md`) — API пути пользователя до сводной ОСВ. +**Репозиторий:** arm-core. **Issue:** RSHB/arm-core#42. Документация — bounded-PR в docs (контракт для фронта, OQ-43, +`10-user-journey.md`, `mockup/INDEX.md`) после реализации; сквозного change нет: DBML и контракт распознавания не +меняются. + +## Why + +Аудит «экраны макета ↔ API» (07.10.2026) и первые комментарии фронтенд-разработчика показали, что три экрана пути +пользователя нельзя собрать без обходных цепочек вызовов: + +- **[42] «Новая консолидация»** — в макете после выбора ГК и ГСК открывается блок «ЮЛ консолидации»: все неархивные ЮЛ + ГК, отмечены члены выбранной ГСК, отметки меняются, «Создать» активна при двух и более отмеченных. `POST /consolidations` + берёт периметр только из состава ГСК; обход «создать → взять в работу → `PUT …/members`» не атомарен, пишет два события + аудита и не спасает ГСК из одного ЮЛ (422 `PERIMETER_TOO_SMALL` по ГСК). +- **[34] панель «Организации»** — ячейки `members[].checklist[]` карточки версии позиционные: какая ячейка ББ, ОФР или + ОСВ 60, фронт узнаёт только из второго запроса `GET …/checklist` (`rules[]`). Колонок «Тип» и «ОСВ» (перечень счетов + загруженных ОСВ, в макете — и вне комплекта: 51, 52, 55) взять неоткуда. + +## What Changes + +- **`POST /consolidations`** — необязательное `legal_entity_ids` (≤ 500): без него периметр — текущие члены ГСК, как + сейчас; с ним — ровно выбранные ЮЛ ГК (422 `ENTITY_NOT_IN_GROUP`, `PERIMETER_TOO_SMALL` — по выбранным после + схлопывания дублей). ГСК остаётся обязательной и проверяется (`RELATED_GROUP_MISMATCH`), в версии не хранится. + Одна транзакция, одно событие `CONSOLIDATION_CREATE` с выбранным периметром. +- **Ячейки чек-листа самоописывающие** — `ChecklistCellOut` получает `document_type` и `account` своего правила; меняются + и карточка версии, и `GET …/checklist` (поля только добавляются; CSV не меняется). +- **Участник периметра в карточке** — `MemberOut.entity_type` (`ЮЛ`/`ИП`) и `MemberOut.osv_accounts` — счета действующих + ОСВ-документов ЮЛ за год версии с кварталом не позже квартала версии, в натуральном порядке. + +Таблицы DBML v5.16 (только чтение, без изменений): `consolidation`, `consolidation_version`, `consolidation_member`, +`legal_entity`, `related_group`, `related_group_member`, `document`, `stored_file`, `required_document_rule`, +`audit_event`. Правила: `05-domain-rules.md` §9 (статусы версии), §3 п. 1 (подходящесть документа версии — не меняется). + +## Решения владельца (07.10.2026) + +- **П-1:** баннер «Осталось: N» и «Показать ЮЛ без ИНН» — все несопоставленные строки свода (`unmapped`, + `unmapped_total`); API не меняется, меняется описание в docs. +- **П-2:** ГСК в версии не хранится; периметр живёт в `consolidation_member`, в справочник переезжает по факту + верификации (Срез 7). Колонка «ГСК» на [34] до верификации не заполняется. +- **П-3:** допущение OQ-43 пересматривается — «К отчетности» доступна и в статусах «в работе» (только docs; API читает + свод в любом статусе). +- Фильтр файлов ЮЛ по версии не делается: drawer [39] в макете показывает все документы ЮЛ; существующий + `GET /legal-entities/{id}/files` описывается в контракте; «Проверка: Период» остаётся «—» (OQ-120). +- Колонка «ОСВ» — действующие документы за год версии до её квартала в любом статусе (вариант B). +- Ячейки чек-листа — самоописывающие, без `rules[]` в карточке (подход B). + +## Non-goals + +- UI (делает другой разработчик). +- Хранение ГСК версии, «Утверждён в РФ» / «Верифицирован в ГО» / «Дата верификации» (Срез 7). +- `period_check` документа относительно версии (OQ-120), фильтр файлов по версии. +- Шапка формы ОКЕИ/ОПФ/форма собственности/аудит (П-4, нужна новая версия контракта), монозаёмщики [60] (П-6), + поиск ГК по РФ (П-11), «Прочие документы» в чек-листе (П-12), единицы в экспорте (П-13). +- Изменение DBML, миграции, контракт распознавания. + +## Impact + +- `api/src/schemas/consolidations.py` (`ConsolidationCreate`, `ChecklistCellOut`, `MemberOut`), + `api/src/services/consolidations.py` (`create`, `set_members`, `_card`, `checklist`, `_cell_out`), + `api/src/repositories/consolidations.py` (счета ОСВ участников). +- Тесты: `test_api_consolidations.py`, `test_api_consolidation_members.py`, `test_api_consolidation_checklist.py`, + `test_schemas_consolidations.py`, `test_openapi_routes.py`. +- Фронт: новые поля только добавляются; существующие клиенты не ломаются. diff --git a/openspec/changes/front-api-gaps/specs/consolidations-api/spec.md b/openspec/changes/front-api-gaps/specs/consolidations-api/spec.md new file mode 100644 index 0000000..f37529b --- /dev/null +++ b/openspec/changes/front-api-gaps/specs/consolidations-api/spec.md @@ -0,0 +1,147 @@ +# Spec Delta: consolidations-api + +## MODIFIED Requirements + +### Requirement: Создание консолидации +`POST /consolidations` (`CONSOLIDATION_EDIT`, policy по ГК) с телом `{company_group_id, related_group_id, year, quarter, +legal_entity_ids?}` SHALL в одной транзакции создать `consolidation` (`reporting_date`: Q1 → 01.04.year, Q2 → 01.07.year, +Q3 → 01.10.year, Q4 → 01.01.year+1; ASSUMPTION(OQ-40)), `consolidation_version` №1 (`INITIATED`, `scenario = CREATE`, +`approach = BASIC`, `goodwill_enabled = false`, `initiator_id` и `initiator_office_id` из пользователя, +`is_in_work = true`), `consolidation_member` периметра (`is_head_entity = false`), первую запись +`consolidation_status_history` (`from_status` NULL → `INITIATED`), подхват подтверждённых документов ЮЛ периметра (см. +`documents-api` «Подхват документов») и одно событие `audit_event(CONSOLIDATION_CREATE)`, у которого +`after_value.members` — отсортированный список ЮЛ периметра; ответ 201 с карточкой версии. Периметр SHALL быть: без +`legal_entity_ids` (поле отсутствует или `null`) — текущие члены ГСК; с `legal_entity_ids` — ровно эти ЮЛ после +схлопывания дублей (порядок первого вхождения), каждое SHALL принадлежать ГК консолидации +(`legal_entity.company_group_id`), иначе 422 `ENTITY_NOT_IN_GROUP` с перечнем `legal_entity_ids` чужих в порядке +запроса; состав ГСК при явном списке не проверяется и не меняется. ГСК SHALL быть обязательной, принадлежать ГК и иметь +`kind = gsk`, иначе 422 `RELATED_GROUP_MISMATCH`. Периметр меньше двух ЮЛ (членов ГСК без списка либо выбранных ЮЛ после +схлопывания) → 422 `PERIMETER_TOO_SMALL`; пара (ГК, год, квартал) уже есть → 409 `CONSOLIDATION_EXISTS` (в том числе при +гонке двух запросов — UNIQUE `(company_group_id, year, quarter)`); `year` вне 2000..2100 → 422. Порядок проверок: ГК +видима (404) → ГСК видима (404) и соответствует ГК (422) → периметр (422) → период свободен (409). Блокировка на +создателя MUST NOT ставиться; `MEMBERSHIP_CHANGE` при создании MUST NOT писаться. `related_group_id` в `consolidation` +SHALL оставаться NULL (фиксируется при верификации, Срез 7); `related_group_member` MUST NOT изменяться. + +#### Scenario: Консолидация РБ за Q3 2024 +- **WHEN** `ka_rf` Санкт-Петербургского РФ создаёт консолидацию ГК «Русский бисквит», ГСК из двух ЮЛ, 2024, Q3, без + `legal_entity_ids` +- **THEN** 201; версия №1 `INITIATED`, `reporting_date = 2024-10-01`, два `consolidation_member`, история с одной записью, + `audit_event(CONSOLIDATION_CREATE)` с `consolidation_version_id`; активной блокировки нет + +#### Scenario: Выбранный периметр шире ГСК +- **WHEN** в ГК три ЮЛ, в ГСК два из них, создаётся консолидация с `legal_entity_ids` из всех трёх ЮЛ ГК +- **THEN** 201; в `members` три ЮЛ; `after_value.members` события `CONSOLIDATION_CREATE` — три ЮЛ; состав ГСК не изменился + +#### Scenario: ГСК из одного ЮЛ, второе отмечено вручную +- **WHEN** в ГСК один текущий член, в `legal_entity_ids` он и ещё одно ЮЛ той же ГК +- **THEN** 201; в `members` два ЮЛ + +#### Scenario: ГСК из одного ЮЛ без списка +- **WHEN** в ГСК один текущий член, `legal_entity_ids` не передан +- **THEN** 422 `PERIMETER_TOO_SMALL`; `consolidation` и версия не созданы + +#### Scenario: ЮЛ другой ГК в списке +- **WHEN** в `legal_entity_ids` попало ЮЛ, `company_group_id` которого не равен ГК консолидации +- **THEN** 422 `ENTITY_NOT_IN_GROUP` с этим ЮЛ в `legal_entity_ids`; `consolidation` не создана + +#### Scenario: Дубли схлопываются до одного ЮЛ +- **WHEN** `legal_entity_ids` — один и тот же идентификатор дважды +- **THEN** 422 `PERIMETER_TOO_SMALL` + +#### Scenario: Повтор периода +- **WHEN** консолидация ГК за 2024 Q3 уже есть, создаётся ещё одна +- **THEN** 409 `CONSOLIDATION_EXISTS` + +### Requirement: Список консолидаций ГК и карточка версии +`GET /company-groups/{id}/consolidations` (`CONSOLIDATION_VIEW`, policy) SHALL возвращать периоды ГК (`consolidation_id`, +`year`, `quarter`, `reporting_date`) по убыванию года и квартала, в каждом — версии по убыванию `version_no` +(`id`, `version_no`, `status`, `scenario`, `approach`, `lock` — активная блокировка или null, `created_at`, +`members_count`); параметр `in_work_only=true` SHALL оставлять только версии со статусом не из +{`VERIFIED`, `CANCELLED`, `ARCHIVED`, `REJECTED`}. `GET /consolidations/versions/{id}` SHALL отдавать карточку: поля +версии и консолидации, `company_group {id, name}`, инициатора и его офис, `lock`, `members[]`, `history[]` +(`from_status`, `to_status`, `changed_by`, `changed_at`, `comment`). Член периметра SHALL содержать `id`, `name`, `inn`, +`entity_type` (`ЮЛ`/`ИП`, из `legal_entity`), `is_head_entity`, `checklist[]` (ячейки по правилам подхода, см. +«Чек-лист комплекта») и `osv_accounts[]` — уникальные `osv_account_code` документов ЮЛ, у которых `document_type = OSV`, +`period_year` равен году версии, `period_quarter` не позже квартала версии, статус документа не `ERROR`, файл не в +`SUPERSEDED` и не в `ERROR`; документы без квартала или без кода счёта не учитываются; статус подтверждения и привязка к +версии не важны; порядок — натуральная сортировка кода счёта (как столбцы чек-листа: `01 < 51 < 58.03 < 60`). Консолидация +ГК вне видимости и несуществующий идентификатор — 404 `NOT_FOUND` с сообщением `CONSOLIDATION_NOT_FOUND` / +`VERSION_NOT_FOUND` и одинаковым телом (так же во всех спецификациях среза: идентификаторы `…_NOT_FOUND` — текст +сообщения при фиксированном коде `NOT_FOUND`). + +#### Scenario: Дерево версий +- **WHEN** у ГК консолидации 2024 Q1 (версии 1 и 2) и 2024 Q3 (версия 1), запрошен список +- **THEN** первым период 2024 Q3, затем 2024 Q1 с версиями `[2, 1]`; `members_count` у каждой версии + +#### Scenario: Чужой филиал +- **WHEN** `ka_rf` другого филиала запрашивает карточку версии +- **THEN** 404 `NOT_FOUND` (сообщение `VERSION_NOT_FOUND`), тело совпадает с ответом на случайный `uuid` + +#### Scenario: Счета ОСВ участника +- **WHEN** версия за 2024 Q3; у ЮЛ есть ОСВ 60 за Q1 2024 (`RECOGNIZED`), ОСВ 51 за Q3 2024 (`CONFIRMED`), ОСВ 58.03 за + Q3 2024 (`NEEDS_CHECK`), ОСВ 62 за Q4 2024, ОСВ 01 за 2023, ОСВ 76 в заменённом (`SUPERSEDED`) файле и ОСВ 90 в + документе `ERROR` +- **THEN** `osv_accounts = ["51", "58.03", "60"]` + +#### Scenario: Тип ЮЛ +- **WHEN** в периметре индивидуальный предприниматель +- **THEN** у него `entity_type = "ИП"`, у остальных — `"ЮЛ"` + +### Requirement: Чек-лист комплекта +`GET /consolidations/versions/{id}/checklist` (`CONSOLIDATION_VIEW`, policy) SHALL вернуть `rules[]` (правила +`required_document_rule` подхода версии: `document_type`, `account`, `is_mandatory`; порядок — `BALANCE`, `INCOME`, +затем ОСВ по коду счёта) и `rows[]` по ЮЛ периметра (по имени) с `cells[]` в порядке `rules`. Ячейка `{document_type, +account, status, missing_quarters, document_ids}`: `document_type` и `account` — правила ячейки (ячейка +самоописывающая, фронт не опирается на позицию); `CONFIRMED` — пара (ЮЛ, правило) полностью покрыта привязанными +документами (ОСВ: по правилу выбора источников `consolidated-osv`; ББ/ОФР: документ за квартал версии); +`NEEDS_CHECK` / `RECOGNIZED` — есть неподтверждённый документ ЮЛ того же типа, счёта и года, подходящий версии +(приоритет `NEEDS_CHECK`); `PROCESSING` — у ЮЛ есть файл `UPLOADED` или `PROCESSING`; `ERROR` — последний по +`uploaded_at` файл ЮЛ в `ERROR`; иначе `MISSING`. `missing_quarters` SHALL перечислять кварталы без квартальной ОСВ при +частичном покрытии. Правило ОСВ SHALL закрываться только документом с `osv_account_code`, равным `account` правила +(OQ-04). Те же ячейки (с `document_type` и `account`, в том же порядке) SHALL отдаваться в карточке версии у каждого +члена. С `?format=csv` SHALL отдаваться тот же чек-лист файлом `text/csv; charset=utf-8` с BOM и разделителем `;`: +заголовок «Организация;ИНН;<правило>…» (правила — «Бух. баланс», «ОФР», «ОСВ <счёт>»), ячейки — подписи макета: +`CONFIRMED` «Загружен», `PROCESSING` «Обработка», `NEEDS_CHECK`/`RECOGNIZED` «Требуется проверка», `ERROR` «Ошибка +загрузки», `MISSING` «Отсутствует» (с « (нет: Q1, Q2)» при `missing_quarters`); `Content-Disposition` — `attachment` с +именем `checklist-<ГК>-<год>-<квартал>.csv` в RFC 5987. + +#### Scenario: Пробел Q2 +- **WHEN** у ЮЛ привязаны квартальные ОСВ 60 за Q1 и Q3 2024, версия за 2024 Q3 +- **THEN** ячейка ОСВ 60: `document_type = OSV`, `account = "60"`, `status = MISSING`, `missing_quarters = ["Q2"]`, + `document_ids` — два документа + +#### Scenario: Субсчёт не закрывает правило +- **WHEN** у ЮЛ подтверждена только ОСВ 58.03 за квартал версии +- **THEN** ячейка ОСВ 58 — `MISSING` + +#### Scenario: Файл в обработке +- **WHEN** у ЮЛ загружен файл, задание ещё не завершено, других документов нет +- **THEN** все ячейки ЮЛ — `PROCESSING` + +#### Scenario: Ячейки карточки подписаны +- **WHEN** запрошена карточка версии подхода `BASIC` +- **THEN** у каждого члена `checklist[i].document_type` и `checklist[i].account` равны `rules[i]` чек-листа той же + версии; ячейка ББ — `document_type = BALANCE`, `account = null` + +#### Scenario: Чек-лист в CSV +- **WHEN** запрошен чек-лист версии с `format=csv`, у ЮЛ подтверждён ББ, ОСВ 60 отсутствует за Q2 +- **THEN** файл начинается с BOM, первая строка — «Организация;ИНН;Бух. баланс;ОФР;ОСВ 60…», в строке ЮЛ — «Загружен» и «Отсутствует (нет: Q2)» + +### Requirement: Границы входных данных +Входы консолидаций SHALL иметь верхние границы: `legal_entity_ids` тела `PUT …/members` и тела `POST /consolidations` — +не больше 500 идентификаторов, `comment` тела `POST …/status` — не больше 1000 символов, `q` фильтров `GET …/osv` и +экспорта — не больше 500 символов. Превышение SHALL давать 422 со списком ошибок валидации (`detail[].loc` указывает +поле) до обращения к БД. Версия консолидации SHALL проверяться на видимость до блокировки `FOR UPDATE`: невидимая +версия отвечает 404 сразу, не ожидая транзакцию, которая держит её строку. + +#### Scenario: Периметр длиннее предела +- **WHEN** в `PUT …/members` передано 501 ЮЛ +- **THEN** 422, `detail[0].loc = ["body", "legal_entity_ids"]`, периметр не изменился + +#### Scenario: Периметр создания длиннее предела +- **WHEN** в `POST /consolidations` передано 501 ЮЛ в `legal_entity_ids` +- **THEN** 422, `detail[0].loc = ["body", "legal_entity_ids"]`, консолидация не создана + +#### Scenario: Невидимая версия при занятой строке +- **WHEN** версию чужого филиала держит `FOR UPDATE` другая транзакция, пользователь без видимости вызывает `POST …/lock` +- **THEN** 404 `VERSION_NOT_FOUND` без ожидания diff --git a/openspec/changes/front-api-gaps/tasks.md b/openspec/changes/front-api-gaps/tasks.md new file mode 100644 index 0000000..b10cf77 --- /dev/null +++ b/openspec/changes/front-api-gaps/tasks.md @@ -0,0 +1,17 @@ +# Tasks: API до сводной ОСВ — пробелы по макету и комментариям фронтенда + +План реализации — `docs/superpowers/plans/2026-10-07-front-api-gaps.md` (пишется после одобрения спеки). Репозиторий — +arm-core; команды — из корня (`make`), `pytest` — из `api/`. + +- [ ] 1 Создание консолидации с выбранным периметром: `ConsolidationCreate.legal_entity_ids`, общая проверка периметра + для `create` и `set_members` (`services/consolidations.py`), тесты `test_api_consolidations.py`, + `test_schemas_consolidations.py` +- [ ] 2 Самоописывающие ячейки чек-листа: `ChecklistCellOut.document_type/account`, `_cell_out(rule, cell)`, тесты + `test_api_consolidation_checklist.py`, карточка версии +- [ ] 3 Участник периметра: `MemberOut.entity_type`, `MemberOut.osv_accounts` (`repositories/consolidations.osv_accounts`), + тесты карточки версии +- [ ] 4 OpenAPI (`test_openapi_routes.py`), README «Консолидации», CLAUDE.md репозитория +- [ ] 5 Документы workspace (bounded-PR в docs): контракт для фронта (файлы/документы, справочники, новые поля, + «Ответственный»), OQ-43 (П-3), баннер (П-1), `10-user-journey.md` шаг 2.1 и строка про CSV, + `mockup/INDEX.md` [42], `dev/api-contract-slice-4.md` +- [ ] 6 Завершение: `make check-full`, `openspec validate --strict`, финальное ревью ветки, PR arm-core (Closes #42)