Andrey Epifancev 1cce1575b8 openspec: архив counter-pairs, Purpose consolidations-ui (#195, AURORA-1746, M-7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:10:42 +04:00

20 KiB
Raw Blame History

consolidated-osv-ui Specification

Purpose

Аналитик видит сводную ОСВ версии [30]: сводку с несопоставленными строками, пробелы по кварталам, таблицу с фильтрами в заголовках колонок и экспорт в xlsx; сопоставляет контрагента строки с ЮЛ справочника [32].

Requirements

Requirement: Сводка версии

Экран «Сводная ОСВ» [30] SHALL показывать шапку версии (ГК, период, номер, статус) и сводку GET /consolidations/versions/{id}/osv/summary: строк всего, документов, несопоставленных (unmapped_total — счётчик «Осталось N» — строки без сопоставленного ЮЛ и без ИНН из файла, arm-core #74; сопоставление — «Указать» в строке), автоматически сопоставленных.

Scenario: Контрольные цифры РБ 2024 Q3

  • WHEN открыт свод версии РБ 2024 Q3 после подтверждения 22 ОСВ из fixtures/osv/{rb,schastl}/2024-Q{1,2,3}
  • THEN «строк» = 1385, «документов» = 22, «несопоставленных» = 1260

Requirement: Таблица свода и фильтры

Экран SHALL показывать строки GET /consolidations/versions/{id}/osv постранично (размер страницы ≤ 2000): ЮЛ, счёт, контрагент, ИНН контрагента, сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец Дт/Кт (суммы — строками с разделителями разрядов) и последней колонкой «Тип контрагента» — бейдж ГСК (синий) / ГК (зелёный) / Внешний (серый), как в макете [30]. Фильтры — в заголовках колонок: ЮЛ (legal_entity_id), корень счёта (account_root), поиск по контрагенту (q, ≤ 500 символов), тип контрагента (counterparty_type, одно значение — на колонке «Тип контрагента»); только несопоставленные (unmapped) — кнопкой «Показать ЮЛ без ИНН» в баннере и воронкой колонки «ИНН» (подсвечена, пока фильтр включён, макет [32-osv-no-inn]). «Применить» и «Сбросить» SHALL закрывать окно фильтра. Смена фильтра SHALL перечитывать первую страницу.

Scenario: Строка из контрольных цифр

  • WHEN фильтр ЮЛ «Пекарня Счастливочная» и поиск «бисквит»
  • THEN строка 60.01 показывает сальдо нач. Кт 4 454 151.93, обороты Дт 128 058 143.65, Кт 125 950 450.77, сальдо кон. Кт 2 346 459.05, признак «ГСК»

Requirement: Ручное сопоставление контрагента

Ячейка ИНН (моноширинный шрифт, по центру) SHALL показывать, как в макете [32-osv-no-inn]:

  • ИНН из файла (counterparty_inn) — просто текстом, сопоставлена строка или нет (строка с ИНН сопоставления не требует, решение владельца 08.10.2026, отмена П-1);
  • строка сопоставлена, ИНН в файле нет — ИНН ЮЛ справочника (counterparty_entity_inn) кнопкой-«таблеткой» (osv-inn-mapped-button); нажатие открывает ту же модалку для пересопоставления, у текущего ЮЛ вместо «Связать» — неактивное «Связано»;
  • нет ни ЮЛ, ни ИНН — подсвеченная ячейка со ссылкой «Указать» (osv-inn-link-button).

«Указать» и «таблетка» SHALL открывать модалку «Выбор организации для связи с <имя контрагента из файла>» [32-osv-map-counterparty]: поиск, таблица ИНН / Организация / ГК / ГСК / ГСЗ / «Связать», рекомендуемые (recommended) — первыми под заголовком «Рекомендуемые» с зелёной подсветкой. Кандидаты — GET …/counterparty-candidates?scope=&entry_id=&q= с паузой 300 мс после ввода: по умолчанию scope=group (периметр и ГК); галочка «Искать во всём справочнике» — scope=all, при поиске короче 3 символов запрос не отправляется и показывается подсказка. «Связать» SHALL отправлять POST …/osv/entries/{entry_ids.at(-1)}/counterparty {legal_entity_id}, после успеха — сообщение (с кем связано, сколько записей ОСВ сопоставлено автоприменением, создан или перенесён алиас), закрывать модалку и перечитывать страницу свода и сводку («Осталось N» на клиенте не вычисляется). Ответы 409:

  • ALIAS_TAKEN — подтверждение «Имя уже закреплено за <владельцы>. Перенести?» → повтор с move_alias = true;
  • LOCK_REQUIRED — подтверждение «Взять версию в работу?» → POST …/lock → повтор; отказ — ничего не меняется;
  • LOCKED_BY_OTHER (от правки или от POST …/lock) — «Версию держит <имя>» (имя — из ответа POST …/lock или из lock карточки версии), без перехвата;
  • VERSION_NOT_EDITABLE и прочие ошибки — текстом сервера.

Scenario: Сопоставление «Пекарня Счастливочная ООО» в ОСВ РБ

  • WHEN в своде РБ 2024 Q3 у строки контрагента «Пекарня Счастливочная ООО» без сопоставления нажато «Указать» и у рекомендуемой «Пекарни Счастливочной» — «Связать» (версия не взята в работу → «Взять в работу»)
  • THEN строка показывает ЮЛ и тип «ГСК», «Осталось N» уменьшается на число сопоставленных строк, у ЮЛ появляется алиас

Scenario: Алиас занят

  • WHEN имя строки уже алиас другого ЮЛ и аналитик выбирает третье ЮЛ
  • THEN показан вопрос о переносе с именами владельцев; «Перенести» повторяет запрос с move_alias = true

Requirement: Пробелы

Экран SHALL показывать gaps[] сводки списком «ЮЛ — счёт — недостающие кварталы» с пояснением, что строки этих пар в своде отсутствуют.

Scenario: Пробелы РБ 2024 Q3

  • WHEN открыт свод версии РБ 2024 Q3
  • THEN показано 7 пробелов, у каждого недостающие кварталы «Q1, Q2»

Requirement: Экспорт

Кнопка «Скачать xlsx» SHALL получать GET /consolidations/versions/{id}/osv/export с текущими фильтрами через клиент API с токеном и сохранять файл; имя — из Content-Disposition, а если заголовок недоступен — Сводная_ОСВ_<ГК>_<год>_<квартал>.xlsx.

Scenario: Экспорт с фильтром

  • WHEN аналитик скачивает свод с фильтром по ЮЛ
  • THEN сохраняется xlsx, а ошибка сервера показывается текстом, а не скачивается

Requirement: Единицы и знаки после запятой

Над таблицей сводной ОСВ [30] SHALL быть два выпадающих списка: «Единицы» — «руб.», «тыс. руб.», «млн руб.», «млрд руб.» (по умолчанию «руб.»: суммы API — в рублях) и «Знаков после запятой» — 0, 1, 2, 3 (по умолчанию 2). Выбор SHALL менять только показ сумм шести денежных колонок (сальдо на начало, обороты, сальдо на конец, Дт/Кт); запрос, фильтры и экспорт в xlsx не меняются. Пересчёт SHALL выполняться над строкой суммы из API без арифметики с плавающей точкой: запятая сдвигается на 3/6/9 разрядов, округление до выбранного числа знаков — половина от нуля, разряды разделяются пробелом, дробная часть — через запятую. Нулевая сумма SHALL показываться «—» при любых единицах; ненулевая сумма, округлившаяся до нуля, — нулём с выбранным числом знаков, без знака «минус».

Scenario: Тысячи рублей

  • WHEN выбраны «тыс. руб.» и 3 знака, сумма строки — 24000.00
  • THEN в ячейке «24,000»

Scenario: Округление без потери разрядов

  • WHEN выбраны «тыс. руб.» и 2 знака, сумма — 4454151.93
  • THEN в ячейке «4 454,15»

Scenario: Ноль

  • WHEN сумма ячейки — 0.00, выбраны любые единицы и число знаков
  • THEN в ячейке «—»

Scenario: Малая сумма в крупных единицах

  • WHEN выбраны «млн руб.» и 2 знака, сумма — -0.40
  • THEN в ячейке «0,00»

Requirement: Баннер строк без ИНН

Пока в сводке unmapped_total > 0, над таблицей свода SHALL показываться баннер (стиль osv-mapping-alert макета [30]): заголовок «Есть организации без ИНН и без сопоставления», текст «Осталось: N. Несопоставленные учитываются как внешние; при необходимости укажите организацию кнопкой «Указать».» и кнопка «Показать ЮЛ без ИНН» / «Показать все строки» (фильтр unmapped, как прежде). Баннер MUST NOT требовать сопоставить все строки для перехода к следующему шагу: переход не блокируется (решение заказчика 16.10.2026, AURORA-1388). При unmapped_total = 0 баннера нет.

Scenario: Есть строки без ИНН

  • WHEN открыт свод версии РБ 2024 Q3 до сопоставления (unmapped_total = 1260)
  • THEN баннер «Есть организации без ИНН и без сопоставления», «Осталось: 1260. Несопоставленные учитываются как внешние…»; слов «Для перехода к следующему шагу сопоставьте все» на экране нет

Requirement: Режим «Встречные записи»

Над сводом на вкладке «ОСВ» SHALL быть переключатель режимов «Свод | Встречные записи (N)», N — число пар summary.counter_pairs. Режим — параметр адреса ?view=pairs (без параметра — «Свод»); переключение режима MUST NOT добавлять запись в историю браузера. Шапка свода (единицы, знаки после запятой) действует в обоих режимах.

В режиме «Встречные записи» экран SHALL показывать сверху список пар, под ним на всю ширину две таблицы одна под другой:

  • сверху — список пар одной строкой «таблеток» с переносом, в порядке ответа, строка пары «Б ← А» (имена missing_name и holder_name) с числами holder_rows и missing_unmapped_rows; выбранная пара подсвечена; при входе выбрана первая пара либо пара из адреса (?view=pairs&holder=<id>&missing=<id>);
  • первая таблица — «У <А> есть записи с <Б>»: строки GET …/osv?legal_entity_id=А&counterparty_id=Б — счёт, контрагент, обороты Дт/Кт, сальдо на конец Дт/Кт;
  • вторая таблица — «У <Б> — строки без ИНН и без сопоставления»: строки GET …/osv?legal_entity_id=Б&unmapped=true — счёт, контрагент, обороты Дт/Кт, сальдо на конец Дт/Кт и «Указать» в каждой строке; над таблицей — поиск по контрагенту (q, ≤ 500 символов, как фильтр свода); постранично, как таблица свода. Все четыре колонки сумм MUST быть видны без горизонтальной прокрутки при ширине окна 1440 px (на узком окне допустима прокрутка таблицы). Если missing_rows_total = 0 — вместо таблицы текст «У <Б> в своде нет строк: ОСВ не привязаны или по ним пробелы»; если строки у Б есть, а без ИНН и без сопоставления нет (missing_unmapped_rows = 0) — текст «Строк без ИНН нет: возможно, ИНН контрагента из файла не найден в справочнике или это пропуск в учёте».

Обе таблицы и список пар MUST оставаться внутри белой карточки экрана: тело режима прокручивается по вертикали внутри карточки, а не выходит за её границу.

Пока сводки нет, вместо тела режима показывается индикатор загрузки, а подпись переключателя — «Встречные записи» без «(N)». Если запрос сводки не удался, показывается ошибка с кнопкой «Повторить», повторяющей запрос.

«Указать» SHALL открывать модалку сопоставления из требования «Ручное сопоставление контрагента» с теми же ответами и ошибками, но список кандидатов сразу сужен до участника пары А: одна строка под заголовком «Участник пары» с «Связать» (кандидаты запрашиваются с scope=group и q = имя А), поле поиска и «Искать во всём справочнике» скрыты; флажок «Показать все организации» возвращает обычный список модалки (решение владельца 27.10.2026). Если А среди кандидатов не нашёлся — текст «Участник пары не найден среди кандидатов — включите «Показать все организации»». В обычном своде «Указать» не меняется. После «Связать» экран SHALL перечитывать сводку и обе таблицы. Если выбранная пара исчезла из списка, выбирается первая оставшаяся; пустой список — текст «Все участники периметра видят друг друга» (переключатель — «Встречные записи (0)»).

Scenario: Пара Русского бисквита

  • WHEN открыт свод версии РБ 2024 Q3 до сопоставления и выбран режим «Встречные записи»
  • THEN переключатель «Встречные записи (1)»; сверху пара «Русский бисквит ← Пекарня Счастливочная»; в первой таблице строки Счастливочной с контрагентом «Русский бисквит»; во второй таблице строки РБ без ИНН, среди них «Пекарня Счастливочная ООО» с «Указать»

Scenario: Сопоставление из режима закрывает пару

  • WHEN во второй таблице у «Пекарня Счастливочная ООО» нажато «Указать» и выбрана ООО «Пекарня Счастливочная»
  • THEN после «Связать» список пар пуст, показан текст «Все участники периметра видят друг друга», переключатель — «Встречные записи (0)»

Scenario: Модалка сопоставления сужена до участника пары

  • WHEN в паре «Русский бисквит ← Пекарня Счастливочная» во второй таблице нажато «Указать»
  • THEN в модалке одна строка — ООО «Пекарня Счастливочная» под заголовком «Участник пары» с «Связать»; после «Показать все организации» — поиск и обычный список кандидатов

Scenario: Сводка ещё не получена

  • WHEN открыт режим «Встречные записи», запрос сводки в пути
  • THEN показан индикатор загрузки, подпись переключателя — «Встречные записи» без числа

Scenario: Ошибка запроса сводки

  • WHEN запрос сводки в режиме «Встречные записи» завершился ошибкой
  • THEN показан Alert с текстом ошибки и кнопкой «Повторить»; нажатие повторяет запрос

Scenario: Режим из адреса

  • WHEN открыт адрес …/versions/{id}/osv?view=pairs
  • THEN показан режим «Встречные записи» с первой парой; кнопка «Назад» браузера не проходит по переключениям режимов