docs(openspec): intercompany-matrix-ui — целевое поведение экрана (ревью #158, раунд 3)

Итоги отдаёт бэкенд (AURORA-1756/1757), результирующая таблица и её выгрузка,
«руб.», точное округление, одна сторона Дт/Кт без запроса, правило отметок
дерева по полному плану счетов, отдельные флаги выгрузки, полноэкранный режим.
Known issues — по состоянию 601a125.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-22 16:30:01 +04:00
parent 718d83d6d1
commit b8f0672029
4 changed files with 176 additions and 80 deletions

View File

@ -1,55 +1,55 @@
## Context ## Context
Экран «Шахматка» [31-01] добавлен в `arm-core/web` PR #158 (ветка `feat/chessTable`) как вторая вкладка страницы отчёта по Экран «Шахматка» [31-01] добавлен в `arm-core/web` PR #158 (ветка `feat/chessTable`, AURORA-1538) как вторая вкладка страницы
версии. Страница `layouts/ConsolidatedOsvPage.tsx` разделена: общую шапку и вкладки держит `layouts/ConsolidatioReportPage.tsx`, отчёта по версии. Страница `layouts/ConsolidatedOsvPage.tsx` разделена: общую шапку и вкладки держит
содержимое вкладок — `widgets/ConsolidatedOsvContent` (прежняя сводная ОСВ без изменений поведения) и `layouts/ConsolidationReportPage.tsx`, содержимое вкладок — `widgets/ConsolidatedOsvContent` (прежняя сводная ОСВ без изменений
`widgets/ConsolidatedChessContent`. Бэкенд шахматки — change `2026-10-16-intercompany-matrix`; расчёт — чистая функция поведения) и `widgets/ConsolidatedChessContent` (матрица `ConsolidationChessTable` и результирующая таблица
версии, ничего не хранится. `ConsolidationChessResTable`). Бэкенд — change `2026-10-16-intercompany-matrix` (матрица, результирующая таблица) и
`2026-10-22-intercompany-result-table-export` (выгрузка результирующей таблицы); расчёт — чистая функция версии, ничего не
хранится.
## Decisions (как устроено в ветке) ## Decisions
- **D-1. Маршрут `:tab`.** `OsvTabs` берёт активную вкладку из `useParams().tab` (по умолчанию `osv`) и по выбору заменяет - **D-1. Маршрут `:tab`.** `OsvTabs` берёт активную вкладку из `useParams().tab` (по умолчанию `osv`) и по выбору заменяет
последний сегмент адреса. Страница выбирает содержимое по таблице `tab → компонент`; неизвестный `tab` даёт пустое содержимое. последний сегмент адреса. Неизвестный `tab` заменяется на `osv` (`navigate(…, {replace: true})`).
- **D-2. Ключ версии.** Страница отчёта разделена на обёртку с `key={version_id}` и содержимое: при смене версии карточка, - **D-2. Ключ версии.** Страница отчёта разделена на обёртку с `key={version_id}` и содержимое: при смене версии карточка,
фильтры и данные создаются заново (как было в `ConsolidatedOsvPage`). фильтры и данные создаются заново (как было в `ConsolidatedOsvPage`).
- **D-3. Режим `all` / `manual` выводится из выбора счетов:** `manual` только при непустых Дт и Кт (иначе бэкенд ответит 422), - **D-3. Режим расчёта из выбора счетов:** `manual` — при непустых Дт и Кт, `all` — при пустых обоих; при одной выбранной
в остальных случаях `all`. Один и тот же признак выбирает маршрут экспорта. стороне запроса нет (бэкенд ответил бы 422 на `manual`, а `all` показал бы не то, что выбрано). Тот же признак выбирает
маршрут экспорта.
- **D-4. Единицы, знаки и фильтр компаний — на клиенте.** Запрос повторяется только при смене выбора счетов или версии. - **D-4. Единицы, знаки и фильтр компаний — на клиенте.** Запрос повторяется только при смене выбора счетов или версии.
Единицы — сдвиг запятой в десятичной строке (`utils/chessTableCellFormatValue.ts`), `Intl.NumberFormat` получает строку и
форматирует её как точное десятичное (без float).
- **D-5. План счетов** — `GET /directories/accounts` целиком, деревья Дт и Кт строятся на клиенте по `parent_id`. - **D-5. План счетов** — `GET /directories/accounts` целиком, деревья Дт и Кт строятся на клиенте по `parent_id`.
- **D-6. `apiFetchBlob(path, init)`** принимает `RequestInit`, чтобы выгружать POST-ом с телом; без `init` поведение прежнее. - **D-6. `apiFetchBlob(path, init)`** принимает `RequestInit`, чтобы выгружать POST-ом с телом; без `init` поведение прежнее.
- **D-7. Полноэкранный режим** — Fullscreen API на контейнере блока шахматки, состояние следует за `fullscreenchange`. - **D-7. Полноэкранный режим** — Fullscreen API на контейнере блока, состояние следует за `fullscreenchange`; выпадающие окна
antd рендерятся в `document.fullscreenElement` (`ConfigProvider getPopupContainer`).
- **D-8. Итоги отдаёт бэкенд.** Решение владельца 22.10.2026: все агрегаты (строки «Итого») считает API, фронт сумм не
складывает. Пока API их не отдаёт, строк «Итого» под матрицей и под результирующей таблицей нет; колонка «Итого» результирующей
таблицы — `group_total` из ответа. Итоги в API и правило суммирования — AURORA-1756 (бэк, ждёт аналитику AURORA-1373), вывод на
экран — AURORA-1757.
## Known issues ## Known issues
Открытые проблемы ветки, которые исправляет автор PR; спека их НЕ закрепляет как требования. Открытые проблемы ветки на 601a125 (ревью, раунд 3), которые исправляет автор PR; спека описывает целевое поведение.
1. Глобальный CSS (`App.css`, `ConsolidatedChessTable.css`, `TreeFilterDropdown.css`) с `!important` на классах antd — влияет на 1. Строки «Итого» под матрицей и под результирующей таблицей считаются на клиенте (`sumAmounts`) — убрать (D-8).
все таблицы и деревья приложения; стили должны быть локальными (CSS-модули или `className` экрана). 2. Поиск в дереве счетов: отметки считаются по отфильтрованному дереву, отметка единственного видимого субсчёта отмечает
2. В полноэкранном режиме выпадающие списки фильтров (Компании, Дт, Кт, Единицы, Знаки) не видны: popup рендерится в `body`, родителя, и на бэк уходит весь родительский счёт (`TreeFilterDropdown.tsx`).
вне полноэкранного элемента; нужен `getPopupContainer` на контейнер шахматки. 3. Выбрана одна сторона Дт/Кт: таблица показывает прежние данные, подсказка — всплывающее уведомление и постоянный текст внутри
3. Суммы форматируются через `number` / `parseFloat` / `toFixed` (float в денежном домене), без разделителей тысяч и без «руб.»; выпадающего окна, «Экспорт» активен и выгружает режим `all`.
`utils/money.ts` работает со строками — использовать его. Деление на единицы делать над десятичной строкой. 4. Стили: правило тегов в `App.css` и класс на форме ЮЛ (`CatalogueJurForm.tsx`) — вне экрана; у селекта Кт класса нет;
4. Строка «Итого» суммирует расхождения (`variance_amount`), а в макете 31-01 в итогах — верхние числа (суммы стороны A); итоги нужны `.resRow`, `.empty-cell` не под `.chessTable`; у `Tree` снят `className='filterTree'`.
по каждой из трёх величин либо по макету. Знак `(…)` разбирается эвристикой `parseAmount`, хотя API отдаёт десятичные строки. 5. Желательно: запасные имена файлов (`exportFilename` добавляет `Сводная_ОСВ_`) и общий `exportLoading` на две кнопки;
5. Подписи дочерних счетов в выборе Дт/Кт — UUID: `Select` получает `options` только корневых узлов, выбранные дочерние ключи нечисловое значение в тыс./млн/млрд искажается (`0.abc`); `sumAmounts(...) ?? '0'`; `as any` в форматтере; мёртвый код
выводятся как идентификаторы; нужны подписи «код - название» для любых узлов. (скобки в форматтере, `formatted === '—'`); ячейки по макету (скобки у расхождения, «—», значок `skipped_osv_entry_count`);
6. Выбор счетов только с одной стороны молча даёт режим `all`: пользователь видит выбранный счёт, но данные по всем счетам; заглушки «Год»/«Квартал»; `services/consodilation`, типы вне `types/api.ts`, `withJsonBody`, `Account.kind`.
нужны подсказка/блокировка либо явное поведение.
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 ## Decisions made without discussion
- **2026-10-21 (а). Спеку пишет ревьюер в ветке автора** — по решению владельца (разрешено явно); альтернатива — оставить - **2026-10-21 (а). Спеку пишет ревьюер в ветке автора** — по решению владельца (разрешено явно); альтернатива — оставить
автору. Цена ошибки: автор может не согласиться с формулировками; правится одним коммитом в этом же change. автору. Цена ошибки: автор может не согласиться с формулировками; правится одним коммитом в этом же change.
- **2026-10-21 (б). Спека описывает фактическое поведение, дефекты вынесены в «Known issues».** Обоснование: спека — единственный - **2026-10-21 (б). Спека описывает фактическое поведение, дефекты вынесены в «Known issues».** Отменено 22.10.2026 (е). Обоснование: спека — единственный
источник истины при рулингах, а закреплять баги требованиями нельзя; иначе их закрытие превратится в «нарушение спеки». Цена источник истины при рулингах, а закреплять баги требованиями нельзя; иначе их закрытие превратится в «нарушение спеки». Цена
ошибки: отдельные требования (например, режим `all` при выборе одной стороны, «Итого» из расхождений) описаны как есть ошибки: отдельные требования (например, режим `all` при выборе одной стороны, «Итого» из расхождений) описаны как есть
и после исправления потребуют MODIFIED. и после исправления потребуют MODIFIED.
@ -65,3 +65,18 @@
- **2026-10-21 (д). Дельта к `consolidated-osv-ui` не делается:** вкладки описаны в новой спеке, текст - **2026-10-21 (д). Дельта к `consolidated-osv-ui` не делается:** вкладки описаны в новой спеке, текст
`consolidated-osv-ui` не правился, чтобы не смешивать UI-половины. Цена ошибки: при архивации две спеки частично описывают `consolidated-osv-ui` не правился, чтобы не смешивать UI-половины. Цена ошибки: при архивации две спеки частично описывают
один компонент (`OsvTabs`). один компонент (`OsvTabs`).
- **2026-10-22 (е). Спека переписана на целевое поведение** (раунд 3 ревью): владелец решил, что спеки и тесты чужих PR ведём
мы, а автор правит продуктовый код; целевая спека даёт автору однозначный ориентир, расхождения кода — в «Known issues».
Цена ошибки: до исправлений спека и код ветки расходятся; PR не сливается, пока «Known issues» 1–4 не закрыты.
- **2026-10-22 (ж). Выбрана одна сторона Дт/Кт — пустая таблица с текстом** «Для расчёта по счетам выберите счета и в Дт,
и в Кт» у таблицы и у фильтров, «Экспорт» матрицы недоступен. Альтернатива — оставить прежние данные с подсказкой; отвергнута:
на экране были бы данные, не соответствующие фильтру. Цена ошибки: правка текста и одного условия.
- **2026-10-22 (з). Правило отметок дерева по полному плану счетов** (родитель отмечен ⇔ отмечены все потомки), в том числе при
поиске. Обоснование: бэкенд разворачивает выбранный счёт в потомков, лишняя отметка родителя меняет расчёт. Цена ошибки: если
аналитик захочет «родитель + выбранные субсчета» как отдельный режим — MODIFIED требования.
- **2026-10-22 (и). Запасные имена файлов** `Шахматка_<ГК>_<год>_<квартал>.xlsx` и
`Результирующая_таблица_ВГО_<ГК>_<год>_<квартал>.xlsx`. Цена ошибки: низкая, имя из `Content-Disposition` приоритетнее.
- **2026-10-22 (к). Тесты форматтера** `utils/chessTableCellFormatValue.test.ts` написаны ревьюером: проверяют сдвиг единиц,
точное округление, большие суммы, «-0,00» и пустые значения; случай нечислового значения в тыс./млн/млрд не покрыт — он
в Known issues (5) и тест добавляется вместе с исправлением.
- **2026-10-22 (л). В ветку влит `main`** (PR #159), чтобы выгрузка результирующей таблицы работала на стенде из ветки.

View File

@ -2,27 +2,30 @@
В ветке `feat/chessTable` (PR RSHB/arm-core#158, автор — Голов) во фронте появился экран «Шахматка» — вкладка страницы В ветке `feat/chessTable` (PR RSHB/arm-core#158, автор — Голов) во фронте появился экран «Шахматка» — вкладка страницы
отчёта по версии консолидации рядом со «Сводной ОСВ». Бэкенд шахматки (`GET/POST …/intercompany-matrix/*`) уже в main отчёта по версии консолидации рядом со «Сводной ОСВ». Бэкенд шахматки (`GET/POST …/intercompany-matrix/*`) уже в main
(change `2026-10-16-intercompany-matrix`), у экрана спеки нет. Спеку пишет ревьюер в ветке автора по решению владельца (change `2026-10-16-intercompany-matrix`), у экрана спеки нет. Спеку и тесты ведёт ревьюер в ветке автора по решению
(21.10.2026): она фиксирует фактическое поведение экрана и отделяет от него открытые дефекты (`design.md`, «Known issues»), владельца (21–22.10.2026): спека задаёт целевое поведение экрана, расхождения кода ветки с ней — `design.md`, «Known issues».
чтобы дефекты не закрепились требованием.
## What Changes ## What Changes
Репозиторий — только arm-core (`web/`). Экран «Шахматка» [31-01], эндпоинты arm-core: Репозиторий — только arm-core (`web/`). Экран «Шахматка» [31-01], эндпоинты arm-core:
`GET …/intercompany-matrix/all`, `POST …/intercompany-matrix/manual`, `GET …/intercompany-matrix/all/export`, `GET …/intercompany-matrix/all`, `POST …/intercompany-matrix/manual`, `GET …/intercompany-matrix/all/export`,
`POST …/intercompany-matrix/manual/export`, `GET /directories/accounts`. `POST …/intercompany-matrix/manual/export`, `GET …/intercompany-matrix/result-table`,
`GET …/intercompany-matrix/result-table/export`, `GET /directories/accounts`.
- Маршрут `/groups/:group_id/versions/:version_id/:tab`: страница отчёта (`ConsolidatioReportPage`) с общей шапкой и - Маршрут `/groups/:group_id/versions/:version_id/:tab`: страница отчёта (`ConsolidationReportPage`) с общей шапкой и
вкладками (`OsvTabs`); `tab = osv` — сводная ОСВ, `tab = chess` — шахматка. Прежний адрес `…/osv` продолжает работать. вкладками (`OsvTabs`); `tab = osv` — сводная ОСВ, `tab = chess` — шахматка. Прежний адрес `…/osv` продолжает работать.
- Таблица ЮЛ × ЮЛ: в ячейке три числа (сумма A, сумма Б, расхождение), строка «Итого». - Таблица ЮЛ × ЮЛ: в ячейке три числа (сумма A, сумма Б, расхождение); итоги считает бэкенд (пока API их не отдаёт —
строки «Итого» нет).
- Результирующая таблица ВГО по статьям с колонкой «Итого» (`group_total`) и выгрузкой в XLSX.
- Фильтры: компании, счета Дт, счета Кт (деревом), единицы, знаков после запятой; режимы расчёта `all` / `manual`. - Фильтры: компании, счета Дт, счета Кт (деревом), единицы, знаков после запятой; режимы расчёта `all` / `manual`.
- Экспорт в XLSX (`all` / `manual`), полноэкранный режим. - Экспорт в XLSX (`all` / `manual`, результирующая таблица), полноэкранный режим.
- Новая спека `intercompany-matrix-ui` (ADDED Requirements). - Новая спека `intercompany-matrix-ui` (ADDED Requirements).
## Non-goals ## Non-goals
- Исправление дефектов экрана (перечень — `design.md`, «Known issues»): они остаются автору PR. - Исправление продуктового кода по «Known issues» (`design.md`) — за автором PR; спеку и тесты ведёт ревьюер.
- Результирующая таблица ВГО (`GET …/intercompany-matrix/result-table`) и пресеты счетов — во фронте не реализованы. - Пресеты счетов — во фронте не реализованы.
- Итоги «Итого» в API и их вывод — AURORA-1756, AURORA-1757.
- Выбор года и квартала на экране: селекторы заглушены (`disabled`), версия задаётся адресом. - Выбор года и квартала на экране: селекторы заглушены (`disabled`), версия задаётся адресом.
- Изменения бэкенда и DBML: шахматка — чистая функция от версии, ничего не хранится (DECISION-5). - Изменения бэкенда и DBML: шахматка — чистая функция от версии, ничего не хранится (DECISION-5).
- Вкладки «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» — недоступны. - Вкладки «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» — недоступны.
@ -38,8 +41,9 @@
## Impact ## Impact
- `web/src/App.tsx`, `web/src/layouts/{ConsolidatioReportPage,index}.ts(x)`, `web/src/widgets/ConsolidatedOsv/OsvTabs.tsx`, - `web/src/App.tsx`, `web/src/layouts/{ConsolidationReportPage,index}.ts(x)`, `web/src/widgets/ConsolidatedOsv/OsvTabs.tsx`,
`web/src/widgets/ConsolidatedOsvContent/`, `web/src/widgets/ConsolidatedChessContent/`, `web/src/widgets/ConsolidatedOsvContent/`, `web/src/widgets/ConsolidatedChessContent/`,
`web/src/widgets/ConsolidationChessTable/`, `web/src/components/TreeFilterDropdown/`, `web/src/widgets/ConsolidationChessTable/`, `web/src/widgets/ConsolidationChessResTable/`,
`web/src/services/consodilation/chess.ts`, `web/src/types/chess/chess.ts`, `web/src/api/client.ts` (`apiFetchBlob` принимает `init`). `web/src/widgets/ConsolidationChessFormatTableFilter/`, `web/src/components/TreeFilterDropdown/`,
`web/src/utils/chessTableCellFormatValue.ts`, `web/src/services/consodilation/intercompanyMatrix.ts`, `web/src/types/chess/chess.ts`, `web/src/api/client.ts` (`apiFetchBlob` принимает `init`).
- Права: экран читает данные под `CONSOLIDATION_VIEW`; меню по правам не скрывается. - Права: экран читает данные под `CONSOLIDATION_VIEW`; меню по правам не скрывается.

View File

@ -4,9 +4,10 @@
Страница `/groups/:group_id/versions/:version_id/:tab` SHALL показывать шапку версии (ГК, период, статус отчёта) один раз Страница `/groups/:group_id/versions/:version_id/:tab` SHALL показывать шапку версии (ГК, период, статус отчёта) один раз
и под ней ряд вкладок шагов консолидации и содержимое активной вкладки. Активная вкладка определяется сегментом `:tab`: и под ней ряд вкладок шагов консолидации и содержимое активной вкладки. Активная вкладка определяется сегментом `:tab`:
`osv` — «ОСВ» (сводная ОСВ), `chess` — «Шахматка». Выбор вкладки SHALL менять последний сегмент адреса на ключ вкладки `osv` — «ОСВ» (сводная ОСВ), `chess` — «Шахматка». Выбор вкладки SHALL менять последний сегмент адреса на ключ вкладки
(навигация, не локальное состояние), активная вкладка подсвечена зелёным и выделена жирным. Вкладки «Таблица корректировки», (навигация, не локальное состояние), активная вкладка подсвечена зелёным и выделена жирным. Неизвестный ключ вкладки SHALL
«Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» SHALL быть недоступны с подсказкой «Будет в следующих срезах». заменяться на `osv` без новой записи в истории браузера. Вкладки «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий»,
При смене `version_id` всё состояние страницы (карточка версии, данные, фильтры) SHALL создаваться заново. «Подтверждение КФО клиента» SHALL быть недоступны с подсказкой «Будет в следующих срезах». При смене `version_id` всё
состояние страницы (карточка версии, данные, фильтры) SHALL создаваться заново.
#### Scenario: Переход на шахматку #### Scenario: Переход на шахматку
- **WHEN** открыта сводная ОСВ версии по адресу `…/versions/{id}/osv` и нажата вкладка «Шахматка» - **WHEN** открыта сводная ОСВ версии по адресу `…/versions/{id}/osv` и нажата вкладка «Шахматка»
@ -16,14 +17,18 @@
- **WHEN** открыт адрес `…/versions/{id}/osv` - **WHEN** открыт адрес `…/versions/{id}/osv`
- **THEN** показана сводная ОСВ, вкладка «ОСВ» активна - **THEN** показана сводная ОСВ, вкладка «ОСВ» активна
#### Scenario: Неизвестная вкладка
- **WHEN** открыт адрес `…/versions/{id}/abc`
- **THEN** адрес заменяется на `…/versions/{id}/osv`, показана сводная ОСВ; кнопка «Назад» браузера не возвращает на `…/abc`
### Requirement: Таблица шахматки ЮЛ × ЮЛ ### Requirement: Таблица шахматки ЮЛ × ЮЛ
Экран SHALL загружать матрицу при открытии вкладки (`GET /consolidations/versions/{id}/intercompany-matrix/all`) и показывать Экран SHALL загружать матрицу при открытии вкладки (`GET /consolidations/versions/{id}/intercompany-matrix/all`) и показывать
таблицу: первая колонка «ДБ / КТ» (закреплена слева) — компании периметра в порядке ответа (`companies[]`), остальные колонки — таблицу: первая колонка «ДБ / КТ» (закреплена слева) — компании периметра в порядке ответа (`companies[]`), остальные колонки —
те же компании. В ячейке пары (строка A, колонка Б) SHALL быть три числа друг под другом: сумма `amount_a`, сумма `amount_b` те же компании. В ячейке пары (строка A, колонка Б) SHALL быть три числа друг под другом: сумма `amount_a`, сумма `amount_b`
и расхождение `variance_amount`; расхождение выделяется красным, если `exceeds_threshold = true`. Если ячейки пары в ответе нет и расхождение `variance_amount`; расхождение выделяется красным, если `exceeds_threshold = true`. Если ячейки пары в ответе нет
(в том числе на диагонали), SHALL показываться прочерк «—» на приглушённом фоне. Ниже таблицы SHALL закрепляться строка «Итого» (в том числе на диагонали), SHALL показываться прочерк «—» на приглушённом фоне. Итоговые суммы (строка «Итого») экран SHALL
с одним числом по каждой колонке. Пока идёт загрузка, таблица показывает индикатор загрузки; ошибка API показывается NOT вычислять сам: агрегаты отдаёт бэкенд, пока API их не возвращает — строки «Итого» под матрицей нет. Пока идёт загрузка,
уведомлением с текстом сервера. таблица показывает индикатор загрузки; ошибка API показывается уведомлением с текстом сервера.
#### Scenario: Ячейка с расхождением выше порога #### Scenario: Ячейка с расхождением выше порога
- **WHEN** для пары `(A, Б)` сервер вернул `exceeds_threshold = true` - **WHEN** для пары `(A, Б)` сервер вернул `exceeds_threshold = true`
@ -33,36 +38,79 @@
- **WHEN** в `cells[]` нет ячейки для пары компаний - **WHEN** в `cells[]` нет ячейки для пары компаний
- **THEN** на их пересечении показан прочерк «—» - **THEN** на их пересечении показан прочерк «—»
#### Scenario: Итоги не считаются на клиенте
- **WHEN** матрица загружена
- **THEN** под таблицей нет строки «Итого»; ни одна сумма на экране не получена сложением сумм ответа на клиенте
### Requirement: Результирующая таблица ВГО
Под матрицей экран SHALL показывать результирующую таблицу ВГО, загруженную при открытии вкладки
(`GET /consolidations/versions/{id}/intercompany-matrix/result-table`): первая колонка «Статья» — статьи ответа (`articles[]`,
`name`) в порядке ответа, далее колонки компаний (`companies[]`) с суммой статьи по компании (`amounts[]` по `company_id`) и
колонка «Итого» со значением `group_total` из ответа. Строки «Итого» под таблицей нет, пока API не отдаёт итог по статьям
(клиент суммы не складывает). У таблицы свои выбор единиц и знаков (по правилам требования «Единицы и знаки», независимо от
матрицы), кнопка «Экспорт» и кнопка «На весь экран». Пока идёт загрузка, таблица показывает индикатор; ошибка API (в том числе
422 `RESULT_ACCOUNTS_MISSING`) показывается уведомлением с текстом сервера.
#### Scenario: Колонка «Итого» из ответа
- **WHEN** сервер вернул статью «Дебиторская задолженность» с `group_total = "1296510.00"`, выбраны «тыс. руб.» и 2 знака
- **THEN** в колонке «Итого» этой статьи показано `1 296,51`
#### Scenario: Нет обязательного счёта
- **WHEN** сервер ответил 422 `RESULT_ACCOUNTS_MISSING`
- **THEN** показано уведомление с текстом сервера, матрица шахматки при этом показывается как обычно
### Requirement: Единицы и знаки ### Requirement: Единицы и знаки
Экран SHALL позволять выбрать единицы суммы — «тыс. руб.» (по умолчанию), «млн. руб.», «млрд руб.» — и число знаков после Экран SHALL позволять выбрать единицы суммы — «руб.», «тыс. руб.» (по умолчанию), «млн. руб.», «млрд руб.» — и число знаков
запятой 0–3 (по умолчанию 2). Выбор применяется на клиенте к уже загруженным значениям (повторного запроса не делает): после запятой 0–3 (по умолчанию 2). Выбор применяется на клиенте к уже загруженным значениям (повторного запроса не делает):
значение делится на 1000 / 1 000 000 / 1 000 000 000 и показывается с выбранным числом знаков, десятичный разделитель — запятая. запятая сдвигается в десятичной строке на 3 / 6 / 9 разрядов, значение округляется до выбранного числа знаков (половина —
Значение, которое не удаётся прочитать как число, SHALL показываться как пришло. от нуля) без перевода в число с плавающей точкой и показывается с разделителем разрядов и десятичной запятой. Значение,
округлённое до нуля, показывается без минуса. Пустое значение — пустая ячейка. Значение, которое не удаётся прочитать как
число, SHALL показываться как пришло — при любых единицах.
#### Scenario: Смена единиц #### Scenario: Смена единиц
- **WHEN** выбраны «млн. руб.» и 1 знак, а сервер вернул `amount_a = "2346459.05"` - **WHEN** выбраны «млн. руб.» и 1 знак, а сервер вернул `amount_a = "2346459.05"`
- **THEN** в ячейке показано `2,3` - **THEN** в ячейке показано `2,3`
#### Scenario: Точное округление
- **WHEN** выбраны «тыс. руб.» и 2 знака, а сервер вернул `"1005"`
- **THEN** показано `1,01` (не `1,00`, как дало бы округление числа с плавающей точкой)
#### Scenario: Маленькая отрицательная сумма
- **WHEN** выбраны «тыс. руб.» и 2 знака, а сервер вернул `"-4.00"`
- **THEN** показано `0,00`, а не `-0,00`
### Requirement: Фильтр компаний ### Requirement: Фильтр компаний
Выбор «Компании» (множественный, пустой выбор — «Все компании») SHALL ограничивать таблицу на клиенте: остаются строки и Выбор «Компании» (множественный, пустой выбор — «Все компании») SHALL ограничивать матрицу на клиенте: остаются строки и
колонки выбранных компаний и ячейки, у которых обе компании выбраны. Запрос на сервер при этом не отправляется. Список колонки выбранных компаний и ячейки, у которых обе компании выбраны. Запрос на сервер при этом не отправляется. Список
вариантов — компании последнего загруженного ответа. Выпадающее окно выбора — дерево с поиском по названию, кнопками вариантов — компании последнего загруженного ответа. Выпадающее окно выбора — дерево с поиском по названию, кнопками
применения и сброса. применения и сброса.
#### Scenario: Две компании из периметра #### Scenario: Две компании из периметра
- **WHEN** выбраны две компании из пяти - **WHEN** выбраны две компании из пяти
- **THEN** таблица содержит две строки и две колонки, и «Итого» считается по оставшимся ячейкам - **THEN** матрица содержит две строки и две колонки
### Requirement: Фильтр счетов Дт и Кт и режимы расчёта ### Requirement: Фильтр счетов Дт и Кт и режимы расчёта
Экран SHALL загружать план счетов `GET /directories/accounts` один раз при открытии и строить из него два дерева по иерархии Экран SHALL загружать план счетов `GET /directories/accounts` один раз при открытии и строить из него два дерева по иерархии
`parent_id`: дебетовые счета (`is_debit`) и кредитовые (`is_credit`); подпись узла — «код - название». Выбор счетов Дт и Кт — `parent_id`: дебетовые счета (`is_debit`) и кредитовые (`is_credit`); подпись узла — «код - название», в том числе у выбранных
множественный, пустой выбор — «Все счета». дочерних счетов в поле выбора. Выбор счетов Дт и Кт — множественный, пустой выбор — «Все счета».
Режим расчёта определяется выбором: если выбраны счета и в Дт, и в Кт, SHALL выполняться Отметки в дереве SHALL считаться по полному плану счетов: родительский счёт отмечен тогда и только тогда, когда отмечены все
`POST …/intercompany-matrix/manual` с `{debit_account_ids, credit_account_ids}` (режим `manual`); в остальных случаях его потомки в полном дереве. Поиск по дереву скрывает не совпавшие узлы, но не меняет этого правила: отметка единственного
(ничего не выбрано либо выбрана только одна сторона) SHALL выполняться `GET …/intercompany-matrix/all` (режим `all`). Каждое видимого при поиске субсчёта не отмечает его родителя, а снятие отметки с субсчёта снимает её и с родителя. Скрытые поиском
изменение выбора счетов перезапрашивает матрицу. Кнопка «Сбросить фильтры» появляется, когда выбрано что-либо в компаниях, Дт отметки при отметке видимых узлов сохраняются.
или Кт, и очищает все три выбора. Выбор единиц и знаков кнопкой не сбрасывается. Ошибка загрузки плана счетов показывается уведомлением.
Режим расчёта определяется выбором:
- ничего не выбрано ни в Дт, ни в Кт — SHALL выполняться `GET …/intercompany-matrix/all` (режим `all`);
- выбраны счета и в Дт, и в Кт — SHALL выполняться `POST …/intercompany-matrix/manual` с `{debit_account_ids,
credit_account_ids}` (режим `manual`);
- выбрана только одна сторона — запрос SHALL NOT отправляться, таблица матрицы SHALL быть пустой и вместо данных показывать
текст «Для расчёта по счетам выберите счета и в Дт, и в Кт»; тот же текст SHALL показываться у фильтров Дт/Кт (не
всплывающим уведомлением) и только в этом состоянии; кнопка «Экспорт» матрицы SHALL быть недоступна.
Каждое изменение выбора счетов, приводящее к режиму `all` или `manual`, перезапрашивает матрицу; на экране остаётся ответ на
последний запрос, индикатор загрузки снимается и при переходе в состояние «выбрана одна сторона». Кнопка «Сбросить фильтры»
появляется, когда выбрано что-либо в компаниях, Дт или Кт, и очищает все три выбора. Выбор единиц и знаков кнопкой не
сбрасывается. Ошибка загрузки плана счетов показывается уведомлением.
#### Scenario: Обе стороны выбраны #### Scenario: Обе стороны выбраны
- **WHEN** выбран счёт 60.01 в Дт и счёт 62.01 в Кт - **WHEN** выбран счёт 60.01 в Дт и счёт 62.01 в Кт
@ -70,28 +118,52 @@
#### Scenario: Выбрана одна сторона #### Scenario: Выбрана одна сторона
- **WHEN** выбран счёт только в Дт - **WHEN** выбран счёт только в Дт
- **THEN** отправляется `GET …/intercompany-matrix/all` (режим `all`); выбор Дт остаётся в фильтре - **THEN** запрос матрицы не отправляется, таблица пуста и показывает «Для расчёта по счетам выберите счета и в Дт, и в Кт»,
тот же текст виден у фильтров, «Экспорт» матрицы недоступен, выбор Дт остаётся в фильтре
#### Scenario: Выбрана одна сторона во время загрузки
- **WHEN** выбраны Дт и Кт, запрос `manual` ещё выполняется, и пользователь очищает Кт
- **THEN** индикатор загрузки снят, ответ прежнего запроса на экран не выводится, таблица в состоянии «выбрана одна сторона»
#### Scenario: Поиск и отметка субсчёта
- **WHEN** в дереве Дт введён поиск, видна ветка 60 с единственным субсчётом 60.02, и отмечен 60.02
- **THEN** выбранным остаётся только 60.02; после сброса поиска счёт 60 и его прочие субсчета не отмечены
#### Scenario: Снятие субсчёта отмеченного родителя
- **WHEN** отмечен счёт 60 (отмечены 60, 60.01, 60.02) и снята отметка с 60.01
- **THEN** выбранным остаётся 60.02, счёт 60 не отмечен
#### Scenario: Сброс #### Scenario: Сброс
- **WHEN** выбраны компании и счета, нажато «Сбросить фильтры» - **WHEN** выбраны компании и счета, нажато «Сбросить фильтры»
- **THEN** все три выбора пусты, матрица перезапрошена в режиме `all`, кнопка исчезла - **THEN** все три выбора пусты, матрица перезапрошена в режиме `all`, кнопка исчезла
### Requirement: Экспорт шахматки в XLSX ### Requirement: Экспорт в XLSX
Кнопка «Экспорт» SHALL скачивать XLSX в том же режиме, что и таблица: при выбранных счетах и в Дт, и в Кт — Кнопка «Экспорт» матрицы SHALL скачивать XLSX в том же режиме, что и таблица: в режиме `manual` —
`POST …/intercompany-matrix/manual/export` с телом `{debit_account_ids, credit_account_ids}`, иначе `POST …/intercompany-matrix/manual/export` с телом `{debit_account_ids, credit_account_ids}`, в режиме `all` —
`GET …/intercompany-matrix/all/export`. Файл сохраняется под именем из `Content-Disposition`, при его отсутствии — «Шахматка». `GET …/intercompany-matrix/all/export`; при выбранной одной стороне кнопка недоступна. Кнопка «Экспорт» результирующей таблицы
Пока идёт выгрузка, кнопка в состоянии загрузки; ошибка API показывается уведомлением. Фильтр компаний, единицы и знаки SHALL скачивать `GET …/intercompany-matrix/result-table/export`. Файл сохраняется под именем из `Content-Disposition`, при его
в запрос экспорта не передаются. отсутствии — `Шахматка_<ГК>_<год>_<квартал>.xlsx` и `Результирующая_таблица_ВГО_<ГК>_<год>_<квартал>.xlsx` соответственно.
Пока идёт выгрузка, в состоянии загрузки только нажатая кнопка; ошибка API показывается уведомлением. Фильтр компаний, единицы и
знаки в запрос экспорта не передаются.
#### Scenario: Экспорт по всем счетам #### Scenario: Экспорт по всем счетам
- **WHEN** счета не выбраны и нажато «Экспорт» - **WHEN** счета не выбраны и нажато «Экспорт» матрицы
- **THEN** отправлен `GET …/intercompany-matrix/all/export`, браузер сохраняет файл `intercompany-matrix_….xlsx` - **THEN** отправлен `GET …/intercompany-matrix/all/export`, браузер сохраняет файл `intercompany-matrix_….xlsx`
#### Scenario: Экспорт результирующей таблицы
- **WHEN** нажато «Экспорт» результирующей таблицы
- **THEN** отправлен `GET …/intercompany-matrix/result-table/export`, кнопка «Экспорт» матрицы не переходит в состояние загрузки
### Requirement: Полноэкранный режим ### Requirement: Полноэкранный режим
Кнопка «На весь экран» SHALL переводить блок шахматки (панель фильтров и таблицу) в полноэкранный режим средствами Fullscreen API Кнопка «На весь экран» матрицы и результирующей таблицы SHALL переводить свой блок (панель фильтров и таблицу) в
с белым фоном и таблицей на высоту окна; в нём кнопка меняется на «Свернуть». Выход из полноэкранного режима клавишей Esc полноэкранный режим средствами Fullscreen API с белым фоном и таблицей на высоту окна; в нём кнопка меняется на «Свернуть».
SHALL возвращать обычный вид и подпись кнопки. Выпадающие списки фильтров и подсказки SHALL оставаться видимыми в полноэкранном режиме. Выход клавишей Esc SHALL возвращать
обычный вид и подпись кнопки. Отказ браузера войти в полноэкранный режим показывается уведомлением.
#### Scenario: Вход и выход #### Scenario: Вход и выход
- **WHEN** нажато «На весь экран», затем Esc - **WHEN** нажато «На весь экран», затем Esc
- **THEN** блок разворачивается на весь экран, затем возвращается в страницу, кнопка снова «На весь экран» - **THEN** блок разворачивается на весь экран, затем возвращается в страницу, кнопка снова «На весь экран»
#### Scenario: Фильтр в полноэкранном режиме
- **WHEN** матрица в полноэкранном режиме и открыт выбор «Компании»
- **THEN** выпадающее окно видно поверх полноэкранного блока

View File

@ -1,6 +1,11 @@
## 1. Спека и ревью (RSHB/arm-core#158) ## 1. Спека и ревью (RSHB/arm-core#158, AURORA-1538)
- [x] 1.1 Спека `intercompany-matrix-ui` по фактическому поведению ветки `feat/chessTable`. - [x] 1.1 Спека `intercompany-matrix-ui` (ревьюер; с раунда 3 — целевое поведение).
- [ ] 1.2 Автор исправляет пункты «Known issues» из `design.md` и обновляет требования спеки отдельным коммитом. - [x] 1.2 Тесты форматтера сумм `utils/chessTableCellFormatValue.test.ts` (ревьюер).
- [ ] 1.3 Перед PR: `make web-check`, `npm test`, `npm run lint` — зелёные, вывод в описание PR. - [ ] 1.3 Автор исправляет «Known issues» 1–4 из `design.md` (желательное — 5 — по возможности).
- [ ] 1.4 Сверка экрана с макетом 31-01 в браузере (`docs/mockup/INDEX.md`). - [ ] 1.4 Перед слиянием: `make web-check`, `npm test`, `npm run lint` — зелёные, вывод в описание PR.
- [ ] 1.5 Сверка экрана с макетом 31-01 / 31-01-01 в браузере (`docs/mockup/INDEX.md`), в том числе полноэкранный режим.
## 2. После слияния
- [ ] 2.1 Итоги «Итого» из API — AURORA-1756 (бэк), AURORA-1757 (фронт).