From b8f0672029a9fd08da65be3ddabe741fe2c27843 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 22 Oct 2026 16:30:01 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20intercompany-matrix-ui=20?= =?UTF-8?q?=E2=80=94=20=D1=86=D0=B5=D0=BB=D0=B5=D0=B2=D0=BE=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=8D=D0=BA?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B0=20(=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20#1?= =?UTF-8?q?58,=20=D1=80=D0=B0=D1=83=D0=BD=D0=B4=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Итоги отдаёт бэкенд (AURORA-1756/1757), результирующая таблица и её выгрузка, «руб.», точное округление, одна сторона Дт/Кт без запроса, правило отметок дерева по полному плану счетов, отдельные флаги выгрузки, полноэкранный режим. Known issues — по состоянию 601a125. Co-Authored-By: Claude Opus 5.5 --- .../changes/intercompany-matrix-ui/design.md | 81 ++++++----- .../intercompany-matrix-ui/proposal.md | 28 ++-- .../specs/intercompany-matrix-ui/spec.md | 132 ++++++++++++++---- .../changes/intercompany-matrix-ui/tasks.md | 15 +- 4 files changed, 176 insertions(+), 80 deletions(-) diff --git a/openspec/changes/intercompany-matrix-ui/design.md b/openspec/changes/intercompany-matrix-ui/design.md index 69d920d..eafd997 100644 --- a/openspec/changes/intercompany-matrix-ui/design.md +++ b/openspec/changes/intercompany-matrix-ui/design.md @@ -1,55 +1,55 @@ ## 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`; расчёт — чистая функция -версии, ничего не хранится. +Экран «Шахматка» [31-01] добавлен в `arm-core/web` PR #158 (ветка `feat/chessTable`, AURORA-1538) как вторая вкладка страницы +отчёта по версии. Страница `layouts/ConsolidatedOsvPage.tsx` разделена: общую шапку и вкладки держит +`layouts/ConsolidationReportPage.tsx`, содержимое вкладок — `widgets/ConsolidatedOsvContent` (прежняя сводная ОСВ без изменений +поведения) и `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`) и по выбору заменяет - последний сегмент адреса. Страница выбирает содержимое по таблице `tab → компонент`; неизвестный `tab` даёт пустое содержимое. + последний сегмент адреса. Неизвестный `tab` заменяется на `osv` (`navigate(…, {replace: true})`). - **D-2. Ключ версии.** Страница отчёта разделена на обёртку с `key={version_id}` и содержимое: при смене версии карточка, фильтры и данные создаются заново (как было в `ConsolidatedOsvPage`). -- **D-3. Режим `all` / `manual` выводится из выбора счетов:** `manual` только при непустых Дт и Кт (иначе бэкенд ответит 422), - в остальных случаях `all`. Один и тот же признак выбирает маршрут экспорта. +- **D-3. Режим расчёта из выбора счетов:** `manual` — при непустых Дт и Кт, `all` — при пустых обоих; при одной выбранной + стороне запроса нет (бэкенд ответил бы 422 на `manual`, а `all` показал бы не то, что выбрано). Тот же признак выбирает + маршрут экспорта. - **D-4. Единицы, знаки и фильтр компаний — на клиенте.** Запрос повторяется только при смене выбора счетов или версии. + Единицы — сдвиг запятой в десятичной строке (`utils/chessTableCellFormatValue.ts`), `Intl.NumberFormat` получает строку и + форматирует её как точное десятичное (без float). - **D-5. План счетов** — `GET /directories/accounts` целиком, деревья Дт и Кт строятся на клиенте по `parent_id`. - **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 -Открытые проблемы ветки, которые исправляет автор PR; спека их НЕ закрепляет как требования. +Открытые проблемы ветки на 601a125 (ревью, раунд 3), которые исправляет автор 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. +1. Строки «Итого» под матрицей и под результирующей таблицей считаются на клиенте (`sumAmounts`) — убрать (D-8). +2. Поиск в дереве счетов: отметки считаются по отфильтрованному дереву, отметка единственного видимого субсчёта отмечает + родителя, и на бэк уходит весь родительский счёт (`TreeFilterDropdown.tsx`). +3. Выбрана одна сторона Дт/Кт: таблица показывает прежние данные, подсказка — всплывающее уведомление и постоянный текст внутри + выпадающего окна, «Экспорт» активен и выгружает режим `all`. +4. Стили: правило тегов в `App.css` и класс на форме ЮЛ (`CatalogueJurForm.tsx`) — вне экрана; у селекта Кт класса нет; + `.resRow`, `.empty-cell` не под `.chessTable`; у `Tree` снят `className='filterTree'`. +5. Желательно: запасные имена файлов (`exportFilename` добавляет `Сводная_ОСВ_`) и общий `exportLoading` на две кнопки; + нечисловое значение в тыс./млн/млрд искажается (`0.abc`); `sumAmounts(...) ?? '0'`; `as any` в форматтере; мёртвый код + (скобки в форматтере, `formatted === '—'`); ячейки по макету (скобки у расхождения, «—», значок `skipped_osv_entry_count`); + заглушки «Год»/«Квартал»; `services/consodilation`, типы вне `types/api.ts`, `withJsonBody`, `Account.kind`. ## Decisions made without discussion - **2026-10-21 (а). Спеку пишет ревьюер в ветке автора** — по решению владельца (разрешено явно); альтернатива — оставить автору. Цена ошибки: автор может не согласиться с формулировками; правится одним коммитом в этом же change. -- **2026-10-21 (б). Спека описывает фактическое поведение, дефекты вынесены в «Known issues».** Обоснование: спека — единственный +- **2026-10-21 (б). Спека описывает фактическое поведение, дефекты вынесены в «Known issues».** Отменено 22.10.2026 (е). Обоснование: спека — единственный источник истины при рулингах, а закреплять баги требованиями нельзя; иначе их закрытие превратится в «нарушение спеки». Цена ошибки: отдельные требования (например, режим `all` при выборе одной стороны, «Итого» из расхождений) описаны как есть и после исправления потребуют MODIFIED. @@ -65,3 +65,18 @@ - **2026-10-21 (д). Дельта к `consolidated-osv-ui` не делается:** вкладки описаны в новой спеке, текст `consolidated-osv-ui` не правился, чтобы не смешивать UI-половины. Цена ошибки: при архивации две спеки частично описывают один компонент (`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), чтобы выгрузка результирующей таблицы работала на стенде из ветки. diff --git a/openspec/changes/intercompany-matrix-ui/proposal.md b/openspec/changes/intercompany-matrix-ui/proposal.md index a9fd070..4bb57d4 100644 --- a/openspec/changes/intercompany-matrix-ui/proposal.md +++ b/openspec/changes/intercompany-matrix-ui/proposal.md @@ -2,27 +2,30 @@ В ветке `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»), -чтобы дефекты не закрепились требованием. +(change `2026-10-16-intercompany-matrix`), у экрана спеки нет. Спеку и тесты ведёт ревьюер в ветке автора по решению +владельца (21–22.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`. +`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` продолжает работать. -- Таблица ЮЛ × ЮЛ: в ячейке три числа (сумма A, сумма Б, расхождение), строка «Итого». +- Таблица ЮЛ × ЮЛ: в ячейке три числа (сумма A, сумма Б, расхождение); итоги считает бэкенд (пока API их не отдаёт — + строки «Итого» нет). +- Результирующая таблица ВГО по статьям с колонкой «Итого» (`group_total`) и выгрузкой в XLSX. - Фильтры: компании, счета Дт, счета Кт (деревом), единицы, знаков после запятой; режимы расчёта `all` / `manual`. -- Экспорт в XLSX (`all` / `manual`), полноэкранный режим. +- Экспорт в XLSX (`all` / `manual`, результирующая таблица), полноэкранный режим. - Новая спека `intercompany-matrix-ui` (ADDED Requirements). ## Non-goals -- Исправление дефектов экрана (перечень — `design.md`, «Known issues»): они остаются автору PR. -- Результирующая таблица ВГО (`GET …/intercompany-matrix/result-table`) и пресеты счетов — во фронте не реализованы. +- Исправление продуктового кода по «Known issues» (`design.md`) — за автором PR; спеку и тесты ведёт ревьюер. +- Пресеты счетов — во фронте не реализованы. +- Итоги «Итого» в API и их вывод — AURORA-1756, AURORA-1757. - Выбор года и квартала на экране: селекторы заглушены (`disabled`), версия задаётся адресом. - Изменения бэкенда и DBML: шахматка — чистая функция от версии, ничего не хранится (DECISION-5). - Вкладки «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» — недоступны. @@ -38,8 +41,9 @@ ## 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/ConsolidationChessTable/`, `web/src/components/TreeFilterDropdown/`, - `web/src/services/consodilation/chess.ts`, `web/src/types/chess/chess.ts`, `web/src/api/client.ts` (`apiFetchBlob` принимает `init`). + `web/src/widgets/ConsolidationChessTable/`, `web/src/widgets/ConsolidationChessResTable/`, + `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`; меню по правам не скрывается. 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 index 9c37bd1..e569e3d 100644 --- a/openspec/changes/intercompany-matrix-ui/specs/intercompany-matrix-ui/spec.md +++ b/openspec/changes/intercompany-matrix-ui/specs/intercompany-matrix-ui/spec.md @@ -4,9 +4,10 @@ Страница `/groups/:group_id/versions/:version_id/:tab` SHALL показывать шапку версии (ГК, период, статус отчёта) один раз и под ней ряд вкладок шагов консолидации и содержимое активной вкладки. Активная вкладка определяется сегментом `:tab`: `osv` — «ОСВ» (сводная ОСВ), `chess` — «Шахматка». Выбор вкладки SHALL менять последний сегмент адреса на ключ вкладки -(навигация, не локальное состояние), активная вкладка подсвечена зелёным и выделена жирным. Вкладки «Таблица корректировки», -«Гудвилл», «КФО», «КФО Общий», «Подтверждение КФО клиента» SHALL быть недоступны с подсказкой «Будет в следующих срезах». -При смене `version_id` всё состояние страницы (карточка версии, данные, фильтры) SHALL создаваться заново. +(навигация, не локальное состояние), активная вкладка подсвечена зелёным и выделена жирным. Неизвестный ключ вкладки SHALL +заменяться на `osv` без новой записи в истории браузера. Вкладки «Таблица корректировки», «Гудвилл», «КФО», «КФО Общий», +«Подтверждение КФО клиента» SHALL быть недоступны с подсказкой «Будет в следующих срезах». При смене `version_id` всё +состояние страницы (карточка версии, данные, фильтры) SHALL создаваться заново. #### Scenario: Переход на шахматку - **WHEN** открыта сводная ОСВ версии по адресу `…/versions/{id}/osv` и нажата вкладка «Шахматка» @@ -16,14 +17,18 @@ - **WHEN** открыт адрес `…/versions/{id}/osv` - **THEN** показана сводная ОСВ, вкладка «ОСВ» активна +#### Scenario: Неизвестная вкладка +- **WHEN** открыт адрес `…/versions/{id}/abc` +- **THEN** адрес заменяется на `…/versions/{id}/osv`, показана сводная ОСВ; кнопка «Назад» браузера не возвращает на `…/abc` + ### Requirement: Таблица шахматки ЮЛ × ЮЛ Экран SHALL загружать матрицу при открытии вкладки (`GET /consolidations/versions/{id}/intercompany-matrix/all`) и показывать таблицу: первая колонка «ДБ / КТ» (закреплена слева) — компании периметра в порядке ответа (`companies[]`), остальные колонки — те же компании. В ячейке пары (строка A, колонка Б) SHALL быть три числа друг под другом: сумма `amount_a`, сумма `amount_b` и расхождение `variance_amount`; расхождение выделяется красным, если `exceeds_threshold = true`. Если ячейки пары в ответе нет -(в том числе на диагонали), SHALL показываться прочерк «—» на приглушённом фоне. Ниже таблицы SHALL закрепляться строка «Итого» -с одним числом по каждой колонке. Пока идёт загрузка, таблица показывает индикатор загрузки; ошибка API показывается -уведомлением с текстом сервера. +(в том числе на диагонали), SHALL показываться прочерк «—» на приглушённом фоне. Итоговые суммы (строка «Итого») экран SHALL +NOT вычислять сам: агрегаты отдаёт бэкенд, пока API их не возвращает — строки «Итого» под матрицей нет. Пока идёт загрузка, +таблица показывает индикатор загрузки; ошибка API показывается уведомлением с текстом сервера. #### Scenario: Ячейка с расхождением выше порога - **WHEN** для пары `(A, Б)` сервер вернул `exceeds_threshold = true` @@ -33,36 +38,79 @@ - **WHEN** в `cells[]` нет ячейки для пары компаний - **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: Единицы и знаки -Экран SHALL позволять выбрать единицы суммы — «тыс. руб.» (по умолчанию), «млн. руб.», «млрд руб.» — и число знаков после -запятой 0–3 (по умолчанию 2). Выбор применяется на клиенте к уже загруженным значениям (повторного запроса не делает): -значение делится на 1000 / 1 000 000 / 1 000 000 000 и показывается с выбранным числом знаков, десятичный разделитель — запятая. -Значение, которое не удаётся прочитать как число, SHALL показываться как пришло. +Экран SHALL позволять выбрать единицы суммы — «руб.», «тыс. руб.» (по умолчанию), «млн. руб.», «млрд руб.» — и число знаков +после запятой 0–3 (по умолчанию 2). Выбор применяется на клиенте к уже загруженным значениям (повторного запроса не делает): +запятая сдвигается в десятичной строке на 3 / 6 / 9 разрядов, значение округляется до выбранного числа знаков (половина — +от нуля) без перевода в число с плавающей точкой и показывается с разделителем разрядов и десятичной запятой. Значение, +округлённое до нуля, показывается без минуса. Пустое значение — пустая ячейка. Значение, которое не удаётся прочитать как +число, SHALL показываться как пришло — при любых единицах. #### Scenario: Смена единиц - **WHEN** выбраны «млн. руб.» и 1 знак, а сервер вернул `amount_a = "2346459.05"` - **THEN** в ячейке показано `2,3` +#### Scenario: Точное округление +- **WHEN** выбраны «тыс. руб.» и 2 знака, а сервер вернул `"1005"` +- **THEN** показано `1,01` (не `1,00`, как дало бы округление числа с плавающей точкой) + +#### Scenario: Маленькая отрицательная сумма +- **WHEN** выбраны «тыс. руб.» и 2 знака, а сервер вернул `"-4.00"` +- **THEN** показано `0,00`, а не `-0,00` + ### Requirement: Фильтр компаний -Выбор «Компании» (множественный, пустой выбор — «Все компании») SHALL ограничивать таблицу на клиенте: остаются строки и +Выбор «Компании» (множественный, пустой выбор — «Все компании») SHALL ограничивать матрицу на клиенте: остаются строки и колонки выбранных компаний и ячейки, у которых обе компании выбраны. Запрос на сервер при этом не отправляется. Список вариантов — компании последнего загруженного ответа. Выпадающее окно выбора — дерево с поиском по названию, кнопками применения и сброса. #### Scenario: Две компании из периметра - **WHEN** выбраны две компании из пяти -- **THEN** таблица содержит две строки и две колонки, и «Итого» считается по оставшимся ячейкам +- **THEN** матрица содержит две строки и две колонки ### Requirement: Фильтр счетов Дт и Кт и режимы расчёта Экран SHALL загружать план счетов `GET /directories/accounts` один раз при открытии и строить из него два дерева по иерархии -`parent_id`: дебетовые счета (`is_debit`) и кредитовые (`is_credit`); подпись узла — «код - название». Выбор счетов Дт и Кт — -множественный, пустой выбор — «Все счета». +`parent_id`: дебетовые счета (`is_debit`) и кредитовые (`is_credit`); подпись узла — «код - название», в том числе у выбранных +дочерних счетов в поле выбора. Выбор счетов Дт и Кт — множественный, пустой выбор — «Все счета». -Режим расчёта определяется выбором: если выбраны счета и в Дт, и в Кт, SHALL выполняться -`POST …/intercompany-matrix/manual` с `{debit_account_ids, credit_account_ids}` (режим `manual`); в остальных случаях -(ничего не выбрано либо выбрана только одна сторона) SHALL выполняться `GET …/intercompany-matrix/all` (режим `all`). Каждое -изменение выбора счетов перезапрашивает матрицу. Кнопка «Сбросить фильтры» появляется, когда выбрано что-либо в компаниях, Дт -или Кт, и очищает все три выбора. Выбор единиц и знаков кнопкой не сбрасывается. Ошибка загрузки плана счетов показывается уведомлением. +Отметки в дереве SHALL считаться по полному плану счетов: родительский счёт отмечен тогда и только тогда, когда отмечены все +его потомки в полном дереве. Поиск по дереву скрывает не совпавшие узлы, но не меняет этого правила: отметка единственного +видимого при поиске субсчёта не отмечает его родителя, а снятие отметки с субсчёта снимает её и с родителя. Скрытые поиском +отметки при отметке видимых узлов сохраняются. + +Режим расчёта определяется выбором: +- ничего не выбрано ни в Дт, ни в Кт — 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: Обе стороны выбраны - **WHEN** выбран счёт 60.01 в Дт и счёт 62.01 в Кт @@ -70,28 +118,52 @@ #### Scenario: Выбрана одна сторона - **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: Сброс - **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 показывается уведомлением. Фильтр компаний, единицы и знаки -в запрос экспорта не передаются. +### Requirement: Экспорт в XLSX +Кнопка «Экспорт» матрицы SHALL скачивать XLSX в том же режиме, что и таблица: в режиме `manual` — +`POST …/intercompany-matrix/manual/export` с телом `{debit_account_ids, credit_account_ids}`, в режиме `all` — +`GET …/intercompany-matrix/all/export`; при выбранной одной стороне кнопка недоступна. Кнопка «Экспорт» результирующей таблицы +SHALL скачивать `GET …/intercompany-matrix/result-table/export`. Файл сохраняется под именем из `Content-Disposition`, при его +отсутствии — `Шахматка_<ГК>_<год>_<квартал>.xlsx` и `Результирующая_таблица_ВГО_<ГК>_<год>_<квартал>.xlsx` соответственно. +Пока идёт выгрузка, в состоянии загрузки только нажатая кнопка; ошибка API показывается уведомлением. Фильтр компаний, единицы и +знаки в запрос экспорта не передаются. #### Scenario: Экспорт по всем счетам -- **WHEN** счета не выбраны и нажато «Экспорт» +- **WHEN** счета не выбраны и нажато «Экспорт» матрицы - **THEN** отправлен `GET …/intercompany-matrix/all/export`, браузер сохраняет файл `intercompany-matrix_….xlsx` +#### Scenario: Экспорт результирующей таблицы +- **WHEN** нажато «Экспорт» результирующей таблицы +- **THEN** отправлен `GET …/intercompany-matrix/result-table/export`, кнопка «Экспорт» матрицы не переходит в состояние загрузки + ### Requirement: Полноэкранный режим -Кнопка «На весь экран» SHALL переводить блок шахматки (панель фильтров и таблицу) в полноэкранный режим средствами Fullscreen API -с белым фоном и таблицей на высоту окна; в нём кнопка меняется на «Свернуть». Выход из полноэкранного режима клавишей Esc -SHALL возвращать обычный вид и подпись кнопки. +Кнопка «На весь экран» матрицы и результирующей таблицы SHALL переводить свой блок (панель фильтров и таблицу) в +полноэкранный режим средствами Fullscreen API с белым фоном и таблицей на высоту окна; в нём кнопка меняется на «Свернуть». +Выпадающие списки фильтров и подсказки SHALL оставаться видимыми в полноэкранном режиме. Выход клавишей Esc SHALL возвращать +обычный вид и подпись кнопки. Отказ браузера войти в полноэкранный режим показывается уведомлением. #### Scenario: Вход и выход - **WHEN** нажато «На весь экран», затем Esc - **THEN** блок разворачивается на весь экран, затем возвращается в страницу, кнопка снова «На весь экран» + +#### Scenario: Фильтр в полноэкранном режиме +- **WHEN** матрица в полноэкранном режиме и открыт выбор «Компании» +- **THEN** выпадающее окно видно поверх полноэкранного блока diff --git a/openspec/changes/intercompany-matrix-ui/tasks.md b/openspec/changes/intercompany-matrix-ui/tasks.md index 3fca357..0cdaf87 100644 --- a/openspec/changes/intercompany-matrix-ui/tasks.md +++ b/openspec/changes/intercompany-matrix-ui/tasks.md @@ -1,6 +1,11 @@ -## 1. Спека и ревью (RSHB/arm-core#158) +## 1. Спека и ревью (RSHB/arm-core#158, AURORA-1538) -- [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`). +- [x] 1.1 Спека `intercompany-matrix-ui` (ревьюер; с раунда 3 — целевое поведение). +- [x] 1.2 Тесты форматтера сумм `utils/chessTableCellFormatValue.test.ts` (ревьюер). +- [ ] 1.3 Автор исправляет «Known issues» 1–4 из `design.md` (желательное — 5 — по возможности). +- [ ] 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 (фронт).