From 91fdc6c0f27004fa8d60d599049e7bf58dd31127 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 21 Oct 2026 14:06:40 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20change=20intercompany-matrix-?= =?UTF-8?q?ui=20=E2=80=94=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D1=88=D0=B0?= =?UTF-8?q?=D1=85=D0=BC=D0=B0=D1=82=D0=BA=D0=B8=20(=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E=20#158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proposal, design (Known issues — открытые дефекты ветки за автором; Decisions made without discussion), tasks и спека intercompany-matrix-ui: вкладки по маршруту :tab, таблица ЮЛ x ЮЛ, фильтры компаний и счетов Дт/Кт, режимы all/manual, единицы и знаки, экспорт XLSX, полноэкранный режим. Описано фактическое поведение ветки; дефекты в требования не вошли. Co-Authored-By: Claude Sonnet 5.5 --- .../changes/intercompany-matrix-ui/design.md | 67 +++++++++++++ .../intercompany-matrix-ui/proposal.md | 45 +++++++++ .../specs/intercompany-matrix-ui/spec.md | 97 +++++++++++++++++++ .../changes/intercompany-matrix-ui/tasks.md | 6 ++ 4 files changed, 215 insertions(+) create mode 100644 openspec/changes/intercompany-matrix-ui/design.md create mode 100644 openspec/changes/intercompany-matrix-ui/proposal.md create mode 100644 openspec/changes/intercompany-matrix-ui/specs/intercompany-matrix-ui/spec.md create mode 100644 openspec/changes/intercompany-matrix-ui/tasks.md diff --git a/openspec/changes/intercompany-matrix-ui/design.md b/openspec/changes/intercompany-matrix-ui/design.md new file mode 100644 index 0000000..69d920d --- /dev/null +++ b/openspec/changes/intercompany-matrix-ui/design.md @@ -0,0 +1,67 @@ +## Context + +Экран «Шахматка» [31-01] добавлен в `arm-core/web` PR #158 (ветка `feat/chessTable`) как вторая вкладка страницы отчёта по +версии. Страница `layouts/ConsolidatedOsvPage.tsx` разделена: общую шапку и вкладки держит `layouts/ConsolidatioReportPage.tsx`, +содержимое вкладок — `widgets/ConsolidatedOsvContent` (прежняя сводная ОСВ без изменений поведения) и +`widgets/ConsolidatedChessContent`. Бэкенд шахматки — change `2026-10-16-intercompany-matrix`; расчёт — чистая функция +версии, ничего не хранится. + +## Decisions (как устроено в ветке) + +- **D-1. Маршрут `:tab`.** `OsvTabs` берёт активную вкладку из `useParams().tab` (по умолчанию `osv`) и по выбору заменяет + последний сегмент адреса. Страница выбирает содержимое по таблице `tab → компонент`; неизвестный `tab` даёт пустое содержимое. +- **D-2. Ключ версии.** Страница отчёта разделена на обёртку с `key={version_id}` и содержимое: при смене версии карточка, + фильтры и данные создаются заново (как было в `ConsolidatedOsvPage`). +- **D-3. Режим `all` / `manual` выводится из выбора счетов:** `manual` только при непустых Дт и Кт (иначе бэкенд ответит 422), + в остальных случаях `all`. Один и тот же признак выбирает маршрут экспорта. +- **D-4. Единицы, знаки и фильтр компаний — на клиенте.** Запрос повторяется только при смене выбора счетов или версии. +- **D-5. План счетов** — `GET /directories/accounts` целиком, деревья Дт и Кт строятся на клиенте по `parent_id`. +- **D-6. `apiFetchBlob(path, init)`** принимает `RequestInit`, чтобы выгружать POST-ом с телом; без `init` поведение прежнее. +- **D-7. Полноэкранный режим** — Fullscreen API на контейнере блока шахматки, состояние следует за `fullscreenchange`. + +## Known issues + +Открытые проблемы ветки, которые исправляет автор PR; спека их НЕ закрепляет как требования. + +1. Глобальный CSS (`App.css`, `ConsolidatedChessTable.css`, `TreeFilterDropdown.css`) с `!important` на классах antd — влияет на + все таблицы и деревья приложения; стили должны быть локальными (CSS-модули или `className` экрана). +2. В полноэкранном режиме выпадающие списки фильтров (Компании, Дт, Кт, Единицы, Знаки) не видны: popup рендерится в `body`, + вне полноэкранного элемента; нужен `getPopupContainer` на контейнер шахматки. +3. Суммы форматируются через `number` / `parseFloat` / `toFixed` (float в денежном домене), без разделителей тысяч и без «руб.»; + `utils/money.ts` работает со строками — использовать его. Деление на единицы делать над десятичной строкой. +4. Строка «Итого» суммирует расхождения (`variance_amount`), а в макете 31-01 в итогах — верхние числа (суммы стороны A); итоги нужны + по каждой из трёх величин либо по макету. Знак `(…)` разбирается эвристикой `parseAmount`, хотя API отдаёт десятичные строки. +5. Подписи дочерних счетов в выборе Дт/Кт — UUID: `Select` получает `options` только корневых узлов, выбранные дочерние ключи + выводятся как идентификаторы; нужны подписи «код - название» для любых узлов. +6. Выбор счетов только с одной стороны молча даёт режим `all`: пользователь видит выбранный счёт, но данные по всем счетам; + нужны подсказка/блокировка либо явное поведение. +7. Гонка ответов: `loadData` не отменяет и не отбрасывает устаревшие ответы (последний пришедший побеждает, не последний + запрошенный); нужен `AbortController` или счётчик запросов. Эффект зависит от `loadData`/`notification` не по правилам хуков. +8. Нет результирующей таблицы ВГО (`GET …/intercompany-matrix/result-table`), пресетов выбора счетов и выбора года/квартала + (заглушки `disabled` в панели фильтров), экран макета 31-01 реализован не полностью. +9. Прочее по ревью: `any` в таблице и дереве, `console.log` в обработчике полноэкранного режима, ошибки не `ApiError` молча + глотаются (`loadData`, `download`), экспорт не передаёт единицы/знаки/компании, пустой `Select` для года/квартала, + опечатки в именах (`ConsolidatioReportPage`, `consodilation`, `expoerLoading`). + +После исправления дефектов требования соответствующих разделов спеки уточняются отдельным change. + +## Decisions made without discussion + +- **2026-10-21 (а). Спеку пишет ревьюер в ветке автора** — по решению владельца (разрешено явно); альтернатива — оставить + автору. Цена ошибки: автор может не согласиться с формулировками; правится одним коммитом в этом же change. +- **2026-10-21 (б). Спека описывает фактическое поведение, дефекты вынесены в «Known issues».** Обоснование: спека — единственный + источник истины при рулингах, а закреплять баги требованиями нельзя; иначе их закрытие превратится в «нарушение спеки». Цена + ошибки: отдельные требования (например, режим `all` при выборе одной стороны, «Итого» из расхождений) описаны как есть + и после исправления потребуют MODIFIED. +- **2026-10-21 (в). При восстановлении затёртого фронта** (коммит 175343b) из `origin/main` взяты целиком все файлы без + шахматочных изменений, а в `App.tsx`, `layouts/index.ts`, `api/client.ts` (параметр `init`), `OsvTabs.tsx`, + `ConsolidatedOsvContent.tsx` оставлены изменения шахматки. Возвращён `key={version_id}` (разделением страницы на обёртку и + содержимое — поведение как в main). Обоснование: изменения PR в этих файлах относятся к шахматке; цена ошибки — незамеченная + откатившаяся правка main в этих пяти файлах (построчно сверено с `origin/main`, расхождений кроме шахматочных нет). +- **2026-10-21 (г). Механические правки biome без смены поведения** (коммит e19291c): тип `IntercompanyMatrixResponse` у `let res`, + `Number.isNaN`, `key` у `span` в `OsvTabs`, `?? null` вместо пустого фрагмента. Предупреждения eslint (8 новых в коде шахматки: + `any`, deps хуков, `console`) не исправлялись — они входят в Known issues. Цена ошибки: `key` на `span` внутри `Tooltip` + безвреден, но избыточен. +- **2026-10-21 (д). Дельта к `consolidated-osv-ui` не делается:** вкладки описаны в новой спеке, текст + `consolidated-osv-ui` не правился, чтобы не смешивать UI-половины. Цена ошибки: при архивации две спеки частично описывают + один компонент (`OsvTabs`). diff --git a/openspec/changes/intercompany-matrix-ui/proposal.md b/openspec/changes/intercompany-matrix-ui/proposal.md new file mode 100644 index 0000000..a9fd070 --- /dev/null +++ b/openspec/changes/intercompany-matrix-ui/proposal.md @@ -0,0 +1,45 @@ +## Why + +В ветке `feat/chessTable` (PR RSHB/arm-core#158, автор — Голов) во фронте появился экран «Шахматка» — вкладка страницы +отчёта по версии консолидации рядом со «Сводной ОСВ». Бэкенд шахматки (`GET/POST …/intercompany-matrix/*`) уже в main +(change `2026-10-16-intercompany-matrix`), у экрана спеки нет. Спеку пишет ревьюер в ветке автора по решению владельца +(21.10.2026): она фиксирует фактическое поведение экрана и отделяет от него открытые дефекты (`design.md`, «Known issues»), +чтобы дефекты не закрепились требованием. + +## What Changes + +Репозиторий — только arm-core (`web/`). Экран «Шахматка» [31-01], эндпоинты arm-core: +`GET …/intercompany-matrix/all`, `POST …/intercompany-matrix/manual`, `GET …/intercompany-matrix/all/export`, +`POST …/intercompany-matrix/manual/export`, `GET /directories/accounts`. + +- Маршрут `/groups/:group_id/versions/:version_id/:tab`: страница отчёта (`ConsolidatioReportPage`) с общей шапкой и + вкладками (`OsvTabs`); `tab = osv` — сводная ОСВ, `tab = chess` — шахматка. Прежний адрес `…/osv` продолжает работать. +- Таблица ЮЛ × ЮЛ: в ячейке три числа (сумма A, сумма Б, расхождение), строка «Итого». +- Фильтры: компании, счета Дт, счета Кт (деревом), единицы, знаков после запятой; режимы расчёта `all` / `manual`. +- Экспорт в XLSX (`all` / `manual`), полноэкранный режим. +- Новая спека `intercompany-matrix-ui` (ADDED Requirements). + +## Non-goals + +- Исправление дефектов экрана (перечень — `design.md`, «Known issues»): они остаются автору PR. +- Результирующая таблица ВГО (`GET …/intercompany-matrix/result-table`) и пресеты счетов — во фронте не реализованы. +- Выбор года и квартала на экране: селекторы заглушены (`disabled`), версия задаётся адресом. +- Изменения бэкенда и DBML: шахматка — чистая функция от версии, ничего не хранится (DECISION-5). +- Вкладки «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» — недоступны. + +## Capabilities + +### New Capabilities +- `intercompany-matrix-ui`: экран «Шахматка» версии консолидации. + +### Modified Capabilities + +Нет: `consolidated-osv-ui` не правится (вкладки описаны в новой спеке, см. design.md). + +## Impact + +- `web/src/App.tsx`, `web/src/layouts/{ConsolidatioReportPage,index}.ts(x)`, `web/src/widgets/ConsolidatedOsv/OsvTabs.tsx`, + `web/src/widgets/ConsolidatedOsvContent/`, `web/src/widgets/ConsolidatedChessContent/`, + `web/src/widgets/ConsolidationChessTable/`, `web/src/components/TreeFilterDropdown/`, + `web/src/services/consodilation/chess.ts`, `web/src/types/chess/chess.ts`, `web/src/api/client.ts` (`apiFetchBlob` принимает `init`). +- Права: экран читает данные под `CONSOLIDATION_VIEW`; меню по правам не скрывается. diff --git a/openspec/changes/intercompany-matrix-ui/specs/intercompany-matrix-ui/spec.md b/openspec/changes/intercompany-matrix-ui/specs/intercompany-matrix-ui/spec.md new file mode 100644 index 0000000..9c37bd1 --- /dev/null +++ b/openspec/changes/intercompany-matrix-ui/specs/intercompany-matrix-ui/spec.md @@ -0,0 +1,97 @@ +## ADDED Requirements + +### Requirement: Вкладки страницы отчёта по версии +Страница `/groups/:group_id/versions/:version_id/:tab` SHALL показывать шапку версии (ГК, период, статус отчёта) один раз +и под ней ряд вкладок шагов консолидации и содержимое активной вкладки. Активная вкладка определяется сегментом `:tab`: +`osv` — «ОСВ» (сводная ОСВ), `chess` — «Шахматка». Выбор вкладки SHALL менять последний сегмент адреса на ключ вкладки +(навигация, не локальное состояние), активная вкладка подсвечена зелёным и выделена жирным. Вкладки «Таблица корректировки», +«Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» SHALL быть недоступны с подсказкой «Будет в следующих срезах». +При смене `version_id` всё состояние страницы (карточка версии, данные, фильтры) SHALL создаваться заново. + +#### Scenario: Переход на шахматку +- **WHEN** открыта сводная ОСВ версии по адресу `…/versions/{id}/osv` и нажата вкладка «Шахматка» +- **THEN** адрес становится `…/versions/{id}/chess`, шапка версии остаётся, под вкладками показана шахматка + +#### Scenario: Прежний адрес сводной ОСВ +- **WHEN** открыт адрес `…/versions/{id}/osv` +- **THEN** показана сводная ОСВ, вкладка «ОСВ» активна + +### Requirement: Таблица шахматки ЮЛ × ЮЛ +Экран SHALL загружать матрицу при открытии вкладки (`GET /consolidations/versions/{id}/intercompany-matrix/all`) и показывать +таблицу: первая колонка «ДБ / КТ» (закреплена слева) — компании периметра в порядке ответа (`companies[]`), остальные колонки — +те же компании. В ячейке пары (строка A, колонка Б) SHALL быть три числа друг под другом: сумма `amount_a`, сумма `amount_b` +и расхождение `variance_amount`; расхождение выделяется красным, если `exceeds_threshold = true`. Если ячейки пары в ответе нет +(в том числе на диагонали), SHALL показываться прочерк «—» на приглушённом фоне. Ниже таблицы SHALL закрепляться строка «Итого» +с одним числом по каждой колонке. Пока идёт загрузка, таблица показывает индикатор загрузки; ошибка API показывается +уведомлением с текстом сервера. + +#### Scenario: Ячейка с расхождением выше порога +- **WHEN** для пары `(A, Б)` сервер вернул `exceeds_threshold = true` +- **THEN** третье число ячейки (расхождение) показано красным, первые два — обычным цветом + +#### Scenario: Пары нет в ответе +- **WHEN** в `cells[]` нет ячейки для пары компаний +- **THEN** на их пересечении показан прочерк «—» + +### Requirement: Единицы и знаки +Экран SHALL позволять выбрать единицы суммы — «тыс. руб.» (по умолчанию), «млн. руб.», «млрд руб.» — и число знаков после +запятой 0–3 (по умолчанию 2). Выбор применяется на клиенте к уже загруженным значениям (повторного запроса не делает): +значение делится на 1000 / 1 000 000 / 1 000 000 000 и показывается с выбранным числом знаков, десятичный разделитель — запятая. +Значение, которое не удаётся прочитать как число, SHALL показываться как пришло. + +#### Scenario: Смена единиц +- **WHEN** выбраны «млн. руб.» и 1 знак, а сервер вернул `amount_a = "2346459.05"` +- **THEN** в ячейке показано `2,3` + +### Requirement: Фильтр компаний +Выбор «Компании» (множественный, пустой выбор — «Все компании») SHALL ограничивать таблицу на клиенте: остаются строки и +колонки выбранных компаний и ячейки, у которых обе компании выбраны. Запрос на сервер при этом не отправляется. Список +вариантов — компании последнего загруженного ответа. Выпадающее окно выбора — дерево с поиском по названию, кнопками +применения и сброса. + +#### Scenario: Две компании из периметра +- **WHEN** выбраны две компании из пяти +- **THEN** таблица содержит две строки и две колонки, и «Итого» считается по оставшимся ячейкам + +### Requirement: Фильтр счетов Дт и Кт и режимы расчёта +Экран SHALL загружать план счетов `GET /directories/accounts` один раз при открытии и строить из него два дерева по иерархии +`parent_id`: дебетовые счета (`is_debit`) и кредитовые (`is_credit`); подпись узла — «код - название». Выбор счетов Дт и Кт — +множественный, пустой выбор — «Все счета». + +Режим расчёта определяется выбором: если выбраны счета и в Дт, и в Кт, SHALL выполняться +`POST …/intercompany-matrix/manual` с `{debit_account_ids, credit_account_ids}` (режим `manual`); в остальных случаях +(ничего не выбрано либо выбрана только одна сторона) SHALL выполняться `GET …/intercompany-matrix/all` (режим `all`). Каждое +изменение выбора счетов перезапрашивает матрицу. Кнопка «Сбросить фильтры» появляется, когда выбрано что-либо в компаниях, Дт +или Кт, и очищает все три выбора. Выбор единиц и знаков кнопкой не сбрасывается. Ошибка загрузки плана счетов показывается уведомлением. + +#### Scenario: Обе стороны выбраны +- **WHEN** выбран счёт 60.01 в Дт и счёт 62.01 в Кт +- **THEN** отправляется `POST …/intercompany-matrix/manual` с идентификаторами этих счетов и таблица показывает ответ + +#### Scenario: Выбрана одна сторона +- **WHEN** выбран счёт только в Дт +- **THEN** отправляется `GET …/intercompany-matrix/all` (режим `all`); выбор Дт остаётся в фильтре + +#### Scenario: Сброс +- **WHEN** выбраны компании и счета, нажато «Сбросить фильтры» +- **THEN** все три выбора пусты, матрица перезапрошена в режиме `all`, кнопка исчезла + +### Requirement: Экспорт шахматки в XLSX +Кнопка «Экспорт» SHALL скачивать XLSX в том же режиме, что и таблица: при выбранных счетах и в Дт, и в Кт — +`POST …/intercompany-matrix/manual/export` с телом `{debit_account_ids, credit_account_ids}`, иначе +`GET …/intercompany-matrix/all/export`. Файл сохраняется под именем из `Content-Disposition`, при его отсутствии — «Шахматка». +Пока идёт выгрузка, кнопка в состоянии загрузки; ошибка API показывается уведомлением. Фильтр компаний, единицы и знаки +в запрос экспорта не передаются. + +#### Scenario: Экспорт по всем счетам +- **WHEN** счета не выбраны и нажато «Экспорт» +- **THEN** отправлен `GET …/intercompany-matrix/all/export`, браузер сохраняет файл `intercompany-matrix_….xlsx` + +### Requirement: Полноэкранный режим +Кнопка «На весь экран» SHALL переводить блок шахматки (панель фильтров и таблицу) в полноэкранный режим средствами Fullscreen API +с белым фоном и таблицей на высоту окна; в нём кнопка меняется на «Свернуть». Выход из полноэкранного режима клавишей Esc +SHALL возвращать обычный вид и подпись кнопки. + +#### Scenario: Вход и выход +- **WHEN** нажато «На весь экран», затем Esc +- **THEN** блок разворачивается на весь экран, затем возвращается в страницу, кнопка снова «На весь экран» diff --git a/openspec/changes/intercompany-matrix-ui/tasks.md b/openspec/changes/intercompany-matrix-ui/tasks.md new file mode 100644 index 0000000..3fca357 --- /dev/null +++ b/openspec/changes/intercompany-matrix-ui/tasks.md @@ -0,0 +1,6 @@ +## 1. Спека и ревью (RSHB/arm-core#158) + +- [x] 1.1 Спека `intercompany-matrix-ui` по фактическому поведению ветки `feat/chessTable`. +- [ ] 1.2 Автор исправляет пункты «Known issues» из `design.md` и обновляет требования спеки отдельным коммитом. +- [ ] 1.3 Перед PR: `make web-check`, `npm test`, `npm run lint` — зелёные, вывод в описание PR. +- [ ] 1.4 Сверка экрана с макетом 31-01 в браузере (`docs/mockup/INDEX.md`).