Сводная ОСВ: пары участников без встречных записей вместо «сопоставьте все» (#195, AURORA-1746) #196

Merged
aep merged 26 commits from feat/195-counter-pairs into main 2026-10-27 22:09:35 +03:00
Owner

Closes #195. Jira: AURORA-1746 (замечание заказчика — чек-лист Циликова 23.10.2026, строка 10, AURORA-1865). Docs: RSHB/arm-ka-docs#176.

Что и зачем

Баннер над сводной ОСВ требовал «сопоставьте все внешние организации без ИНН», модалка [31-00] всплывала при каждом уходе с «ОСВ» — хотя переход не блокируется (AURORA-1388), а счётчик на реальных данных до нуля не доходит. Заказчик понял это как обязанность прокликать каждую строку.

Теперь перед шахматкой проверяется то, что для неё важно: пары участников периметра без встречных записей (у А есть строки с контрагентом Б, у Б нет строк с А). Ничего не хранится — считается на лету из свода (§3 п. 9).

  • Ядро: counter_pairs в domain/consolidated_osv.py; summary.counter_pairs[]; фильтр counterparty_id у GET …/osv и экспорта.
  • Модалка перед шахматкой: «Нет встречных записей: N пар у M участников» + «Показать пары / Остаться / Перейти»; раз на версию за сессию браузера и снова, если список пар изменился; для завершённых версий не показывается; без пар — переход без модалки при любом числе строк без ИНН.
  • Режим «Свод | Встречные записи (N)» на вкладке «ОСВ»: пары сверху, под ними две таблицы на всю ширину — «у А есть записи с Б» и «у Б — строки без ИНН и без сопоставления» с поиском и «Указать».
  • Баннер: без «сопоставьте все» — несопоставленные учитываются как внешние.

Спека и решения — openspec/changes/counter-pairs/, план — docs/superpowers/plans/2026-10-27-counter-pairs.md.

Фронт простыми словами

  • Решение «показывать ли модалку» и всё, что экран решает сам (какую пару выбрать после сопоставления, куда откатить страницу, какой текст пустого состояния), вынесено в чистые функции (utils/consolidations.ts, widgets/ConsolidatedOsv/counterPairs.ts) и покрыто vitest — компонентов в проекте не тестируем, новые npm-пакеты не добавляли.
  • Что модалку уже показывали — помнит sessionStorage браузера (ключ armka:counter-pairs:<version_id>); если хранилище недоступно, модалка просто показывается каждый раз.
  • Режим и выбранная пара — в адресе (?view=pairs&holder=…&missing=…), без лишних записей в истории: «Назад» ведёт со страницы отчёта, а не по переключениям.
  • Если сводка ещё грузится, уход с «ОСВ» дожидается её, а не проскакивает без проверки.
  • В режиме пар строки всего свода не загружаются.

Rulings I made

  • R-1 (design #9). needsUnmappedWarning удалён вместе с использованием (иначе tsc краснел между коммитами).
  • R-2 (design #10). «Ещё K строк без ИНН…» — «строк» без склонения; M — число разных missing_id.
  • R-3 (design #11). Ревью задачи документации объединено с финальным ревью ветки.
  • R-4 (design #12). Подпись пары: «записей: N · без ИНН у <организация>: M».
  • R-5 (design #13). Раскладка режима: пары сверху «таблетками», две таблицы на всю ширину одна под другой (суммы видны без прокрутки на 1440).
  • R-6 (design #14). Уход с «ОСВ»: закрытая версия — без запроса сводки; при незагруженной сводке — запрос, при ошибке переход без модалки; вкладки на время запроса заблокированы; Spin и «Повторить» при ошибке сводки; подпись без «(N)», пока сводки нет.

Плюс решения из обсуждения дизайна (таблица Decisions made without discussion в design.md): «после исправлений» = изменился список пар; новых эндпоинтов нет; учитываются все строки свода; колонки режима — счёт, контрагент, обороты и сальдо на конец; поиск над таблицей строк без ИНН.

Находки (обе волны)

Маркер Суть Статус
T1.1 проверка members по строке rejected: результат верен
T1.2 слабый тест unrelated_members rejected: покрыт тестом порядка
T2.1 == 1 без noqa rejected: ruff не флагует
T3.1 M по holder_id вместо missing_id fixed 4942120
T3.2 склонение «строк» fixed 4942120 (R-2)
T4.1 двойной safeSessionStorage() fixed 8054ff2
T4.2 переход без проверки при незагруженной сводке fixed 8054ff2, 95171f4, 7c523b9 (R-6)
T4.3 логика модалки без тестов fixed 8054ff2, 95171f4: вынесена в чистые функции, vitest
T4.4 длинная строка баннера rejected: lint чистый
T5.1–T5.3 stale mapping, пустая страница, эффект без зависимостей fixed 7df8a93, e2e4162
T5.4 двойной fetch, фоновая загрузка свода fixed 8054ff2, e4c030d, d230fb1
T5.5 потеря пары при «Свод» ↔ «Встречные записи» fixed 8054ff2
T5.6 логика режима без тестов fixed e2e4162, e4c030d, d230fb1: вынесена в чистые функции, vitest
T7.1–T7.5 ревью волны 2 (Spin при ошибке, привязка к версии и др.) fixed 95171f4
T8.1–T8.4 ревью раскладки fixed 95171f4, 2bb5683
H-1 раскладка режима: суммы обрезаны fixed e2e4162, e4c030d (R-5)
I-1 служебные слова в подписи пары fixed 397a1a7
I-2 проверки пар на фикстурах fixed 397a1a7
I-3 устаревшая документация fixed 397a1a7, docs#176
I-4 / O-3 стенд изменён при ручной проверке fixed: данные восстановлены, diff до/после пуст
I-5 / O-4 предупреждение antd о статическом message после «Связать» rejected: было до ветки (CounterpartyMapModal)
I-6 таблицы вылезали за карточку fixed e4c030d
I-7 ledger без статусов fixed
M-1…M-6, M-8…M-13 мелкие правки финальных ревью fixed (397a1a7, 2bb5683, e4c030d, 7c523b9, fb13389, d230fb1, docs dfccabc)
M-7 Purpose спеки consolidations-ui deferred: правится при openspec archive (чеклист закрытия)
O-1 make up из worktree fixed: работает при docs-worktree рядом
O-2 неверное ожидание в брифе fixed c24bc3d
O-5 пустые состояния на стенде fixed: проверены, скриншоты
O-6, O-7 check-full без фикстур, docs make check fixed
O-8 fix-раунд Task 7 (T7.1–T7.5) лежит в коммите 95171f4 вместе с docs Task 8 rejected: историю не переписывали

Отложено решением владельца (вне этого PR): AURORA-1866 — строки с ИНН из файла вне справочника не дают закрыть пару; AURORA-1867 — подсветка кандидата по сумме.

Проверки

  • ARM_KA_FIXTURES=../../fixtures make check-full — EXIT 0: быстрые 1914 passed, 1 skipped; интеграционные 1179 passed; lint чистый; web build ок (после него менялся только web — make web-check EXIT 0).
  • npm test — 424 passed; npm run lint — 0 errors.
  • openspec validate counter-pairs --strict — valid.
  • Ручной сценарий на стенде (РБ 2024 К3): баннер, модалка, режим пар, поиск, «Указать → Связать» закрывает пару, три пустых состояния, раскладка на 1440.

🤖 Generated with Claude Code

Closes #195. Jira: AURORA-1746 (замечание заказчика — чек-лист Циликова 23.10.2026, строка 10, AURORA-1865). Docs: RSHB/arm-ka-docs#176. ## Что и зачем Баннер над сводной ОСВ требовал «сопоставьте все внешние организации без ИНН», модалка [31-00] всплывала при каждом уходе с «ОСВ» — хотя переход не блокируется (AURORA-1388), а счётчик на реальных данных до нуля не доходит. Заказчик понял это как обязанность прокликать каждую строку. Теперь перед шахматкой проверяется то, что для неё важно: **пары участников периметра без встречных записей** (у А есть строки с контрагентом Б, у Б нет строк с А). Ничего не хранится — считается на лету из свода (§3 п. 9). - **Ядро:** `counter_pairs` в `domain/consolidated_osv.py`; `summary.counter_pairs[]`; фильтр `counterparty_id` у `GET …/osv` и экспорта. - **Модалка перед шахматкой:** «Нет встречных записей: N пар у M участников» + «Показать пары / Остаться / Перейти»; раз на версию за сессию браузера и снова, если список пар изменился; для завершённых версий не показывается; без пар — переход без модалки при любом числе строк без ИНН. - **Режим «Свод | Встречные записи (N)»** на вкладке «ОСВ»: пары сверху, под ними две таблицы на всю ширину — «у А есть записи с Б» и «у Б — строки без ИНН и без сопоставления» с поиском и «Указать». - **Баннер:** без «сопоставьте все» — несопоставленные учитываются как внешние. Спека и решения — `openspec/changes/counter-pairs/`, план — `docs/superpowers/plans/2026-10-27-counter-pairs.md`. ## Фронт простыми словами - Решение «показывать ли модалку» и всё, что экран решает сам (какую пару выбрать после сопоставления, куда откатить страницу, какой текст пустого состояния), вынесено в чистые функции (`utils/consolidations.ts`, `widgets/ConsolidatedOsv/counterPairs.ts`) и покрыто vitest — компонентов в проекте не тестируем, новые npm-пакеты не добавляли. - Что модалку уже показывали — помнит `sessionStorage` браузера (ключ `armka:counter-pairs:<version_id>`); если хранилище недоступно, модалка просто показывается каждый раз. - Режим и выбранная пара — в адресе (`?view=pairs&holder=…&missing=…`), без лишних записей в истории: «Назад» ведёт со страницы отчёта, а не по переключениям. - Если сводка ещё грузится, уход с «ОСВ» дожидается её, а не проскакивает без проверки. - В режиме пар строки всего свода не загружаются. ## Rulings I made - **R-1 (design #9).** `needsUnmappedWarning` удалён вместе с использованием (иначе `tsc` краснел между коммитами). - **R-2 (design #10).** «Ещё K строк без ИНН…» — «строк» без склонения; M — число разных `missing_id`. - **R-3 (design #11).** Ревью задачи документации объединено с финальным ревью ветки. - **R-4 (design #12).** Подпись пары: «записей: N · без ИНН у <организация>: M». - **R-5 (design #13).** Раскладка режима: пары сверху «таблетками», две таблицы на всю ширину одна под другой (суммы видны без прокрутки на 1440). - **R-6 (design #14).** Уход с «ОСВ»: закрытая версия — без запроса сводки; при незагруженной сводке — запрос, при ошибке переход без модалки; вкладки на время запроса заблокированы; Spin и «Повторить» при ошибке сводки; подпись без «(N)», пока сводки нет. Плюс решения из обсуждения дизайна (таблица `Decisions made without discussion` в design.md): «после исправлений» = изменился список пар; новых эндпоинтов нет; учитываются все строки свода; колонки режима — счёт, контрагент, обороты и сальдо на конец; поиск над таблицей строк без ИНН. ## Находки (обе волны) | Маркер | Суть | Статус | |---|---|---| | T1.1 | проверка members по строке | rejected: результат верен | | T1.2 | слабый тест unrelated_members | rejected: покрыт тестом порядка | | T2.1 | `== 1` без noqa | rejected: ruff не флагует | | T3.1 | M по holder_id вместо missing_id | fixed 4942120 | | T3.2 | склонение «строк» | fixed 4942120 (R-2) | | T4.1 | двойной safeSessionStorage() | fixed 8054ff2 | | T4.2 | переход без проверки при незагруженной сводке | fixed 8054ff2, 95171f4, 7c523b9 (R-6) | | T4.3 | логика модалки без тестов | fixed 8054ff2, 95171f4: вынесена в чистые функции, vitest | | T4.4 | длинная строка баннера | rejected: lint чистый | | T5.1–T5.3 | stale mapping, пустая страница, эффект без зависимостей | fixed 7df8a93, e2e4162 | | T5.4 | двойной fetch, фоновая загрузка свода | fixed 8054ff2, e4c030d, d230fb1 | | T5.5 | потеря пары при «Свод» ↔ «Встречные записи» | fixed 8054ff2 | | T5.6 | логика режима без тестов | fixed e2e4162, e4c030d, d230fb1: вынесена в чистые функции, vitest | | T7.1–T7.5 | ревью волны 2 (Spin при ошибке, привязка к версии и др.) | fixed 95171f4 | | T8.1–T8.4 | ревью раскладки | fixed 95171f4, 2bb5683 | | H-1 | раскладка режима: суммы обрезаны | fixed e2e4162, e4c030d (R-5) | | I-1 | служебные слова в подписи пары | fixed 397a1a7 | | I-2 | проверки пар на фикстурах | fixed 397a1a7 | | I-3 | устаревшая документация | fixed 397a1a7, docs#176 | | I-4 / O-3 | стенд изменён при ручной проверке | fixed: данные восстановлены, diff до/после пуст | | I-5 / O-4 | предупреждение antd о статическом `message` после «Связать» | rejected: было до ветки (`CounterpartyMapModal`) | | I-6 | таблицы вылезали за карточку | fixed e4c030d | | I-7 | ledger без статусов | fixed | | M-1…M-6, M-8…M-13 | мелкие правки финальных ревью | fixed (397a1a7, 2bb5683, e4c030d, 7c523b9, fb13389, d230fb1, docs dfccabc) | | M-7 | Purpose спеки consolidations-ui | deferred: правится при `openspec archive` (чеклист закрытия) | | O-1 | `make up` из worktree | fixed: работает при docs-worktree рядом | | O-2 | неверное ожидание в брифе | fixed c24bc3d | | O-5 | пустые состояния на стенде | fixed: проверены, скриншоты | | O-6, O-7 | check-full без фикстур, docs make check | fixed | | O-8 | fix-раунд Task 7 (T7.1–T7.5) лежит в коммите 95171f4 вместе с docs Task 8 | rejected: историю не переписывали | Отложено решением владельца (вне этого PR): AURORA-1866 — строки с ИНН из файла вне справочника не дают закрыть пару; AURORA-1867 — подсветка кандидата по сумме. ## Проверки - `ARM_KA_FIXTURES=../../fixtures make check-full` — EXIT 0: быстрые 1914 passed, 1 skipped; интеграционные 1179 passed; lint чистый; web build ок (после него менялся только web — `make web-check` EXIT 0). - `npm test` — 424 passed; `npm run lint` — 0 errors. - `openspec validate counter-pairs --strict` — valid. - Ручной сценарий на стенде (РБ 2024 К3): баннер, модалка, режим пар, поиск, «Указать → Связать» закрывает пару, три пустых состояния, раскладка на 1440. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 24 commits 2026-10-27 21:28:30 +03:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Подпись пары без служебных слов, тест пары на фикстурах РБ, модалка при уходе с «ОСВ» на любую вкладку,
без «Экспорт» в режиме пар, README и ARCHITECTURE, таблица решений design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Сводка живёт только в состоянии ожидающего перехода; при summary=null запрашивается заново.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Тело режима прокручивается внутри карточки; ключ и страница — одно состояние (currentPage);
запасная горизонтальная прокрутка таблиц на узком окне.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Возврат A→B→A даёт первую страницу, а не прежнюю.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep added 1 commit 2026-10-27 21:59:17 +03:00
Решение владельца после показа на стенде: «Указать» во «Встречных записях»
открывает список из одного участника пары; «Показать все организации» —
обычный список. Чистая pairCandidateRows + vitest, спека и design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep added 1 commit 2026-10-27 22:01:07 +03:00
aep merged commit 3ed70c2996 into main 2026-10-27 22:09:35 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/arm-core#196
No description provided.